Overview
When to use it
Use a Page Type when a plugin provides a page with its own URL, for example:
- Garden Explorer
- A plugin report screen
- A listing page generated by a plugin
- A custom search or browse page
You do not need a Page Type when you only want to display something inside an article body.
Diagram source
flowchart TD
Q{"What does the plugin display?"}
Q -->|"A page with its own URL"| Page["pageTypes"]
Q -->|"Embedded in an article body"| Renderer["renderers"]
Page --> P1["Example: /explore"]
Page --> P2["Example: /report"]
Renderer --> R1["Canvas"]
Renderer --> R2["Bases"]
Renderer --> R3["Excalidraw"]
Features embedded inside an article, such as Canvas, Bases, and Excalidraw, use
renderers instead.
Responsibilities
| Layer | Responsibility |
|---|---|
| Core | Page Type contract, validation, path enumeration, resolution, and conflicts |
| Plugin | Page Type ID, public paths, and a framework-independent HTML body |
| HonoX integration | Generic route resolver and SSG parameter helper |
| Site application | Catch-all route, document frame, metadata, and safe HTML rendering |
| Theme | Tokens and stable hooks; no knowledge of a Page Type ID is required |
A plugin does not own the whole page. The overall relationship looks like this:
Diagram source
flowchart LR
Plugin["Plugin<br/>Provides the page"]
Core["Core<br/>Page contract / resolution"]
Integration["HonoX Integration<br/>Routing / SSG"]
Site["Site<br/>Document / rendering"]
Theme["Theme<br/>CSS"]
Plugin --> Core
Core --> Integration
Integration --> Site
Theme --> Site
The route and document frame are owned by the site application. A plugin provides the page content, but it does not generate a whole document:
<html>
<head>
...
<body>
PluginPageContext.manifest is the resolved manifest. Its entries still
contains draft and scheduled, so a Page Type must read publicEntries
(routable) or discoverableEntries (public only) for page output instead.
Diagram source
flowchart LR
All["All content"]
Publish["Publish boundary"]
Public["publicEntries / discoverableEntries"]
Page["Plugin page"]
All --> Publish
Publish --> Public
Public --> Page
A Page Type should not search for non-public content on its own. This keeps the Page System on the same publication boundary as a normal site.
The problem the Page System solves
Before the Page System, every plugin with a standalone page needed a dedicated route on the site side:
Diagram source
flowchart LR
Request["Request"]
Route["Plugin-specific route"]
Component["Plugin-specific component"]
Frame["Site document frame"]
Request --> Route
Route --> Component
Component --> Frame
That approach did not work by adding a plugin alone; it also required changing the site application's routes, which coupled plugins tightly to the site.
With the Page System, every plugin page participates in one shared route resolver:
Diagram source
flowchart LR
Request["Request"]
Catch["Catch-all route"]
Resolver["resolveRiebeckiteRoute"]
Page["Plugin page"]
Content["Content"]
Redirect["Redirect"]
Request --> Catch
Catch --> Resolver
Resolver --> Page
Resolver --> Content
Resolver --> Redirect
Adding a new plugin page no longer requires adding a HonoX route.