Block

Block fields embed block model instances into pages and entries. There are two block field types in SleekCMS — a single block field and a dynamic block field — and they serve different purposes. A single block field embeds exactly one block instance of a specific block model. A dynamic block field lets editors compose a variable-length stack of block instances from a set of allowed block types.

This page covers both field types: how they work, how they're configured, how they render in templates and the content API, and how to choose between them. The dynamic block field is the more powerful of the two and has its own dedicated page with deeper coverage of the editor experience, allowed block configuration, and layout governance.


Single Block Fields

A single block field embeds one instance of a specific block model into a page, entry, or another block. Unlike a dynamic block field where editors choose from multiple block types, a single block field is tied to one block model. The block always appears in the editor, always renders in the output, and always has the same structure.

When to Use a Single Block Field

Use a single block field when a section of the content should always be a specific block type — no choice, no variation. The block is a fixed part of the model's structure, not something editors opt into.

Fixed hero section — A landing page model that always opens with a hero block. Every page of this type has a hero, and the hero always has the same field structure (heading, subheading, background image, CTA). The hero isn't optional, and editors don't choose it from a list — it's built into the model.

Metadata block — An entry model that always includes a structured metadata block with fields for author, publish date, reading time, and tags. The metadata block is a permanent part of the entry, not a composable section.

Footer CTA — A blog post page model that always ends with a CTA block. Every post has the same CTA structure at the bottom. Editors fill in the content, but they can't remove the CTA or swap it for a different block type.

The common thread is that the block is mandatory and its type is predetermined. Editors control the content within the block, but not whether the block exists or what type it is.

Configuring a Single Block Field

When you add a single block field to a model, you select which block model it references. That's the entire configuration — the field is now bound to that block model. In the editor, the block's fields appear inline within the parent model, and editors fill them in like any other fields.

A single block field produces one object in the API response, nested under the field's handle. Unlike dynamic block fields, there's no _type identifier because the block type is always the same — it's defined by the field configuration, not selected at runtime.

Data Structure

A page model with a hero single block field that references a "Hero" block model with heading, subheading, and image fields produces this API shape:

{
  "title": "Product Launch",
  "hero": {
    "heading": "Introducing Our New Platform",
    "subheading": "Everything you need, nothing you don't.",
    "image": { "url": "https://img.sleekcms.com/..." }
  }
}

The hero key contains the block's field data as a flat object. Your frontend accesses it directly: page.hero.heading, page.hero.image.url.

Rendering in Templates

In the site builder, a single block field is rendered using the same render() helper that handles dynamic blocks. The difference is that you pass a single block object instead of an array:

<%- render(item.hero) %>

This looks up the block model's EJS template, passes the block's field data as item, and outputs the rendered HTML. The page template doesn't need to know the block's internal structure — it delegates rendering to the block template.

You can also access the block's fields directly in the page template without using render(), if you prefer to handle the rendering inline:

<section class="hero" style="background-image: url('<%= item.hero.image.url %>')">
  <h1><%= item.hero.heading %></h1>
  <p><%= item.hero.subheading %></p>
</section>

The choice depends on whether the block has its own template you want to reuse. If the same block model appears in multiple places — as a single block field on one model and inside a dynamic block field on another — using render() ensures consistent output from the same template.


Dynamic Block Fields

A dynamic block field is the composable layout mechanism in SleekCMS. It lets editors build a section of content by adding, removing, reordering, and hiding block instances from a set of allowed block types. The result is an ordered array of block instances — a stack of sections whose composition varies per record.

Dynamic block fields are what power the page-builder experience. Editors assemble pages from pre-built sections, each with its own fields and template, while the block models ensure every section is structurally sound.

Configuring Allowed Block Types

When you add a dynamic block field to a model, you configure which block types editors can use. There are three selection modes:

All blocks — Every block model in the site is available. Use this for maximum flexibility, typically on general-purpose landing page models. New block models are automatically included.

Blocks with prefix — Only block models whose handle starts with a specific prefix are available. This is a convention-based approach that scales well. A dynamic block field configured with the prefix blog- automatically includes any block model whose handle starts with blog- (e.g., blog-hero, blog-callout, blog-code-sample). Adding a new block with the matching prefix makes it available without reconfiguring the field.

Selected blocks — You explicitly choose which block models are available. This is the tightest control and the most common configuration. A homepage model might allow hero, features, testimonials, pricing, cta, and faq blocks. Each model gets exactly the blocks that make sense for its content type.

Data Structure

A dynamic block field produces an array of objects in the API response. Each object includes a _type identifier corresponding to the block model handle, plus the block's field data:

{
  "title": "Homepage",
  "sections": [
    {
      "_type": "hero",
      "heading": "Welcome",
      "subheading": "Build something great.",
      "image": { "url": "https://img.sleekcms.com/..." }
    },
    {
      "_type": "features",
      "heading": "Why Us",
      "items": [...]
    },
    {
      "_type": "cta",
      "heading": "Get Started",
      "button_label": "Sign Up",
      "button_url": "/signup"
    }
  ]
}

The array order matches the order editors set in the editor. Hidden blocks are excluded from the API response — only visible sections are delivered.

Rendering in Templates

In the site builder, the page template renders all dynamic block sections with a single call to render():

<%- render(item.sections) %>

This iterates over every block instance in the array, looks up each block model's EJS template, renders it with the block's field data as item, and concatenates the HTML output. The page template doesn't need to know what types of blocks are present or how to render them — it delegates entirely to the block templates.

A separator can be inserted between rendered blocks:

<%- render(item.sections, '<hr class="section-divider">') %>

Rendering in Frontend Frameworks

When consuming dynamic blocks through the content API, your frontend iterates over the sections array and renders the appropriate component for each block type:

const blockComponents = {
  hero: HeroSection,
  features: FeaturesGrid,
  testimonials: TestimonialCarousel,
  cta: CTABanner,
};

function PageSections({ sections }) {
  return (
    <>
      {sections.map(block => {
        const Component = blockComponents[block._type];
        return Component ? <Component key={block._id} {...block} /> : null;
      })}
    </>
  );
}

Dynamic Blocks — Full coverage of the editor experience, layout governance, and design system alignment.


Nested Blocks

Both single block fields and dynamic block fields support nesting. A block model can include a block field that references other block types, creating hierarchical content structures where sections contain sub-sections.

A "Section" block model might include a dynamic block field that allows "Card" blocks, creating a grid of cards within a section. A "Tabs" block model might include a dynamic block field that allows "Tab Panel" blocks. A "Two Column Layout" block might include two single block fields, one for each column.

Page
└── Dynamic Block Field (sections)
    ├── Hero Block
    │   └── Fields (heading, image, CTA)
    ├── Features Section Block
    │   └── Dynamic Block Field → Card Blocks
    │       ├── Card (icon, title, description)
    │       ├── Card (icon, title, description)
    │       └── Card (icon, title, description)
    └── Two Column Block
        ├── Single Block Field (left) → Content Block
        └── Single Block Field (right) → Sidebar Block

The nesting pattern differs between the two field types. A single block field always embeds one specific block type at a fixed position in the parent block. A dynamic block field lets editors compose the nested content from allowed block types. The choice depends on whether the nested structure is fixed or composable.

Nesting in the API

Nested blocks appear as nested objects and arrays in the API response, following the same structure recursively:

{
  "_type": "features_section",
  "heading": "Why Choose Us",
  "cards": [
    { "_type": "card", "icon": "⚡", "title": "Fast", "description": "..." },
    { "_type": "card", "icon": "🔒", "title": "Secure", "description": "..." }
  ]
}

Nesting in Templates

Nested blocks render through the same render() mechanism. A parent block template calls render() on its nested block fields, and each nested block renders using its own template:

<!-- features-section.ejs -->
<section class="features">
  <h2><%= item.heading %></h2>
  <div class="card-grid">
    <%- render(item.cards) %>
  </div>
</section>
<!-- card.ejs -->
<div class="card">
  <span class="card-icon"><%= item.icon %></span>
  <h3><%= item.title %></h3>
  <p><%= item.description %></p>
</div>

Keep nesting depth manageable. Two levels — a section containing cards — is intuitive for editors. Three or four levels can make the editing interface difficult to navigate. Design your block architecture to balance structural richness with editorial usability.


Block Reusability Across Models

One of the key advantages of both block field types is that the block models they reference are defined independently and can be used across your entire site. A "CTA" block model with heading, button label, and button URL fields can appear as a single block field on a blog post model, inside a dynamic block field on a homepage model, and inside another dynamic block field on a landing page model — all referencing the same block model.

This means:

One template, many contexts. The CTA block's EJS template renders it the same way regardless of which page or entry contains it. Changing the CTA template updates the rendering everywhere.

One schema, many instances. The CTA block's field structure — its heading, button label, button URL — is defined once. Every model that includes it gets the same editor fields.

Structural changes propagate. Adding a "background color" field to the CTA block model makes that field available on every page and entry that includes a CTA block, whether through a single block field or a dynamic block field.

This is a design system principle applied to content. Block models are your content components, and changes to the component definition propagate across the site. You maintain content structures in one place rather than updating the same fields on every model that uses them.


Choosing Between Single Block and Dynamic Block Fields

The two block field types serve different needs, and many models use both.

Use a single block field when:

  • The block is mandatory — every record of this model should have it.
  • The block type is predetermined — editors don't choose between options.
  • The block appears at a fixed position in the model's structure — it's not part of a composable area.
  • You want a simpler editing experience — the block's fields appear inline without the overhead of an "add section" interface.

Use a dynamic block field when:

  • Editors need to compose layouts from multiple block types.
  • The number and order of sections varies per record.
  • Editors should be able to add, remove, reorder, and hide sections.
  • You want the page-builder experience with live preview.

Combining both is common. A blog post model might have a single block field for a fixed hero section at the top, fixed fields for title and metadata, and a dynamic block field for the composable article body below. The hero is always there, always the same type. The body sections are composable.

Capability Single Block Field Dynamic Block Field
Number of block instances Always one Variable (editor-controlled)
Block type Fixed (one model) Chosen from allowed set
Editors can add/remove No Yes
Editors can reorder No Yes
Editors can hide No Yes
API response shape Object Array of objects with _type
render() input Single object Array
Best for Fixed, mandatory sections Composable, flexible layouts

What's Next