Standalone Pages
GoPress ships a built-in page content type for standalone pages such as About, Terms, and Privacy β the pages almost every site needs regardless of which theme is active. Pages live on the same unified Content model as posts and custom theme types, so they require no extra tables.
Concepts
| Concept | Description |
|---|---|
page type |
A core content type (alongside post and contact_message), registered by the engine and surviving theme switches. |
| Rootless permalink | Page URLs sit at the site root: /about, /privacy β no /blog/β¦ style prefix. |
| Hierarchical | Pages can have a parent page (ParentID), so the admin can organize them as a tree. |
| No archive | Pages have no public list page; each page is an individual URL. |
| Page template | A theme-declared layout a page can opt into (e.g. full-width, landing, embed). |
Because page is a normal registered content type, the admin Pages screen, its sidebar entry, list/create/edit/delete, and REST-free routing are all generated by the same data-driven machinery used for every other type β no page-specific admin code.
Managing Pages
The admin sidebar shows Pages under the Content section. From there you can:
- Create/edit/delete pages with the shared content editor (title, rich-text body, excerpt, featured image).
- Set a Parent page to nest a page under another.
- Assign a Category β pages share the core
categorytaxonomy with posts, so same-attribute pages can be grouped (and appear in/category/{term}archives). - Choose a Page template (see below).
- Add an Embed code snippet (see below).
- Publish / draft / schedule like any content.
Rootless URLs and Reserved Slugs
A published page with slug about is served at /about. Resolution happens last, after every prefixed content type and taxonomy has been tried, so a page can never shadow an archive like /blog or /products.
For that reason, saving a page whose slug collides with a system route (admin, api, static, sitemap.xml, β¦) or an existing archive/taxonomy prefix (blog, products, category, β¦) is rejected β the page would otherwise be unreachable. Choose a different slug when prompted.
This version supports flat page URLs (
/about).ParentIDorganizes pages in the admin but does not add URL nesting (/about/team) yet.
Page Templates
A theme can advertise selectable per-page layouts (WordPress-style "page templates") in theme.toml:
[[page_templates]]
name = "Full-width Page"
template = "page-full-width"
[[page_templates]]
name = "Embed / Feature Page"
template = "page-embed"
templateis the page-bundle file name undertemplates/pages/(without.tmpl).nameis the label shown in the editor's Page template dropdown.- The choice is stored per page as the
page_templatemeta value.
At render time the theme resolves the template through this hierarchy (first hit wins):
<selected page_template> β page-<slug> β page β single β index
So a page with no explicit selection falls back to page-<slug> then a generic page template. If you switch to a theme that doesn't provide the previously-selected template, rendering degrades gracefully down the same chain. Pages always render through the theme's base layout, so the site header, footer, and styles are shared automatically.
A minimal theme page template only needs the standard content data:
{{define "content"}}
<section class="section"><div class="container">
<h1>{{.Item.Title}}</h1>
<div class="post-body">{{safeHTML .Item.Content}}</div>
</div></section>
{{end}}
Embedding External Widgets
There are two ways to put a functional fragment (a stock chart, map, video, calculator) on a page. Pick based on who controls the markup.
1. Per-page embed code (content editor)
The page editor has an Embed code field. Paste a third-party <iframe> embed and it renders inside a responsive container on the page:
<iframe src="https://www.youtube.com/embed/VIDEO_ID" title="Player"
allow="encrypted-media; picture-in-picture" allowfullscreen></iframe>
The pasted markup is stored raw and sanitized at render time through an iframe allowlist (content.SanitizeEmbed):
- Allowed:
<iframe>(withsrclimited tohttp/https) plus safe attributes (title,width,height,style,allow,allowfullscreen,loading,referrerpolicy,sandbox, β¦) and a few wrapper tags (div,p,span,figure). - Stripped:
<script>(inline andsrc),on*event handlers,javascript:URLs,<object>,<embed>, and anything else off the allowlist.
What this means in practice:
- β
Pure
<iframe>embeds work β YouTube, Vimeo, Bilibili, Google Maps, TradingView's iframe widget, most chart/video/map providers. - β
<script>-based snippets do not work (their script is stripped). Use the iframe form if the provider offers one, or the template approach below. - The sanitizer only filters the markup you paste (to prevent stored XSS in your own page). JavaScript inside the embedded site β the YouTube player, the chart's own scripts β runs normally, because it lives in the iframe's separate origin.
- Because iframes are origin-isolated they cannot read the GoPress page's DOM or cookies. Editing pages is RBAC-gated to trusted roles (editor and up). Authors keep control of the iframe's own
sandboxattribute when they include one.
2. Template-owned feature (theme developer)
When "the page is a feature" β a bespoke dashboard, a script-based widget, custom JS/CSS β put the markup in a theme page template (see above). Template code is trusted and is not sanitized, so scripts and any markup are allowed. Declare it in [[page_templates]], and the editor selects it per page. Use the page's rich-text body for surrounding copy.
Architecture Notes
pageis framework surface, registered by the engine exactly likepostβ it is not a theme or business type, so it does not violate the "no hardcoded content types in core" boundary.- Everything else is driven by generic capabilities on
ContentTypeDef, not by apagespecial case:Rewrite.Rootless(root-level single URL),Hierarchical(parent/child), andReadOnly(whether the admin exposes full CRUD). A theme may declare its own rootless or hierarchical types the same way. - Themes interact only through core extension points (template hierarchy, the
theme.toml[[page_templates]]declaration, thecategorytaxonomy). No themeβplugin coupling is introduced; the dependency direction staystheme β core. - Page CRUD reuses the core
contentRBAC resource, socontent.create/read/update/deletegovern pages just like posts.
