Type something to search...
How to Deploy Sanity Studio to the Web

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 hostingSelf-hosted static buildEmbedded in Next.js
Commandsanity deploysanity build + your hostPart of your app's build
URL*.sanity.studioYour own domainA route on your site, such as /studio
CostIncluded in all plansYour host's pricingYour app's hosting
Studio auto-updatesYesYes, if built with sanity buildNo
CORS setup neededNoYes, with credentialsYes, with credentials
Best forMost teamsCustom domains, internal networksOne 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

FlagWhat it does
--url <hostname>Sets the hostname without prompting
--no-buildDeploys an existing dist/ build instead of rebuilding
--dry-runReports what would be deployed without uploading
--source-mapsIncludes source maps for easier debugging
--no-minifySkips minification for a faster build
--schema-requiredFails the deploy if the schema can't be deployed
-yUnattended 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.

  1. In sanity.io/manage, open your project's API settings and create a token with permission to deploy Studios.
  2. Add it to your repository secrets as SANITY_AUTH_TOKEN.
  3. 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_TOKEN isn't set or lacks deploy permission.
  • Deploy goes to the wrong project: check api.projectId in sanity.cli.ts and 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.

Tags :
Share :

Related Posts

Building a Static Site With Astro and Sanity

Building a Static Site With Astro and Sanity

To build a static site with Astro and Sanity, keep Astro's default output: 'static', configure @sanity/astro with useCdn: false, and fetch

Dive Deeper
Common Sanity Mistakes and How to Fix Them

Common Sanity Mistakes and How to Fix Them

The most common Sanity mistakes are using the CDN where you need fresh data, fetching whole documents with ..., leaving slugs unvalidated, ship

Dive Deeper
How to Create a Custom Sanity Plugin

How to Create a Custom Sanity Plugin

To create a custom Sanity plugin, wrap a piece of Studio configuration in definePlugin from the sanity package and add it to the plugins ar

Dive Deeper