Template Context and Data Access
Every EJS template in the site builder — page templates, block templates, entry templates, and the layout template — executes with a rich context object. This context gives your template access to the current record's data, the full site content, helper methods for querying and rendering content, image utilities, and injection methods for adding resources to the page head.
This page is the complete reference for everything available inside an EJS template. It covers the content payload structure, the item variable, content query methods, rendering helpers, image and media utilities, head injection methods, and how these pieces fit together in practice.
The Content Payload
During a build, the entire site's content is loaded as a single JSON payload. This payload is what every template draws from — whether you're accessing the current record, looking up an entry, querying pages, or iterating over option sets.
The payload has this top-level structure:
{
"pages": [ ... ],
"entries": { ... },
"images": { ... },
"options": { ... }
}
pages — An array of all page records across all page models. Each page object contains the fields defined by its model, plus system properties like _path, _slug, and _meta.
entries — An object keyed by entry model handle. Single entries are objects; entry collections are arrays. Each entry contains its model's fields plus a _meta property.
images — An object of named images uploaded at the site level (distinct from images attached to individual content records).
options — An object of option sets keyed by handle. Each option set is an array of { label, value } pairs.
You don't interact with this payload directly. Instead, you access it through the item variable and the helper methods described in this page. But understanding the payload structure helps you reason about what data is available and how it's organized.
The item Variable
When a template renders, the current record's data is available as item. This is the primary variable you work with in every template.
What item contains depends on which template is rendering:
In a page template — item is the page record. It contains all the fields defined in the page model, plus system properties.
In a block template — item is the block instance. It contains the fields defined in the block model. When a page has a dynamic block field and the builder renders each block, each block template receives its own block data as item.
In an entry template — item is the entry record, with all the fields defined in the entry model.
In the layout template — item is the same page record that the page template receives. The layout wraps the page, and both share the same item.
System Properties
Every page record includes system properties prefixed with an underscore:
| Property | Description | Present On |
|---|---|---|
_path |
The full URL path of the page (e.g., /blog/hello-world) |
All pages |
_slug |
The slug segment for collection pages (e.g., hello-world) |
Collection pages |
_meta.key |
Internal record identifier | All records |
_meta.created_at |
ISO 8601 creation timestamp | All records |
_meta.updated_at |
ISO 8601 last-update timestamp | All records |
Field Data Shapes
The shape of each field in item mirrors how it's defined in the model:
Text, Number, Boolean, Date, Code, Video — Primitive values (string, number, boolean).
Markdown, Rich Text — HTML strings. Markdown is pre-rendered to HTML in the payload, so you don't need a parser.
Dropdown — The selected option's value as a string (not the label).
Image — An object with url, raw, alt, source, light, dark, and _meta properties.
Reference (one-to-one) — The full referenced entry object, resolved inline. You access the entry's fields directly — item.author.name, not a separate lookup.
Reference (one-to-many) — An array of full entry objects.
Group — A nested object. Fields inside a group are accessed with dot notation: item.seo.meta_title.
Collection — An array of objects, each with the collection's sub-fields.
Dynamic Blocks — An array of block objects, each with a _type property identifying the block model and the block's field data.
Location — An object with coordinates and a static map link.
<!-- Primitive fields -->
<h1><%= item.title %></h1>
<time datetime="<%= item.published_date %>"><%= item.published_date %></time>
<!-- Rich text / Markdown (already HTML — use unescaped output) -->
<div class="content"><%- item.body %></div>
<!-- Image object -->
<img src="<%= item.hero_image.url %>" alt="<%= item.hero_image.alt %>">
<!-- Resolved reference -->
<span>By <%= item.author.name %></span>
<!-- Group (nested object) -->
<meta name="description" content="<%= item.seo.meta_description %>">
<!-- Collection (array of objects) -->
<% item.faqs.forEach(faq => { %>
<details>
<summary><%= faq.question %></summary>
<p><%- faq.answer %></p>
</details>
<% }); %>
<!-- Dropdown value used for conditional logic -->
<section class="theme-<%= item.color_theme %>">
Content Query Methods
These methods let any template query the full site content — pages, entries, images, and options — regardless of which record is currently rendering. They're available directly in the template context, no imports needed.
getContent(search?)
Returns the full content payload, or filters it with a JMESPath query. This is the most flexible content access method — you can reach any part of the payload with the right query.
<!-- Full payload -->
<% const content = getContent(); %>
<!-- All pages -->
<% const allPages = getContent('pages'); %>
<!-- A specific entry by handle -->
<% const footer = getContent('entries.footer'); %>
<!-- Filter pages with JMESPath -->
<% const aboutPage = getContent('pages[?_path==`/about`] | [0]'); %>
<!-- Pages matching a condition -->
<% const featuredPosts = getContent('pages[?featured==`true`]'); %>
getPage(path)
Finds a single page by exact path. Returns the page object, or undefined if no page matches.
<% const about = getPage('/about'); %>
<% if (about) { %>
<a href="<%- path(about) %>"><%= about.title %></a>
<% } %>
<% const home = getPage('/'); %>
getPages(path, opts?)
Finds all pages whose path starts with the given prefix. Returns an array.
Pass { collection: true } to return only collection pages (pages with a slug), excluding the index page at the base path.
<!-- All pages under /blog -->
<% const posts = getPages('/blog/'); %>
<!-- Only collection records (excludes the /blog index page) -->
<% const blogPosts = getPages('/blog/', { collection: true }); %>
getEntry(handle)
Gets an entry by its model handle. Returns the entry object for single entries, or an array of entry objects for entry collections. Returns undefined if not found.
<% const footer = getEntry('footer'); %>
<footer>
<p><%- footer.copyright_text %></p>
<nav>
<% footer.links.forEach(link => { %>
<a href="<%- link.url %>"><%- link.label %></a>
<% }); %>
</nav>
</footer>
<% const nav = getEntry('main-navigation'); %>
getSlugs(basePath)
Extracts slugs from collection pages under a path. Returns an array of slug strings.
<% const blogSlugs = getSlugs('/blog'); %>
<!-- Returns: ['hello-world', 'nextjs-tips', 'latest-news'] -->
getImage(name)
Gets a named site-level image by key. Returns the image object or undefined.
<% const logo = getImage('site-logo'); %>
<img src="<%- src(logo, '200x50') %>" alt="Site Logo">
<% const hero = getImage('homepage-hero'); %>
<% if (hero) { %>
<div style="background-image: url('<%- src(hero, '1920x1080') %>')"></div>
<% } %>
getOptions(name)
Gets an option set by handle. Returns an array of { label, value } pairs, or undefined.
<% const countries = getOptions('countries'); %>
<select name="country">
<% countries?.forEach(opt => { %>
<option value="<%- opt.value %>"><%- opt.label %></option>
<% }); %>
</select>
<% const categories = getOptions('blog-categories'); %>
<ul class="categories">
<% categories?.forEach(cat => { %>
<li data-value="<%- cat.value %>"><%- cat.label %></li>
<% }); %>
</ul>
Rendering Helpers
render(val, separator?)
Renders block content or entry templates. This is the method that makes composable layouts work — it delegates rendering to the appropriate block or entry template.
When you pass a single block object, render() looks up the block model's template and renders it with the block's data as item. When you pass an array (a dynamic block field), it renders each block in sequence and concatenates the HTML output.
An optional separator string is inserted between rendered blocks.
<!-- Render all sections in a dynamic block field -->
<%- render(item.sections) %>
<!-- Render with a separator between blocks -->
<%- render(item.sections, '<hr>') %>
<!-- Render a single block field -->
<%- render(item.sidebar_block) %>
The render() call is what connects page templates to block templates. A page template doesn't need to know how to render a hero banner or a pricing table — it calls render(item.sections) and each block's own template handles the rest.
→ Dynamic Blocks → Model Templates
path(obj)
Returns the URL path for a page object. If the object doesn't have a _path property, returns '#'.
<a href="<%- path(item) %>">Current Page</a>
<% findPages('/blog/').forEach(post => { %>
<a href="<%- path(post) %>"><%= post.title %></a>
<% }); %>
Image Utilities
SleekCMS provides image helper methods that generate optimized image URLs with resizing parameters and produce ready-to-use HTML elements. These work with the image processing service that handles on-the-fly resizing, format conversion, and optimization.
All image utilities accept an image object (the value from an image field) and an attr parameter that specifies dimensions and options.
The attr Parameter
The attr parameter can be either a string shorthand or an object with detailed options:
String shorthand — A "WxH" string like "800x600" that sets width and height.
Object form — An object with any combination of these properties:
| Property | Type | Description |
|---|---|---|
w |
number | Width in pixels |
h |
number | Height in pixels |
size |
string | Shorthand for width × height (e.g., '400x300') |
fit |
string | Resize fitting mode: cover, contain, fill, inside, outside |
type |
string | Output format: webp, avif, png, jpg |
class |
string | CSS class (for img() and picture() only) |
style |
string | Inline CSS (for img() and picture() only) |
src(obj, attr)
Generates an optimized image URL with resizing parameters. Returns a URL string.
When the image object is invalid or missing, returns a placeholder URL from placehold.co. When the image is from Unsplash, it handles Unsplash query parameter conventions automatically.
<!-- String dimensions -->
<img src="<%- src(item.hero, '800x600') %>" alt="<%= item.hero.alt %>">
<!-- Object with options -->
<img src="<%- src(item.thumbnail, { w: 400, h: 300, fit: 'cover', type: 'webp' }) %>">
<!-- Size shorthand in object form -->
<img src="<%- src(item.thumbnail, { size: '400x300', fit: 'cover' }) %>">
<!-- Placeholder fallback when image is missing -->
<img src="<%- src(null, '400x300') %>">
<!-- Output: https://placehold.co/400x300.png -->
img(obj, attr)
Generates a complete <img> HTML element. Handles src URL generation, alt attribute escaping, and optional class and style attributes. Returns an HTML string.
Falls back to a placeholder element when the image object is invalid. Use this as a simpler alternative to picture() when you don't need dark/light theme variants.
<!-- Basic img element -->
<%- img(item.hero, '800x600') %>
<!-- Output: <img src="https://example.com/hero.png?w=800&h=600" alt="Hero description"> -->
<!-- With object attributes -->
<%- img(item.thumbnail, { w: 400, h: 300, fit: 'cover', type: 'webp' }) %>
<!-- With class and style -->
<%- img(item.hero, { size: '800x600', class: 'hero-img', style: 'object-fit: cover' }) %>
<!-- Placeholder fallback -->
<%- img(null, '400x300') %>
<!-- Output: <img src="https://placehold.co/400x300.png" alt=""> -->
picture(obj, attr)
Generates a <picture> element with support for dark and light theme variants. When the image object has dark or light variant properties, the output includes <source> elements with prefers-color-scheme media queries for automatic theme switching.
Falls back to a simple <picture> with a single <img> when no variants are present.
<!-- Basic picture element -->
<%- picture(item.hero, '800x600') %>
<!-- Output: <picture><img src="..." alt="..."></picture> -->
<!-- Image with dark mode variant -->
<%- picture(item.logo, '200x50') %>
<!-- Output when dark variant exists:
<picture>
<source srcset="https://.../logo-dark.png?w=200&h=50" media="(prefers-color-scheme: dark)">
<img src="https://.../logo.png?w=200&h=50" alt="Logo">
</picture>
-->
<!-- Image with both dark and light variants -->
<%- picture(item.brand_logo, '300x100') %>
<!-- Output:
<picture>
<source srcset="https://.../logo-dark.png" media="(prefers-color-scheme: dark)">
<source srcset="https://.../logo-light.png" media="(prefers-color-scheme: light)">
<img src="https://.../logo.png" alt="Brand Logo">
</picture>
-->
<!-- With class and style -->
<%- picture(item.logo, { size: '200x50', class: 'logo', style: 'max-width: 100%' }) %>
svg(obj, attr?)
Inlines an SVG file from a URL with optional custom attributes applied to the root <svg> element. This gives you full CSS control over the SVG's colors, sizing, and animations — something not possible with <img> tags.
Returns the inline SVG markup. Returns an empty SVG with an error attribute if the object is invalid or the URL doesn't point to an .svg file.
<!-- Inline SVG -->
<%- svg(item.icon) %>
<!-- Output:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 2L2 7l10 5 10-5-10-5z"/>
</svg>
-->
<!-- With custom attributes -->
<%- svg(item.logo, { width: 120, height: 40, class: 'logo' }) %>
<!-- Output:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 60" width="120" height="40" class="logo">
<text x="10" y="40">My Logo</text>
</svg>
-->
Head Injection Methods
These methods add resources to the page's <head> section (and scripts to the end of <body>). They can be called from any template — page, block, entry, or layout — and the builder collects all injected resources and places them in the correct location in the final HTML output.
All injection methods (except title) support an optional order parameter that controls the position of the output. Lower numbers appear first. Duplicate entries (based on content hash) are automatically deduplicated, so calling the same injection twice won't produce duplicate tags.
title(value)
Sets the page's <title> tag.
<% title(item.title + ' | My Website') %>
<!-- Output: <title>About Us | My Website</title> -->
meta(value, order?)
Adds a <meta> tag. Pass an object with the tag's attributes.
<% meta({ name: 'author', content: 'John Doe' }) %>
<!-- Output: <meta name="author" content="John Doe"> -->
<% meta({ property: 'og:type', content: 'website' }) %>
<!-- Output: <meta property="og:type" content="website"> -->
link(value, order?)
Adds a <link> tag. Accepts a string URL (auto-detects the type based on file extension) or an object with explicit attributes.
<!-- String URL — type auto-detected -->
<% link('/css/custom.css') %>
<!-- Output: <link rel="stylesheet" type="text/css" href="/css/custom.css"> -->
<% link('/feed.xml') %>
<!-- Output: <link rel="alternate" type="application/rss+xml" href="/feed.xml"> -->
<% link('/favicon.ico') %>
<!-- Output: <link rel="icon" href="/favicon.ico"> -->
<!-- Object with explicit attributes -->
<% link({ rel: 'canonical', href: 'https://example.com' + path(item) }) %>
<!-- Output: <link rel="canonical" href="https://example.com/about"> -->
<% link({ rel: 'preconnect', href: 'https://fonts.googleapis.com' }) %>
style(value, order?)
Adds inline CSS. Pass a string of CSS rules, or an object with a content property.
<!-- Inline CSS string -->
<% style('.hero { background: #000; color: #fff; }') %>
<!-- Output: <style>.hero { background: #000; color: #fff; }</style> -->
<!-- Object form -->
<% style({ content: '.btn { padding: 10px 20px; }' }) %>
<!-- Output: <style>.btn { padding: 10px 20px; }</style> -->
script(value, order?)
Adds a script to the page, appended to the end of <body>. Accepts a string (interpreted as a URL if it ends in .js, otherwise as inline code) or an object with src, content, and other script attributes.
<!-- External script (string ending in .js) -->
<% script('/js/analytics.js') %>
<!-- Output: <script src="/js/analytics.js"></script> -->
<!-- Inline JavaScript -->
<% script('console.log("Page loaded");') %>
<!-- Output: <script>console.log("Page loaded");</script> -->
<!-- External script with attributes -->
<% script({ src: '/js/app.js', defer: true }) %>
<!-- Output: <script src="/js/app.js" defer="true"></script> -->
<!-- Module script -->
<% script({ src: '/js/module.js', type: 'module' }) %>
<!-- Output: <script src="/js/module.js" type="module"></script> -->
<!-- Inline script via object -->
<% script({ content: 'window.config = { debug: true };' }) %>
<!-- Output: <script>window.config = { debug: true };</script> -->
Client-Compatible Methods
The template context methods — getContent, getPage, getPages, getEntry, getSlugs, getImage, and getOptions — are designed to be API-compatible with the @sleekcms/client JavaScript library. They share the same method names, parameters, and return shapes.
This means the same mental model applies whether you're querying content in a site builder template or in a Next.js component using the client library. A call like getPages('/blog/', { collection: true }) works identically in both contexts.
Complete Example
A page template that combines the context features covered in this page:
<% title(item.title + ' | My Site') %>
<% meta({ property: 'og:title', content: item.title }) %>
<% meta({ property: 'og:type', content: 'article' }) %>
<% link({ rel: 'canonical', href: 'https://mysite.com' + path(item) }) %>
<article>
<h1><%= item.title %></h1>
<% if (item.author) { %>
<p class="byline">
<img src="<%- src(item.author.headshot, '48x48') %>" alt="">
By <%= item.author.name %>
</p>
<% } %>
<% if (item.hero) { %>
<%- picture(item.hero, { size: '1200x600', class: 'hero-image' }) %>
<% } %>
<div class="content">
<%- render(item.sections) %>
</div>
<aside>
<h2>Related Posts</h2>
<% getPages('/blog/', { collection: true }).slice(0, 3).forEach(post => { %>
<a href="<%- path(post) %>">
<img src="<%- src(post.image, '300x200') %>" alt="">
<span><%= post.title %></span>
</a>
<% }); %>
</aside>
</article>
<% const footer = getEntry('footer'); %>
<footer>
<p><%- footer.copyright_text %></p>
<% footer.socials.forEach(social => { %>
<a href="<%- social.link %>"><i class="<%- social.icon %>"></i></a>
<% }); %>
</footer>
This template sets the page title and meta tags via injection, renders the current page's author reference, hero image with dark mode support, dynamic block sections, related posts from the same collection, and a footer from an admin-only entry — all using the methods documented on this page.
Quick Reference
Content
| Context | Returns | Purpose |
|---|---|---|
item |
Object | Current record's data |
pages |
Array | All page records |
entries |
Object | All entries keyed by model handle |
images |
Object | Named site-level images |
options |
Object | Option sets keyed by handle |
Access Methods
| Method | Returns | Purpose |
|---|---|---|
getContent(search?) |
Any | Full payload or JMESPath query result |
getPage(path) |
Object | undefined | Single page by exact path |
getPages(path, opts?) |
Array | Pages matching a path prefix |
getEntry(handle) |
Object | Array | undefined | Entry by model handle |
getSlugs(basePath) |
string[] | Slug values for a collection |
getImage(name) |
Object | undefined | Named site-level image |
getOptions(name) |
Array | undefined | Option set by handle |
Rendering
| Method | Returns | Purpose |
|---|---|---|
render(val, separator?) |
string | Render blocks or entry templates |
path(obj) |
string | URL path for a page object |
src(obj, attr) |
string | Optimized image URL |
img(obj, attr) |
string | <img> HTML element |
picture(obj, attr) |
string | <picture> element with theme variants |
svg(obj, attr?) |
string | Inline SVG markup |
Head and Meta
| Method | Returns | Purpose |
|---|---|---|
title(value) |
void | Set page <title> |
meta(value, order?) |
void | Add <meta> tag |
link(value, order?) |
void | Add <link> tag |
style(value, order?) |
void | Add inline <style> |
script(value, order?) |
void | Add <script> to body |
What's Next
- Model Templates — Binding EJS templates to page, entry, and block models.
- Layout — The shared layout template that wraps page output.
- Site Builder — The overall build pipeline and preview system.
- Dynamic Blocks — How
render()powers composable page layouts. - Content Field Types — The field types that determine
itemdata shapes. - Content API — How the same content structure flows through the API.
- @sleekcms/client — The JavaScript client with matching method signatures.