Layouts, snippets & templates
Layouts wrap a page’s rendered sections in real HTML. Snippets are small reusable partials. Templates are prebuilt starting points a content editor can pick from when creating a new page.
Layouts
Flat .liquid files in theme/layouts/, no schema, no subfolder. The filename (without .liquid) is the layout’s identifier, referenced by a page’s own "layout" field in its content JSON. A page with no "layout" field defaults to a layout named exactly theme — every theme must define layouts/theme.liquid as its default/fallback layout.
Available variables in a layout:
{{ content_for_layout | raw }} the page's rendered sections - always needs | raw
{{ page.title }} the page's title
{{ page.author }} the page's author, if set
{{ page.publishDate }} the page's publish date, if set
{% for tag in page.tags %} the page's tags, if any
{{ menus.<handle>.items }} each menu in content/menus/, keyed by filename
{{ menus.<handle>.name }} that menu's display name (blank when it has none)
{{ settings.<name> }} a site setting - see Site settings
A layout never sees page.sections — it’s a wrapper, not a second place to render content. The same page fields (page.title, page.author, page.publishDate, page.tags) are also available inside sections and blocks.
Worked example — layouts/theme.liquid
<!doctype html>
<html lang="en">
<head>
<title>{{ page.title }}</title>
<link rel="stylesheet" href="/assets/style.css">
<link rel="icon" href="/assets/favicon.svg" type="image/svg+xml">
</head>
<body>
<header class="site-header">{% render 'site-name', name: 'My Site' %}</header>
<nav class="site-nav">
{% for item in menus.main.items %}
<a href="{{ item.url }}">{{ item.label }}</a>
{% endfor %}
</nav>
{{ content_for_layout | raw }}
<footer>
{% if settings.instagram_url != blank %}
<a href="{{ settings.instagram_url }}">Instagram</a>
{% endif %}
</footer>
</body>
</html>
A menu’s handle is its filename, so content/menus/main.json is menus.main. Editors can rename a menu’s display name freely; changing its handle in the admin warns about every template that still uses the old one, since those menus would render empty.
Snippets
Flat .liquid files in theme/snippets/, no schema, no subfolder. Invoked from any layout, section, or block with the standard Liquid render tag — never include:
{% render 'snippet-name', param1: 'value', param2: some_variable %}
A snippet only sees the parameters explicitly passed to it — the calling template’s own scope does not leak in. The one exception is settings: site settings are available in every snippet without being passed. Keep snippets small and focused (a site name label, a social icon, a card) — a whole page section belongs in sections/ or blocks/ instead.
The responsive-media snippet
Every new site ships snippets/responsive-media.liquid. Render every content image and video through it: it writes the responsive markup and the attribute that lets an editor drag a replacement from the media library onto the live preview. See Rendering images and video.
Worked example — snippets/site-name.liquid
<span class="site-name">{{ name }}</span>
Page templates
theme/templates/ is optional — a theme with no templates folder (or an empty one) is entirely normal; the admin simply offers no template picker and every new page starts blank. When present, each file is a prebuilt starting point (a blog article, a product page, a landing page) that a content editor can pick from when creating a new page: flat, one .json file per template, named directly (e.g. theme/templates/blog-article.json) — no subfolders, same convention as sections/.
A template file is a real page — exactly the same JSON shape as any file under content/pages/, validated against the identical schema, using the theme’s own current section and block types. There is no separate template format to author: build a template the way you’d build any other page, then save it under theme/templates/ instead of content/pages/. Fill each section with short placeholder copy — a template is a starting point to edit, not a blank form.
- Its
"title"is the label an editor picks from — name it for the kind of page ("Blog Article"), never"Untitled". - Set its
"type"to that kind too ("article","project"), not"page". Every page created from the template inherits itstype, and that value is what listings filter on — an article template left at"type": "page"produces articles no blog index can find, with nothing failing to warn you.
A template’s own "published" value is never honoured when a page is actually created from it — the admin always creates the new page as an unpublished draft. A template that fails to parse as JSON, or fails validation against the current theme, is skipped — never a startup failure. If a template you added doesn’t appear in the admin’s picker, check that it validates the same way a real page would.
Next
Continue to Site settings, then The content model for the full page, menu, and redirect schemas.