Noon Docs · Getting Started · Concepts · Guides · Reference

A template holds the parts of a site that repeat across many pages: a header, a footer, a sidebar, or the shared body of every blog post. An agent writes a template with manage_template, using the same {blocks: [...]} body shape a page uses.

What a template is

A template is a stored row with a slug, a title, a type, and a body. The body is validated and rendered exactly like a page layout: the same block types, the same authoring rules, the same publish lifecycle.

The four template types

  • header, footer, sidebar: region templates. Each renders in that slot on every page that uses it.
  • post-detail: the body of a single blog post route. This is not a region. It is the article's own <article> content, in place of the page-layout body a page would otherwise carry.

A template's type cannot change after it is created. Moving a template's content into a different slot means creating a new template of the right type, not updating the existing one.

Blocks a region template refuses

A header, footer, or sidebar body rejects a few block types that a page may use freely:

  • PostList: a feed rendered on every page is not what a region is for.
  • FAQ: the search-engine markup for an FAQ block ties to one specific page, and a region has no single owning page.
  • FormBlock: a form's submissions resolve to the page that owns it, and a region has no single owning page.
  • Poll: a poll's ballot is addressed by the page that carries it, the same reasoning as FormBlock.
  • Any block carrying an anchor: an anchor must be unique on the page it appears on, and a region renders unmodified on every page, so the same anchor would collide with itself.

Blocks a post-detail template may use

A post-detail body accepts the same general-purpose blocks a region does (HeroSection, RichText, ImageBlock, ProductGrid, Callout, SocialLinks, Countdown), plus one block a region cannot carry: PostField.

PostField is a slot bound to one fixed part of the post:

FieldMeaning
titleThe post's title.
bylineThe post's author, from its frontmatter.
publishedAtThe post's publish date.
bodyThe post's compiled MDX content.
tagsThe post's tags.
coverImageThe post's cover image.
{ "type": "PostField", "props": { "field": "title" } }

A post-detail body must carry exactly one title slot and exactly one body slot. The other four fields are optional and may each appear at most once.

The manage_template tool

action is create, update, or unpublish. There is no read action: read templates back with query on the templates collection.

create needs slug, title, type, and body:

{
  "action": "create",
  "slug": "site-footer",
  "title": "Footer",
  "type": "footer",
  "body": {
    "blocks": [
      { "type": "SocialLinks", "props": { "links": [{ "platform": "x", "href": "https://x.com/example" }] } }
    ]
  }
}

update needs id, expectedRevision, title, and body. slug and conditions are optional. type is never part of an update.

{
  "action": "update",
  "id": "tmpl_01h...",
  "expectedRevision": 2,
  "title": "Footer",
  "body": { "blocks": [ /* ... */ ] }
}

unpublish needs id and expectedRevision, and takes no content:

{ "action": "unpublish", "id": "tmpl_01h...", "expectedRevision": 3 }

Unpublishing keeps the published body in place, so republishing later needs no re-authoring. The result names an outcome and a regionBecameEmpty flag, telling you whether that slot now has nothing published in it at all.

Every write is CAS-guarded: a mismatched expectedRevision returns a revision_conflict error naming the current revision to retry with.

A template's draft is published the same way a page's is, through the shared publish tool:

{ "entityType": "template", "id": "tmpl_01h...", "expectedRevision": 0 }

Where a template renders: conditions

A template may carry optional conditions: {pathGlob?, country?}. Both clauses, if present, must match for the template to be a candidate at all.

  • pathGlob is either an exact absolute path (/docs/getting-started) or an absolute literal prefix ending in /** (/docs/**). No other wildcard syntax is accepted.
  • country is a two-letter country code (DE, US).

A template published with no conditions is unconditional: it is the default for its type. At most one unconditional template per type may be published at once. Publishing a new unconditional template of a type automatically replaces the previously published unconditional one of that type; the replaced template keeps its body and can be republished later without re-authoring. Publishing a conditional template never replaces anything. Any number of conditional templates of the same type may be published side by side.

How Noon picks the winner

When a page renders, Noon looks at every published template of a type whose conditions match the request, and picks the most specific one:

  1. An exact-path match beats a /** prefix match, which beats no path condition at all.
  2. Among prefix matches, the longer literal prefix wins.
  3. If the path comparison is still tied, a template with a matching country condition beats one with none.

If nothing conditional matches, the unconditional default, if published, renders. If a request's path cannot be resolved at all, every conditional template is treated as non-matching, but the unconditional default may still serve. Two published templates that tie on every point above are broken by comparing their ids as plain text and picking the lower one. This only happens if you publish two templates with identical conditions, which is worth avoiding rather than relying on.

Check your work before publishing

explain_template_resolution is a read-only tool that answers "which template would render here, and why" without changing anything.

{ "path": "/docs/getting-started", "country": "DE", "type": "header" }

The answer names the winner, if any, and lists every published candidate of that type: whether each of its clauses matched, and for every candidate that lost, the first reason it lost against the winner. Omit type to check every template type in one call.

Site settings

manage_site replaces the whole site settings document in one call. Any key you omit resets to its default, so read the current settings with query before writing if you only want to change one field.

KeyWhat it holds
siteNameThe site's display name, used in page titles and structured data.
titleTemplateOne of three fixed patterns for the page <title>: {{title}}, {{title}} – {{siteName}}, or {{siteName}} – {{title}}.
defaultMetaDescriptionThe fallback meta description for pages that don't set their own.
defaultOgImageThe fallback social-preview image.
faviconThe site's favicon: an .ico file, or a .png at one of a fixed set of sizes.
siteLogoThe site's logo image.
defaultLocaleThe site's default language tag, e.g. en or de-DE.
identityThe site's organization or person identity, with its social profile links, used in structured data.
permalinkPatternThe URL pattern blog posts are served under.
customCssOne block of site-wide CSS. See the themes guide.
analyticsMatomo analytics configuration, or none.

There is no navigation key on site settings. Site navigation, such as a menu bar or a footer link list, is authored as a header or footer template, built from ordinary blocks like SocialLinks or RichText.