Sanity
Start here
Section titled “Start here”If you only do a few things, do these:
- Create the project in the customer’s own Sanity organization, or create an organization for them.
- Add a payment card from Cardboard when the project needs a paid plan, so we have control over invoices.
- Make datasets private.
- Use API tokens with the lowest role that works, stored as secrets and never sent to the browser.
- Only add the CORS origins the project actually needs.
Organization and ownership
Section titled “Organization and ownership”Create the project in the customer’s organization
Section titled “Create the project in the customer’s organization”Every Sanity project should belong to the customer, not to TRY Dig or to an individual developer. This makes it simple to hand over the project, move billing, or end the engagement without having to transfer anything.
- The customer already has a Sanity organization: ask them to invite you as an administrator, and create the project there.
- The customer has no Sanity organization: create a new organization named after the customer, and create the project in it. Add a contact person from the customer as administrator as well, so they are never locked out of their own content.
Never create customer projects under a personal Sanity account. If a project already lives in the wrong place, move it to the right organization under Project settings → Transfer project at sanity.io/manage.
Billing and Cardboard
Section titled “Billing and Cardboard”If the project needs a paid plan, or if usage could go above the free quotas, create a dedicated payment card for the customer in Cardboard and add it to the organization under Organization settings → Billing. This gives us control over and insight into the invoices, and makes it easy to see which costs belong to which customer.
- Use one card per customer, not a shared card across customers.
- Set a limit on the card that matches the expected cost.
- If the customer pays Sanity directly with their own card, note this in the project’s Backstage entry so it’s clear who owns the costs.
Datasets
Section titled “Datasets”Make datasets private
Section titled “Make datasets private”Public datasets can be read by anyone who knows the project ID, without a token. The project ID is visible in the frontend, so in practice all published content is open to the world through the API, including documents that are not linked from the website.
Set every dataset to private, either when creating it (sanity dataset create production --visibility private) or afterwards under Project settings → Datasets. Read content server-side with a read token.
Only keep a dataset public if there is a concrete reason, and make sure it contains nothing that shouldn’t be public.
Separate environments
Section titled “Separate environments”Use separate datasets for production and development/testing (for example production and development), so testing and schema changes don’t affect live content. Never use production data containing personal information in development without a good reason.
Backups
Section titled “Backups”Sanity keeps history for documents, but deleted datasets and mass changes can’t always be undone. Before large migrations or schema changes, take an export:
sanity dataset export production production-backup.tar.gzFor projects where content is business-critical, set up regular exports (for example with a scheduled GitHub Actions workflow), or use Sanity’s built-in backups if the plan supports it.
API tokens
Section titled “API tokens”Least privilege
Section titled “Least privilege”Create tokens under Project settings → API → Tokens, with the lowest role that works:
- Viewer for reading content from the frontend server.
- Editor only for jobs that actually need to write, such as imports or migrations.
- Avoid Administrator tokens for applications.
Create one token per application and environment, and give it a descriptive name (for example website-production-read). Then you can revoke a single token without affecting anything else.
Keep tokens on the server
Section titled “Keep tokens on the server”Tokens must never end up in the browser. Watch out for environment variables with prefixes that make them public, such as NEXT_PUBLIC_, PUBLIC_ or VITE_. Only the project ID, dataset name and API version are safe to expose.
Store tokens as secrets in the hosting platform (see Vercel), and follow the general secrets management guidelines. If a token leaks, revoke it in Sanity immediately and create a new one.
Drafts and preview
Section titled “Drafts and preview”Drafts can contain unpublished and sensitive content. Use the published perspective for public content, and only fetch drafts (drafts / previewDrafts) server-side, behind preview mode or authentication.
CORS origins
Section titled “CORS origins”CORS origins control which websites can call the Sanity API from the browser with the user’s login. Under Project settings → API → CORS origins:
- Add only the domains that need it, such as the Studio URL and the website’s domains.
- Don’t use wildcards (
*) or broad patterns. - Only enable Allow credentials for origins that host the Studio.
- Remove old domains and test URLs that are no longer in use.
Webhooks
Section titled “Webhooks”When Sanity calls our endpoints through webhooks (for example for revalidation), set a secret on the webhook and verify the signature in the endpoint, for example with @sanity/webhook. Without verification, anyone can call the endpoint.
Queries
Section titled “Queries”Pass user-controlled values to GROQ as parameters, never by building query strings by hand. This works like prepared statements in SQL and prevents injection. See Input Validation.
// Goodclient.fetch(`*[_type == "post" && slug.current == $slug][0]`, { slug });
// Badclient.fetch(`*[_type == "post" && slug.current == "${slug}"][0]`);Also pin apiVersion to a fixed date in the client configuration, so behavior doesn’t change unexpectedly when Sanity releases new API versions.