Theme System Overview
GoPress themes are Go packages that register themselves with the engine. A theme controls frontend rendering, templates, static assets, menu locations, theme settings, and optional demo data.
Main Features
BaseThemeruntime for rewrite resolution, template fallback, SEO injection, and common helpers.- Theme-declared content types from
theme.toml, including rewrite slugs and optional page-template mapping. - WordPress-like template hierarchy.
- Built-in fallback templates for archives, singles, and taxonomy pages.
- Standard frontend hook slots for plugins.
- Menu locations and language-aware menu rendering.
- URL-based active menu helper from the core funcmap.
- Runtime Core version helper (
goPressVersion) sourced fromversion/version.go. - Site-timezone-aware
formatDateandformatDateTimehelpers fromBaseFuncMap. - Responsive image helpers backed by media variants.
- Demo data import through
DemoDataProvider. - Admin theme-card logo through
LogoProviderβ drop astatic/logo.svg;BaseThemereads it automatically (no Go code) and core sanitizes it before inlining.
Built-in Themes
| Slug | Name | Type |
|---|---|---|
modern-company |
Modern Company | Company website. |
financial-news |
Financial News | Finance/news portal. |
atelier-slate |
Atelier Slate | Digital studio. |
axis-form |
Axis Form | Architecture and interior portfolio. |
florafi |
FloraFi | Stablecoin and fintech product site. |
civic-estate |
Civic Estate | Commercial real estate. |
terra-trail |
Terra Trail | Outdoor travel. |
go-press-landing |
GoPress Landing | SaaS landing page. |
mono-journal |
Mono Journal | Monochrome personal journal and blog. |
shop-starter |
Shop Starter | Lightweight single-page reference storefront for Commerce. |
Dynamic Content Routing
Core does not assume a site must have product, service, or showcase. The only always-registered editorial content type is post; themes add their own content types through theme.toml.
For each registered content type, rewrite_slug defines the public archive/detail URL shape, and optional templates selects the theme page bundles used for archive and detail rendering:
[[content_types]]
name = "module"
label = "Module"
label_plural = "Modules"
archive_title_key = "page_title_module"
has_archive = true
rewrite_slug = "modules"
templates = { archive = "products", single = "product-detail" }
With that configuration, /modules and /modules/{slug} resolve to the module content type while reusing templates/pages/products.tmpl and templates/pages/product-detail.tmpl. If templates is omitted, BaseTheme tries conventional names derived from the content type and rewrite slug before falling back to generic archive/single templates and built-in fallback pages.
Templates should generate content and term links with archiveURL, contentURL, and taxonomyURL instead of hard-coding /products, /services, /category, or similar paths.
Navigation active state should also come from core helpers. Use isMenuURLActive .Ctx menuURL against menu item URLs instead of comparing .ActivePage to theme-specific content type names or labels. The helper follows the current request URL, rewrite slugs, language prefixes, and detail-page paths.
A custom PageService is now cheap: it embeds core scaffolding (coreTheme.BasePageService, or coreTheme.SEOPageService when the theme renders SEO), so it no longer duplicates data-access or SEO plumbing. New themes can pick either the BaseTheme + gin.H path for the quickest start, or a typed PageService for type safety β see the SEO integration guide.
Frontend Extension Slots
Production themes expose stable semantic locations instead of requiring plugins to inspect final HTML:
<head>
...
{{renderHook "theme.head.end" .}}
</head>
<body>
{{renderHook "theme.body.open" .}}
...
<script src="/static/js/main.js"></script>
{{renderHook "theme.footer.end" .}}
</body>
The primary navigation list also ends with
{{renderHook "header.nav.after" .}}. Theme CSS should style direct navigation
children rather than every descendant, and mobile code should detect nested
extension menus from DOM structure while keeping aria-expanded synchronized.
| Slot | Typical use |
|---|---|
theme.head.end |
Verification tags, analytics, preconnect, external CSS. |
theme.body.open |
Noscript tags, bootstrap code, site banners. |
theme.footer.end |
Deferred scripts, chat, heatmaps. |
header.nav.after |
Language and account menus. |
Each slot appears exactly once. Plugins return markup matching its surrounding semantics and remove their filter handles when deactivated.
Public Account UI
Themes can render provider-neutral account UI with the core helpers currentUser, isLoggedIn, loginURL, loginProviderURL, logoutURL, and loginProviders. A theme may choose where and how account controls appear, but it must not import or special-case identity plugins such as Google Identity or MetaMask Identity.
Use loginProviders to discover enabled sign-in choices and loginProviderURL to attach a validated same-site return path to each provider's core-published begin URL. See Public Accounts and External Identity for template examples and cache/security notes.
Theme and Plugin Boundary
Built-in GoPress themes and themes intended for production use must expose the standard semantic hook slots and use core helpers. Plugins should inject through those slots. Neither side should import the other. The required slots and the repository-level contract test are documented in Creating Themes.
Module-level plugin requirements may be declared in theme.toml, but runtime
theme code still uses only generic core hooks, helpers, and capabilities.
