Template code reference
テンプレートで使える特殊コード
KuroCMS のテンプレートは、[[...]] という特殊コードを含む「未展開の HTML 原本」です。公開ビルド時に CMS がこれらを実データへ展開します。テンプレートを自作・修正する人が使える表現を、役割とサンプルつきでまとめました。
先頭マーカー必須
HTML 原本の 1 行目は必ず <!-- kurocms-template-api:1 --> で始めます。未知の値は空文字に、オブジェクトの直接出力は不可、セクションの閉じ忘れは描画エラーになります。[[html:…]] は無加工出力なので信頼できる CMS データにのみ使ってください。
1. 基本構文 [[...]]
| 構文 | 動作 | 例 |
|---|---|---|
[[path]] / [[value:path]] | 値を HTML エスケープして出力 | [[site.name]] |
[[html:path]] | 無加工で HTML 出力(信頼できる CMS データのみ) | [[html:article.bodyHtml]] |
[[#if path]]…[[/if]] | 値が真のとき中身を出力 | [[#if page.isArticle]]…[[/if]] |
[[#unless path]]…[[/unless]] | 値が偽のとき中身を出力 | [[#unless article.coverUrl]]…[[/unless]] |
[[#each path]]…[[/each]] | 配列を反復(中は相対パス / [[this]]) | [[#each articles]][[title]][[/each]] |
[[#each type:{slug}:{N}]]…[[/each]] | 指定タイプの記事を新しい順に N 件(下の 3 章) | [[#each type:news:5]] |
2. データモデル(参照できるパス)
| パス | 内容 |
|---|---|
page.isHome / isAbout / isPrivacy / isTerms / isArticle / isType / isCategory / isRecipe | 現在ページの種別フラグ。記事下部の関連記事などは [[#if page.isArticle]] で囲む。isRecipe はレシピカードを持つ記事で true |
site.name / site.lang / site.dir / site.basePath | サイト名 / 表示言語 / 書字方向(rtl|ltr) / 公開 URL プレフィックス |
content.{key} | 管理画面「サイト文字編集」で編集するサイトテキスト(例 content.footer-text) |
navigation.types / navigation.categories | タイプ / カテゴリ配列(件数は含まない)。各項目の isCurrent は現在ページに対応する項目だけ true → ナビの現在地ハイライトに使う |
articles | 一覧ページの記事配列(slug, tid, title, summary, publishAt, coverUrl, categories) |
article | 記事詳細(上記 + type, bodyHtml, updatedAt)。記事ページ以外は null |
article.recipe | レシピカードの内容(下の 7 章)。カードが無い記事では null |
pagination | page / totalPages / prevUrl / nextUrl(旧ページング用。一覧では原則 null) |
type.id / type.name · category.id / category.name | 現在のタイプ / カテゴリ |
integrations.blueskyHandle | Bluesky ハンドル |
3. テンプレートから記事を引く(データ参照) KuroCMS v1.8.79+
articles はそのページのスコープの記事しか持ちません(TOP なら全タイプの最新が混ざります)。TOP に「お知らせ枠=news」「製品枠=products」のようなタイプ別の枠を作りたいときは、テンプレートから名指しで引きます。
| 記法 | 内容 | 例 |
|---|---|---|
[[#each type:{slug}:{N}]] | 指定タイプの記事を新しい順に N 件 | [[#each type:news:5]] |
[[#each category:{slug}:{N}]] | 指定カテゴリの記事を新しい順に N 件 | [[#each category:tips:3]] |
[[#each articles:latest:{N}]] | 全タイプ横断で最新 N 件 | [[#each articles:latest:8]] |
[[#each type:all]] | タイプ一覧(件数つき) | [[#each type:all]][[name]][[/each]] |
[[#each category:all]] | カテゴリ一覧(件数つき) | [[#each category:all]][[name]][[/each]] |
- 件数
Nは 1〜50(省略時 10) - 存在しない slug は空配列。
[[#each]]が何も出さないだけでテンプレートは壊れません - マークアップは 100% テンプレート側の責務です。CMS は HTML を組み立てず、記事カードのデータだけを渡します
- テンプレートに書いた参照だけを取得します。使わなければクエリは 1 本も増えません
配置例(トップページのタイプ別 2 枠)
<h2>更新情報・お知らせ</h2>
<ul>
[[#each type:news:5]]
<li><a href="[[site.basePath]]/[[tid]]/[[slug]]/"><time>[[date]]</time>[[title]]</a></li>
[[/each]]
</ul>
<h2>主な製品・サービス</h2>
[[#each type:products:3]]
<a class="card" href="[[site.basePath]]/[[tid]]/[[slug]]/">
[[#if coverUrl]]<img src="[[coverUrl]]" alt="">[[/if]]
<h3>[[title]]</h3><p>[[summary]]</p>
</a>
[[/each]] 4. カルーセルと広告 KuroCMS v1.8.79+
画像カルーセル [[carousel:{key}|{opts}]]
「どんな見た目・サイズか」はテンプレート、「どの画像を出すか」は運用者という分担です。テンプレートに画像 ID を直書きしません。
テンプレート
[[carousel:hero|100%x320,fade,arrows,5s]] サイトテキスト hero
img-100,img-101,img-102 - サイズ
100%/100%x320/640x360(数値だけなら px。既定 100%x320) - 切替
fade(既定) /slide - 矢印
arrows(既定) /noarrows - ドット
dots(既定) /nodots - 自動送り
5s(既定) /0s=自動送りなし - 順序は自由(
[[img-100|60%,right]]と同じカンマ区切り) - 画像 0 枚なら何も出力せず、1 枚なら矢印もドットも出しません
自動送りは prefers-reduced-motion とマウスホバーで停止します。ルートに class="kuro-carousel" が付くのでテンプレート側で装飾できます。
広告(Google AdSense)
テンプレートに [[ad]] を置くと AdSense の広告ユニットに展開します。預けるのは ID だけで、配布された広告コードを貼り付ける必要はありません(スクリプト本体は CMS が組み立てます)。
設定(サイトテキスト ad-adsense)
ca-pub-1234567890123456,1234567890,9876543210 1 行にカンマ区切りで書きます。先頭がクライアント、以降が枠 ID です。キーを分けないのは、分けると片方の設定漏れに気付けないためです。
[[ad]]=1 番目の枠、[[ad:2]]=2 番目の枠(1 始まり)- 未設定・形式不正・枠番号が範囲外のときは何も出力しません(壊れたタグを残さない)
- AdSense のローダーは実際に広告を出したページにだけ読み込まれます
- ユニットには
class="kuro-ad adsbygoogle"が付くので、.kuro-adをテンプレート側で自由に装飾できます
Amazon アソシエイトは未対応です。トラッキング ID は成果の帰属に使う識別子であって、それだけで広告ユニットを描画する仕組みではありません(ネイティブショッピングウィジェットには別途インスタンス ID が必要)。
5. サイトテキスト内で使える表現
管理画面「サイト文字編集」の各値(content.{key})の中では、テンプレートパーサーに渡す前に次が展開されます。
| 表現 | 内容 |
|---|---|
[[type:all]] / [[category:all]] | タイプ / カテゴリ一覧 JSON |
[[type:slug:N]] / [[category:slug:N]] | 指定タイプ / カテゴリの記事 N 件 |
[[articles:latest:N]] | 最新記事 N 件 |
[[article:slug]] | 指定記事のデータ |
[[mid]] | 画像・動画・音声タグへ展開(例 [[img-816]]) |
[[sid]] | 対応する SNS ウィジェットへ展開 |
[[lang]] | 言語スイッチャ・ウィジェットへ展開(ナビ等に配置) |
[[search]] | 記事検索ウィジェットへ展開(ナビの TYPE 左などに配置)。PC は入力欄+検索アイコン、スマホはアイコン→タップでダイアログ |
[[privacy]] / [[terms]] | プライバシーポリシー / 利用規約ページへのリンクに展開。対応するサイトテキストが空なら何も出ない(デッドリンクを作らない)ので、フッターに無条件で置いてよい |
[[ad]] / [[ad:2]] | Google AdSense の広告ユニット(下の 4 章) |
[[carousel:{key}|{opts}]] | 画像カルーセル。画像はサイトテキスト {key} にメディア ID をカンマ区切りで並べる(下の 4 章) |
6. 予約サイトテキスト slug
実体を保存しなくても、CMS がビルド時に自動生成する特別な content.* です。テンプレートに置くだけで機能します。
[[html:content.related-N]] 関連記事ストリップ。記事ページで、その記事と同じカテゴリの公開日が近い記事を N 枚並べます。中央の ←・→ が当記事で、左に古い記事 floor(N/2) 枚・右に新しい記事 ceil(N/2) 枚。端(最古/最新)では反対側から補充して常に N 枚を保ちます。画像とタイトルのみの小さなカードで、画面幅が狭くなっても折り返さず縮小して N 枚を維持します。
Nは 1〜20。枚数を変えるだけ:related-2/related-4/related-6- 記事ページ以外・カテゴリ未設定・近傍記事なしのときは空文字(何も出ません)
- データは書き換えず、表示専用。KuroCMS 本体が対応バージョン以外なら安全に無視されます
配置例(記事本文の直後 = 筆者表示の下)
[[#if page.isArticle]]
<article>
<div class="prose">[[html:article.bodyHtml]]</div>
[[html:content.related-4]]
</article>
[[/if]] [[html:archives]] | 月別アーカイブ切替の <select>。一覧ページで使用 |
7. レシピカード page.isRecipe
本文エディタの鍋アイコンから「レシピカード」(人数・時間・材料・手順)を挿入できます。カードを 1 枚置いた記事が、そのままレシピ記事になります — 専用の記事タイプは要りません。タイプが news でも blog でも同じように働きます。カードの中身はテンプレートからも構造化データとして取り出せるので、独自レイアウトで描き直せます。
page.isRecipe / article.recipe KuroCMS v1.8.78+ [[html:article.bodyHtml]] を出すだけでもカードはそのまま表示されます。下の値を使うのは、テンプレートの意匠に合わせて組み直したいときだけです。
page.isRecipe | レシピカードを持つ記事ページで true。タイプ名の文字列比較はせず、必ずこのフラグで分岐する |
article.recipe.yield | 人数(自由文字列。例「4人分」) |
article.recipe.prepTimeMinutes | 下準備(分)。未入力ならキーごと存在しない |
article.recipe.cookTimeMinutes | 調理(分)。未入力ならキーごと存在しない |
article.recipe.totalMinutes | 下準備+調理。どちらも未入力なら null |
article.recipe.ingredients | 材料の配列。[[#each]] の中で [[name]] / [[amount]](amount は省略可) |
article.recipe.instructions | 手順の配列。[[#each]] の中で [[text]] |
- 構造化データ(Schema.org
Recipe)は KuroCMS が自動で出します。テンプレート側で JSON-LD を書かないでください(二重になります) - 1 記事 = 1 レシピ。2 枚目はエディタ側で挿入できないようになっています(鍋アイコンがロックされます)。料理名・説明・完成画像・公開日はカードではなく記事共通のもの(
article.title/article.summary/article.coverUrl)を使います amountは省略できるので[[#if amount]]で囲みます- カードが無い / 壊れている記事では
page.isRecipeが false になり、通常記事としてそのまま描けます(構造化データもArticleに戻ります)
記事ページでの表示例
[[#if page.isRecipe]]
<section class="recipe">
<p class="meta">[[article.recipe.yield]][[#if article.recipe.totalMinutes]] ・ 合計 [[article.recipe.totalMinutes]] 分[[/if]]</p>
<h2>材料</h2>
<ul>
[[#each article.recipe.ingredients]]
<li>[[name]][[#if amount]] <span>[[amount]]</span>[[/if]]</li>
[[/each]]
</ul>
<h2>作り方</h2>
<ol>
[[#each article.recipe.instructions]]<li>[[text]]</li>[[/each]]
</ol>
</section>
[[/if]] 8. 記事ページの最小サンプル
<!-- kurocms-template-api:1 -->
<!doctype html>
<html lang="[[site.lang]]">
<body>
[[#if page.isArticle]]
<article>
[[#if article.coverUrl]]<img src="[[article.coverUrl]]" alt="">[[/if]]
<h1>[[article.title]]</h1>
<div class="prose">[[html:article.bodyHtml]]</div>
[[html:content.related-4]]
</article>
[[/if]]
</body>
</html> Template code reference
Special codes you can use in templates
A KuroCMS template is an “unexpanded” HTML source that contains [[...]] special codes. At publish/build time the CMS expands them into real data. This page lists every expression a template author can use, with its role and samples.
Required leading marker
The first line of the HTML source must begin with <!-- kurocms-template-api:1 -->. Unknown values render as an empty string, objects cannot be output directly, and an unclosed section is a render error. [[html:…]] is raw output — use it only for trusted CMS data.
1. Core syntax [[...]]
| Syntax | Behaviour | Example |
|---|---|---|
[[path]] / [[value:path]] | Output a value, HTML-escaped | [[site.name]] |
[[html:path]] | Output raw HTML (trusted CMS data only) | [[html:article.bodyHtml]] |
[[#if path]]…[[/if]] | Render the body when the value is truthy | [[#if page.isArticle]]…[[/if]] |
[[#unless path]]…[[/unless]] | Render the body when the value is falsy | [[#unless article.coverUrl]]…[[/unless]] |
[[#each path]]…[[/each]] | Loop an array (relative paths / [[this]] inside) | [[#each articles]][[title]][[/each]] |
[[#each type:{slug}:{N}]]…[[/each]] | N newest articles of a given type (see §3) | [[#each type:news:5]] |
2. Data model (paths you can reference)
| Path | Meaning |
|---|---|
page.isHome / isAbout / isPrivacy / isTerms / isArticle / isType / isCategory / isRecipe | Current-page type flags. Wrap article-only markup in [[#if page.isArticle]]. isRecipe is true on an article that has a recipe card |
site.name / site.lang / site.dir / site.basePath | Site name / language / direction (rtl|ltr) / public URL prefix |
content.{key} | Site texts edited in the admin “Site text” tab (e.g. content.footer-text) |
navigation.types / navigation.categories | Type / category arrays (counts not included). Each item has isCurrent — true only for the one matching the current page; use it to highlight the active nav item |
articles | List-page article array (slug, tid, title, summary, publishAt, coverUrl, categories) |
article | Article detail (above + type, bodyHtml, updatedAt). null off article pages |
article.recipe | Recipe-card contents (see §7). null when the article has no card |
pagination | page / totalPages / prevUrl / nextUrl (legacy paging; normally null on listings) |
type.id / type.name · category.id / category.name | Current type / category |
integrations.blueskyHandle | Bluesky handle |
3. Pulling articles into a template (data refs) KuroCMS v1.8.79+
articles only holds the articles in the current page's scope (on the home page that means the newest across all types). When you want per-type blocks — a “News” box and a “Products” box on the home page — name what you want directly from the template.
| Syntax | Meaning | Example |
|---|---|---|
[[#each type:{slug}:{N}]] | N newest articles of the given type | [[#each type:news:5]] |
[[#each category:{slug}:{N}]] | N newest articles of the given category | [[#each category:tips:3]] |
[[#each articles:latest:{N}]] | The N newest articles across all types | [[#each articles:latest:8]] |
[[#each type:all]] | All types (with counts) | [[#each type:all]][[name]][[/each]] |
[[#each category:all]] | All categories (with counts) | [[#each category:all]][[name]][[/each]] |
Nis 1–50 (defaults to 10)- An unknown slug yields an empty array — the
[[#each]]simply renders nothing; the template never breaks - Markup is entirely up to the template. The CMS builds no HTML — it only hands over the article-card data
- Only the refs you actually write are fetched, so templates that don't use them issue no extra queries
Example (two per-type blocks on the home page)
<h2>Latest news</h2>
<ul>
[[#each type:news:5]]
<li><a href="[[site.basePath]]/[[tid]]/[[slug]]/"><time>[[date]]</time>[[title]]</a></li>
[[/each]]
</ul>
<h2>Products</h2>
[[#each type:products:3]]
<a class="card" href="[[site.basePath]]/[[tid]]/[[slug]]/">
[[#if coverUrl]]<img src="[[coverUrl]]" alt="">[[/if]]
<h3>[[title]]</h3><p>[[summary]]</p>
</a>
[[/each]] 4. Carousel and ads KuroCMS v1.8.79+
Image carousel [[carousel:{key}|{opts}]]
The template decides how it looks and how big it is; the editor decides which images appear. Image IDs never get hard-coded into a template.
Template
[[carousel:hero|100%x320,fade,arrows,5s]] Site text “hero”
img-100,img-101,img-102 - Size
100%/100%x320/640x360(bare numbers mean px; default 100%x320) - Transition
fade(default) /slide - Arrows
arrows(default) /noarrows - Dots
dots(default) /nodots - Auto-advance
5s(default) /0sto disable - Order does not matter (same comma style as
[[img-100|60%,right]]) - With no images nothing is rendered; with one image the arrows and dots are omitted
Auto-advance pauses for prefers-reduced-motion and while the pointer is over it. The root carries class="kuro-carousel" so you can style it from the template.
Ads (Google AdSense)
Placing [[ad]] in a template renders an AdSense unit. You only supply IDs — never paste the distributed ad snippet (the CMS assembles the script itself).
Setup (site text ad-adsense)
ca-pub-1234567890123456,1234567890,9876543210 Write it as one comma-separated line: the client first, then the slot IDs. It is deliberately a single key — splitting it across keys makes a half-finished setup easy to miss.
[[ad]]is slot 1,[[ad:2]]is slot 2 (1-based)- Renders nothing when unset, malformed, or the slot number is out of range — no broken tags
- The AdSense loader is injected only on pages that actually rendered an ad
- Units carry
class="kuro-ad adsbygoogle", so you can style.kuro-adfrom the template
Amazon Associates is not supported. A tracking ID is an attribution identifier — on its own it does not render an ad unit (native shopping widgets need a separate instance ID).
5. Expressions inside site texts
Inside each admin “Site text” value (content.{key}), the following are expanded before the value reaches the template parser.
| Expression | Meaning |
|---|---|
[[type:all]] / [[category:all]] | Type / category list JSON |
[[type:slug:N]] / [[category:slug:N]] | N articles from a given type / category |
[[articles:latest:N]] | The latest N articles |
[[article:slug]] | A specific article’s data |
[[mid]] | Expands to image / video / audio tags (e.g. [[img-816]]) |
[[sid]] | Expands to the matching SNS widget |
[[lang]] | Expands to the language-switcher widget (place it in the nav) |
[[search]] | Expands to the article-search widget (place it in the nav, e.g. left of TYPE). Desktop shows an input + search icon; mobile shows the icon → a dialog on tap |
[[privacy]] / [[terms]] | Expands to a link to the privacy-policy / terms page. Renders nothing while the matching site text is empty (no dead links), so you can place them unconditionally in the footer |
[[ad]] / [[ad:2]] | A Google AdSense unit (see §4) |
[[carousel:{key}|{opts}]] | An image carousel. List the media IDs comma-separated in site text {key} (see §4) |
6. Reserved site-text slugs
Special content.* keys the CMS generates at build time — no stored value needed. Just place them in the template.
[[html:content.related-N]] Related-articles strip. On an article page it shows N cards from the same category, chosen as the date-nearest neighbours. The centre ←・→ is the current article: floor(N/2) older on the left, ceil(N/2) newer on the right. At the ends (newest/oldest) it fills from the opposite end so N is always kept. Cards are image + title only and scale down (never wrap) as the viewport narrows, keeping all N.
Nis 1–20. Change the count with the slug:related-2/related-4/related-6- Renders empty off article pages, when the article has no category, or when there are no neighbours
- Display-only — it never rewrites stored data, and older KuroCMS versions ignore it safely
Placement (right after the body = below the author byline)
[[#if page.isArticle]]
<article>
<div class="prose">[[html:article.bodyHtml]]</div>
[[html:content.related-4]]
</article>
[[/if]] [[html:archives]] | Monthly-archive <select> switcher (list pages) |
7. Recipe cards page.isRecipe
The pot icon in the body editor inserts a “recipe card” (yield, times, ingredients, steps). Any article carrying one card is a recipe article — no dedicated article type is needed; it works the same whether the type is news or blog. The card’s contents are also exposed to the template as structured data, so you can lay them out yourself.
page.isRecipe / article.recipe KuroCMS v1.8.78+ Simply outputting [[html:article.bodyHtml]] already renders the card as-is. Use the paths below only when you want to rebuild the layout to match your template.
page.isRecipe | True on an article page that has a recipe card. Branch on this flag — never compare the type name as a string |
article.recipe.yield | Yield — free text (e.g. “Serves 4”) |
article.recipe.prepTimeMinutes | Prep time in minutes. Absent entirely when not filled in |
article.recipe.cookTimeMinutes | Cook time in minutes. Absent entirely when not filled in |
article.recipe.totalMinutes | Prep + cook. null when neither is filled in |
article.recipe.ingredients | Ingredient array. Inside [[#each]] use [[name]] / [[amount]] (amount is optional) |
article.recipe.instructions | Instruction array. Inside [[#each]] use [[text]] |
- KuroCMS emits the structured data (Schema.org
Recipe) for you. Do not write JSON-LD in the template — it would be duplicated - One article = one recipe. The editor blocks a second card (the pot icon locks). The dish name, description, finished photo and date come from the article itself (
article.title/article.summary/article.coverUrl), not from the card amountis optional — wrap it in[[#if amount]]- When the card is missing or corrupt,
page.isRecipeis false and the article renders as an ordinary one (the structured data falls back toArticle)
Rendering it on the article page
[[#if page.isRecipe]]
<section class="recipe">
<p class="meta">[[article.recipe.yield]][[#if article.recipe.totalMinutes]] · [[article.recipe.totalMinutes]] min total[[/if]]</p>
<h2>Ingredients</h2>
<ul>
[[#each article.recipe.ingredients]]
<li>[[name]][[#if amount]] <span>[[amount]]</span>[[/if]]</li>
[[/each]]
</ul>
<h2>Steps</h2>
<ol>
[[#each article.recipe.instructions]]<li>[[text]]</li>[[/each]]
</ol>
</section>
[[/if]] 8. Minimal article-page sample
<!-- kurocms-template-api:1 -->
<!doctype html>
<html lang="[[site.lang]]">
<body>
[[#if page.isArticle]]
<article>
[[#if article.coverUrl]]<img src="[[article.coverUrl]]" alt="">[[/if]]
<h1>[[article.title]]</h1>
<div class="prose">[[html:article.bodyHtml]]</div>
[[html:content.related-4]]
</article>
[[/if]]
</body>
</html>