Blog-Doc passes a data context to every template render. Some variables are available everywhere — others only on specific routes.

Shared data (all templates)

These are available on every template regardless of route. Select a tab to explore each group.

Sourced from app/data/settings.json.

Variable Type Example
site.title string "My Blog"
site.description string "A personal blog"
site.url string "https://example.com" (no trailing slash)
site.language string "en"
site.logo string "/images/logo.png"
site.icon string "/images/favicon.png"
site.headerCode string Raw HTML injected into <head>
site.footerCode string Raw HTML injected before </body>
site.postsPerPage number 10
site.year number 2026

Note: site.year is the only site variable sourced from admin/functions/site-data.js

Sourced from the active theme's theme.json.

Variable Type Example
theme.id string "default"
theme.name string "Default"
theme.version string "1.0.0"
theme.assetsUrl string /app/themes/my-theme/assets
theme.path string /app/themes/my-theme
theme.features array ["blog"]
theme.menus array ["primary", "footer"]

Note on theme.assetsUrl in static builds: During live development, theme.assetsUrl resolves to /app/themes/{id}/assets. The static build engine automatically rewrites this prefix to /assets in every rendered HTML file, so theme authors can use {{ theme.assetsUrl }} freely without any isGenerateStatic conditionals.
The same mechanism applies to theme.path which resolves to /app/themes/{id}: filesystem path to the theme root; useful for advanced template logic or tooling — {{ theme.path/custom/file.ext }}

Object where each key is a menu location name and each value is an array of items. Sourced from app/data/menus.json.

Each item has the shape { id, label, url, children? }. Children support up to 3 levels of nesting.
Every item in a menus object carry its nesting depth field (0 = top-level, 1 = child, 2 = grandchild).

{
    "id": "6fe4e3b5-d2f4-48b2-9b3a-7751c3ec11e3",
    "label": "Getting Started",
    "url": "/getting-started",
    "children": [
        {
            "id": "1dbef501-be21-48f1-aaaa-0d5a2ceeccbc",
            "label": "Project Structure",
            "url": "/getting-started/project-structure"
        }
    ]
}

Use {{#each}} for top-level items, {{#each1}} for depth-2, and {{#each2}} for depth-3:

{{#each menus["primary"]}}
    <a href="{{ url }}" data-depth="{{depth}}">{{ label }}</a>
    {{#if children | length}}
        {{#each1 children}}
            <a href="{{ url }}" data-depth="{{depth}}">{{ label }}</a>
        {{/each1}}
   {{/if}}
{{/each}}
Variable Type Notes
isGenerateStatic boolean false during live dev. true when the build engine renders the template.

Use this flag to conditionally include dev-only scripts:

{{#if isGenerateStatic}}
<!-- static output: no dev scripts -->
{{#else}}
<script src="/admin/assets/js/live-reload.js"></script>
{{/if}}

One flag is true per render. All others are absent (falsy). Use them to branch logic in shared partials.

Variable Set on Notes
homeRoute Home page (home.html)
blogRoute Blog index (blog.html)
postRoute Single post (post.html) Also sets contentRoute and fileType = "post"
pageRoute Single page (page.html) Also sets contentRoute and fileType = "page"
taxonomyRoute Taxonomy pages (taxonomy.html)
notFoundRoute 404 page (404.html)
contentRoute Any content route — pages and posts Convenience flag true on both content types
fileType "page" or "post" on content routes

Per-route variables

Select a template below to see what extra variables it receives.

Route: GET / — Also sets homeRoute = true

Receives the shared data above, plus:

posts[] — Array of recent published posts (up to postsPerPage, newest first). Each item:

Property Type Notes
slug string URL identifier
title string
description string
date string YYYY-MM-DD
category string Slugified — use in hrefs
categoryName string Raw display name
tags string[] Raw tag strings
featured_image string | null
excerpt string Up to 200 chars of plain text

pages[] — All published pages sorted by order then title. Each item: slug, title, description, order, root, status, url, featured_image.

pagination — Same shape as blog, but currentPage is always 1 and hasPrev is always false. Check pagination.hasNext to know if there are more posts.

Routes: GET /blog, GET /blog/page/:num — Also sets blogRoute = true

Receives shared data, plus:

posts[] — Paginated slice of published posts. Same shape as home's posts but contains only the entries for the current page.

pagination:

Property Type Notes
currentPage number Current page number (e.g. 2).
totalPages number Total number of available pages.
prevPage number | null Previous page number, or null if on the first page.
nextPage number | null Next page number, or null if on the last page.
hasPrev boolean true if a previous page exists.
hasNext boolean true if a next page exists.
firstPageHref string URL to the first page of the paginated posts.
lastPageHref string URL to the last page of the paginated posts.
prevPageHref string | null URL to the previous page (newer posts), or null if on the first page.
nextPageHref string | null URL to the next page (older posts), or null if on the last page.

Note: when prevPage is 1, link to /blog not /blog/page/1.

Route: GET /blog/:slug — Also sets postRoute = true, contentRoute = true, fileType = "post"

Receives shared data, plus:

post — Full frontmatter plus slug:

Variable Type Notes
post.slug string
post.title string
post.description string
post.date string YYYY-MM-DD
post.category string Slugified — use in hrefs
post.categoryName string Raw display name
post.tags string[] Raw tag strings
post.relatedPosts object[] | null null when no related posts are set on this post
post.featured_image string | null
post.status string Always "published"
post.author string

relatedPosts — Posts related to the current one:

Posts also support a related_posts field: an ordered array of up to 4 post slugs. Set it from the Related Posts panel in the post editor — a searchable checkbox list of all published posts. The selection order is preserved and exposed to templates as the relatedPosts array, letting theme authors render a "Read Also" or "You Might Also Like" section below the article. Stale slugs pointing to deleted or re-drafted posts are silently dropped at render time.

Variable Type Notes
post.relatedPosts object[] | null null when no related posts are set
post.relatedPosts[n].slug string URL identifier
post.relatedPosts[n].title string
post.relatedPosts[n].description string Empty string if not set
post.relatedPosts[n].date string YYYY-MM-DD; empty string if not set
post.relatedPosts[n].category string | null Raw category string (not slugified); null if unset
post.relatedPosts[n].featured_image string | null

Order is preserved from the editor (insertion order = author's editorial intent). Max 4 items. Stale slugs (deleted or re-drafted posts) are silently dropped at render time.

prevPost and nextPost — Posts before and after the current one:

These variables provide adjacent-post navigation for the current article. prevPost contains the chronologically older published post, while nextPost contains the chronologically newer published post. Each value is either a post object or null when no adjacent post exists, allowing themes to build "Previous" and "Next" navigation links at the end of an article.

Variable Type Notes
prevPost object | null Chronologically older post; null if none exists
prevPost.slug string
prevPost.title string
prevPost.featured_image string | null
nextPost object | null Chronologically newer post; null if none exists
nextPost.slug string
nextPost.title string
nextPost.featured_image string | null

html_content — The post's Markdown body rendered to HTML. Inject unescaped: {{ html_content }}

Routes: GET /:slug (root), GET /:l1/:l2 (depth-2), GET /:l1/:l2/:l3 (depth-3), GET /pages/:slug (fallback) — Also sets pageRoute = true, contentRoute = true, fileType = "page"

Receives shared data, plus:

page — Full frontmatter plus hierarchy fields derived at render time:

Variable Type Notes
page.slug string
page.title string
page.description string
page.order number | undefined
page.root boolean true if anchors a URL hierarchy
page.parent string | undefined Parent slug
page.featured_image string | null
page.url string Full derived URL
page.depth number 1, 2, or 3
page.child boolean true at depth 2
page.grandchild boolean true at depth 3
page.ancestors array [{ slug, title, url }, …] — empty for depth-1
page.path string[] Array of slugs from root to current

html_content — The page's Markdown body rendered to HTML.

Routes: /categories/:category, /tags/:tag (and paginated) — Also sets taxonomyRoute = true

Receives shared data, plus:

Variable Type Notes
taxonomyType string "category" or "tag"
taxonomyTerm string | null Convenience variable: categoryName or tagName
category string | null Slugified — null when taxonomyType is "tag"
categoryName string | null Raw display name — null when type is "tag"
tag string | null Slugified — null when type is "category"
tagName string | null Raw display name — null when type is "category"

Plus the same posts[] and pagination as blog.html.

Triggered: Any unmatched route — Also sets notFoundRoute = true

Note: page, post, posts, and pagination are not available here.

Variable Type Value
notFoundRoute boolean true
title string "Page Not Found"
description string "The page you requested could not be found."

Shared data (site, theme, menus, isGenerateStatic) is still available.