Docs / Standalone Pages

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 category taxonomy 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). ParentID organizes 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"
  • template is the page-bundle file name under templates/pages/ (without .tmpl).
  • name is the label shown in the editor's Page template dropdown.
  • The choice is stored per page as the page_template meta 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> (with src limited to http/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 and src), 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 sandbox attribute 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

  • page is framework surface, registered by the engine exactly like post β€” 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 a page special case: Rewrite.Rootless (root-level single URL), Hierarchical (parent/child), and ReadOnly (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, the category taxonomy). No theme↔plugin coupling is introduced; the dependency direction stays theme β†’ core.
  • Page CRUD reuses the core content RBAC resource, so content.create/read/update/delete govern pages just like posts.