releases.shpreview
Sanity/Sanity Changelog

Sanity Changelog

Mon
Wed
Fri
JunJulAugSepOctNovDecJanFebMarAprMay
Less
More
Releases67Avg21/moVersionsv0.4.0 to v13.0.0
v5.29.0

This release improves the Sanity init setup process, adds a page-builder template, enhances dataset export and authentication features, and fixes several bugs including array field initialization, document scrolling, reference input loading, and media preview permissions.

Add best practice skills during init

sanity init will now prompt to configure Sanity MCP and install the sanity-best-practices agent skill for detected AI editors in a single step during sanity init. Add --no-skills to opt out.

🐛 Notable bugfixes and improvements

  • CLI: adds page-builder template using @​sanity/presets.
  • CLI: adds the no-strict-asset-verification flag to datasets export.
  • CLI: MCP can now use oauth for claude code config.
  • CLI: Allow ctrl-c to cancel browser login.
  • Fixed a bug where an array field with an initialValue set on both the parent field and a child field within defineArrayMember would incorrectly use the child field’s value. The parent’s initialValue now takes precedence, consistent with object fields and the documented behavior; the child’s initialValue still fills any keys the parent omits.
  • Fixes incoming references input flashing loading state.
  • Fixes an issue causing the document editor to unexpectedly scroll to the top in certain circumstances after entering Studio using a deep-link.
  • Fixed a regression in v5.26.0 where editors with a custom role lacking the Project datasets read permission saw a permissions error toast and broken media previews. Previews now load normally for these roles.
  • Fixes intermittent issue preventing navigating by URL to Portable Text Editor deep-links.
  • Fixes a bug where an array field with an initialValue set on both the parent field and a child field within defineArrayMember would incorrectly use the child field’s value. The parent’s initialValue now takes precedence, consistent with object fields and the documented behavior; the child’s initialValue still fills any keys the parent omits.
  • Fixes a bug where the incoming references input loading state would flash.
  • Fixes an issue causing the document editor to unexpectedly scroll to the top in certain circumstances after entering Studio using a deep-link.
  • Fixes a regression in v5.26.0 where editors with a custom role lacking the Project datasets read permission saw a permissions error toast and broken media previews. Previews now load normally for these roles.
  • Fixes an intermittent issue preventing navigating by URL to Portable Text Editor deep-links.

In addition to all the product updates from across Sanity, the documentation surface continues to grow with updated Next.js articles, a new guide for search, and additional guidance on organizing your blueprints. The platform also received a variety of fixes, along with some exciting personalizing and search improvements.

New and updated docs

Next.js updates

next-sanity 13 was released, and with it comes updates to many of the Sanity + Next.js articles. There’s a new guide on how to use Sanity Live with cache components.

GROQ search patterns

We’ve added a new guide on the different ways to implement search with GROQ. This includes patterns for both traditional search using BM25 (the industry standard ranking algorithm) as well as embeddings-based semantic similarity searching.

Everyday Content Agent prompts and workflows

Our content operations team put together an operator-focused guide filled with practical tips and prompts for making the most of Content Agent.

Managing Blueprints and Functions in a monorepo

The new Project layout and monorepos guide takes you through different ways to add Blueprints and Functions to your project, along with our recommended organization patterns. If you haven’t tried Blueprints yet, this is a great time to start.

Platform improvements

Better search

Sanity Learn now shows up in docs search. The Cmd-K palette gained a dedicated Learn group that surfaces public Learn courses and lessons alongside your regular docs results. It’s powered by a live GROQ full-text search (BM25 ranking plus semantic similarity), so conceptual or slightly-misspelled queries still find the right course.

Personalized content

Logged-in Sanity users now have the ability to see dynamic code snippets with their own customizable project and dataset details. Any time you see a PROJECT_ID or DATASET_NAME placeholder, click to populate with your own values.

Better for AI agents

Changelog history is now part of the Markdown and LLM output. When an agent or LLM fetches a doc’s .md twin (for example /docs/ai/mcp-server.md), it now gets a Related changelog entries section listing the versioned changes that reference that document, matching the live changelog tab one-to-one. This works for articles and HTTP/OpenAPI reference pages, while the raw JSON and YAML spec output stays clean.

The API reference site is now discoverable by agents. sanity.io/llms.txt now links out to reference.sanity.io/llms.txt. Compliant agents (Cursor, Claude Code, Aider, Continue, and others) follow that link to reach the per-package descriptions and Markdown twins on the reference site. This closes a gap where agents searching for SDK hooks bounced off /docs and never found the canonical reference.

Platform and dependencies

The docs app moved to Next.js 16.2.6, React 19.2.6, and the latest next-sanity (cache-components line), alongside routine Sanity client and dependency bumps.

Fixes
  • Search results for headings whose IDs start with a number now land on the correct anchor instead of jumping to the top of the page.
  • Deep links to a heading inside a non-default tab now activate that tab and scroll to the heading, instead of silently doing nothing.
  • The Astro, Nuxt, and React Router quickstart cards on the docs homepage navigate reliably again after hydration, now guarded by a regression test.
v2.20.0

New Features

  • give_feedback tool: Report tool errors, missing capabilities, confusing output, or documentation issues directly from your MCP client.

Improvements

  • Authentication: Adds a note about restarting your MCP client after reauthenticating with mcp configure.
v5.28.0

This release fixes critical bugs in Visual Editing including null reference errors when resolving paths, unnecessary page reloads when switching perspectives in the Presentation tool, and incorrect preview content when using agent bundles.

🐛 Notable bugfixes and improvements

  • Fixes a regression in the Visual Editing schema union-type resolver that could throw TypeError: Cannot read properties of null when resolving paths on documents that don’t exist on the active perspective.
  • Fixes a regression where switching the perspective in the Presentation tool caused the preview iframe to do a full page reload. Mainly visible to next-sanity@13 users with visual editing enabled. Users on next-sanity@12 or earlier, integrations built on @sanity/react-loader or @sanity/core-loader, and next-sanity@13 setups using the usePresentationQuery hook were not affected. Affected users on next-sanity@13 should also bump to next-sanity@^13.0.5 or later to pick up the matching client-side fix.
  • Fixes Presentation previews when an agent bundle (e.g., Content Agent’s “Proposed changes”) is selected. Previously the preview iframe always received the drafts perspective regardless of the active agent bundle, so iframes rendered draft content instead of the bundle’s content. Iframes now receive the agent bundle’s full perspective stack, and combining an agent bundle with a scheduled draft chip continues to work as before. No API or behavior change for non-agent perspectives.
v5.27.0

This release improves image preview display in the Portable Text editor and fixes toolbar menu rendering issues.

🐛 Notable bugfixes and improvements

  • Improves Portable Text editor block image previews: images are now displayed in the correct aspect ratio, preventing cropping and zooming.
  • Fixes an issue preventing Portable Text toolbar menus from rendering correctly.
  • Fixed an issue where the comments field wrapper displayed an empty element when the field component was hidden.
v2.12.0

Releases support

The document store now includes release actions, and a new useAllReleases hook makes it easier to fetch releases from your application.

You can now work with Content Releases from your application. The document store exposes release actions for creating, scheduling, and publishing releases, and the new useAllReleases hook returns all releases in your project. Use it to build release pickers, dashboards, or scheduling UI.

import {useAllReleases} from '@sanity/sdk-react'

function ReleaseList() {
  const releases = useAllReleases()
  // ...
}
v13.0.0

next-sanity v13 ships first-class support for Next.js Cache Components (cacheComponents: true), cleans up long-deprecated APIs, and adds new escape hatches for error handling and connection lifecycle events.

Cache Components support

You can now pass cacheComponents: true to defineLive to opt in to Next.js Cache Components. This unlocks full 'use cache' support for sanityFetch, giving you fine-grained, tag-based cache invalidation that integrates directly with Sanity Live.

We've been running this in production for the past couple of months. sanity.io runs with cacheComponents: true, and sanity.io/docs runs with cacheComponents: false. Both have been solid.

The fastest way to adopt Cache Components is with the sanity-live-cache-components skill, which drives the migration with an AI agent. For best results, set up AGENTS.md first, then run:

npx skills add https://github.com/sanity-io/next-sanity --skill sanity-live-cache-components

Suggested prompt for your agent:

Use the /sanity-live-cache-components skill to migrate this app to use Cache Components. When verifying with `next dev`, test both draft mode enabled and draft mode disabled because each mode has different rendering rules. `next build --debug-prerender` is not sufficient to verify that draft mode works correctly.

Breaking changes

See the v12 to v13 migration guide for full details and code snippets. The key changes are:

  • The default cache-invalidation behavior changed. revalidateSyncTags on <SanityLive> is replaced by action; sanityFetch no longer caches the internal sync-tag lookup when cacheComponents: false.
  • Removed the <SanityLive> props that were on by default: refreshOnFocus, refreshOnReconnect.
  • Removed the opt-in <SanityLive> / defineLive props: refreshOnMount, intervalOnGoAway, fetchOptions, stega; onGoAway signature changed (no longer receives interval).
  • Removed deprecated hooks: useDraftModePerspective, useIsLivePreview, useDraftModeEnvironment.
  • Removed deprecated tag option on sanityFetch and tag prop on <SanityLive>. Use requestTag instead.
  • Renamed next-sanity/live type exports: DefinedSanityFetchTypeDefinedFetchType, DefinedSanityLivePropsDefinedLiveProps, DefineSanityLiveOptionsDefineLiveOptions.

What else is new

Customize or silence the welcome message with onWelcome

By default, <SanityLive> logs a welcome message to the console when the live event stream connects. Pass a custom onWelcome handler to replace it with your own logic, or pass onWelcome={false} to disable it entirely.

"use client";

import type { SanityLiveOnWelcome } from "next-sanity/live";

export const onWelcome: SanityLiveOnWelcome = (
  event,
  { includeDrafts, waitFor }
) => {
  console.info(
    `<SanityLive${
      includeDrafts ? " includeDrafts" : ""
    }> is connected and listening for live events to ${
      includeDrafts
        ? "all content including drafts and version documents in content releases"
        : "published content"
    }.${
      waitFor === "function"
        ? " Events will be delayed until after a Sanity Function has processed them."
        : ""
    }`
  );
};
import { onWelcome } from "./client-functions";
import { SanityLive } from "@/sanity/lib/live";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <SanityLive onWelcome={onWelcome} />
    </>
  );
}
Error boundaries with retry via onError="throw"

The default behavior still logs errors with console.error (CORS errors use console.warn). Pass onError="throw" to throw errors during render so they can be caught by the Next.js unstable_catchError API, which supports unstable_retry for retrying the render. This lets you build rich error UIs. For example, a toast that offers a retry button when Sanity Live can't connect.

"use client";

import { isCorsOriginError } from "next-sanity/live";
import { unstable_catchError, type ErrorInfo } from "next/error";
import { useEffect } from "react";
import { toast } from "sonner";

function SanityLiveErrorBoundary(
  _props: {},
  { error, unstable_retry }: ErrorInfo
) {
  useEffect(() => {
    let toastId: string | number | undefined;
    if (isCorsOriginError(error)) {
      const { addOriginUrl } = error;
      toastId = toast.warning(`Sanity Live couldn't connect`, {
        description: `${new URL(window.origin).host} is blocked by CORS policy`,
        richColors: true,
        duration: Infinity,
        action: addOriginUrl
          ? { label: "Manage", onClick: (e) => { e.preventDefault(); window.open(addOriginUrl.toString(), "_blank"); } }
          : { label: "Retry", onClick: () => unstable_retry() },
        cancel: addOriginUrl
          ? { label: "Retry", onClick: () => unstable_retry() }
          : undefined,
      });
    } else if (error instanceof Error) {
      console.error(error);
      toastId = toast.error(error.message, {
        richColors: true,
        duration: Infinity,
        action: { label: "Retry", onClick: () => unstable_retry() },
      });
    } else {
      console.error(error);
      toastId = toast.error("Unknown error", {
        description: "Check the console for more details",
        richColors: true,
        duration: Infinity,
        action: { label: "Retry", onClick: () => unstable_retry() },
      });
    }
    return () => { toast.dismiss(toastId); };
  }, [error, unstable_retry]);

  return null;
}

export default unstable_catchError(SanityLiveErrorBoundary);
import SanityLiveErrorBoundary from "./SanityLiveErrorBoundary";
import { SanityLive } from "@/sanity/lib/live";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <SanityLiveErrorBoundary>
        <SanityLive onError="throw" />
      </SanityLiveErrorBoundary>
    </>
  );
}
Strict mode for staged Cache Components migrations

Enable strict: true in defineLive to split your Cache Components migration across two PRs: first update all sanityFetch and <SanityLive> call sites to satisfy the new requirements, then flip cacheComponents: true in next.config.ts and add 'use cache' to functions that call sanityFetch.

Other additions
  • usePresentationQuery, useIsPresentationTool, and useVisualEditingEnvironment no longer require both <SanityLive> and <VisualEditing> to be rendered in layout.tsx. Rendering <VisualEditing> alone is now enough
  • onRestart prop on <SanityLive>: calls router.refresh() by default; pass onRestart={false} to disable
  • onReconnect prop on <SanityLive>: logs to console by default; pass onReconnect={false} to disable
  • includeDrafts prop on <SanityLive>: override the automatic draft-mode detection when a browserToken is set
  • resolvePerspectiveFromCookies utility: exposes the same perspective resolution sanityFetch uses internally, useful for custom toolbars and cacheComponents: true boundaries
  • stega: true now works on the published perspective, enabling Visual Editing overlays on published content (useful for Vercel Content Link)

Improved search

Search now handles partial matches more accurately, making it easier to find what you’re looking for.

Improved private video support

Private videos now support proper thumbnails, timeline scrubbing, and full-screen previews.

v5.26.0

This release improves studio navigation and user experience by automatically routing to the first visible workspace, enabling proper upload field restrictions, resolving validation error visibility for date fields, correcting preview data handling for arrays, and fixing authentication issues with image cropping in private datasets.

🐛 Notable bugfixes and improvements

  • Studio now routes to the first workspace the user can see, fixing the case where the configured default is hidden from them.
  • Fixes an issue where you could still upload to fields where the schema type has had upload disabled with options.disableNew.
  • Fixes “useDocumentDivergences must be used within a DocumentDivergencesContext” error in third-party code that renders the FormBuilder component.
  • Custom validation rules on date/datetime fields no longer hide behind “Required” when the user’s input doesn’t match dateFormat. The field-level tooltip and the global validation panel now surface the parser error directly (e.g., Invalid date. Must be on the format "YYYY-MM-DD"), making the format requirement actionable without any schema changes.
  • Document / list previews that use preview.select with arrays of primitives and dotted paths (e.g., names.0, names.length) now receive the correct values instead of dropping the first element or misshaping the array.
  • The crop dialog now authenticates private-dataset image requests directly, fixing a 403 that surfaced in Firefox and on deployed studios.
  • CLI: The CLI now supports login with the --with-token flag. Use it to pass standard input to the command to bypass the interactive login. For example: npx sanity login --with-token < token.txt.
v5.25.1

This release fixes CSS file handling errors and improves the Studio pane layout by truncating long document titles instead of allowing them to overflow.

🐛 Notable bugfixes and improvements

  • Fixes an issue that caused the Error: Unknown file extension &quot;.css&quot; error.
  • Long document titles in the Studio pane header now truncate with an ellipsis instead of overflowing into adjacent panes (e.g. when the Comments pane is open).
  • fix: show login screen instead of error when session expires
v2.11.1

Fixed an issue where the authentication subscription would stop after a failed user fetch. This could cause apps to hang indefinitely when the Sanity Dashboard issued a new token after expiry, since the SDK wouldn't pick up the refreshed session. The subscription now stays active through fetch errors, ensuring token refreshes work as expected.

v5.25.0

This release fixes crashes when opening certain documents, improves reference handling in modals, corrects document list ordering, and tightens timezone handling and UI layering in the Studio.

🐛 Notable bugfixes and improvements

  • Fixes an issue where clicking a reference inside an open array-item modal would close the modal instead of opening the referenced document.
  • Fixes a crash on the document pane (Cannot read properties of null (reading &#x27;schemaType&#x27;)) that could occur when opening documents whose schema relies on hidden callbacks. Divergences are now disabled until form state is fully resolved.
  • Release publish dates now follow the user’s selected Content Releases timezone.
  • Fixes an issue where studios running on localhost would suggest registering with Sanity.
  • Fixes a bug where popovers, like the Portable Text Edit Link editor, could render over the document pane’s version chips and actions bar.
  • Fixes Presentation Mode so documents created by Content Agent are now viewable. The preview perspective cookie now always updates to match the current workspace perspective, not a stale value from when the preview loaded.
  • Fixes a regression introduced in 5.23.0 that could cause the studio to crash during server-side rendering.
  • Version documents now show the “saving**”** and “saved**”** indicators when editing them.
  • Fixes an issue with the incorrect ordering in document lists that are ordered by a field in an array of objects, or a dereferenced field in an array of references.

Field labels in Canvas

Field labels replace the previous content mapping approach with a direct, visible connection between what you write and where it goes in Studio.

When you set a content type on a Canvas document, field labels appear inline as purple headings that correspond to fields in your schema. You can see and adjust the structure of your document as you write, without switching to a separate panel.

What changed

Structure is now part of the document. Content mapping previously lived in a side panel you had to open to check the state of your document. Field labels sit inline, so structure is visible as you write.

Content-to-field assignment works differently. The background AI process that continuously mapped content is gone. Field labels can be applied in two ways: manually using the = slash command, or automatically via AI labeling that you trigger when you’re ready. After AI labeling runs, you can always review and adjust the results.

Inclusion replaces content/context toggling. Instead of marking blocks as “content” or “context,” you now toggle individual blocks between “included in Studio” and “excluded” using an eye icon in the margin. Excluded blocks stay in the document and will not be sent to Studio.

Delivering to Studio is an explicit action. Previously, connecting a Canvas document to Studio started a continuous sync. Now you first select a content type, then choose “Create new Studio document” or “Update Studio document” when you’re ready. Setting a content type alone doesn’t create anything in Studio.

Studio documents are never locked. Content mapping blocked Studio editing while a Canvas document was linked. With field labels, Studio documents can always be freely edited. When you push an update from Canvas, it flags any fields that have changed in Studio since your last update so you can review conflicts before overwriting.

Applying field labels

Field labels can be applied to an entire document or a selected range. AI reads your content against the selected content type, sorts it according to the schema, and applies labels for you to review.

This is especially useful when bringing existing content into Canvas from tools like Google Docs or Notion and structuring it for Studio quickly.

For finer control, use the = slash command to assign a label to any block manually. You can also select text and pick “Label this content” from the context toolbar to label a specific section.

Including and excluding content

Within any field label, toggle individual blocks between included and excluded using the eye icon. Only included content flows to Studio when you create or update a document.

Excluded blocks are useful for keeping editorial notes, content briefs, reviewer comments, or alternative drafts inline without cluttering the Studio document. You can draft multiple variations within a field label and toggle which one to include before sending to Studio.

Sending to Studio

Open the content type panel and select “Create new Studio document” to send labeled content as a new draft. If your team uses Content Releases, you can create the document directly into an existing release.

Once created, the Studio document stays connected to the Canvas document. Continue refining in Canvas and push updates when ready. If fields have been edited in Studio since your last update, Canvas flags the conflicts before anything is overwritten.

What stays the same

The writing experience (editor, formatting, slash commands, Markdown shortcuts), AI assistance (ghostwrite, show options, rewrite), notes (context, fact, style, inspiration), and real-time collaboration all remain unchanged. Field labels re-imagine how structure is surfaced and how content moves from Canvas to Studio.

v2.19.0

New Features

  • deploy_studio tool: Deploys a hosted Sanity Studio bound to an MCP-managed schema. Given an appHost, the tool provisions or redeploys a Studio at https://&lt;appHost&gt;.sanity.studio/ that renders the managed schema and is available in Sanity Dashboard.
  • MCP-managed schemas: Schemas deployed via deploy_schema are now owned and resolved by the MCP server directly, rather than relying on a Studio codebase to publish them. get_schema, list_workspace_schemas, and the document tools read from this managed source first, falling back to Studio-deployed and legacy schemas when no managed schema exists for a workspace.

Fixes

  • Legacy schema adoption: Better handles invalid legacy schema manifests by returning clear recovery guidance instead of leaking raw validation errors.

Enterprise organizations managing large teams on Sanity can now use User Attributes to create parameterized roles that adapt to each user automatically. Instead of creating a separate custom role for every combination of location, department, or language, you define a small number of roles that resolve dynamically based on who the user is.

How it works

User Attributes are key-value pairs attached to a user's profile within an organization (ex: location=&quot;london&quot;, department=&quot;editorial&quot;, language=&quot;french&quot;). Attributes come from two sources:

  • SAML (automatic): Captured from identity provider assertions during SSO login. Refreshed on every authentication.
  • Sanity (manual): Set by administrators through Manage or the Management API. Used to supplement IdP data or override specific values.

When both sources provide the same attribute, the Sanity value takes precedence. Removing the override reveals the SAML value again.

Parameterized roles with GROQ filters

Attributes can be referenced in content resource filters using the user::attributes() function. Instead of creating a separate role for each branch office, define a single role with a filter like:

_type == "post" && branch == user::attributes().branch

Reducing role complexity at scale

Organizations managing dozens of locations, markets, or business units have been forced to create a separate custom role for every permutation of access. User Attributes reduces this complexity. Your roles become more powerful because they adapt to each user's context rather than requiring a static definition for every combination.

Attributes also support temporary reassignments. An administrator can override a user's location attribute in Sanity without modifying the identity provider. When the override is removed, the SAML value takes effect again on the next login.

Important details

  • Fail-closed security: If a user is missing an attribute that a content resource filter references, the filter denies access by default.
  • Supported types: string, integer, number, boolean, and array variants of each (except boolean).
  • Enterprise plans: User Attributes is available on certain Enterprise plans.
v2.11.0

This release adds two new React hooks for working with organization metadata, expands the parameter options on the projects store, and moves the SDK to a single shared Sanity instance.

New hooks for organization metadata

Use useOrganization to get metadata for a single organization. Pass includeMembers: true to include members, or includeFeatures: true to include features.

function OrganizationName({organizationId}: {organizationId: string}) {
  const organization = useOrganization({organizationId})

  return 

# {organization.name}

}
const organizationWithMembers = useOrganization({organizationId, includeMembers: true})
const organizationWithFeatures = useOrganization({organizationId, includeFeatures: true})

Use useOrganizations to get metadata for every organization the current user can access.

const organizations = useOrganizations()

return (
  
    {organizations.map((organization) => (
      {organization.name}
    ))}
  
)

useOrganizations accepts the same options, plus includeImplicitMemberships.

const organizationsWithMembers = useOrganizations({includeMembers: true})
const organizationsWithFeatures = useOrganizations({includeFeatures: true})
const organizationsIncludingImplicit = useOrganizations({includeImplicitMemberships: true})

Both hooks support suspense. The release also exports new public types: Organization, OrganizationBase, OrganizationMember, and OrganizationsOptions.

More options on useProjects

The useProjects hook now accepts includeFeatures, onlyExplicitMembership, and organizationId as typed options. Use organizationId to scope results to a single organization, onlyExplicitMembership to exclude implicit memberships, and includeFeatures to include feature data on each project.

This release also exports new public types: Project, ProjectBase, ProjectMember, ProjectMemberRole, ProjectMetadata, and ProjectsOptions.

Single Sanity instance

The SDK now uses one shared Sanity instance internally.

Blueprint stacks now support organization scope.

Some resource types, starting with Scheduled Functions, exist only at the organization level and cannot be deployed from a project-scoped stack. Organization-scoped stacks reach across multiple projects from a single blueprint, so they grow with your Sanity footprint. Project-scoped stacks remain a fine default when your work focuses on a single project, or when you don't have org-level permissions.

Create a new organization-scoped stack

If you're starting fresh, use blueprints init rather than promoting. The new scope wizard asks which organization you're working in, whether to scope the new stack to that organization or to a single project, and, if you pick project scope, which project. Every choice is editable: back up to change an answer and the rest of the wizard updates to match.

Promote with one command

Promoting a stack lifts an existing stack from a single project to its parent organization, so one stack can manage resources that span projects (including the new Scheduled Functions) without disturbing any of the project-scoped resources you already deploy.

Run blueprints promote on an existing project-scoped stack. The CLI performs a single atomic operation that switches the scope, preserves the original project ID so existing resources keep resolving, and updates your local .sanity/blueprint.config.json file.

npx sanity@latest blueprints promote
Existing resources keep working

Promotion does not redeploy or rewrite anything that’s already deployed. Document functions, webhooks, CORS origins, and other project-scoped resources continue to work. The stack remembers its original project and resolves project-scoped resources against it.

Mix scopes in one blueprint

After promoting, the same blueprint can declare both project-scoped and organization-scoped resources side by side. Add an explicit project attribute on a resource to target a different project from the same stack.

import {
  defineBlueprint,
  defineDocumentFunction,
  defineScheduledFunction,
} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    // Project-scoped resource, resolves against the stack’s default project
    defineDocumentFunction({
      name: 'on-publish',
      event: {on: ['create', 'update']},
    }),

    // Organization-scoped resource, enabled by promotion
    defineScheduledFunction({
      name: 'daily-digest',
      event: {expression: '0 9 * * *'},
    }),
  ],
})

Requirements

  • Administrator or the new Blueprints Deployer role on the organization the stack will live under. Project-level roles alone are not sufficient.
  • To promote a stack, its project must belong to an organization. Standalone projects cannot be promoted.
  • No active deployments or operations on the stack at the time of promotion.
  • Deploying an organization-scoped stack requires an organization admin or blueprints deployer role. Alternatively, use a Blueprints Deployer token, created from the organization’s Manage interface or with the blueprints mint-deploy-token command.

Promotion is one-way but risk-free. The command is also idempotent, so it’s always safe to retry. The promotion event will be listed in the logs for that stack; use the blueprints logs command to see your stack’s history.

Get started

Read Promote a stack to organization scope for a full walkthrough, or revisit the Blueprints introduction to learn how stacks fit into the bigger picture.

Scheduled Functions are now available. They run on a timer instead of in response to document events. Use them for daily digests, cache expiration, periodic content syncs, or any task that needs to happen on a fixed cadence.

Run code on a schedule

Define a scheduled function with defineScheduledFunction from @sanity/blueprints. Schedules accept either a UNIX cron expression or an explicit object with minute, hour, dayOfWeek, month, and dayOfMonth fields.

import {defineBlueprint, defineScheduledFunction} from '@sanity/blueprints'

export default defineBlueprint({
  resources: [
    defineScheduledFunction({
      name: 'expire-cache',
      event: {expression: '0 0 * * *'},
    }),
  ],
})

Inside the function, the scheduledEventHandler helper from @sanity/functions gives you typed access to the run context.

import {scheduledEventHandler} from '@sanity/functions'

export const handler = scheduledEventHandler(async ({context}) => {
  const time = new Date().toLocaleTimeString()
  console.log(`👋 Your Sanity Function was called at ${time}`)
})

Get started

Add a scheduled function with the CLI:

npx sanity@latest functions add --name expire-cache --type scheduled-function --language ts

Scheduled functions run independently of any single project, so they require an organization-scoped blueprint stack. If your stack is project-scoped, run npx sanity@latest blueprints promote before deploying.

Read the Create a Scheduled Function quickstart for a step-by-step walkthrough, or browse the Functions documentation.

Last Checked
13h ago
Latest
2.20.1
Tracking since Jan 22, 2026