---
title: "Pages Plugin"
description: "Standalone pages management, rendered as HTML, Markdown, PDF or JSON."
type: "docs"
category: "doc"
tags: []
authors: [Anonymous]
date: "2026-08-21"
last_update: "2026-08-21"
time_minutes: 3
draft: false
unlisted: false
url: "https://www.derafu.dev/docs/ui/content/pages"
---

# Pages plugin

Manages standalone pages (an "about us", a landing page, changelog, etc.), including `.html.twig` files in addition to Markdown. Same content model and format negotiation (HTML/Markdown/PDF/JSON) as [Docs](./docs)/[FAQ](./faq), just without a listing/tag view — pages are meant to be linked directly, not browsed.

## Route and the `/pages` prefix

| Route | Path | Description |
|---|---|---|
| `pages_page` | `GET /pages/{uri}` | Shows a page. Format negotiated the same way as every other content plugin (`Accept` header, or `.md`/`.pdf`/`.json` suffix). |

The route is registered under `/pages` **on purpose**, not at the root (`/about` instead of `/pages/about`). The `{page:.+}` pattern is a catch-all, and the router tries routes with parameters *before* it tries file-based routes (`Derafu\Routing\Parser\FileSystemParser`, used for standalone Twig pages placed directly in `templates/pages/` by the website itself, with no content model). A catch-all with no prefix would shadow every one of those file-based pages: the router would never even get to ask the file-system parser about a URI this route already claims, since it checks parsers in order and stops at the first one that matches.

We verified this in practice: with the plugin enabled and content under `resources/content/pages/`, every existing hand-written route (static ones, and file-based ones from `templates/pages/`) kept working exactly as before — only URIs actually starting with `/pages/` are affected.

If a website wants pages at the root anyway, it can register its own route pointing at the same handler instead of importing this one:

```yaml
pages_page:
  path: /{page:.+}
  handler: 'Derafu\Content\Plugin\Pages\PagesController::show'
```

Registering that **before** any route that relies on `FileSystemParser`'s auto-discovery turns this trade-off off for that whole website: from then on, every flat page must be a content item (title/tags/draft/etc. via frontmatter), not a hand-authored Twig template dropped into `templates/pages/`. Pick one mechanism per website, not both, once this is unprefixed.

## Configuration (`services.yaml`)

Enabled under `derafu.content.config.plugins.pages`:

```yaml
parameters:
    derafu.content.config:
        plugins:
            pages:
              path: 'resources/content/pages'
```

| Option | Type | Default | Description |
|---|---|---|---|
| `path` | string | `resources/content/pages` | Directory scanned for pages, relative to the website root. |
| `include` | array of glob patterns | `['**.{markdown,md,html.twig}']` | Files considered content, relative to `path`. Unlike other plugins, this also matches `.html.twig` files. |
| `exclude` | array of glob patterns | `[]` | Files excluded even if matched by `include`. |
| `showLastUpdateAuthor` | bool | `false` | Whether to show who last updated the page. |
| `showLastUpdateTime` | bool | `false` | Whether to show when the page was last updated. |

## Content frontmatter

Pages use exactly the [generic content frontmatter](./frontmatter), nothing added.



---
Last updated on 21/08/2026

