Hook System
GoPress hooks are the Go equivalent of WordPress do_action and
apply_filters. They are the primary way plugins and themes extend core without
importing one another.
API
// Action: side effects only.
e.Hooks.AddAction(name, fn, priority) hook.Handle
e.Hooks.RemoveAction(handle)
e.Hooks.DoAction(ctx, name, args...)
// Filter: transform one value through an ordered chain.
e.Hooks.AddFilter(name, fn, priority) hook.Handle
e.Hooks.RemoveFilter(handle)
e.Hooks.ApplyFilter(name, value, args...) interface{}
Lower priority numbers run first. Every registration returns a hook.Handle;
plugins must retain those handles and remove them during Deactivate so runtime
disable is complete.
Hook names and payloads are public contracts. Keep payloads stable and generic; do not expose a theme-private struct or provider SDK type to the other side. A filter with no subscribers must return its input unchanged, and an action with no subscribers must have no effect.
Engine Lifecycle Hooks
| Hook | Timing | Arguments |
|---|---|---|
engine.init |
After bootstrap completes. | *core.Engine |
middleware.early |
Before page-cache middleware. | *gin.Engine |
routes.register |
After admin routes and before the frontend catch-all. | *gin.Engine |
options.bulk_updated |
After admin saves options in bulk. | None |
Theme Template Slots
Plugins contribute local frontend HTML through semantic slots declared by the theme:
| Hook | Go constant | Location and typical use |
|---|---|---|
header.nav.after |
hook.ThemeHeaderNavAfter |
End of the primary navigation list; language switchers or account menus. |
theme.head.end |
hook.ThemeHeadEnd |
Before </head>; verification meta, analytics, preconnect, or CSS. |
theme.body.open |
hook.ThemeBodyOpen |
Immediately after <body>; noscript tags, bootstrap code, or banners. |
theme.footer.end |
hook.ThemeFooterEnd |
Before </body> after theme scripts; deferred scripts or widgets. |
<ul class="nav-menu">
{{$homeURL := langPrefixURL .Ctx "/"}}
<li><a href="{{$homeURL}}" class="{{if isMenuURLActive .Ctx $homeURL}}active{{end}}">{{T .Ctx "nav_home"}}</a></li>
{{renderHook "header.nav.after" .}}
</ul>
Global slots belong in the base layout and should appear exactly once:
<head>
...
{{renderHook "theme.head.end" .}}
</head>
<body>
{{renderHook "theme.body.open" .}}
...
{{renderHook "theme.footer.end" .}}
</body>
See Theme Frontend Slots.
Menu Hooks
| Hook | Go constant | Contract |
|---|---|---|
menu.location.resolve |
hook.MenuLocationResolve |
Filter after location lookup. Value: *menu.Menu; args: location string. |
menu.deleted |
hook.MenuDeleted |
Action after deletion. Args: menuID uint. |
Language-aware menu assignment is implemented through these generic hooks;
core/menu has no multilingual-specific branch.
Admin Extension Hooks
| Hook | Go constant | Contract |
|---|---|---|
admin.content_list.tabs |
admin.HookContentListTabs |
Filter list tabs. Value: []admin.ContentListTab; args: *gin.Context, typeName string. |
admin.content.permalink_prefix |
admin.HookContentPermalinkPrefix |
Filter an editor permalink prefix. Value: string; args: *gin.Context, *content.Content. |
admin.content_form.fields |
hook.AdminContentFormFields |
Filter editor meta-box HTML. Value: template.HTML; args: *content.Content, *content.ContentTypeDef. |
admin.content.saved |
hook.AdminContentSaved |
Action after the content row is saved. Args: *gin.Context, *content.Content. |
SEO Hook
seo.content.meta (hook.SEOContentMeta) filters a single content page's
rewrite.SEOMeta before rendering. Its arguments are the content row and its
metadata map. The SEO Extras plugin is the reference
implementation.
Sitemap Transformers
type TransformerHandle
handle := e.Sitemap.AddTransformer(fn)
e.Sitemap.RemoveTransformer(handle)
A transformer can add alternate-language links or derived URL entries. The remove handle provides the same hot-disable guarantee as actions and filters.
Extension Pattern
- Core defines the data contract, hook name, and trigger point.
- A plugin registers actions or filters during activation and saves handles.
- With no plugin, filters pass values through and actions have no side effects.
- Deactivation removes every handle and any symmetric transformer registration.
This pattern keeps core complete on its own while allowing features to be composed without theme/plugin cross-dependencies.
