Sanity CMS Development

Sanity CMS development, where the schema and the query decide everything else.

You’ve already chosen Sanity. What’s still open decides whether the project ages well. Documents have to be shaped for editors who aren’t developers, GROQ has to be written for a render layer that stays fast under load, and Portable Text obliges you to build a renderer on every surface that displays a word of it.

If you’re still comparing content platforms, or scoping the application around the CMS, start at our Next.js web development service instead. Everything below assumes Sanity is settled.

What you own once the Sanity work is done.

Six artefacts, all of them files in your repository. A Sanity project gets judged on what the next engineer finds in the schema folder, not on how the Studio looked at launch.

A schema in code, with the arguments beside it

Every document, object and reference written with defineType and defineField, grouped and previewed for the people who edit them. A decision log sits beside each shape saying why that one won.

A GROQ query layer, projections not spreads

One query per route. Filter, order, slice, then project only the fields a component renders, and nothing anywhere gets assembled by string concatenation.

Portable Text renderers treated as a contract

A components map covering every block style, list, decorator, annotation and custom type in the schema, carrying the fallback that stops an unknown type rendering as nothing.

A Studio your editors can navigate

Structure builder panes grouped by how your team works rather than by document type, and custom inputs wherever a plain string field lies about what the data is.

Revalidation that reaches every page a document touches

GROQ-powered webhooks carrying a filter and a projection, cache tags for the document and its type, and a reverse-reference lookup, so editing an author refreshes every post naming them.

A migration that parses instead of copies

WordPress HTML turned into blocks, spans and typed objects, shortcodes given real schema types, assets re-uploaded, the lot imported as NDJSON. A redirect map ships with it.

Engineering position

Six things a dataset makes hard to change later.

Shape the document twice: once for the editor, once for the render layer

Editors want few fields, plain names, sensible defaults and a preview that identifies a document without opening it. A render layer wants narrow types it can switch on. The two pull apart in the page-builder array first. Thirty block objects turn every route into a union the front end defends forever, and two of those thirty are hero variants that differ by a background colour. Content belongs in the schema, layout in the front end.

GROQ projections are the whole performance story on a content-heavy route

The spread operator inside a projection returns every attribute of every matched document, so a listing that wants a title, a slug and an image ships the full Portable Text body of each entry. Filter, order, slice, then project explicitly, dereferencing with the arrow only into a sub-projection that names its own fields. Query length matters too. Past a point the client falls back to POST, POST is not cached, and a route you thought was a CDN read now hits the live Content Lake every time.

Portable Text is a serialisation contract, and annotations are where it breaks

Portable Text is an array of blocks. Each block holds children spans, each span holds marks, and a mark is either a simple decorator or a key pointing into that block’s markDefs. A renderer that assumes marks carry their own data handles bold and italic perfectly and drops every internal link. Give the components map an explicit fallback, because the default for a forgotten type is to render nothing, and nothing looks identical to an empty field.

Webhook revalidation has a failure mode nobody documents

The standard wiring is a GROQ-powered webhook that fires on a change and revalidates a tag. It’s wrong twice over. A webhook tells you which document changed, not which pages render it, so renaming an author leaves two hundred posts showing the old byline. Tag by type as well as by id, and resolve the affected routes with a reverse-reference query. Worse, the webhook can arrive before the API CDN has the new document, so revalidating through the CDN re-caches the exact thing you were replacing.

Sanity Studio bends a long way, and stops at the server

Structure builder replaces the default document lists with panes matching how your team works, input components swap in where a plain string field misrepresents the data, and document actions bend to a real editorial process. What stops is the server. No hook runs before a document is written and no beforeChange enforces an invariant for every client, so validation is advisory to anything holding a token. Access control is granular on the higher plans and coarse below them, so settle the plan before you settle the model.

Sanity or Payload: we ship both, so neither answer protects us

Sanity wins when several people edit the same content at once, when nobody wants to operate a database or a CMS server, and when one body of structured content feeds a website, an app and an email system. Payload wins when content and application data belong in one Postgres database, when hosting has to be self-managed, or when access rules have to be enforced server-side. Our Payload CMS development page argues the other side in the same detail.

When not to use it

Five briefs that don’t belong in Sanity.

We like Sanity, which is exactly why we draw the boundary. If the real question is whether to leave the platform you’re on, our WordPress to Next.js migration page argues both directions.

What you’re calling content is application data

Orders, invoices and anything with a transactional lifecycle don’t belong in a content lake. There’s no SQL, no transaction across documents, no aggregate over a million rows. Put that in Postgres.

Data residency or self-hosting is a requirement

Content Lake is hosted, in the regions Sanity offers, on infrastructure you do not run. If a security review wants the content database inside your own environment, self-hosted Payload is the answer.

The bill has to be predictable at high traffic

Sanity meters API requests, bandwidth and assets. Cached well, that’s a rounding error. On a route that fell back to uncached queries, it’s a variable cost sitting against a fixed budget.

Nobody on the team can review a TypeScript pull request

The schema is code, so a new field means a commit and a deploy. If a non-engineer has to add fields from a UI, you’re describing Directus, Strapi or WordPress.

You want a CMS to solve a speed problem

Replatforming rarely fixes performance on its own, and a badly queried Sanity site is slower than a well-cached WordPress one. Diagnose the live site first. That’s a different engagement.

How it runs

The four promises, and the things that change the price.

Your repo, your Sanity project, from commit one

The Studio, the schema, the query layer and the decision log all live in your GitHub organisation, and the Sanity project sits under your billing rather than ours.

A staging URL by the end of week one

The Studio is deployed and clickable before the schema is finished, because arguing about a document type goes faster when an editor can open it. A build and a written note every Friday.

Decisions in writing

Document type against embedded object, reference against inline, which custom Portable Text types exist and what renders them, and how revalidation works out which routes are affected.

Fixed scope, quoted before kickoff

Change requests are priced and confirmed in writing before anyone starts them. A content model grows a tail of small discoveries, and that tail gets quoted rather than absorbed.

We build with Sanity. We’re not a Sanity partner agency, and nothing on this page will imply we are. Judge us on the reasoning above and on how we document the builds we publish.

Pricing follows the published rate card. Marketing sites start at $7,500, platforms at $28,000, and the minimum for a build is $7,500. Those figures are estimates in US dollars, not quotations; the quote that follows scoping is fixed in writing before kickoff. What moves it, roughly in that order, is how many document types are genuinely distinct, how many surfaces need a Portable Text renderer, and whether the content you have gets parsed rather than copied. The argument we make about what an MVP actually costs applies here too. The estimate follows the decisions, not the page count.

If the site is live and the complaint is speed rather than structure, read why your Next.js site is slow before you commission a replatform. Otherwise send us the content you have and the shape you want, and get a fixed quote against a written schema.

FAQ

The questions that decide it

Sanity accepts almost any document shape and GROQ hands back all of it, so nothing fails loudly until the content grows. What you’re paying for is documents shaped for editors who aren’t developers, projections written instead of spreads, Portable Text treated as a contract, and revalidation that reaches the pages referencing a document rather than only the document itself.

We build both, so neither answer protects us. Sanity wins on real-time collaborative editing, a hosted Content Lake nobody has to operate, and one body of content reused across web, app and email. Payload wins when content sits beside application data in one Postgres database, when hosting has to be self-managed, or when a metered bill is hard to defend.

Yes, and the HTML is the hard half. WordPress stores a post as one rendered blob carrying shortcodes and inline styles, while Sanity stores Portable Text, which is structured. So the migration is really a parser. HTML becomes blocks and spans, shortcodes become typed objects, media gets re-uploaded as assets, and the whole run repeats from scratch with a redirect map beside it.

Usually the query rather than the network. A projection using the spread operator pulls every field of every matched document, including the full Portable Text bodies you only wanted a title from. A long query string then forces a POST, and POST skips the API CDN and hits the live Content Lake on every request.

We quote against the published rate card. Marketing sites start at $7,500, platforms at $28,000, and the minimum for a build is $7,500. Those figures are estimates in US dollars, not quotations; the quote that follows scoping is fixed in writing before kickoff. What moves the number is how many document types are genuinely distinct, how many surfaces need a Portable Text renderer, and whether the content you already have gets parsed rather than copied.

No. We build with Sanity and won’t imply more than that. The partner directory is a real programme with real requirements, and a page claiming a badge it hasn’t earned tells you something about how the code gets written. Ask us to walk you through a schema instead.

Settle the content model before the first document type.

Send the content you have and the editing experience you want. You’ll get a proposed document model back, with the query layer and the Portable Text contract written beside it. Fixed scope, fixed price.