One model for linked entries and nested components?
Uniwoven has one type system with two kinds of content type. A document is an entry of its own, and other entries link to it by reference. A component lives inside the entry that uses it and can hold other components, to any depth. One article can link its author as a document and carry its hero and FAQ as components.
One article, modelled both ways
article "Why we hand-build frames" document, at /journal/hand-built
├─ author ──▶ person document, linked by reference
├─ hero component, stored inline
│ └─ cta ──▶ page component holding a reference
├─ body (rich text)
│ └─ embed ──▶ note "Frame warranty" document, tracked as a link
└─ faq component
└─ items: faqItem × 3 list of componentsThe person and the note have entries of their own and can be linked from any page. The hero and FAQ belong to this article and nothing else.
Documents and components, one set of rules
Both kinds share field types, validation and localisation. What differs is where the data lives.
Documents are addressable.
A document has its own entry and status, and a page also has its place in the page tree. Reference fields store its ID and can be limited to certain types.
Components nest to any depth.
A component is stored inside its parent and can contain components, including its own type. Click it on the page and the inspector shows only its fields.
Rich text has its own format.
We define the stored format, and the editor widget converts to it. Swapping the widget never touches stored documents. Embeds inside rich text are tracked as links, like any other reference.
Model changes never rewrite content.
Delete a field and its data stays in storage, unserved. Changing a field's type is blocked in the modelling screen and goes through a branched environment instead.
Every entry can carry a brief.
The brief says what the page is for, before anyone writes. The assistant writes from it, and the site never receives it.
Write the schema once, in your frontend
Component types don't have to be modelled in the CMS at all. Write one zod schema per component in your frontend, and the CMS derives the content type from it. That type is read-only in the modelling screen, so nobody edits it in two places. A compatible schema change, such as a new optional field, goes straight in. A breaking one opens a drift report listing the entries it affects.
// components/contract/hero.tsx
export const heroSchema = z.object({
heading: z.string().min(1).max(80),
tagline: z.string().min(1).max(200).meta({ cms: 'multiline' }),
variant: z.enum(['full', 'split']).meta({
guidance: 'full for landing pages, split when the image matters',
}),
image: assetRef('image').optional(),
cta: ctaSchema.optional(),
})
export type HeroProps = z.infer<typeof heroSchema>
// Derived: the hero component type, from the manifest (abridged)
{
"apiId": "hero",
"fields": [
{ "apiId": "heading", "type": "text", "required": true, "minLength": 1, "maxLength": 80 },
{ "apiId": "tagline", "type": "longText", "required": true, "minLength": 1, "maxLength": 200 },
{ "apiId": "variant", "type": "text", "required": true, "allowedValues": ["full", "split"] },
{ "apiId": "image", "type": "asset", "accept": ["image"] },
{ "apiId": "cta", "type": "component", "allowedTypes": ["cta"] }
],
"guidance": { "fields": { "variant": "full for landing pages, split when the image matters" } }
}
Where other headless CMSes draw the line
Contentful links well and nests through references.
Entries link cleanly. A component, though, is an entry of its own that the page references, so a deep page becomes a chain of entries and dialogs. Uniwoven stores page-only components inline.
Storyblok nests well and links more loosely.
Blocks nest inside a story, and the editor is good at it. Linking one story to another goes through link and reference fields resolved per request. Uniwoven treats references as first-class and tracks every one. Contentful and Storyblok as of October 2026, from public documentation and our project experience. Corrections welcome.
An entry publishes all its locales at once; per-locale publishing and locale fallback chains are Delivered in your rollout. The delivery API filters references by ID, not by fields inside the referenced entry. A field rename is a delete plus an add, and the modelling screen warns you. If your model needs more than this, a Setup Review tells you before you commit to anything.
See the Setup Review: €3,900 →What content architects ask us
Yes. Documents link to each other by reference, and components live inside the entry that uses them, nested to any depth. Both kinds share one type system, with the same field types and validation, so each piece is modelled where it belongs rather than where the CMS prefers it.
Where the model shows up next
Define components in your code
Write each component's schema once, in your frontend. The content type is derived from it.
Component contract →Change the model on a copy
Type changes and remodels run on a full copy, swapped in by alias.
Environments and export →Keep, fix or replace your CMS?
Check whether this model is worth keeping before you move anything.
Keep, fix or replace →See your content model in Uniwoven
A free walkthrough. We shape the sample space like your site, with your page types modelled both ways, and show you around. There's nothing to prepare.
Book a walkthrough →