Learn

Content modeling, done right the first time

Your content model is the hardest thing in your stack to change later. This is a practical guide to designing content types, fields, and relationships that hold up as your team and your site grow.

Why the model is the most expensive thing to change

Content modeling is the practice of defining the structure of your content before you write any of it: which content types exist, what fields each type holds, how those types relate to each other, and which pieces are reusable across the site.

Every headless CMS asks you to do this on day one, usually before you know enough about the project to do it well. This page is the guide we wish we could hand every team on that first day.

Start modeling free · Read the API docs

Copy is cheap to change. Design is moderately expensive. The content model sits underneath both, and everything downstream depends on its shape:

  • Your queries. Every API call and template references field keys by name.
  • Your components. A React or Vue component that expects metadata.author.title breaks the moment authors stop being a relationship.
  • Your editors' habits. People learn where things live. Moving fields costs retraining.
  • Your existing content. Restructuring 4,000 published objects is a migration script, a review pass, and a deploy.

The goal is not a perfect model. The goal is a model that absorbs change without a migration. That means being deliberate about a small number of decisions up front.

The four building blocks

In Cosmic, the whole model is made of four concepts. Most headless CMSs use the same ideas under different names, so this maps cleanly if you are coming from somewhere else.

1. Object Types are your content types: Posts, Authors, Products, Landing Pages. A type defines the schema that every entry of that type follows.

2. Objects are the individual entries: one blog post, one author, one product. Each object has a title, a slug, a status, and its metadata.

3. Metafields are the fields inside a type. Cosmic ships a wide set of field types, covered in the table below.

4. Relationships connect objects to each other. A post points to an author. A product points to many categories. These are real references, not copied text, which is what makes a model maintainable.

Field types you can model with

Picking the right field type is most of the job. A select and a text field look similar in the dashboard and behave very differently after two years of use.

Field typeUse it forStores
textShort single-line values: SEO titles, SKUs, button labelsString
textareaPlain multi-line copy: teasers, meta descriptionsString
markdown / rich-textLong-form body contentMarkdown string
numberPrices, ordering, durations, countsNumber
selectOne choice from a fixed list: status, tier, sizeString
multi-selectSeveral choices from a fixed listArray of strings
switchTrue/false flags: featured, archivedBoolean
datePublish dates, event dates, expiriesDate string
file / filesImages, video, PDFs, galleriesMedia reference
objectA single relationship: post to authorObject reference
objectsA many relationship: post to tagsArray of references
repeaterRepeating groups of fields: FAQ rows, feature cardsArray of field groups
jsonStructured data with a shape your app ownsJSON
color, emojiTheming and small visual tokensString

Three rules of thumb that save real pain:

  • Use select instead of text for anything with a fixed set of values. Free-text status fields turn into Published, published, and Publish inside a month, and then your filters quietly miss rows.
  • Use object or objects instead of repeating a name. If you type an author's name into every post, renaming that author is a find-and-replace across your entire archive.
  • Use file or files for media instead of a text field holding a URL. A real media reference returns both the original asset and a transformable URL, so every crop, width, and format is a query parameter rather than another upload to track. The image CDN page covers how those URLs behave, and why modeling images as references is what makes responsive delivery a template concern rather than a content one.

A worked example: a blog that scales

The most common first model is a blog, and the most common mistake is putting everything on one type. Here is the shape that holds up.

Authors (authors)

Field keyType
imagefile
biotextarea
roletext
twittertext

Categories (categories)

Field keyType
descriptiontextarea

Posts (posts)

Field keyType
imagefile
contentmarkdown
teasertextarea
published_datedate
authorobject, related to authors
categoryobject, related to categories
tagsobjects, related to tags
featuredswitch
seo_titletext
seo_descriptiontextarea

Three separate types, joined by references. Now the author bio lives in exactly one place, category pages are a filter rather than a hand-maintained list, and the SEO fields are explicit instead of derived from the title.

Notice that image on both Authors and Posts is a file, not a text field. That one choice is what lets a template request an author avatar at 64px and the same post hero at four responsive widths, from a single stored original.

Fetching it with the Cosmic JavaScript SDK looks like this:

npm install @cosmicjs/sdk
import { createBucketClient } from '@cosmicjs/sdk'; const cosmic = createBucketClient({ bucketSlug: process.env.COSMIC_BUCKET_SLUG!, readKey: process.env.COSMIC_READ_KEY!, }); // One request returns posts with their author and category resolved const { objects: posts } = await cosmic.objects .find({ type: 'posts' }) .props(['title', 'slug', 'metadata', 'created_at']) .depth(1) .limit(10);

depth(1) is the payoff for modeling relationships properly. One request returns each post with its related author and category objects already populated, so your template can read post.metadata.author.metadata.image without a second round trip.

Because image is a media reference, that same response also carries the transformable URL for each asset, which is how a responsive srcset gets built without a second API call or a build-time image step:

const base = posts[0].metadata.image.imgix_url; const hero = `${base}?w=1200&auto=format,compress`;

Filtering by a relationship is a query rather than a code change:

const { objects } = await cosmic.objects .find({ type: 'posts', 'metadata.category': categoryId, }) .props(['title', 'slug', 'metadata.teaser']) .depth(1);

Cosmic exposes content over a REST API and this TypeScript SDK. There is no GraphQL layer to learn, and props gives you field-level control over payload size, which is usually the reason people reach for GraphQL in the first place.

Modeling relationships

One to one. A post has one author. Use a single object field. Keep the direction on the side that changes most often, which is almost always the child.

One to many. A category has many posts. Do not add a posts field to Categories. Model the reference once on Posts, then query posts filtered by category. Storing the relationship on both sides means two places to keep in sync, and they will drift.

Many to many. A post has many tags, a tag has many posts. Use an objects field on the side you query from most, usually Posts.

Singletons. Pages that exist exactly once, like a homepage or a pricing page, deserve their own type with named copy slots rather than a wall of markdown. Named fields mean an editor can change the hero headline without touching anything else, and your component contract stays stable.

Reusable blocks and repeaters

Two patterns cover most "the marketing team wants to rearrange this page" requests.

Repeaters model a list of identical structures. An FAQ section is a repeater of question and answer pairs. A feature grid is a repeater of icon, title, and description. Editors add and reorder rows without a developer.

Reusable content blocks model a snippet that appears in many places and must stay identical everywhere: a support address, a compliance disclaimer, a promotional callout. Define it once, reference it from any rich-text field, and update it in one place.

The test for which to use: if the same words need to appear in twelve articles, it is a block. If the same shape needs to repeat within one page, it is a repeater.

Modeling media and alt text

Media deserves the same deliberate treatment as text, and it usually gets none.

The first decision is the one above: file or files, never a text field holding a URL. A stored URL is a dead end. A media reference is queryable, and it comes back with a transformable URL so a single upload serves every size and format you will ever need.

The second decision is where alt text lives. Put it on the media record itself rather than adding an image_alt text field to every type that happens to use an image. Alt text describes the asset, so it belongs to the asset. Store it once and every object referencing that image inherits the description, instead of the same photo carrying three different descriptions on three different pages.

The third is folders. Media libraries get messy faster than content does, because uploads happen in a hurry. Agree on a folder convention early, in the same conversation where you agree on field keys.

How the delivery side of this works, including resizing, format negotiation, and what actually counts against a plan allowance, is covered on the image CDN page.

Seven anti-patterns to avoid

  1. The god type. One pages type with forty optional fields, most of them blank on any given entry. Split it by shape, not by subject.
  2. HTML in a text field. Pasting markup into a plain text field puts presentation inside your content and makes the same content unusable on another surface.
  3. Free-text values that should be a select. Covered above, and worth repeating because it is the single most common one.
  4. Duplicating instead of referencing. Author names, company names, and prices typed into multiple objects. Every duplicate is a future inconsistency.
  5. Modeling your page layout. Fields named left_column_text and row_3_heading tie your content to one design. The next redesign forces a migration.
  6. No SEO fields. Deriving every meta description from a teaser works until someone needs a different one. Add explicit seo_title and seo_description fields from the start.
  7. Skipping helper text. A required field with no explanation gets filled with whatever the editor guessed. One sentence of helper text prevents a category of bad data.

A content modeling checklist

Run through this before you build your types, and again before you publish at volume.

  • Every distinct shape of content has its own type.
  • Anything repeated across entries is a relationship, not typed text.
  • Every fixed set of values is a select or multi-select.
  • Every type has explicit SEO title and description fields.
  • Media is modeled as file or files, never a text field holding a URL.
  • Alt text lives on the media record, not duplicated per type.
  • Field keys are lowercase, snake_case, and stable. Renaming a key breaks queries.
  • Required fields are genuinely required, so drafts are not blocked by fields nobody has yet.
  • Every non-obvious field has helper text.
  • Long-form body content is in a markdown or rich-text field, never a plain text field.
  • No field name references a layout position.
  • Someone who is not a developer has tried to create one entry of each type without help.

That last item is the one people skip, and it finds more problems than the other eleven combined.

Modeling for AI agents as well as editors

A new consideration in 2026: your content model is now also an interface for AI. When an agent connects to your CMS through the Cosmic MCP server, the thing it reads to understand your content is your schema.

That makes model quality directly practical. Descriptive type names, clear field keys, and helper text are the context an agent uses to decide where content belongs. A type named content_2 with fields f1 and f2 is guesswork for a human and guesswork for a model. A type named blog-posts with markdown_content, teaser, and author is self-documenting to both.

The same discipline that makes a model easy for a new hire makes it usable by an agent.

What good modeling actually buys you

The outcome of a clean model is that content changes stop being engineering tickets. That is the value FINN describes:

"Cosmic is: us never having to ask a developer to change anything on the backend of our website."

Maximilian Wuhr, Co-Founder at FINN

That is a modeling result before it is a product result. The fields were named well and scoped tightly enough that non-developers could own them.

Start with a model you will not regret

You can build and test a complete content model on Cosmic in an afternoon, on the free plan, without a credit card. Create your types, add a handful of objects, and query them with the SDK before you commit to a shape.

Create a free Bucket · Talk to our CEO about your model

Go deeper: Content modeling best practices · API-first CMS · Image CDN · Cosmic MCP server

Frequently asked questions
cosmic logo
cosmic logo

Start building today

No credit card required • Free forever