Type something to search...
How to Set Initial Values in Sanity Documents

How to Set Initial Values in Sanity Documents

To set initial values in Sanity, add an initialValue property to a field or to a whole document type in your schema. It can be a static value, such as initialValue: false, or a function that returns the value, which lets you use the current date, the logged-in user or data fetched from your dataset. The value is applied once, when a new document is created in the Studio. For several different starting points for the same type, such as "Blog post" and "Podcast episode" variants of a post, define initial value templates in schema.templates in sanity.config.ts.

Good defaults save editors time and reduce mistakes. A boolean that should normally be false, a publish date that should default to today, or an author field that should default to the person writing all benefit from an initial value. This article covers field-level and document-level initial values, dynamic and async resolvers, initial value templates with parameters, how to surface templates in the Studio, and the rules about when initial values apply.

Field-Level Initial Values

The simplest form sits directly on the field:

// schemaTypes/post.ts
import { defineArrayMember, defineField, defineType } from 'sanity'

export const post = defineType({
  name: 'post',
  type: 'document',
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({
      name: 'featured',
      type: 'boolean',
      initialValue: false,
    }),
    defineField({
      name: 'status',
      type: 'string',
      options: { list: ['draft', 'in-review', 'approved'] },
      initialValue: 'draft',
    }),
    defineField({
      name: 'tags',
      type: 'array',
      of: [defineArrayMember({ type: 'string' })],
      initialValue: ['general'],
    }),
  ],
})

Every new post now starts with featured set to false, status set to "draft" and one tag. Initial values are type-checked when you use defineField, so a string on a boolean field is flagged in your editor. See using defineType and defineField for how that checking works.

Initial Values for Objects

Object fields take an object, and nested fields can also have their own initial values:

defineField({
  name: 'seo',
  type: 'object',
  fields: [
    defineField({ name: 'title', type: 'string' }),
    defineField({ name: 'noIndex', type: 'boolean', initialValue: false }),
  ],
})

When an object type is reused, putting initialValue inside the object type means every use gets the same defaults. The post on building reusable object types in Sanity covers that pattern.

Initial Values for References

References need the reference shape, not just an ID:

defineField({
  name: 'category',
  type: 'reference',
  to: [{ type: 'category' }],
  initialValue: { _type: 'reference', _ref: 'category-general' },
})

Make sure the referenced document exists, or the field will show a broken reference. More on reference fields is in how to use references in Sanity schemas.

Dynamic Initial Values

Pass a function when the value should be calculated at creation time. The classic example is today's date:

defineField({
  name: 'publishedAt',
  type: 'datetime',
  initialValue: () => new Date().toISOString(),
}),
defineField({
  name: 'eventDate',
  type: 'date',
  initialValue: () => new Date().toISOString().slice(0, 10),
}),

A date field stores YYYY-MM-DD, so slice the ISO string. A datetime field stores the full ISO timestamp.

Without the function, new Date() would run once when the Studio loads, and every document created in that session would get the same timestamp. Wrapping it in a function makes it run each time a document is created.

Using the Context

Initial value functions receive two arguments: parameters (used by templates, covered below) and a context object. The context includes:

  • currentUser: the logged-in user, with id, name, email and roles.
  • getClient: a function that returns a configured client for fetching data.
  • projectId, dataset and schema: details about the current workspace.

That lets you default an author reference to the person creating the document:

defineField({
  name: 'author',
  type: 'reference',
  to: [{ type: 'author' }],
  initialValue: async (_params, { currentUser, getClient }) => {
    if (!currentUser?.email) return undefined
    const client = getClient({ apiVersion: '2026-11-01' })
    const authorId = await client.fetch<string | null>(
      `*[_type == "author" && email == $email][0]._id`,
      { email: currentUser.email },
    )
    return authorId ? { _type: 'reference', _ref: authorId } : undefined
  },
}),

Returning undefined leaves the field empty when no matching author exists. Keep async resolvers fast, because the editor waits for them before the new document opens.

Document-Level Initial Values

You can also set initialValue on the document type itself, returning several fields at once:

export const post = defineType({
  name: 'post',
  type: 'document',
  initialValue: () => ({
    featured: false,
    status: 'draft',
    publishedAt: new Date().toISOString(),
  }),
  fields: [
    // ...
  ],
})

Document-level values are useful when defaults depend on each other, or when you want all defaults for a type in one place. If both a document-level and a field-level initial value set the same field, the document-level value takes precedence.

Pick one approach per type where you can. Mixing both makes it hard to tell where a default comes from.

Initial Value Templates

Templates let you offer several starting points for the same document type. Each template appears as its own option in the Studio's Create menu.

Defining Templates

Templates live in schema.templates in your config. The resolver receives the existing templates, including the default one generated for each type, so you can add to them:

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

export default defineConfig({
  name: 'default',
  title: 'My Studio',
  projectId: 'your-project-id',
  dataset: 'production',
  plugins: [structureTool()],
  schema: {
    types: schemaTypes,
    templates: (prev) => [
      ...prev,
      {
        id: 'post-podcast',
        title: 'Podcast episode',
        schemaType: 'post',
        value: {
          format: 'podcast',
          tags: ['podcast'],
        },
      },
      {
        id: 'post-tutorial',
        title: 'Tutorial',
        schemaType: 'post',
        value: () => ({
          format: 'tutorial',
          tags: ['tutorial'],
          publishedAt: new Date().toISOString(),
        }),
      },
    ],
  },
})

Editors now see Post, Podcast episode and Tutorial when creating content. All three create post documents; only the starting values differ.

Templates With Parameters

A template can accept parameters, which makes it reusable in different contexts. This one creates a post for a given author:

templates: (prev) => [
  ...prev,
  {
    id: 'post-by-author',
    title: 'Post by author',
    schemaType: 'post',
    parameters: [{ name: 'authorId', type: 'string' }],
    value: ({ authorId }: { authorId: string }) => ({
      author: { _type: 'reference', _ref: authorId },
    }),
  },
],

Parameterised templates only work as expected inside the Structure Builder, because that's where you can pass the parameters. Elsewhere in the Studio, such as the global Create menu, there's nothing to supply authorId, so use them from structure and filter them out of other menus with newDocumentOptions, shown below.

Using a Template in Structure

Here's a structure where each author has a list of their posts, and the "create new" button in that list uses the parameterised template:

// structure.ts
import type { StructureResolver } from 'sanity/structure'

export const structure: StructureResolver = (S) =>
  S.list()
    .title('Content')
    .items([
      S.listItem()
        .title('Posts by author')
        .child(
          S.documentTypeList('author')
            .title('Authors')
            .child((authorId) =>
              S.documentList()
                .title('Posts')
                .schemaType('post')
                .filter('_type == "post" && author._ref == $authorId')
                .params({ authorId })
                .initialValueTemplates([
                  S.initialValueTemplateItem('post-by-author', { authorId }),
                ]),
            ),
        ),
      S.divider(),
      ...S.documentTypeListItems(),
    ])
// sanity.config.ts (plugins)
plugins: [structureTool({ structure })],

Clicking Create inside an author's list creates a post with the author already filled in. The Studio walkthrough in understanding Sanity Studio explains how panes and lists fit together.

Controlling Which Templates Appear

As templates multiply, the Create menu can get crowded. document.newDocumentOptions filters the options shown, with a context that tells you where the menu is being opened:

// sanity.config.ts
export default defineConfig({
  // ...
  document: {
    newDocumentOptions: (prev, { creationContext }) => {
      if (creationContext.type === 'global') {
        // Keep the global Create button to templates that need no parameters
        return prev.filter((item) => item.templateId !== 'post-by-author')
      }
      return prev
    },
  },
})

creationContext.type is 'global' for the main Create button, 'structure' for buttons inside a list pane, and 'document' when creating from a reference field. This is also how singleton documents are kept out of create menus; see creating singleton documents in Sanity.

When Initial Values Apply

Initial values have clear limits, and knowing them prevents confusion:

  • Only on creation in the Studio: they're applied when a new document is created through the Studio interface, including from templates and reference fields.
  • Not to existing documents: adding an initialValue to a field doesn't backfill documents that already exist. Use a migration for that.
  • Not through the API: documents created with client.create() don't get schema initial values. The schema lives in the Studio, not the Content Lake.
  • Not a fallback: if an editor clears a field, it stays empty. For guaranteed defaults in your front end, use coalesce() in GROQ.

A GROQ fallback looks like this:

*[_type == "post"]{
  title,
  "status": coalesce(status, "draft"),
  "featured": coalesce(featured, false)
}

To backfill existing documents, a quick script with @sanity/client works for small datasets:

// scripts/backfill-featured.ts
import { createClient } from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  apiVersion: '2026-11-01',
  token: process.env.SANITY_WRITE_TOKEN,
  useCdn: false,
})

const ids = await client.fetch<string[]>(`*[_type == "post" && !defined(featured)]._id`)

const tx = client.transaction()
for (const id of ids) tx.patch(id, (p) => p.setIfMissing({ featured: false }))
await tx.commit()

console.log(`Updated ${ids.length} documents`)

For larger datasets, use the Sanity CLI's migration tooling, which batches mutations and supports dry runs. The essential Sanity CLI commands post lists the relevant commands.


FAQ: Initial Values in Sanity

You've probably set initialValue to new Date().toISOString() directly. Wrap it in an arrow function that returns the date string, so it runs each time a document is created.

No. Initial values only apply when a new document is created in the Studio. To add a value to existing documents, run a migration or a patch script.

Yes. An initialValue function can be async and receives a context with getClient, so you can query your dataset and return the result. Keep the query fast, as editors wait for it.

The document-level initial value takes precedence when both set the same field. To avoid confusion, choose one approach for each type.

In the Structure Builder. Sanity only supports parameterised templates there, because that's where you can pass the parameters, using S.initialValueTemplateItem with the template ID and a parameters object.

No. Schema initial values are applied by the Studio. When creating documents with the client, include the default values yourself.


Conclusion

Initial values are a small schema feature with a big effect on editing. Static values handle simple defaults, functions handle dates and anything that must be calculated at creation, and async resolvers with getClient and currentUser let you pre-fill fields like the author. Document-level initial values keep related defaults together.

When one type needs several starting points, initial value templates add them to the Create menu, and parameters plus the Structure Builder let you create documents in context. Remember that initial values only apply to new documents created in the Studio, and use coalesce() or a migration when you need defaults everywhere else.

Tags :
Share :

Related Posts

Building a Static Site With Astro and Sanity

Building a Static Site With Astro and Sanity

To build a static site with Astro and Sanity, keep Astro's default output: 'static', configure @sanity/astro with useCdn: false, and fetch

Dive Deeper
Common Sanity Mistakes and How to Fix Them

Common Sanity Mistakes and How to Fix Them

The most common Sanity mistakes are using the CDN where you need fresh data, fetching whole documents with ..., leaving slugs unvalidated, ship

Dive Deeper
How to Create a Custom Sanity Plugin

How to Create a Custom Sanity Plugin

To create a custom Sanity plugin, wrap a piece of Studio configuration in definePlugin from the sanity package and add it to the plugins ar

Dive Deeper