
How to Deploy Sanity Studio to the Web
The quickest way to deploy Sanity Studio is to run npx sanity deploy from your Studio folder. The CLI builds the Studio, asks you to choose a hostname the first time, and publishes it to a free URL like https://your-name.sanity.studio. Alternatively, run npx sanity build and upload the static dist/ folder to any host such as Vercel or Netlify, with single-page app routing and your domain added to the project's CORS origins, or embed the Studio as a route in a Next.js app with NextStudio from next-sanity/studio.
Each option has trade-offs around URLs, automatic updates, deployment pipelines and how closely the Studio sits with your website. This guide covers all three approaches step by step, how to configure sanity.cli.ts for repeatable deploys, how to deploy from CI with GitHub Actions, how to register a self-hosted Studio, how to remove a deployment and how to fix the most common problems.
Choosing a Deployment Option
| Sanity hosting | Self-hosted static build | Embedded in Next.js | |
|---|---|---|---|
| Command | sanity deploy | sanity build + your host | Part of your app's build |
| URL | *.sanity.studio | Your own domain | A route on your site, such as /studio |
| Cost | Included in all plans | Your host's pricing | Your app's hosting |
| Studio auto-updates | Yes | Yes, if built with sanity build | No |
| CORS setup needed | No | Yes, with credentials | Yes, with credentials |
| Best for | Most teams | Custom domains, internal networks | One codebase and one deployment |
Sanity recommends its own hosting for most projects, because the Studio gets the latest platform integrations and needs no extra configuration. If you're still setting up your project, start with how to set up your first Sanity project.
Option 1: Deploy to Sanity Hosting
Step 1: Check Your Configuration
Make sure sanity.cli.ts points at the right project and dataset:
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
api: {
projectId: 'abc123xy',
dataset: 'production',
},
deployment: {
autoUpdates: true,
},
})
The Sanity project structure post explains how this file relates to sanity.config.ts.
Step 2: Run the Deploy Command
npx sanity deploy
If you aren't logged in, the CLI asks you to log in first. On the first deploy it then asks for a hostname:
? Studio hostname (<value>.sanity.studio): my-company
Hostnames can contain letters, numbers and hyphens, but can't start or end with a hyphen, and must not already be taken. The CLI builds the Studio, uploads it and prints the URL:
✔ Deploying to sanity.studio
Success! Studio deployed to https://my-company.sanity.studio/
You can also pass the hostname directly to skip the prompt:
npx sanity deploy --url my-company
Step 3: Save the App ID
After the first deploy, the Studio appears under Studios in your project at sanity.io/manage, with an app ID. Add it to sanity.cli.ts so future deploys, from any machine or pipeline, update the same Studio without prompting:
export default defineCliConfig({
api: { projectId: 'abc123xy', dataset: 'production' },
deployment: {
appId: 'your-studio-app-id',
autoUpdates: true,
},
})
Older projects set studioHost: 'my-company' instead. That still works but is deprecated, and deployment.appId takes precedence if both are set.
Step 4: Invite Your Editors
Send editors the URL and invite them to the project from sanity.io/manage or the CLI:
npx sanity users invite editor@example.com --role editor
They log in with their own accounts, and what they can do depends on their role and your plan. The Sanity pricing guide explains roles and seats.
What Auto-Updates Means
With autoUpdates: true, the deployed Studio loads the core Sanity packages at runtime rather than freezing them into the bundle. Patch and minor releases reach your editors automatically, while your schema and custom code stay as you deployed them. You only redeploy when you change your own code. With auto-updates turned off, the Studio stays on the exact version you built until you upgrade and redeploy.
Useful Deploy Flags
| Flag | What it does |
|---|---|
--url <hostname> | Sets the hostname without prompting |
--no-build | Deploys an existing dist/ build instead of rebuilding |
--dry-run | Reports what would be deployed without uploading |
--source-maps | Includes source maps for easier debugging |
--no-minify | Skips minification for a faster build |
--schema-required | Fails the deploy if the schema can't be deployed |
-y | Unattended mode, accepting defaults |
A deploy also uploads your schema to the Content Lake, which lets platform features like the Dashboard and AI tools understand your content model.
Deploying from CI with GitHub Actions
Deploying from a pipeline means every merge to main updates the Studio. Local login sessions aren't available in CI, so you authenticate with a token instead.
- In sanity.io/manage, open your project's API settings and create a token with permission to deploy Studios.
- Add it to your repository secrets as
SANITY_AUTH_TOKEN. - Use environment variables for project settings so the same config works everywhere.
// sanity.cli.ts
import { defineCliConfig } from 'sanity/cli'
export default defineCliConfig({
api: {
projectId: process.env.SANITY_STUDIO_PROJECT_ID,
dataset: process.env.SANITY_STUDIO_DATASET,
},
deployment: {
appId: process.env.SANITY_STUDIO_APP_ID,
autoUpdates: true,
},
})
# .github/workflows/deploy-studio.yml
name: Deploy Sanity Studio
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx sanity deploy -y
env:
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
SANITY_STUDIO_PROJECT_ID: ${{ vars.SANITY_STUDIO_PROJECT_ID }}
SANITY_STUDIO_DATASET: production
SANITY_STUDIO_APP_ID: ${{ vars.SANITY_STUDIO_APP_ID }}
Remember that SANITY_STUDIO_ variables are written into the Studio's JavaScript, so they're fine for project IDs and dataset names but never for secrets. The auth token isn't prefixed and isn't bundled.
Option 2: Self-Host the Static Build
The Studio is a static single-page app, so any static host can serve it.
Step 1: Build the Studio
npx sanity build
The output goes to dist/. To build into another folder, pass it as an argument, such as npx sanity build public. Test it locally before uploading:
npx sanity preview
Step 2: Configure Single-Page App Routing
Studio URLs like /structure/post;abc123 don't exist as files, so your host must serve index.html for any path that isn't a real file.
Vercel (vercel.json in the Studio folder, with the output directory set to dist):
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
Netlify (netlify.toml):
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Step 3: Add the Domain to CORS
The Studio talks to the Content Lake from the browser with the user's credentials, so its origin must be allowed with credentials enabled:
npx sanity cors add https://studio.example.com --credentials
Forgetting this step is the most common reason a self-hosted Studio shows a CORS or login error.
Step 4: Register the External Studio
Serving the files doesn't tell Sanity where the Studio lives. Register it so features like the Dashboard can link to it, and so your schema is deployed:
npx sanity deploy --external --url https://studio.example.com
This uploads no files. Run it as part of each deployment (the --url flag is only needed the first time or when the URL changes), and add --schema-required in pipelines so schema problems fail the job.
Hosting Under a Sub-Path
To serve the Studio at a path such as https://example.com/studio, set the base path in sanity.cli.ts:
export default defineCliConfig({
api: { projectId: 'abc123xy', dataset: 'production' },
project: {
basePath: '/studio',
},
})
The SANITY_STUDIO_BASEPATH environment variable overrides this if you need different values per environment.
Option 3: Embed the Studio in Next.js
Embedding puts the Studio at a route in your Next.js App Router app, so the site and Studio share a codebase and deployment. Running npx sanity@latest init inside a Next.js project can set this up for you. To do it by hand:
Step 1: Install the Packages
npm install sanity next-sanity styled-components
Step 2: Set the Base Path
// sanity.config.ts (in the Next.js project root)
import { defineConfig } from 'sanity'
import { structureTool } from 'sanity/structure'
import { visionTool } from '@sanity/vision'
import { schemaTypes } from './src/sanity/schemaTypes'
export default defineConfig({
name: 'default',
title: 'My Site',
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
basePath: '/studio',
plugins: [structureTool(), visionTool()],
schema: { types: schemaTypes },
})
Step 3: Add a Catch-All Route
Every sub-path under /studio must render the Studio, so use an optional catch-all segment:
// src/app/studio/[[...tool]]/page.tsx
import { NextStudio } from 'next-sanity/studio'
import config from '../../../../sanity.config'
export const dynamic = 'force-static'
export { metadata, viewport } from 'next-sanity/studio'
export default function StudioPage() {
return <NextStudio config={config} />
}
The re-exported metadata and viewport set sensible defaults for the Studio page, such as preventing search indexing and getting zoom behaviour right on mobile.
Step 4: Keep Site-Only Components Out of the Studio Layout
If your root layout includes site components like a header, <SanityLive /> or <VisualEditing />, split the app into route groups, such as (site) and (studio), each with its own layout.tsx. Route groups don't change URLs, so basePath stays /studio, but the relative import path to sanity.config gains one more ../.
Step 5: Allow the Origin
npx sanity cors add http://localhost:3000 --credentials
npx sanity cors add https://www.example.com --credentials
Then deploy your Next.js app as usual and the Studio is live at /studio. The trade-off is that embedded Studios don't receive auto-updates; you upgrade the sanity package and redeploy your site instead. For ways to fetch and render content in the same app, see render Portable Text in React.
Removing or Renaming a Deployed Studio
To take down a Studio on Sanity hosting, or to free its hostname so you can pick a new one:
npx sanity undeploy
The CLI identifies the Studio from deployment.appId (or the older studioHost) and asks for confirmation. Hostnames are released asynchronously, so it can take a few minutes before the name can be reused. Your content isn't affected. Only the Studio's files are removed.
Troubleshooting Common Problems
- CORS error or endless login on a self-hosted or embedded Studio: add the exact origin, including protocol and port, with
--credentials. - 404 when refreshing a page: your host isn't rewriting unknown paths to
index.html. - Hostname already taken: choose another; hostnames are global across all Sanity projects.
- CI deploy asks for login:
SANITY_AUTH_TOKENisn't set or lacks deploy permission. - Deploy goes to the wrong project: check
api.projectIdinsanity.cli.tsand any environment variables used in CI. - Unsupported Node.js version: use Node.js 22.12 or later locally and in your pipeline.
- Build too large: a single deployment can be up to 2 GB, so check for large files accidentally placed in
static/.
FAQ: Deploying Sanity Studio
Yes. Hosting the Studio on a sanity.studio subdomain is included on every plan, including Free. You only pay for seats and usage according to your plan, not for the Studio hosting itself.
Sanity's own hosting uses sanity.studio subdomains. For a custom domain, build the Studio with sanity build and host the dist folder yourself, or embed it in an app served on your domain, then add that origin to CORS with credentials.
No. Content lives in the Content Lake, so editors' changes appear immediately. You only redeploy the Studio when you change its code, such as schemas, plugins or configuration.
Run npx sanity undeploy to remove the existing deployment, wait a few minutes for the hostname to be released if you want to reuse it, then run npx sanity deploy and enter the new hostname.
Deploy separately if you want auto-updates and the least configuration. Embed it if you prefer a single codebase and URL, accepting that you'll upgrade the sanity package and redeploy your site to get Studio updates.
No. Variables prefixed with SANITY_STUDIO_ are written into the built JavaScript, so anyone who loads the Studio can read them, even without logging in. Use them for project IDs and dataset names only.
Conclusion
For most teams, npx sanity deploy is all you need: one command builds the Studio and publishes it to a free sanity.studio URL with automatic updates. Add the appId to sanity.cli.ts and a deploy token to CI, and every merge keeps your editors on the latest code.
If you need your own domain, build the static Studio and host it anywhere, as long as you configure single-page routing, CORS with credentials and register it with --external. If you'd rather keep everything in one Next.js app, NextStudio on a catch-all route gives you an embedded Studio at /studio. Pick the option that matches how your team already deploys, and the Studio will fit right in.


