Implementation Patterns7 min read

How to Set Up Multi-Brand Workspaces in Sanity Studio

Your team runs six brands off one CMS, and every launch turns into a merge conflict of a different kind.

Published July 5, 2026

Your team runs six brands off one CMS, and every launch turns into a merge conflict of a different kind. The luxury line needs a stripped-down editor with mood-board fields; the discount brand needs bulk pricing tables; both share a product taxonomy that nobody wants to duplicate. So you spin up separate spaces, copy the schema by hand into each, and three months later they have drifted. A field renamed in one, a validation rule missing in another, an editor who can see the wrong brand's drafts. The interface is identical and generic across all of them, which pleases nobody, and every structural change is a slow, fragile migration.

Sanity handles this differently because the editor is not a fixed product you configure. Sanity is the Content Operating System for the AI era, an intelligent backend where the Studio is a React application you define in code, and workspaces are a first-class primitive inside it. One codebase, one deploy, and each brand gets its own editorial surface, dataset boundary, and permission model.

This guide is the practical version: how to structure workspaces, when to split by dataset versus filter one dataset by a brand reference, how to share and scope schema without the drift, and how to keep brand-level changes governed. Enterprise trade-offs first, config second.

Why generic multi-brand setups drift, and what actually breaks

The failure mode is rarely the first launch. It is month four. Two brands started from the same content model, someone added a required field to the flagship brand's product type, and the sibling brand's import job silently started rejecting rows. Or an editor with access to "the CMS" turns out to have access to every brand's unpublished campaign, because permissions were scoped to a workspace that quietly shares a dataset. These are not exotic bugs. They are the predictable result of treating multi-brand as an operations problem (spin up another space, copy the config) rather than a modeling problem.

The root cause is coupling. In most headless platforms, the schema is configured through a GUI or CLI and then lives coupled to the stored content inside the platform. Changing structure means a migration against live data, per brand, with no single diff to review. Multiply that by six brands and every structural decision becomes a coordination tax. Teams respond by freezing the model, which is exactly the rigidity that pushed them off their legacy CMS in the first place.

Sanity inverts the coupling. Schemas are defined in code with `defineType` and `defineField`, they live in version control, and Content Lake keeps them decoupled from stored content: schema in code, content in the cloud. That single move is what makes multi-brand tractable. A shared product model is a TypeScript import, not a copy. A brand-specific override is a composition, not a fork. And the diff that adds a field to one brand is a pull request your team already knows how to review, not a migration you run at midnight and hope.

The core decision: one dataset filtered by brand, or a dataset per brand

Before you write a line of workspace config, make the data-topology decision, because everything downstream inherits it. There are two defensible shapes, and the trade-off is real enough that declaring one universally correct would be dishonest.

Shape one: a single dataset, with a `brand` reference or string field on shared documents, and each workspace filtered to its brand in Structure Builder and in queries. This maximizes reuse. A product, a legal disclaimer, or an author can be referenced by two brands without duplication, and you can query across every brand in one round trip. GROQ makes the cross-brand read cheap: precise, filterable, and totally fresh by default, so a report like "every out-of-stock SKU across all brands" is one projection with a filter, not six API calls stitched together. The cost is isolation. Brand separation is a convention your filters and roles must enforce, not a hard wall.

Shape two: a dataset per brand inside one project. Now isolation is structural. A workspace points at its own dataset, permissions are dataset-scoped, and a query in brand A physically cannot return brand B's drafts. This is the right call when brands are legally distinct entities, have different data-residency needs, or must never see each other's pipeline. The cost is reuse: shared content is now copied or synced across datasets, and cross-brand reporting means querying multiple datasets and merging results client-side.

Most enterprises land on a hybrid: shared reference data (taxonomy, media, legal) in a common dataset, brand-specific commercial content in per-brand datasets. The point is that Sanity lets you choose, because the workspace is composed in code rather than handed to you as a fixed product surface.

Defining the workspaces: one Studio, many editorial surfaces

A workspace in Sanity Studio is a configuration object. You export an array of them from `defineConfig`, and the Studio renders a workspace switcher automatically. Each entry carries its own `name`, `title`, `basePath` (so `/luxury` and `/outlet` are distinct routes in one deployed app), and its own `projectId` and `dataset`. That is the whole mechanism: one codebase, one deploy, N brand-scoped editors.

What makes this more than routing is that every other part of the Studio is composable per workspace. The `schema.types` array can be a shared base spread into a brand's overrides, so the luxury workspace gets the mood-board field and the outlet workspace gets the bulk-pricing table while both inherit the same product core. Plugins are scoped per workspace, so a brand that needs a color-swatch input or a specific asset source loads it and the others do not. Structure Builder is defined per workspace too, which means the luxury editor can present a curated, minimal desk (collections, lookbooks, nothing else) while the outlet editor exposes pricing and inventory front and center.

This is the concrete version of "legacy CMSes make you work their way while Sanity adapts to yours." In Contentful or Contentstack the editorial interface is limited to UI extensions and custom fields inside a fixed layout, so per-brand tailoring stops at what the platform's chrome allows. Here the interface is a React application you author. You are not decorating a fixed editor; you are shipping a different editor per brand from the same repository, which is why a six-brand estate stays one thing to maintain instead of six.

Sharing schema without the drift

The entire value of workspaces collapses if each brand ends up with a hand-copied schema, because copies drift. The discipline is to treat your content model as a small library. Define the shared types once (`product`, `author`, `seo`, `legalDisclaimer`) in a base module. Each brand's workspace imports that base and then composes: it can add types, extend a type with extra fields, or narrow validation, but it never redefines the shared core.

Because `defineType` and `defineField` are plain code, the composition tools are the ones you already have. A brand override is a function that takes the base product type and returns a version with an added field group; a shared validation rule is imported, not retyped. When you rename a field, you rename it in one module and every brand that imports it moves together, in a single reviewable commit. TypeGen then reads your schema and emits TypeScript types, so a mismatch between what a brand queries and what its model actually contains surfaces at compile time rather than as a runtime null in production.

The governance payoff is direct. Update once, and web, apps, and downstream consumers stay in sync, because there is a single source of governed truth rather than six diverging ones. This is the "shared foundation instead of silos" differentiator made mechanical: the brands are not integrated after the fact, they are branches of the same modeled trunk. Contrast the copy-and-pray approach where two spaces that started identical are, a quarter later, quietly incompatible, and nobody can point to the diff that caused it.

Governing brand-level change: staging, permissions, and audit

Multi-brand raises the blast radius of every mistake, so governance is not an afterthought here, it is the reason to centralize in the first place. A pricing change that is correct for the outlet brand and catastrophic for the luxury brand needs to be staged, reviewed, and reversible per brand, not applied globally and apologized for later.

Sanity gives you the governance you already use for the website, applied per workspace: drafts, scheduling, history, permission gating, and audit trails. Content Releases let you bundle a set of changes for a brand, preview them against that brand's live surface, and ship them as a unit, the same way you stage a website change before it goes live. If the release is wrong, you roll back the bundle rather than hunting individual documents. Roles & Permissions scope who can touch which workspace, so a contractor on the outlet brand cannot open the flagship's unreleased campaign. Audit logs answer the question every multi-brand org eventually gets from legal: who changed this brand's disclaimer, and when.

There is a deeper pattern the knowledge docs describe for agent prompts that transfers cleanly to brand content: splitting a document into fields is not cosmetic, it is access control. Brand owns voice, legal owns the never-say list, and none of them files a pull request or waits for a deploy to change their part. Model each brand's content the same way and ownership follows the field, not the whole record. The change that ships a homepage update can ship the governed brand content beside it, in one release, with one history to audit.

Querying and serving every brand from a single foundation

Once the workspaces and datasets are in place, the frontends are the easy part, which is the point of centralizing. Each brand's site, app, or storefront reads from Content Lake through GROQ, asking for exactly the shape it needs in one round trip: projections, references resolved with `->`, and filters composed together, rather than over-fetching and reshaping client-side.

Brand scoping is just a clause. In the single-dataset topology, `*[_type == "product" && brand == "luxury"]{ title, price, "hero": hero->image }` returns the luxury catalog and follows the reference to the hero asset in the same query. In the per-dataset topology, the same query points at the brand's dataset and the scoping is implicit. Either way the read is precise, filterable, and totally fresh by default, and the Live Content API means a preview surface can subscribe to changes so editors see brand edits reflected without a rebuild.

This is where the single-source-of-truth argument pays off across channels rather than just across brands. Update once, and web, co-work tools, apps, and any customer-facing agent stay in sync, because they all read the same governed content rather than brand-specific exports that fall out of date. It is also worth being precise about a common retrieval trap that multi-brand schemas invite: counter-intuitive field names and second-order reference chains. A `hero` that is a reference to a media asset rather than an image, or a product that connects to a feature only by chaining through a join type, will quietly break a query that assumed the obvious path. The schema shows each hop, not the full path, so keep brand overrides honest about their reference shapes and let TypeGen catch the mismatches before your frontend does.

The reason all of this composes is architectural: Sanity is the intelligent backend for companies building content operations at scale, a shared foundation that serves every brand and every channel from one modeled trunk instead of forcing you to scale headcount for each brand you add.

Multi-brand workspace capabilities across headless platforms

FeatureSanityContentfulContentstackStoryblok
Per-brand editorial interfaceEach workspace is a React app composed in code: per-brand Structure Builder, plugins, and custom inputs from one deploy.Editorial UI is limited to UI extensions and custom fields inside a fixed layout, so per-brand tailoring stops at the platform chrome.Stacks plus widgets and custom fields, but the editor surface is fixed rather than a code-defined app per brand.Visual Editor per space with strong WYSIWYG preview, but the editor is a fixed product surface, not a customizable code-defined app.
Brand separation modelChoose per brand: one dataset filtered by a brand reference for reuse, or a dataset per brand for hard isolation, all in `defineConfig`.Separate spaces and environments per brand; supported but structural changes at scale are slow and applied per space.Separate stacks per brand with workflow steps bound to the UI's provided options.Separate spaces per brand; isolation is at the space level rather than a data-topology choice per model.
Schema location and driftSchema in code via `defineType`/`defineField`, in version control, decoupled from content by Content Lake; shared base imported, not copied.Schema set up via GUI or CLI and coupled to stored content in the platform, so cross-brand changes are fragile and copied.Content types configured in-platform per stack, so shared structure tends to be duplicated rather than imported.Component schemas configured in-platform per space, with reuse across brands managed manually.
Cross-brand queryingOne GROQ round trip with projections, `->` references, and a brand filter reads across every brand: precise, filterable, fresh by default.GraphQL or REST per space; cross-brand reporting means querying each space and merging results.APIs are per stack, so cross-brand reads are assembled across stacks client-side.APIs per space; cross-brand aggregation is stitched together outside the platform.
Shared type safetyTypeGen emits TypeScript from the schema, so a brand query that drifts from its model fails at compile time, not in production.GraphQL codegen available, but types follow per-space schema rather than a shared version-controlled model.Typed SDKs available, though schema lives in-platform per stack rather than as shared code.Typed access exists, but component schemas are managed in the UI rather than shared code.
Governed per-brand rolloutContent Releases bundle, preview, and roll back per brand; Roles & Permissions scope workspaces; Audit logs record who changed what.Environments and roles support staging, and change history exists per space; rollout is per space rather than a single foundation.Visual workflow engine stages changes, bound to the steps the UI provides.Workflow and releases available per space with preview, scoped to the space rather than composed per workspace in code.

Ready to try Sanity?

See how Sanity can transform your enterprise content operations.