Generated pages
Some pages don't have a source file of their own: one page per tag, one archive
per month. A **page template** generates many pages from one file.
## Bracketed names
Write a path segment as `[name]` to make it a parameter. The source is a template
if its file name or any folder above it is a parameter:
| Source | URL pattern | Example URLs |
| --- | --- | --- |
| `tags/[tag].jsx` | `/tags/[tag]` | `/tags/go`, `/tags/plan9` |
| `logs/[year]/index.jsx` | `/logs/[year]` | `/logs/2025`, `/logs/2026` |
| `logs/[year]/[month].jsx` | `/logs/[year]/[month]` | `/logs/2026/11` |
| `tags/[tag]/feed.xml.jsx` | `/tags/[tag]/feed.xml` | `/tags/go/feed.xml` |
Names are lowercase letters and digits, starting with a letter, and each name
appears at most once in a path. The whole segment must be the parameter:
`[year]-archive.jsx` is an error, not a page.
Templates must be JSX. A Markdown or Go template file inside a bracketed path is
an error, and so is a static file or `_`-prefixed file inside a bracketed folder.
Put layouts, defaults, and output templates for generated pages in a real folder
above the bracketed one (see [Layouts and data](#layouts-and-data-for-generated-pages)).
## Declaring the pages with `params()`
A template starts with a `params(...)` call that lists the parameter values to
generate. It takes an array of objects, or a function returning one, with
exactly the template's parameter names as keys:
```jsx
// tags/[tag].jsx
params(() => [...new Set(pages("**").flatMap(p => p.tags ?? []))].map(tag => ({ tag })));
data({ title: `Tagged ${page.params.tag}` });
{pages({ where: { tags: page.params.tag } }).reverse().map(p =>
- {p.title}
)}
```
```jsx
// logs/[year]/[month].jsx
params(() => pages("logs/**").map(l => ({
year: l.date.slice(0, 4),
month: l.date.slice(5, 7),
})));
data({ title: `${page.params.year}-${page.params.month}` });
{pages("logs/**")
.filter(l => !l.params && l.date.startsWith(`${page.params.year}-${page.params.month}`))
.map(l => - {l.title ?? l.date}
)}
```
Rules:
- `params()` must be among the template's leading statements, before the
markup, alongside any `data()` call. Anywhere else it's an error.
- Values are strings or numbers (numbers become strings). Each value must be a
non-empty single path segment: no `/` or `\`, not `.` or `..`, and not
starting with `.` or `_`.
- Duplicate sets are merged, so you don't need to deduplicate. An empty array
generates no pages.
- A set with a missing or extra key, or a bad value, fails the build, naming
the template and the set.
## What `params()` can see
`params()` runs before any generated page exists, in its own VM with your
globals and `pages()`. `page` is undefined there. `pages()` returns only real
pages, never generated ones, so one template can't feed another and the set of
pages depends only on the files on disk.
Once all pages exist, page bodies and layouts see everything: a "Tags" index
page can call `pages("tags")` and list every generated tag page.
## Generated page data
A generated page's data is built like any page's, plus `params`:
| Key | Value for `/logs/2026/11` |
| --- | --- |
| `params` | `{ year: "2026", month: "11" }` (always strings) |
| `path` | `/logs/2026/11` |
| `slug` | `11` |
| `src` | The template's source |
| `isDir` | True if the template is an `index.jsx` |
## Layouts and data for generated pages
Globals, defaults, layouts, and output templates are found by the generated
page's path, exactly as if a real file lived there. `logs/_layout.jsx` wraps
`/logs/2026/11`, and a real `logs/2026/_defaults.jsx` applies to it. HTML
generated pages get outputs from `_outputs/` like any page, so
`/logs/2026/11.txt` exists if `_outputs/txt.jsx` does.
If a generated page's parent folder doesn't exist as a page, taragen adds a
grouping node for it, so `pages("logs/*")` reaches years that exist only through
generated pages.
## Overriding a generated page
A real source at the same path wins, silently. To give one tag a hand-written
page with its own intro, add `tags/plan9.md`; every other tag keeps its generated
page. A generated page at the path of a grouping node fills it, keeping its
subpages: that's how `logs/[year]/index.jsx` gives `/logs/2026` an index.
Two templates producing the same path is an error, as is a generated page whose
URL matches a static file, output, alias, or redirect.
## Generated pages in listings
Generated pages show up in `pages()` results, including `**` patterns. A home
stream built from `pages("logs/**")` will include generated month pages; skip
them with a filter on `params`:
```jsx
pages({ path: ["articles/**", "logs/**"] }).filter(p => !p.params).reverse()
```
## Drafts and serving
`params()` follows the draft rules: in `build` its `pages()` calls never see
drafts, so a month that only holds a draft gets no page; in `serve` it does. A
generated page whose own data makes it a draft is skipped by `build`.
In `serve`, a URL that fits the pattern but wasn't generated, like
`/tags/nosuchtag`, isn't a page. Adding a tag to a Markdown file creates its page
on the next reload.