Block
Block fields embed block model instances into pages and entries. There are two ways to do it in SleekCMS — a block field and a stack — and they serve different purposes. A block field is tied to a specific block model and embeds one instance of that block (or a repeatable list of instances of that same block). A stack lets editors compose a variable-length, heterogeneous list of blocks, where each item can be a different block model.
This page covers block fields in depth and summarizes how stacks compare. Stacks have their own dedicated page with deeper coverage of the model syntax, content shape, and editor experience.
Single Block Fields
A single block field embeds one instance of a specific block model into a page or entry. Unlike a stack, where each item can be a different block type, 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 stack items, nothing needs to dispatch on a block marker — the block type is always the same, defined by the field configuration rather than 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 stacks. 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 stack on another — using render() ensures consistent output from the same template.
Stack Fields
A stack is the composable layout mechanism in SleekCMS. It lets editors build a section of content by adding, removing, reordering, and hiding block instances — and each item in the stack can be a different block model. The result is an ordered array of block instances — a stack of sections whose composition varies per record.
Stacks 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.
There is no per-field configuration of which blocks a stack accepts: every block model in the site's block library is available in every stack. Adding a new block model makes it instantly available wherever a stack exists, and curating the block library is how you shape what editors can compose with.
Data Structure
A stack produces an array of objects in the API response. Each object includes a _block identifier carrying the block model handle, plus the block's field data:
{
"title": "Homepage",
"sections": [
{
"_block": "hero",
"heading": "Welcome",
"subheading": "Build something great.",
"image": { "url": "https://img.sleekcms.com/..." }
},
{
"_block": "features",
"heading": "Why Us",
"items": [...]
},
{
"_block": "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 stack 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 stacks 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, i) => {
const Component = blockComponents[block._block];
return Component ? <Component key={i} {...block} /> : null;
})}
</>
);
}
→ Stacks — Full coverage of the model syntax, content shape, and editor experience.
Structure Inside Blocks
Block models cannot contain other block fields or stacks — block-in-block nesting is not allowed. This keeps the dispatch tree shallow and the editing experience predictable. Structure inside a block comes from the other structural fields instead:
- Groups organize related fields into a nested object — a "Two Column Layout" block might use a
leftgroup and arightgroup. - Collections hold repeatable inline items — a "Features Section" block might use a
cardscollection where each card has an icon, title, and description. - Entry references pull in shared content — a "Testimonials" block might reference entries from a testimonials collection.
Page
└── Stack (sections)
├── Hero Block
│ └── Fields (heading, image, CTA)
├── Features Section Block
│ └── Collection (cards)
│ ├── Card item (icon, title, description)
│ ├── Card item (icon, title, description)
│ └── Card item (icon, title, description)
└── Two Column Block
├── Group (left) — heading, body
└── Group (right) — heading, body
Nested Structure in the API
Groups and collections inside a block appear as nested objects and arrays in the API response. Collection items are plain objects — only stack items carry a _block marker:
{
"_block": "features_section",
"heading": "Why Choose Us",
"cards": [
{ "icon": "⚡", "title": "Fast", "description": "..." },
{ "icon": "🔒", "title": "Secure", "description": "..." }
]
}
Nested Structure in Templates
A block's own template renders its internal structure — a collection renders with a plain loop:
<!-- features-section.ejs -->
<section class="features">
<h2><%= item.heading %></h2>
<div class="card-grid">
<% for (const card of item.cards) { %>
<div class="card">
<span class="card-icon"><%= card.icon %></span>
<h3><%= card.title %></h3>
<p><%= card.description %></p>
</div>
<% } %>
</div>
</section>
Keep nesting depth manageable. Two levels — a section containing cards — is intuitive for editors. Deeper structures can make the editing interface difficult to navigate. Design your block architecture to balance structural richness with editorial usability.
Block Reusability Across Models
A key advantage of blocks is that the block models 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, in a stack on a homepage model, and in a stack 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 stack.
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 Fields and Stacks
The two 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 stack 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 stack for the composable article body below. The hero is always there, always the same type. The body sections are composable.
| Capability | Single Block Field | Stack |
|---|---|---|
| Number of block instances | Always one | Variable (editor-controlled) |
| Block type | Fixed (one model) | Any block in the site's library |
| Editors can add/remove | No | Yes |
| Editors can reorder | No | Yes |
| Editors can hide | No | Yes |
| API response shape | Object | Array of objects with _block |
render() input |
Single object | Array |
| Best for | Fixed, mandatory sections | Composable, flexible layouts |
What's Next
- Stacks — Deep dive into the model syntax, content shape, and editor experience.
- Block Models — Defining the block types that block fields reference.
- Content Field Types — The full set of field types available in page, entry, and block models.
- Group Fields — Non-repeatable field containers for organizing related fields.
- Collection Fields — Repeatable inline structures for variable-length lists.
- Page Models — How block fields fit into page model design.
- Template Context and Data Access — The
render()helper and other template utilities.