Managing Sanity Datasets for Staging and Production
This guide belongs to Sanity Studio Customization and covers environments in Sanity: using separate datasets for production and staging, pointing the Studio and frontend at the right one, refreshing staging from production, controlling access, running content migrations safely and keeping webhooks and caches apart.
In Sanity, the schema lives in code and content lives in datasets. That split shapes how environments work: a schema change is deployed by deploying the Studio, while each environment’s content sits in its own dataset. A staging environment is therefore a Studio deployment and a frontend deployment configured to use the staging dataset, plus a routine for keeping that dataset realistic. Getting the configuration and routines right prevents the classic mistakes of editing production content while testing, or testing against stale content that hides problems.
The Problem
A company’s Studio used a dataset name hard-coded in sanity.config.ts, and developers switched it by editing the file. One developer deployed the Studio with staging still set, so editors spent a morning publishing to the staging dataset while the production site stayed unchanged. Separately, the staging dataset had not been refreshed for eight months, so a migration tested there passed but failed in production on document shapes that only existed in newer content.
How to Manage Datasets
Configuration, not code edits. Read the dataset name from environment variables in both Studio and frontend, and set them per deployment. Better, configure a Studio workspace per dataset, so editors see which environment they are in and cannot confuse them.
Refresh staging regularly. Copy production to staging on a schedule with the dataset copy feature or with export and import, and after copying, apply any pending staging-only migrations. Realistic staging content is what makes migration and frontend tests meaningful.
Separate access. Give editors access to production; give developers and testers access to staging. Private datasets require tokens for reads, which lets you keep staging content, which may include unreleased material, out of public reach.
Migrations through staging. Run content migrations with Sanity’s migration tooling on staging first, check the result with the staging frontend, then run them on production.
Webhooks per dataset. Each dataset’s webhook points at its own frontend, with its own secret.
Implementation
A Studio with two workspaces, each bound to a dataset, makes the environment visible in the Studio’s workspace switcher.
// sanity.config.ts
import { defineConfig } from "sanity";
import { structureTool } from "sanity/structure";
import { schemaTypes } from "./schema";
const shared = { projectId: process.env.SANITY_STUDIO_PROJECT_ID!, 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", icon: () => "🧪" },
]);
A scheduled job refreshes staging from production. Dataset copy is available on some plans; export and import works everywhere.
# jobs/refresh-staging.sh: run weekly from CI with a token that can read production and write staging
set -euo pipefail
npx sanity dataset export production production.tar.gz --no-drafts
npx sanity dataset import production.tar.gz staging --replace
node scripts/anonymize-staging.mjs # scrub personal data, then re-run any staging-only migrations
rm production.tar.gz
Content migrations are written with Sanity’s migration tooling and run with a dry run first.
// migrations/price-to-object/index.ts
import { defineMigration, at, set } from "sanity/migrate";
export default defineMigration({
title: "Convert price number to { amount, currency }",
documentTypes: ["product"],
migrate: {
document(doc) {
if (typeof doc.price !== "number") return;
return at("price", set({ amount: doc.price, currency: "EUR" }));
},
},
});
npx sanity migration run price-to-object --dataset staging # dry run: prints the patches
npx sanity migration run price-to-object --dataset staging --no-dry-run
# after checking the staging frontend:
npx sanity migration run price-to-object --dataset production --no-dry-run
Deploy the frontend code that reads both shapes before running the migration on production, as in any expand-and-contract change described in migrating content models.
Personal data in staging
Copying production into staging copies whatever personal data production holds, such as author contact details or form submissions stored as documents. Exclude such types from the export, or anonymize them after import with a migration, and keep staging private. The staging frontend should also send noindex and require authentication.
Frontend configuration per environment
The frontend must follow the same discipline as the Studio. Read the project id, dataset and API version from environment variables set per deployment, and fail at startup if the dataset is missing, rather than defaulting to production. Preview deployments for pull requests should normally use the staging dataset, so reviewers see realistic content without any risk to production caches or analytics. Tag cached data with the dataset name as well as document ids when several deployments share a cache layer, so a staging revalidation can never purge production entries. Finally, show the dataset in the frontend’s draft-mode banner and in a small indicator on staging deployments, which spares testers from wondering which content they are looking at when a result seems wrong.
Configuration Reference
| Item | Recommendation | Why |
|---|---|---|
| Dataset selection | environment variables or workspaces | No accidental cross-environment edits. |
| Staging refresh | weekly export and import, or copy | Realistic tests. |
| Staging visibility | private, frontend behind auth | Unreleased content stays private. |
| Migrations | dry run, staging, then production | Problems found early. |
| Webhooks | per dataset, own secret | Staging never touches production caches. |
| Personal data | excluded or anonymized in staging | Privacy obligations. |
Gotchas & Edge Cases
- Assets. Exports include asset files unless excluded; large media libraries make exports slow. Use dataset copy where available, or exclude assets and reference production assets carefully.
- Replacing staging while in use. A refresh with
--replaceremoves staging-only test content. Announce refreshes and keep test fixtures in a seed script. - Schema versions. A staging Studio deployed with a newer schema than production may see documents it cannot display correctly after a refresh; that is expected and useful for testing migrations.
- Tokens. Refresh jobs need read access to production and write access to staging; use a dedicated token and scope it as narrowly as the plan allows.
Worked Example
The company replaced its hard-coded dataset with two workspaces, a clearly marked staging workspace and a weekly refresh job with anonymization of author contact fields. Migrations moved to Sanity’s migration tooling with dry runs on the refreshed staging dataset. The next migration, converting prices to objects, revealed on staging that some older products had prices stored as strings, which the migration was adjusted to handle before it ran on production. No editor has published to the wrong dataset since, and testers now report problems with realistic content rather than with the stale test entries they used to work around.
Preview Datasets and Feature Work
Some teams add short-lived datasets for large features, such as a site redesign with a new content structure. A feature dataset, copied from production, lets editors build and preview the new content with the feature branch’s Studio and frontend, without disturbing staging. When the feature ships, its content can be migrated into production with scripts, or the migration can be replayed on production content that has moved on in the meantime. Feature datasets cost little to create, but each needs cleanup; name them after the feature and delete them after release. Keep the number small, since each dataset is another place where content, webhooks and access need managing.
Rollout Checklist
- Configure datasets per environment through workspaces or environment variables.
- Keep staging private and refresh it from production on a schedule.
- Exclude or anonymize personal data in staging copies.
- Run migrations with dry runs, on staging first, then production.
- Configure one webhook per dataset with its own secret.
- Mark the staging Studio clearly so editors never confuse environments.
Frequently Asked Questions
Do we need a staging dataset at all?
For teams that change the schema or run migrations, yes. Very small sites can test against production with drafts, but migrations need a separate dataset.
Can staging and production share a Studio deployment?
Yes, with workspaces. One deployment serves both, and the workspace switcher shows which dataset is active.
How long does a dataset copy take?
From seconds to hours depending on size and assets. Schedule refreshes outside working hours and notify testers as soon as they complete.
Can we give each developer a personal dataset?
On plans with enough datasets, yes, created from a staging export. Most teams find a shared staging dataset plus local seed data sufficient and much easier to manage.
Should drafts be copied to staging?
Usually not. Exporting without drafts keeps staging focused on published content and avoids copying unfinished or embargoed work into a less protected environment.