Generated banner for Sanity Studio Upgrade
Generated logo for Sanity Studio Upgrade

Sanity Studio Upgrade

SanityOfficialPublished by Sanity

  • Skill
  • Workflow
  • Software development
  • MIT
  • No issues found

From the author

Produces a tailored Sanity Studio upgrade plan by inspecting the repository's installed versions, config, and source, then reporting only the breaking changes that actually apply. Covers Studio v3 and later; v2 projects are identified and redirected, not planned. Use this skill whenever someone wants to upgrade, migrate, or modernize a Sanity Studio across one or more major versions from v3 onward, asks what will break if they bump the `sanity` package, asks why their Studio broke after an upgrade, or asks how far behind their Studio is. Triggers on "upgrade sanity studio", "migrate our studio to v6", "bump sanity", "what breaks if we upgrade", "our studio is on an old version", "sanity upgrade plan", "is our studio out of date", "we are several majors behind". DO NOT load for upgrading non-Sanity dependencies, for Content Lake `apiVersion` questions, for content or schema migrations that change documents, or for setting up a new Sanity project.
— the description by Sanity

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Sanity Studio upgrade planner provides a focused, repository-specific migration path for Sanity Studio projects from v3 onwards. Rather than providing a generic changelog, it inspects the current project's lockfile, configuration, and source code to identify only the breaking changes that are actually relevant. It emphasizes verification over speculation, requiring live registry lookups for versions and strictly forbidding any automated modification of the user's codebase.

What it does

  • Detects the current Sanity Studio version and identifies if the project is out of scope (v2).
  • Calculates the upgrade span from the current installed version to the latest release.
  • Identifies applicable breaking changes based on the project's specific configuration and dependencies.
  • Provides a sequence of recommended intermediate upgrade stops to isolate potential issues.
  • Assesses plugin compatibility and identifies potential dependency conflicts.
  • Generates a comprehensive report in the repository root for team review.

When to use it

  • When you need to upgrade or modernize a Sanity Studio project from v3 or later.
  • When you want to understand the specific breaking changes involved in a version bump.
  • When you need to determine how far behind your current Studio version is.
  • When you are troubleshooting a broken Studio environment following a previous upgrade attempt.

Advantages

  • Reduces noise by filtering out breaking changes that do not apply to your specific configuration.
  • Prevents common upgrade pitfalls by verifying Node.js version floors and dependency coupling.
  • Provides a safe, read-only analysis that does not modify your repository files.
  • Offers a structured, step-by-step plan that is easy to share with team members.

Usage examples

“upgrade sanity studio”
The agent inspects the local lockfile, determines the current version, and generates a SANITY-UPGRADE-PLAN.md file detailing the necessary steps to reach the latest version.
“what breaks if we upgrade our sanity studio?”
The agent analyzes the current configuration and dependencies to list only the specific breaking changes that will impact the project, excluding irrelevant ones.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/sanity-io/sanity-studio-upgrade/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip sanity-studio-upgrade.zip -d .agents/skills

Documentation by Sanity

Sanity Studio upgrade planner

Generate an upgrade plan for the Sanity Studio in the current repository.

The plan's value is subtraction. A list of every breaking change between two versions already exists; it is called the changelog, and it is not useful to someone mid-upgrade. This skill produces the much shorter list of changes that apply to this repository, plus the questions only a human can answer.

Three rules that determine whether the output is trustworthy

1. Never state a version number from memory. Look it up, and confirm it exists.

Package versions change weekly. Every version in the report must come from either the repository's lockfile (for current state) or a live registry query (for targets). If a lookup is not possible, say so in the report rather than guessing. A plan that names a stale "latest" is worse than no plan, because the reader will act on it.

There is a second, worse version of this failure: a version number that was never published at all, arrived at by assuming one package tracks another's numbering. That one fails at install. Any version you are about to describe as "lockstep" or "matches core" needs an existence check first.

The same applies to API names. An export, an import subpath, or a function signature recalled from memory is a guess, and it is a guess the reader will paste into their editor. Read it from the package's README or exports map, or mark it with an inline // VERIFY: comment at the snippet itself. See section 6 of plugins.md and section 8 of report-template.md.

2. Read and report. Never modify.

Inspect files, run read-only shell commands, query registries. Do not edit package.json, config files, or source. The reader decides what to change; a planner that edits code will be turned off. If the user explicitly asks you to perform the upgrade afterward, that is a separate task they have consented to.

Where the line actually falls, because a vague version of this rule makes an agent hesitate over commands that are fine:

  • Never: writing to any file in the repository, installing into it, or mutating a lockfile. pnpm install, npm install, npm ci and npx without --no-install are all out.

  • Fine: reading files, and network reads such as npm view, a changelog fetch, or npm pack into a temporary directory outside the repository that you then delete.

  • Recommended to the reader, not run by you: pnpm install --lockfile-only is how a dependency prediction gets settled. It belongs in the report as a command for them to run, with what to expect from it. Do not run it yourself, because it rewrites their lockfile.

The one place this skill writes anything is unpacking a tarball to inspect type declarations, and section 6 of plugins.md shows how to keep that outside the project.

3. Separate what you verified from what you inferred.

Every finding lands in one of three buckets: confirmed applicable (you found the condition in the repo), needs a human answer (you cannot determine it from code), or not applicable (omit it entirely). Never pad the report with items you could not check. An honest short report beats a comprehensive-looking one.

One consequence is easy to miss and worth stating outright: a read-only planner has no dependency resolver, so it cannot know what a tree will contain after a change. Predictions about deduplication belong in the report as expectations with the verification command attached, never as findings. See package-coupling.md.

The verification rules, in one place

These are referred to by number throughout the reference files, so they are stated once here rather than restated in each. Everything above is R1 to R4; the rest are the specific ways those go wrong.

RuleWhere it bites
R1Never state a version from memory. Take current state from the lockfile, targets from a live registry query.version-lookup.md §1
R2Read and report. Nothing in the repository changes, nothing is installed, no lockfile is mutated.Above, and plugins.md §6
R3Separate verified from inferred. Never assert what a dependency tree will contain after a change; a planner has no resolver.package-coupling.md §6, plugins.md §5
R4Confirm every recommended version was actually published before it goes in the report.version-lookup.md §3
R5Every "X requires Y" names the manifest it was read from, at the version being installed at that stop. Requirements do not carry backwards across majors.version-lookup.md §6
R6Evaluate a version range, never read it. Anything with a ||, a <, or two comparators in one clause goes through semver.satisfies first.version-lookup.md §6
R7A name in an export list is not a working export. Check its declared type and any @deprecated tag; never means removed.plugins.md §6

R6 and R7 exist because both have already produced confident, wrong, specific advice: a Node version reported as unsupported when it was fine, and a required code edit filed under "already satisfied". They are cheap to run and they fail silently when skipped.

Scope: v3 and later

This skill plans upgrades from Studio v3 onward. Studio v2 is out of scope and is a hard stop.

The reason is not that v2 is hard. It is that v2 to v3 is a rewrite of the Studio's configuration and plugin layer rather than a dependency upgrade: the config format changed, the "parts" system that v2 plugins and overrides were built on no longer exists, packages were consolidated, and much of the v2-era plugin ecosystem was never ported. A generated plan for that boundary would be a list of confident-sounding specifics that this skill cannot verify, handed to someone about to spend weeks on the work. That is the worst possible output.

If you detect v2, stop and report rather than planning. Step 1 covers detection and what to say. Do not write a plan file for a v2 project.

Procedure

Step 1: Establish ground truth, and check the version floor

Read references/detect.md and follow it. It lists the files to read, the commands to run, and the facts to record.

The single most common mistake is reading versions from package.json. Caret ranges are not installed versions. Get the resolved version from the lockfile.

The second most common mistake is reading them from the wrong lockfile. A repository can contain several installs that disagree, so decide which tree the plan is for, take every version fact from that tree, and name it in the report header.

If there is no Sanity Studio in this repository, say so and stop.

Then check the floor before doing anything else. If the resolved sanity major is below 3, or there is no sanity package and the project depends on v2-era packages such as @sanity/base or @sanity/desk-tool, or there is a sanity.json and no sanity.config.*, this is a v2 project. Stop there and tell the reader:

  1. What you detected, and the evidence for it

  2. That this skill covers v3 and later, and why v2 to v3 is a different kind of project: a rewrite of the configuration and plugin layer, not a version bump

  3. That the right starting point is the official v2 to v3 migration guide, which they should find on the Sanity docs site rather than take from you second-hand

  4. That every v2-era plugin needs its own compatibility check, since many were never ported

  5. That once they are on v3, running this skill again will plan the rest of the span, which is a much smaller job

Be useful about it. A hard stop that explains itself and points somewhere is a good answer; a refusal is not. But do not soften it into a partial plan, and do not estimate the v2 to v3 work.

If both sanity.json and sanity.config.* are present, treat the resolved sanity version as authoritative. A leftover sanity.json in an otherwise v3+ project is worth mentioning as cleanup, not a reason to stop.

Step 2: Determine the span

You need two versions: from (resolved, installed) and to (the target).

Read references/version-lookup.md for how to query current versions and changelogs authoritatively.

The target is latest. Recommend it and give the reasoning. Landing short means doing the whole job and still being behind, and more importantly, current plugin versions track current core, so an older core with current plugins often produces a worse dependency tree than the newest release does. Report the target's publish date and a one-line fallback so the reader can apply constraints you cannot see, but that is disclosure, not a hedge, and it does not change the recommendation. Deviate only for a constraint the reader has actually stated, and then give the pin a review date.

State the span explicitly in the report. A v3 project and a v5.31 project are completely different jobs and the reader needs to know which one they have.

Step 3: Load only the boundaries you cross

Read references/boundaries.md first. It is a short index: the coverage bounds, the two-edge fetch rule, and a table saying which boundary files the span needs.

Then read only those files. Boundary content is split one file per major, so a span that crosses one boundary loads one file rather than all of them. A project on 5.31 crossing into v6 reads the index, boundary.v6.md and deprecations.md, and nothing else: the v4 and v5 files would cost context and invite findings that do not apply.

Each boundary.v*.md holds the crossing into that major plus the undeclared changes inside that major's line, because a span that crosses a boundary lands somewhere in the line above it and needs both.

Then read references/package-coupling.md. It applies to every span.

Step 4: Fetch what the references do not cover

references/boundaries.md declares two bounds on its within-line coverage, and both need respecting.

Fetch the changelog for any part of the span that falls above the upper bound or below the lower bound. A project starting below the lower bound needs two fetches: from its current version up to that bound, and from the upper bound to the target. The curated middle is used as-is.

The lower bound matters as much as the upper one, and it fails more quietly. Without it you will trust the file for a range it never examined and report that nothing there applies, which reads as a verified finding and is actually an unexamined gap. Never state that a range is clean unless it sits inside the declared bounds or you fetched it yourself and got real content back.

That last clause is not padding. Below the lower bound the upstream changelog does not exist at all, so the fetch returns almost nothing, and an empty fetch is not a clean range. boundaries.md says which range this affects and what to report instead. Reporting silence as "nothing applies" there is the single easiest way for this skill to produce a confident lie.

Pay particular attention to breaking changes that shipped in minor releases without being labelled as breaking. Those are the ones that surprise people, because nobody reads minor release notes across forty releases. The references list known examples; assume more exist.

Step 5: Test applicability

For each candidate change, find the condition in the repository. If you cannot determine it from code, it goes in the human questions section, not in the findings.

Examples of the difference:

  • "Custom auth providers now replace built-ins instead of appending" → grep the config for an auth block. Present or absent. Determinable.

  • "Default search strategy changed, results will shift" → whether that matters depends on how editors search. Not determinable. Ask.

Prefer conditions you can actually evaluate. grep -r "data-slate" is a real test. "Do you have complex custom components?" is not.

Step 6: Resolve the dependency graph

Apply references/package-coupling.md. This is where most upgrades actually break, and it is the part a changelog cannot tell you, because it depends on which packages this project happens to depend on directly.

The rule that matters most: for any @sanity/* package the project depends on directly, check what version the target sanity release depends on and match that, rather than taking the package's own latest. Taking latest for a shared package like @sanity/ui puts two majors of it in one dependency tree, which produces duplicate-context errors and unstyled components that look like unrelated bugs.

Step 6b: Assess plugins

Read references/plugins.md and follow it whenever the project has any plugin, which is nearly always.

Plugins block more upgrades than the Studio itself does, so they get their own pass and their own section in the report. Three things to carry into it: verify every plugin's compatibility rather than assuming it from who publishes it; let ownership decide the path once something is incompatible, not the verdict; and verify the API shape of any snippet you write against the package, separately from verifying its version, because a compatible version can still have renamed the export you are about to recommend.

Step 7: Write the report

Follow references/report-template.md exactly. Write it to SANITY-UPGRADE-PLAN.md in the repository root unless the user names a different path.

Write a file rather than only printing to the terminal. The plan usually needs to reach people who are not at this keyboard: a lead, a reviewer, a support engineer. A file can go into a pull request or be pasted into a thread; terminal output cannot.

Judgment calls worth making explicitly

Recommend a sequence, not a single jump. Crossing several majors in one commit makes failures impossible to attribute. Suggest intermediate stops at the last release of each major, and say why: it isolates each set of changes.

But a stop is only real if it is reachable. Each one needs its plugin versions resolved, because a plugin peering only recent majors can make an intermediate stop impossible to install. If you cannot resolve a stop, label it provisional rather than presenting the sequence as validated. Fewer verified stops beat more hypothetical ones.

When you drop a stop, name what it costs. Skipping one is often right: a boundary whose only applicable change is a Node floor is not worth a commit of its own, and a stop that cannot be reached without a forced duplicate major is worse than no stop. But the resulting hop then crosses two boundaries at once, and that is the exact thing the sequence exists to prevent. A plan that shows the evidence for skipping and stays silent on the consequence reads as though the trade were free.

So say both halves: why the stop was dropped, and that a failure in the combined hop could originate at either boundary, so debugging it means bisecting rather than reading the plan. One sentence covers it. The reader can accept the trade once they can see it.

Size the work honestly. If the project has two files importing @sanity/ui and no custom auth, say the upgrade is small. Inflating scope to look thorough wastes the reader's week. Equally, if the project imports from sanity/_internal or has a large custom component surface, say the upgrade is substantial rather than producing a plan that makes hard work look easy.

Make the plan survive being skimmed. It will be. A multi-boundary plan runs to several thousand words, and the person reading it is about to spend a week on the work, not an afternoon studying the document. So the top of the plan carries the whole thing in one screen, everything below it is reference to be read as the work reaches it, and findings are grouped by what the reader has to do rather than by which release the change came from. The specific failure to design against: a change that fails silently, sitting in a uniform table between two cosmetic ones, at identical visual weight. Length is not the enemy; flat weighting is.

Write for the engineer who has to do the work, and describe the tree rather than the team. This plan is usually read by someone at the customer, often someone who did not make the decisions that produced the current state. Keep the evidence, the specificity and the severity exactly as strong as the facts support: softening a real blocker or a real cost is a disservice, and an engineer can tell. What to drop is the implied verdict. "The deploy pipeline builds from the caret ranges rather than the lockfile" and "whoever set this up got it wrong" contain the same finding, and only one of them gets acted on. Reserve "you" for what to do next. Stay inside the upgrade, too: an unused dependency is not an upgrade finding, and a plan that drifts into general code review spends the reader's attention on things that do not gate the work.

Name what you could not see. Monorepos hide things: a tsconfig.json that extends a base file outside the repository, a workspace root that hoists React, a CI config in another directory. If a fact you needed was out of reach, put it in the human questions section by name. "I could not read moduleResolution because your tsconfig extends a file outside this repo" is useful. Silently omitting it is not.

Do not promise roadmaps. If asked whether a deprecated API will return or be removed, describe its current documented state and stop. Never speculate about future releases.

Reference files

FileRead when
references/detect.mdAlways, at step 1
references/version-lookup.mdAlways, at step 2 and step 4
references/boundaries.mdAlways, at step 3. Index: coverage bounds and which boundary files to read
references/boundary.v4.mdStep 3, if the span crosses into or sits in v4
references/boundary.v5.mdStep 3, if the span crosses into or sits in v5
references/boundary.v6.mdStep 3, if the span crosses into or sits in v6
references/deprecations.mdStep 3, always. Short, and not tied to a boundary
references/package-coupling.mdAlways, at step 6
references/plugins.mdStep 6b, whenever the project has plugins
references/report-template.mdStep 7

Files

references/detect.md

Detection: establishing repository ground truth

Everything here is read-only. Run it before consulting any breaking-change list, so the list can be filtered against facts rather than assumptions.

Contents

  1. Locate the Studio and check the version floor

  2. Resolve installed versions

  3. Read the configuration

  4. Resolve the TypeScript setup

  5. Detect custom code surface

  6. Detect deprecated and removed API usage

  7. Detect environment and CI

  8. Record sheet


1. Locate the Studio and check the version floor

A Studio is the directory containing sanity.config.ts (v3 and later) or sanity.json (v2). It may not be the repository root.

# v3+ Studio
find . -name "sanity.config.*" -not -path "*/node_modules/*"
# v2 Studio
find . -name "sanity.json" -not -path "*/node_modules/*" -maxdepth 3
# v2-era packages, which have no `sanity` package at all
grep -E '"@sanity/(base|desk-tool|default-layout|dashboard)"' package.json

Check the floor before anything else. Studio v2 is a hard stop for this skill. Any of these means v2:

  • The resolved sanity major is below 3

  • There is no sanity dependency and the project depends on v2-era packages such as @sanity/base or @sanity/desk-tool

  • There is a sanity.json and no sanity.config.*

If so, stop and report per the scope section of SKILL.md. Do not continue through the rest of this file, and do not write a plan file.

If both sanity.json and sanity.config.* exist, the resolved sanity version wins. A stray sanity.json in a v3+ project is cleanup worth mentioning, not a reason to stop.

If you find several Studios, ask which one to plan for, or plan for each and say so. Monorepos with one Studio per tenant or per environment are common.

2. Resolve installed versions

Read the lockfile, not package.json. "sanity": "^4.20.0" tells you what was requested; the lockfile tells you what is installed, and the gap between them is often several minor versions of breaking changes.

Every command in this section runs from the Studio directory, not the repository root. The Studio is frequently a subdirectory, and the node_modules and lockfile that matter are the ones governing it. Set the directory from the path chosen in step 1, and do not auto-pick when step 1 found more than one Studio:

STUDIO=studio        # or `.`, or apps/studio, packages/cms, whatever step 1 found
cd "$STUDIO"

Two things to check before trusting a path here. In a pnpm or Yarn workspace the lockfile usually sits at the workspace root while node_modules is local to the Studio, so the two facts come from different directories. And a Studio with no lockfile of its own is being resolved by a parent manifest, which is itself a finding worth reporting.

# Fastest reliable read, if node_modules exists
cat node_modules/sanity/package.json | grep '"version"'

# Otherwise, from the lockfile governing this directory (may be at the workspace root)
grep -A2 '"sanity@' package-lock.json | head -20     # npm
grep -A2 "^  sanity@" yarn.lock | head -20           # yarn
grep -A2 "  sanity@" pnpm-lock.yaml | head -20       # pnpm

# Whole tree at once, if installed
npm ls sanity @sanity/vision @sanity/ui @sanity/icons @sanity/client styled-components react react-dom

Record the resolved version of: sanity, every @sanity/* package, react, react-dom, styled-components, and every third-party plugin (anything matching sanity-plugin-* or that appears in the config's plugins array).

Note the package manager and its version. It determines whether deduplication is overrides, resolutions, or pnpm.overrides, and pnpm's stricter resolution changes how duplicate majors behave.

Report duplicates that already exist

Any package appearing more than once in the tree is a finding to report now, not only something to verify after the upgrade.

Two majors of a shared package such as @sanity/ui, or a plugin dragging in @sanity/util and @sanity/types from a different Studio major than the installed core, means the tree is already in the state that produces duplicate-context errors and unstyled components. That is worth telling the reader before they change anything, because it may explain bugs they already have, and because fixing it first removes a variable from the upgrade.

Read the dependency tree output rather than only the top-level list. Nested entries are where this shows up:

pnpm why --json @sanity/ui @sanity/icons @sanity/client @sanity/util @sanity/types react styled-components
# npm
npm ls --all --json @sanity/ui @sanity/icons @sanity/client @sanity/util @sanity/types react styled-components

Use the package manager's own output, and treat the grep below as a last resort. pnpm why and npm ls parse the lockfile with the tool that wrote it; a regex does not. It will keep working when the lockfile format changes and it cannot confuse one package's name for a substring of another's. --json also makes the result parseable rather than scraped, which matters when you need the parent that pulled a version in, not just the version.

Reach for the lockfile directly only when neither is available: no node_modules, or the matching package manager is not on the machine. Then anchor the package name and keep prerelease suffixes, or you will report findings that do not exist. A loose pattern turns [email protected] into a styled-components v5 duplicate, @sanity/[email protected] into a nonexistent stable 5.0.0, and @sentry/[email protected] into a React version:

grep -oE "[/'\"]@?[a-zA-Z0-9@/._-]+@[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?" pnpm-lock.yaml \
 | sed -E "s|^['\"/]+||" \
 | grep -E "^(@sanity/(ui|icons|client|util|types)|styled-components|react)@" \
 | sort -u

Put what you find in the report as a finding with the specific versions and which dependency pulled each one in. Do not reduce it to a generic "check for duplicates after installing" line.

This is the observed tree, so it is a finding. What the tree will look like after the upgrade is a prediction, and package-coupling.md covers why a planner must not assert it.

Watch for more than one dependency tree

A repository can contain several installs, and they can disagree. Common shapes: a Studio in a subdirectory with its own lockfile while the repository root has another; a root manifest that depends on sanity for linting; a monorepo with a Studio and a frontend using different package managers.

When this happens:

  • Record which tree you are planning for, and say so in the report header

  • Record the sibling trees' sanity versions too, because two different Studio majors resolving in one repository will confuse editor TypeScript and can mislead anyone reading versions from the wrong place

  • Take every version fact from the target tree's lockfile. A version read from the wrong tree looks authoritative and is simply about a different install

  • If a deployment pipeline builds from a different tree or a different manifest than the one you are planning for, that mismatch is a finding on its own

3. Read the configuration

Read these in full, not by grep. They are short and they answer many questions at once.

  • sanity.config.ts (or .js, or the workspace array if there are several)

  • sanity.cli.ts (or .js)

  • package.json, including the scripts block

  • sanity.json if this is v2

From sanity.config.*, record whether each of these is present:

Look forWhy it matters
auth block, auth.providers, auth.mode, auth.loginMethodHighest-severity v6 change; affects login
search or enableLegacySearchWhether the v6 search default change is already overridden
plugins array contentsEach entry needs a compatibility check
document.actions or document.badgesCustom actions need testing against newer document contexts
toolsCustom tools often reach into Studio internals
form or beta.formSome of these options were removed
scheduledPublishingDeprecated; separate migration
Multiple workspacesWorkspace routing behavior changed
basePathInteracts with the CLI-level basePath

From sanity.cli.ts, record:

Look forWhy it matters
deployment.autoUpdatesDetermines the whole auto-update section, and whether the 6.9.0 enforcement change matters
deployment.appIdNeeded for version-channel selection
studioHostSee the trap note in package-coupling.md
viteCustom Vite config is the most likely v6 build failure
reactStrictModeWhether the v6 default is already set
typegen, schemaExtractionWhether codegen is wired up
Values from a function or env varsSignals multi-tenant or multi-environment deploys

If config values come from a helper function or environment variables, read the helper. A templated studioHost or a per-tenant projectId means there are multiple deployments to upgrade, which belongs in the plan.

4. Resolve the TypeScript setup

cat tsconfig.json

You need the effective moduleResolution. Recent @sanity/* packages resolve types through exports and drop typesVersions, so legacy node10 resolution cannot resolve subpath types at all.

If tsconfig.json has an extends pointing outside the repository, follow it if you can and say so if you cannot. An unresolved moduleResolution is a genuine blocker for any plan involving subpath imports, and it belongs in the human questions section by name rather than being quietly skipped.

# Resolves the full inherited config, including anything reached via `extends`.
# `--no-install` is required: plain `npx tsc` downloads TypeScript when it is not
# already present, and this skill does not install anything.
# Select the fields rather than truncating: `head` will cut a long config off
# above `moduleResolution` and leave you reporting it as unset.
npx --no-install tsc --showConfig 2>/dev/null \
  | grep -E '"(moduleResolution|module|target|jsx|strict|paths)"'

If that prints nothing, TypeScript is not installed in this directory. Read tsconfig.json and follow its extends chain by hand instead of installing anything, and if the chain leaves the repository, say so rather than guessing. An unresolved moduleResolution belongs in the human questions section by name.

Also record whether typescript and @types/react-dom are actual devDependencies or are being inherited from a workspace root.

5. Detect custom code surface

The size of the upgrade is mostly determined by this. Count, then look at examples.

# Which Sanity UI primitives are used, and how much
grep -rn "from '@sanity/ui'" src/ --include=*.ts --include=*.tsx | wc -l
grep -rn "from '@sanity/ui'" src/ --include=*.ts --include=*.tsx

# Icons: barrel imports break when @sanity/icons drops them
grep -rn "from '@sanity/icons'" src/ --include=*.ts --include=*.tsx

# Custom Studio components
grep -rln "defineField\|defineType\|defineArrayMember" src/ | wc -l
grep -rn "components:\s*{" src/ --include=*.ts --include=*.tsx | head -20

# Reaching into Studio internals: highest risk, no compatibility guarantee
grep -rEn "sanity/_internal|sanity/_singletons" src/

# Portable Text editor DOM coupling, in source, styles, and tests
grep -rn "data-slate" . --include=*.ts --include=*.tsx --include=*.js --include=*.css --include=*.scss --include=*.styl
grep -rn "data-slate" e2e/ tests/ cypress/ playwright/ 2>/dev/null

If sanity/_internal or sanity/_singletons imports exist, raise it prominently. Internal APIs carry no compatibility guarantee and change without changelog entries. A multi-major upgrade over internal APIs is a different, riskier project, and the honest recommendation may be to get off them first.

Look at one or two representative custom components rather than all of them. One example reveals the patterns in use, which is what determines whether a rename is twenty edits or four hundred.

6. Detect deprecated and removed API usage

grep -rEn "unstable_use|useTimeLineStore|useDocumentVersionInfo|enableLegacySearch|enhancedObjectDialog|sheetList|useClickOutside|useElementRect|useForwardedRef|useArrayProp|ConditionalWrapper" src/ sanity.config.* sanity.cli.*

# Sanity UI props removed in its v4
grep -rn "space={" src/ --include=*.tsx | wc -l
grep -rEn "<Grid[^>]*(columns|rows)=" src/ --include=*.tsx
grep -rEn "focusFirst|focusLast|boundaryElement|allowedAutoPlacements" src/ --include=*.tsx

# Previews that resolve through a reference: relevant to the search strategy change
grep -rEn "(title|subtitle|description):\s*'[A-Za-z0-9_]+\.[A-Za-z0-9_.]+'" src/

7. Detect environment and CI

node -v
cat .nvmrc .node-version 2>/dev/null
grep -n '"engines"' -A4 package.json

# Sanity CLI invocations: newer CLIs reject unknown flags instead of ignoring them
grep -rEn "sanity (start|dev|build|deploy|undeploy|schema|schemas|typegen|dataset|datasets|graphql|tokens|exec)" \
  package.json .github/ .gitlab-ci.yml Dockerfile* 2>/dev/null

The Node version that matters is the one in CI and on the build host, not the one on this machine. If you can only see the local version, say so and ask.

Also check whether the Studio shares a repository with other applications. If it does, a Node bump should be scoped to this project's CI job rather than the whole runner, and hoisted React may be shared with an app that pins a different version.

8. Record sheet

Carry this forward into the report. If a value is unknown, write "unknown" rather than guessing, and add it to the human questions.

Studio path:
Tree planned for:         path to the lockfile every version below came from
Other trees in repo:      paths + their resolved `sanity` versions
Config format:            sanity.config.* (v3+ required; v2 is a hard stop)
Workspaces:               names, count
Multiple deployments:     yes/no, how they differ

sanity (resolved):
@sanity/* (resolved):
Third-party plugins:
react / react-dom:
styled-components:
Duplicate packages:
Package manager:
Pre-existing duplicates:  package@version pairs + what pulled each in

Node: local / CI / build host
engines.node:
moduleResolution:         value, or unresolved and why
typescript a devDep:      yes/no/inherited

auth block:               absent | providers | mode | loginMethod
search config:            absent | strategy
autoUpdates:              true | false | unset
appId:                    set | unset
studioHost:               set | unset
custom vite config:       yes/no
reactStrictMode:          set | unset

@sanity/ui import sites:  count
@sanity/icons imports:    count, barrel or subpath
custom components:        count + kinds
document actions/badges:  yes/no
custom tools:             yes/no
structure customization:  yes/no
internal API imports:     yes/no  ← raise prominently if yes
data-slate references:    yes/no  ← raise prominently if yes
deprecated APIs found:
CLI invocations in CI:

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Node.js

The author says: Requires network access to the npm registry and sanity.io docs for version and changelog lookups

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Sanity
Publisher
Sanity (Official)
Source
sanity-io/agent-toolkit · skills/sanity-studio-upgrade · commit 3ed3a14
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Sanity.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download sanity-studio-upgrade.zip