Sanity staging and production on one dataset: how to split it safely
By Nayan Kyada · · 6 min read
Part of The Sanity + Next.js Guide
If your Sanity staging and production environments read from the same dataset, you are one bad script or one half-finished edit away from a public mistake. I see this setup on inherited projects constantly: a Vercel preview URL that looks like staging but is wired to the live dataset. This post covers the symptoms, what it costs to ignore, and the safe path to a proper split.
Symptoms that you share one dataset
You probably have this problem if any of these sound familiar:
- Editors are afraid to create a draft because "it might show up on the site".
- The developer tests a schema change by editing live documents.
- The staging site and the production site show identical content, always.
- There is one
.envfile, or the preview and production environment variables in Vercel have the same dataset value. - Nobody can say which documents are test data. Titles like "test 2" or "DELETE ME" are live.
Check it in two minutes. Open your Vercel project settings, compare the Sanity dataset variable across the Production and Preview environments, and open the Studio config to see which dataset it points to. If they all say production, that is your answer.
What it costs to ignore
The risk is not abstract. A shared dataset means:
- Migration scripts run against live content. A patch script with a wrong filter rewrites real pages. Sanity has history, but restoring document by document under pressure is slow.
- Schema changes land before the code is ready. You rename a field in the Studio, editors fill it in, and the production frontend still queries the old name. Pages render blank sections.
- No safe place to rehearse a redesign or a migration. Every big change is tested in front of your customers.
- Preview deployments leak. Draft content for a launch can be visible through a preview URL that someone shares in Slack.
One broken hero on your homepage costs more than the afternoon it takes to fix this properly.
The fix path
The safe order is: back up, create the new dataset, copy content, change config, then change the workflow. Do not start by editing environment variables.
1. Back up production first
Export before touching anything. The archive includes documents and assets.
# run from your Studio folder
npx sanity dataset export production ./backups/production-before-split.tar.gzStore that file somewhere outside the repo. It is your undo button.
2. Create a staging dataset and seed it
npx sanity dataset create staging
npx sanity dataset import ./backups/production-before-split.tar.gz stagingIf you prefer, the CLI also has a dataset copy command that does this in one step. Either way, open the new dataset in the Studio afterwards and confirm that images load, because assets are the part that most often goes wrong on a first attempt.
A word on privacy: a copy of production content may contain real customer data such as form submissions or author emails. If so, scrub those documents in staging before giving anyone access.
3. Expose both datasets in the Studio
Editors need to know which environment they are in. Workspaces make that obvious, with a separate URL path and name for each.
// sanity.config.ts
import {defineConfig} from 'sanity'
import {structureTool} from 'sanity/structure'
import {schemaTypes} from './schemaTypes'
const projectId = process.env.SANITY_STUDIO_PROJECT_ID!
const shared = {
projectId,
plugins: [structureTool()],
schema: {types: schemaTypes},
}
export default defineConfig([
{
...shared,
name: 'production',
title: 'Production',
basePath: '/production',
dataset: 'production',
},
{
...shared,
name: 'staging',
title: 'Staging',
basePath: '/staging',
dataset: 'staging',
},
])Most teams give editors the production workspace only and keep staging for developers and for reviewing big changes. If you want editors to rehearse, tell them clearly that nothing in staging reaches the live site.
4. Drive the dataset from environment variables
The frontend should never hardcode a dataset name. Read it from the environment so Vercel can scope it per environment.
// src/sanity/client.ts
import {createClient} from 'next-sanity'
export const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
apiVersion: '2025-01-01',
useCdn: true,
})In Vercel, set NEXT_PUBLIC_SANITY_DATASET to production for the Production environment and staging for Preview. Redeploy, because public variables are inlined at build time.
5. Fix everything else that points at the old setup
This is the step that gets skipped, and it is why staging "works" but behaves oddly:
- Webhooks. The revalidation webhook is configured per project with its own dataset filter. Create a separate one for staging that calls your preview URL, with its own secret. Otherwise staging edits revalidate production, or nothing revalidates at all. See my notes on Sanity webhook revalidation with batched tags.
- API tokens. Read tokens used for draft mode should be separate for each environment, so a leaked preview token cannot read production drafts.
- CORS origins. Add your staging and preview origins in the project settings so the Studio and any client-side fetches work.
- Preview and draft mode. Point the Presentation or preview setup at the right dataset, as covered in Sanity draft mode preview with Next.js App Router.
How to verify the split worked
Make a deliberate change in staging, such as adding "STAGING TEST" to a page title, and publish it. It should show on the preview URL and nowhere on production. Then check that a production edit does not appear on staging. Finally run a dry-run of any migration script against staging and confirm the document counts match your expectation before it ever sees production.
Also confirm that your build does not fall back to a default dataset when the environment variable is missing. A silent fallback to production quietly rebuilds the original problem.
What to ask a developer
Whether you are hiring me or someone else, ask these before work starts:
- Which datasets exist today, and which environments point at each?
- How will you back up production before any change, and where is the backup stored?
- Will webhooks, tokens and CORS be separated too, or only the dataset name?
- How do schema changes move from staging to production without a gap where the frontend breaks?
- What goes in the handover? My Sanity project handover checklist lists what you should expect, and taking over an existing Next.js project covers the wider inherited-codebase picture.
For most sites this split is a few hours to a day, depending on how tangled the webhooks and previews are. It often falls out of a broader review, such as a Performance & Sanity audit from $800.
This is work I do: if you suspect your Sanity site is running staging and production on one dataset, see my Sanity and Next.js services and I can scope the split for you.
Frequently asked questions
01Can Sanity staging and production share one dataset?
Technically yes, but every editor change, test document and migration script then touches live content. A separate staging dataset in the same project costs little and removes that risk.
02How do I copy a Sanity production dataset to staging?
Export the production dataset with the Sanity CLI, create a new dataset, and import the archive into it. Sanity also has a dataset copy command, and either route keeps documents and assets together.
03Does a second Sanity dataset cost extra?
Dataset limits and pricing depend on your plan, and they change over time. Check the current Sanity pricing page before you create one.