How I audit and prune unused Sanity document types to reclaim Studio performance
May 04, 2026 · 5 min read
Why Studio performance degrades over time
After two years on a client project, their Sanity Studio was loading 840 kB of JavaScript just to render the document list. The culprit: 47 schema types, of which only 22 were actively used in production. Every unused schema pulls in validation logic, preview components, and input field code. I needed a safe audit path that wouldn't break existing content or CI.
This post walks through the four-step process I use to identify, verify, and remove dead schemas without touching production documents.
Step one: list all schema types and count documents
I start with a GROQ query against the dataset to count documents per type. This runs in the Vision plugin or via the Sanity CLI:
// Run in Sanity Vision or via `sanity documents query`
{
"counts": *[!(_id in path("drafts.**"))] | order(_type asc)
| {"type": _type, "count": count(*[_type == ^._type])}
| group(_type) {"_type": _type[0], "total": sum(count)}
}This returns a flat list like {"_type": "pressRelease", "total": 0}. Any type with total: 0 is a candidate for removal. I export this to a CSV and cross-reference with the schema folder.
On that 47-schema project, 18 types had zero documents. Another 7 had fewer than 5 documents created in 2023–2024, all drafts that were never published. That's 25 schemas consuming bundle space for no reason.
Step two: check for hidden references in arrays and blocks
A zero-document type might still be referenced inside portable text blocks or reference arrays. I run a second query to scan _ref fields:
// Find any document that references a given type ID
*[references(*[_type == "pressRelease"]._id)] {_id, _type}If this returns an empty array, the type is safe to remove. If it returns results, I check whether those parent documents are themselves orphaned. On one project, teamMember had zero standalone docs but was referenced in a aboutPage singleton that was actively used. I kept the schema.
I script this check for all zero-count types using the Sanity CLI and Node:
// scripts/audit-refs.ts
import {createClient} from '@sanity/client'
const client = createClient({
projectId: 'abc123',
dataset: 'production',
useCdn: false,
apiVersion: '2024-01-01',
token: process.env.SANITY_TOKEN,
})
const candidateTypes = ['pressRelease', 'oldBlogCategory', 'legacyAuthor']
for (const type of candidateTypes) {
const refs = await client.fetch(
`*[references(*[_type == $type]._id)] {_id, _type}`,
{type}
)
console.log(`${type}: ${refs.length} references`)
}This takes about 90 seconds on a 12k-document dataset. I log results to a JSON file and review in VS Code.
Step three: remove schema files and test Studio build
Once I've confirmed a type is unused, I delete its schema file and remove the import from sanity.config.ts. Then I run sanity dev locally. If the Studio compiles without errors, I check the bundle size:
NODE_ENV=production sanity build --statsThe --stats flag outputs a JSON file with chunk sizes. I compare before/after using a script that diffs sanity-build-stats.json. On that 47-schema project, removing 18 types reduced the main bundle from 840 kB to 680 kB—a 19% drop.
I also open the Studio in a local browser and click through all active document types to ensure no preview components or custom inputs broke. On one project, a shared linkField input was imported by a deleted schema but also used by 12 active ones. Deleting the schema didn't break the Studio, but I had to keep the shared input file.
Step four: deprecate instead of delete if documents might return
If stakeholders might revive a document type later, I don't delete the schema. I add hidden: true to the type definition:
// schemas/pressRelease.ts
import {defineType} from 'sanity'
export default defineType({
name: 'pressRelease',
type: 'document',
title: 'Press Release',
hidden: true, // Removes from Studio UI but keeps validation logic
fields: [
{name: 'title', type: 'string'},
// …
],
})This keeps the schema in the bundle (so no bundle savings) but removes it from the Studio's "Create new document" menu and desk structure. Old documents remain queryable via GROQ. I use this for client-managed types that might get re-enabled in Q3.
Measuring Studio bundle impact
I track Studio bundle size in a studio-metrics.json file committed to the repo. After each schema prune, I log:
{
"date": "2026-05-04",
"totalSchemas": 29,
"mainBundleKB": 680,
"studioLoadTimeMs": 1240
}Studio load time is measured by opening the network panel in Chrome, hard-refreshing, and checking the "Load" event time. On the 47-schema project, pruning 18 types dropped load time from 2.1s to 1.4s on a desktop Ethernet connection. On mobile 4G, it went from 4.8s to 3.2s.
I also run Lighthouse on the Studio URL (https://yourproject.sanity.studio/) and check the JavaScript coverage report. Unused schemas often pull in 60–80 kB of unreachable code.
When not to remove schemas
I don't remove a schema if:
- It's referenced in a migration script that might be re-run.
- It's used in a webhook or custom API route handler outside Sanity.
- It's part of a modular field shared across multiple types (like a
seoobject). - Documents were soft-deleted (moved to a separate dataset) but might be restored.
On one project, legacyBlogPost had zero docs in production but was still used in a staging dataset for QA. I kept the schema to avoid breaking the staging Studio.
Results
Across four client projects in 2025–2026, schema pruning reduced Studio bundle size by an average of 16% and improved perceived load time by 0.8–1.2 seconds. The largest win was a 320 kB drop on a 3-year-old project with 62 schemas, 31 of which were unused.
I run this audit every six months or when onboarding a new developer. It takes 90 minutes and pays off immediately in faster Studio boot times and cleaner schema folders.
Related posts
All posts →How I use Sanity's structure builder to hide draft noise and speed up editor workflow
May 03, 2026 · 4 min read
Sanity Studio ships with every draft visible by default. I use structure builder filters and custom panes to cut editor cognitive load by 70%.
How I Structure Sanity Schemas to Avoid Query Waterfalls in Next.js
Apr 27, 2026 · 5 min read
Denormalising references and embedding common fields in Sanity schemas cuts server component render time by 40–60%. Here's the pattern I ship.
How I Handle Conditional GROQ Projections to Cut Query Payload by 60%
May 02, 2026 · 5 min read
A pattern for projecting only the fields your Next.js components actually render, using GROQ coalesce and select operators to prune unused blocks.