Content Modeling 8 min read

Content Modeling 101: The Difference Between Pages, Entries, and Blocks (And Why It Matters)

Content modeling is the most important decision you make when setting up a CMS project. Get it right and everything downstream is clean. Get it wrong and you're rebuilding. Here's a clear explanation with a worked example.

April 16, 2026
Engineer in helmet presenting architectural plans on a whiteboard in an office setting.

Why this matters more than you might think

Content modeling is the most important decision you make when setting up a CMS project. The model determines what your editors can do, how your API responses are shaped, how your templates are structured, and how maintainable the site will be in a year.

The consequence of getting it wrong is specific: you build a site, it works, content editors start using it — and then six months later the client wants to add a new content type or change how something works, and the model doesn't support it without a rebuild. Technical debt that starts at the schema level never stops compounding.

Get it right and everything downstream is clean.

What is a content model?

A content model is a schema — a description of what fields a piece of content has, what types those fields are, and how different content types relate to each other.

This matters more in a headless CMS than in a traditional one. In WordPress, your content model is largely defined by the theme. In a headless CMS, you define it explicitly, and it becomes the contract between your content and your frontend. Change the model later and you're changing the contract.

The three primitives

In SleekCMS (and in most headless CMSs under different names), there are three fundamental content types. Before explaining each in detail, here's the one question that determines which to use:

Type Has a URL? Shared across pages? Lives inside another record?
Page Yes No No — it is the record
Entry No Yes Referenced
Block No No — owned by parent Embedded

Does it need its own URL? → Page.
Does it live independently and get referenced by multiple pages? → Entry.
Does it define a content component that lives inside another record? → Block.

Pages

A page is anything that maps to a URL. When you create a page model and add content to it, SleekCMS generates a route.

Static pages map to a single URL with a single content record. Homepage (/), About (/about), Contact (/contact), Pricing (/pricing). Each static page model has one instance.

Collection pages map to a URL pattern with a slug, one content record per slug. Blog posts (/blog/[slug]), case studies (/case-studies/[slug]), product pages (/products/[slug]). You create one collection model and add as many instances as you need; each becomes a page at its slug.

In the model file, the collection key ends with []:

// models/pages/blog[].model
{
  title: text,
  date: date,
  author: entry(authors),
  cover: image,
  content: markdown,
  seo: block(seo)
}

What belongs in a page model: Fields specific to that page — hero heading, body copy, featured image, meta description. Fields reused across pages belong in entries.

Entries

An entry is structured content that has no URL of its own but is referenced by multiple pages. Change an entry and every page that references it reflects the update.

Single entries have one record referenced by many pages. Navigation, footer, site settings, global announcement banners. Create one instance; any template that needs it fetches it by handle with getEntry('header').

Entry collections have multiple records, each referenced individually. Authors, categories, team members, testimonials, pricing plans. A blog post references a specific author; an about page references a list of team members.

The critical distinction from pages: entries don't have URLs. An author entry is not a page — unless you also create an author page model that renders an author profile. The entry is data. Whether it becomes a routable page is a separate decision.

// models/entries/authors[].model
{
  name: text,
  role: text,
  bio: paragraph,
  photo: image,
  twitter: link
}

In a blog post template:

<% const author = item.author %>
<%- img(author.photo, '64x64') %>
<strong><%= author.name %></strong>
<span><%= author.role %></span>

Blocks

A block is a content component embedded directly inside another piece of content — a page or an entry. Blocks are the building blocks of composable page layouts.

What a block is: A defined set of fields that together describe a UI component. A hero block has a heading, a subheading, a background image, and a CTA. A features grid block has a title and a list of feature items, each with an icon, a heading, and a description.

How blocks work in a page: A page model includes block references, defined using block(key) in the model. When a page is assembled — either in the editor or in a content JSON file — the block fields are embedded directly inside the page record.

// models/pages/_index.model
{
  hero: block(hero),
  features: block(features-grid),
  testimonials: block(testimonials),
  cta: block(cta-band),
  seo: block(seo)
}

The distinction from entries: Blocks are embedded in their parent — a hero block's data lives inside the page that contains it. It is not shared. If you want the same hero content on two pages, you create the block on each page separately (or model the shared data as an entry). Entries are referenced — an author entry exists once and is referenced by every blog post that names that author.

Keep block models focused. A block that tries to do too many things is hard to edit and hard to template.

A worked example: modeling a marketing site

This is the most useful part of the post. The site: Homepage, Services index, individual Service pages, About, Blog index, Blog posts, Contact.

Homepage → Static page. URL /. Hero, services overview, client logos, CTA — all defined as blocks on the page model. SEO block included.

Services index → Static page. URL /services. The template fetches collection pages using getPages('/services', { collection: true }) — no content fields needed beyond SEO.

Individual service pages → Collection page. URL /services/[slug]. Fields: service name, description, key benefits (repeatable list), related case study references.

About page → Static page. URL /about. Fields: hero block, company story (richtext), team members (entry reference to team collection).

Blog index → Static page. URL /blog. Template fetches posts using getPages('/blog', { collection: true }). No content fields required beyond SEO.

Blog posts → Collection page. URL /blog/[slug]. Fields: title, publish date, author (entry reference), category (entry reference), cover image, body (markdown), SEO block.

Contact → Static page. URL /contact. Fields: form heading, form subheading, office locations (repeatable list). The form itself is HTML in the template with data-sleekcms="contact".

Entries:

  • header (single) — navigation links, logo
  • footer (single) — footer columns, social links, legal text
  • authors[] (collection) — name, bio, photo, social links
  • categories[] (collection) — name, slug, description
  • team-members[] (collection) — name, role, photo, bio

Blocks:

  • hero — heading, subheading, background image, CTA label, CTA URL
  • features-grid — section title, feature cards (repeatable: icon, title, description, link)
  • client-logos — section title, logos (repeatable: image, alt text, optional link)
  • cta-band — heading, subheading, button label, button URL
  • seo — title, description, OG image

Common mistakes

Putting shared content in page models. If the footer is defined in the homepage model, you have to update it in every page model separately. That's the problem entries solve.

Using entries where blocks should be. A hero section that never gets shared across pages and always lives inside a specific page is a block. Modeling it as an entry adds unnecessary indirection in both the editor and the template.

Over-nesting blocks. A block that contains blocks that contain blocks creates a deep template hierarchy that's hard to maintain. If you find yourself nesting more than two levels deep, reconsider the model.

Not planning for collections. A blog that starts as individual static pages (/blog/post-one, /blog/post-two) becomes a maintenance problem quickly. If content will repeat with the same structure, model it as a collection from the start.

Mega-models. A single model with 40 fields for every scenario. Split by context — not every page needs every field, and models with too many fields are painful to edit.

Content modeling is the work that happens before any template gets written or any content gets entered. The editing experience, the API shape, and the template structure all follow directly from the decisions you make here. It's worth getting right.