Using locally scoped variables for objects and documents in Sanity schema files.

44 replies
Last updated: Feb 18, 2023
is there a way to define multiple objects and documents in the same file? without having to inform the config with new
schemaTypes
. like I want to create a document type and then use that document type in the same file for a field? is there a way to compile in place the document type and use that immediately or does sanity have to do that for every single object and document type individually first? I find it annoying that I have to update a gigantic array of documents and objects before I can use them.
AI Update

Great question! There are actually two main ways to handle this without constantly updating that giant schemaTypes array:

1. Inline Object Types (Best for Single-Use Objects)

You can define object types directly inline within fields without adding them to the schemaTypes array. This is perfect for objects you'll only use in one place:

// In your document schema file
export const blogPost = defineType({
  name: 'blogPost',
  type: 'document',
  fields: [
    defineField({
      name: 'author',
      type: 'object', // Inline object - no schemaTypes entry needed!
      fields: [
        { name: 'name', type: 'string' },
        { name: 'bio', type: 'text' }
      ]
    }),
    defineField({
      name: 'relatedPosts',
      type: 'array',
      of: [
        defineArrayMember({
          type: 'object', // Another inline object
          name: 'relatedPost',
          fields: [
            { name: 'title', type: 'string' },
            { name: 'url', type: 'url' }
          ]
        })
      ]
    })
  ]
})

These inline objects work immediately without touching your config file. They're scoped to that specific field, so you can't reuse them elsewhere, but that's often exactly what you want.

2. Auto-Import with import.meta.glob (Best for Organization)

For a cleaner setup where you want to reuse types, you can use import.meta.glob to automatically collect all schema files:

// schemas/index.ts
import { defineType } from 'sanity'

// Automatically import all schema files
const schemaFiles = import.meta.glob('./**/*.ts', { eager: true })

export const schemaTypes = Object.values(schemaFiles)
  .map((module: any) => module.default)
  .filter(Boolean)

// sanity.config.ts
import { defineConfig } from 'sanity'
import { schemaTypes } from './schemas'

export default defineConfig({
  // ...
  schema: {
    types: schemaTypes
  }
})

Now you can create new schema files in your schemas/ directory and they'll automatically be included. No more manual array updates!

The Short Answer

For the specific scenario you described (creating a document type and using it in the same file), you have two options:

  1. If it's reusable: Define it as a separate defineType() in the same file and export both types, then add both to schemaTypes once
  2. If it's single-use: Use an inline object type (shown above) - no schemaTypes entry needed at all

Sanity does need to know about top-level document and object types (the ones in schemaTypes), but inline objects within fields are compiled on-the-fly and work immediately. The import.meta.glob approach just makes managing that top-level array less painful as your schema grows.

like this should be allowed but its not.
 defineField({
      name: "sectionBlocks",
      type: "array",
      title: "Content Blocks",
      description: "Add, and edit section blocks",
      of: [
        {
          type: "reference",
          to: [
            { title: "Section Block", type: "sectionBlock" },
            { title: "Form", type: "form" },
            { title: "Tag Index", type: "tagIndex" }
          ],
        },
      ],
    }),
???
You want to reference an array of different types of references?
no
read that again and see my example
Ah... I see, you are creating multiple schema definitions in the same file.
I do keep my schema files separated so that they are easy to manage. However, I did find it annoying to keep adding references all the time to the main schema file so....
I do something like this in my main schema file....
/**
 * OBJECTS : Root
 * Re-usable objects to make life easier
 */
import * as objects from "./objects";
const allObjects = Object.values(objects).map((myObject) => {
  return { ...myObject };
});
mmmm
export default {
  types: [
    ...allDocuments,
    ...allConfigs,
    ...allObjects,
    ...allPlugs,
    ...allTagIndexTypes,
    ...allBlockTypeOptions,
    ...allMotionAnimationOptions
  ],
};
good idea
Note that I have an 'objects' folder where all of my object are.
I just wish I could do what I have in my example. thats how it works in literally every other schema / migration api out there.
I have an index.ts file that looks like this in my objects folder...
export { default as anchor } from './anchor'
export { default as animatedBLockConfig } from './animatedBlockConfig'
export { default as animatedLineConfig } from './animatedLineConfig'
export { default as animationKeysNumbers } from './animationKeysNumbers'
in that example, anchor, animatedBlockConfig, etc. are all their own files
I don't so much mind having it broken down into spearately managed files. But I tend to over document everything and take DRY as far as possible.
That being said... in v3, it is just a javascript file, so technically I suppose you could do what you're suggesting if you combine my approach and yours. Isn't is just named and default exports at the end of the day?
I dont want to export the named example. I want to use that one-off non-reusable object definition in the same file. its not something that will be used in any other schema definition. Like I said sanity is the snowflake here with how it works.
hmmmm... I see what your saying more clearly now. I have to try this. brb
Ha... i got it to work.
defined an object like this...
const wtf = defineType({
  name: "wtf",
  type: "object",
  title: "What the F",
  description:
    "So weird if this doesn't work",
  fields: [
    {
      name: "name",
      title: "Name",
      type: "string",
      description: "Internal Name for Reference Onl",
    }
  ]
})
then in the main schema...
fields []
fields: [
wtf,
defineField({....
]
hmm but what does that make? what if i want to use the document as an array item
hold on
let me try something
fundamentally, if you haven't added an object or document to the schema, you can't reference as if it has been.
ohhhh damn it totally does work
party
i can just do
of: [article]
instead of
of: [{type: article}]
weird, I’ll be damned if any documentation about that being a possibility exists
right... because of:[{type: article}] is trying to reference something that been added to the schema. But you haven't added it so it doesn't know what you're talking about.
damn thanks! this changes literally everything for me
but of: [article] is just basically using a locally scoped variable, sort of.
Cool, glad I could help. I earned good parking Karma for the day.
I see what you did there.
another gotcha is if I want to scope this to a field with its own name I had to do
defineField({
  ...brands,
  name: "field_name",
}),
they should really document this stuff in their schema docs.
More examples would be nice. I would like more examples for creating custom components and schema builder though. I think those things are really lacking.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?