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:

Liquid
{{ 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
Note

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

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:

Liquid
{% 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

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 its type, 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.
Templates are always created unpublished

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.