Guides
Your own HTML
Use the standard page shell, add to it, replace parts of it or write the whole document.
Every route decides its own HTML. Most routes use the standard page shell, [#html#], and fill it with their own markup. When a page needs more control, you can add to the shell, replace parts of it, or write the whole document yourself.
The standard shell
A route that ends with [#html#] gets a complete document:
- the
<head>with the title, meta description, canonical address, social sharing tags and favicons, - the project's stylesheet and scripts,
header.tpl,main.tplandfooter.tplfrom the route's folder,- for signed-in editors: in-place editing, the page actions and the Nimbly bar,
- the team chat for users with access, and the bot tripwire link.
The shell is plain HTML around your templates. It adds no wrapper markup or classes to your content beyond <header>, <main> and <footer>.
Page variables
Set these before [#html#]:
[#set page-title="[#project.title#]"#]
[#set page-description="Wayfinding for a working port."#]
[#set og-image=[#project.cover#]#]
[#set og-type=article#]
[#set body-classes="bg-stone-50 text-stone-900"#]
[#html#]
| Variable | Effect |
|---|---|
page-title |
The <title> and the sharing title |
page-description |
The meta description; the site description otherwise |
og-image, og-type |
The sharing image (a media UUID, a path or a URL) and type |
page-canonical-url |
The canonical address, when it differs from the requested one |
body-classes |
Classes on <body> |
head |
Extra markup at the end of <head> |
page-actions |
off hides the editor's page actions on this page |
Add to the shell
Load a font, a stylesheet or a library on one page with head:
[#set head="<link rel='stylesheet' href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'>"#]
[#html#]
Put the page's own script in index.js next to its index.tpl; the shell loads it on that route only. For scripts that several templates on a page may ask for, [#collect-script path#] adds a script once, at the end of the page.
Replace part of the shell for the whole site
The shell is built from small templates in core/tpl/html/: meta.tpl, fonts.tpl, stylesheets.tpl, scripts.tpl, favicon.tpl and others. A file with the same name in ext/tpl/html/ replaces one of them and leaves the rest. For example, ext/tpl/html/fonts.tpl sets the site's fonts, and ext/tpl/html/stylesheets.tpl can add a stylesheet next to the default one:
<link rel="stylesheet" href="[#base-url#]/app.css?v=[#app-modified#]">
<link rel="stylesheet" href="[#base-url#]/brand/brand.css">
The admin uses the same shell. Keep app.css and app.js in what you replace, or the admin and in-place editing lose their styles and behavior.
To change the document structure itself, copy core/tpl/html/index.tpl to ext/tpl/html/index.tpl and edit it. The partials keep coming from core. Your copy no longer follows core updates to the shell, so prefer replacing a partial when that is enough.
Write the whole document
A route does not have to use the shell. Its index.tpl can be a complete document:
<!doctype html>
<html lang="[#detect-language#]">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Annual report 2026</title>
<link rel="stylesheet" href="[#base-url#]/report/report.css">
</head>
<body>
[#data chapters sort=sort_order|numeric|asc#]
[#repeat data.chapters tpl=card-chapter#]
<script type="module" src="[#base-url#]/report/report.js"></script>
</body>
</html>
Shortcodes, data, templates and permissions work exactly as on any other page. The route gives up what the shell provides: the meta tags, the default assets, in-place editing, the page actions, the team chat and the bot tripwire link. Add back what you need, for example [#bot-tripwire-link#] before </body>.
Use this for a landing page with its own design system, an embedded widget, a print layout, a single-page application or a page for a kiosk screen.
Read next
- Frontend stack for using React, Vue or another toolset.
- What a route returns for JSON, XML, text, CSV and PDF.