You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
104 hooks — 59 filters, 45 events — and 7 UI regions. 104 are wired: something in the board fires
them today, and the rest are declared but not yet reached by a call site.
The wired column is derived from the tree by scripts/hook-callsites.mjs, not
maintained by hand — a registry entry with no call site is a promise about code
that never runs, and it fails in the quietest possible way: the plugin installs,
the handler registers, nothing happens. plugins/reference is required by its own
test to handle every wired hook, so a hook cannot join that column without
something proving it fires.
A filter is handed a value and returns a replacement; its result is used, so a
filter that throws or returns nothing leaves the value as it was and the chain
carries on with the next plugin. An event is told what happened and its return
value is discarded — which is why anything that only wants to observe should be one:
it cannot corrupt the thing it is watching even when it is wrong.
Handlers run in (priority, plugin key) order. Both halves are declared, so two
plugins compose the same way on every request and on every instance.
Every handler is called inside the host’s try/catch and is timed. Failures are
counted in the database, so a plugin that fails repeatedly is switched off across
the whole board and stays off until an operator clears the record. See
plugins.md for the policy, the lifecycle and the limits.
markdown.parse.text — The raw Markdown source, before it is parsed. Last chance to rewrite input.
markdown.render.html — Rendered HTML, after the renderer has constructed it. Anything added here is trusted output and nothing escapes it afterwards.
markdown.directives — The declarative directive list, so a plugin can add a :::name block or :name[…] span without core changes. Board-wide: rendered bodies are stored and shared, so the set cannot depend on who is reading.
post.body.html — One post’s rendered body, in the context of the thread it is being read in.
signature.html — A member’s rendered signature, wherever it appears.
smilies.list — The smilie set substituted at render. Board-wide, for the same reason the directive list is.
word-filter.patterns — The render-time word filter’s pattern list.
View models
Hook
Kind
Wired
Value
Context
view.header
filter
yes
HeaderModel
ViewerRef & RequestRef
view.user-panel
filter
yes
UserPanelModel
ViewerRef & RequestRef
view.navigation
filter
yes
NavigationModel
ViewerRef & RequestRef
view.footer
filter
yes
FooterModel
ViewerRef & RequestRef
view.forum-jump
filter
yes
ForumJumpModel
ViewerRef & RequestRef
view.announcement
filter
yes
AnnouncementModel
ViewerRef
view.board-index
filter
yes
BoardIndexModel
ViewerRef
view.forum-row
filter
yes
ForumRowSlotModel
ViewerRef
view.thread-row
filter
yes
ThreadRowSlotModel
ViewerRef & ForumRef
view.post-bit
filter
yes
PostBitSlotModel
ViewerRef & ThreadRef
view.post-actions
filter
yes
PostActionsSlotModel
ViewerRef & ThreadRef
view.member-profile
filter
yes
MemberProfileModel
ViewerRef
view.board-stats
filter
yes
BoardStatsModel
ViewerRef
view.who-is-online
filter
yes
WhoIsOnlineModel
ViewerRef
view.latest-threads
filter
yes
LatestThreadsModel
ViewerRef
view.latest-posts
filter
yes
LatestPostsModel
ViewerRef
view.pagination
filter
yes
PaginationModel
ViewerRef
view.search-form
filter
yes
SearchFormModel
ViewerRef
view.search-results
filter
yes
SearchResultsModel
ViewerRef
view.discovery-view
filter
yes
DiscoveryViewModel
ViewerRef
view.auth-page
filter
yes
AuthPageModel
ViewerRef
view.panel-shell
filter
yes
PanelShellModel
ViewerRef
view.panel-nav
filter
yes
PanelNavModel
ViewerRef
view.panel-page
filter
yes
PanelPageModel
ViewerRef
view.panel-section
filter
yes
PanelSectionModel
ViewerRef
view.error-notice
filter
yes
ErrorNoticeModel
ViewerRef & RequestRef
view.shell
filter
yes
ShellModel
ViewerRef & RequestRef
view.notice
filter
yes
NoticeModel
ViewerRef
view.category-block
filter
yes
CategoryBlockModel
ViewerRef
view.subforum-list
filter
yes
SubforumListModel
ViewerRef & ForumRef
view.forum-display
filter
yes
ForumDisplayModel
ViewerRef & ForumRef
view.thread-view
filter
yes
ThreadViewModel
ViewerRef & ThreadRef
view.post-form
filter
yes
PostFormModel
ViewerRef
view.quick-reply
filter
yes
QuickReplyModel
ViewerRef & ThreadRef
view.editor-toolbar
filter
yes
EditorToolbarModel
ViewerRef
view.redirect-notice
filter
yes
RedirectNoticeModel
ViewerRef
view.header — The header model, before the theme renders it.
view.user-panel — The user panel model: greeting, counts, account links.
view.navigation — The breadcrumb trail.
view.footer — The footer model, including its link list.
view.forum-jump — The jump box model. A plugin adding a destination must give it a real forum id — the route re-authorises whatever is submitted.
view.announcement — One announcement, on its way to the theme. Its body is already rendered HTML from the boardu2019s own renderer, so a plugin replacing it is replacing trusted markup — the one hook where that is true of a body.
view.board-index — The index page model.
view.forum-row — One forum row in a listing. Runs once per row — keep it cheap.
view.thread-row — One thread row in a listing. Runs once per row.
view.post-bit — One post as the theme will receive it. The busiest hook on the board: it runs once per post on every thread page.
view.post-actions — The per-post control links. Adding one here does not create permission to use it.
view.member-profile — A member’s profile model, including its custom fields and action links.
view.board-stats — The board totals block.
view.who-is-online — The online list, already resolved against the reader.
view.latest-threads — The index sidebar’s newest-threads panel. Runs again on every refresh of the live region, not only on the page load — keep it cheap.
view.latest-posts — The index sidebar’s newest-posts panel. Same refresh cost as view.latest-threads.
view.pagination — A resolved page-link window.
view.search-form — The search form model, including its filter options.
view.search-results — One page of search results. Already checked against the reader — a hit a plugin adds here has not been, and will be shown to whoever asked.
view.discovery-view — A discovery listing — new posts, today, unanswered — with its tabs. Same warning as the search results: the rows arrive authorised.
view.auth-page — The sign-in, register and password-reset page around its form. The form itself is a region, not a value: nothing here can change what it posts to.
view.panel-shell — The frame around a control panel, including the links to the other panels this viewer may reach. Adding a link grants nothing.
view.panel-nav — A control panel’s section rail, with the current section already resolved. Runs on every panel page.
view.panel-page — One control-panel page’s heading block. Runs on every panel page.
view.panel-section — One labelled section inside a panel page. Runs once per section.
view.error-notice — The error page model. Runs on the page that renders when things are broken.
view.shell — The page frame’s model. Runs on every page including the error pages.
view.notice — A board notice or flash message, before the theme renders it.
view.category-block — One category on the index, with its rendered forum rows.
view.subforum-list — The compact child-forum list above a thread listing.
view.forum-display — A forum page’s model, including its rendered regions.
view.thread-view — A thread page’s model, including its rendered post list.
view.post-form — The composer page’s model. The form itself is app-rendered and arrives as a region.
view.quick-reply — The quick-reply island’s model, at the foot of a thread. The reply form itself is app-rendered and arrives as children.
view.editor-toolbar — The composer’s formatting-toolbar model — its buttons and the attachment picker.
view.redirect-notice — The interstitial shown after a mutation, before the meta refresh fires. The target is re-checked against the board after the filter runs, so this cannot send a member off-site.
thread.create.validate — Validation messages for a new thread. Returning a non-empty list refuses the post.
thread.create.before — The thread draft, before it is written. Subject, body, prefix, options.
thread.created — A thread was created and committed.
post.create.validate — Validation messages for a reply.
post.create.before — The reply draft, before it is written.
post.created — A reply was created and committed.
post.edit.before — An edit’s new body and reason, before the revision is written.
post.edited — A post was edited and a revision recorded.
post.delete.before — A post is about to be soft-deleted. Observation only: refusing is a permission.
post.deleted — A post was soft-deleted.
post.restored — A soft-deleted post was restored.
thread.moved — A thread changed forum. Carries both forum ids.
thread.merged — Two threads became one.
thread.split — Posts were split out into a new thread.
thread.locked — A thread was opened or closed.
thread.stickied — A thread was pinned or unpinned.
attachment.upload.validate — Validation messages for an upload, after the magic-byte check. A plugin may refuse a file core would accept; it can never accept one core refused.
attachment.uploaded — A file finished uploading and re-encoding.
attachment.deleted — An attachment was removed, by a member or by the orphan sweep.
poll.created — A poll was attached to a thread.
poll.voted — A vote was cast, once per option chosen. It fires again when a poll that allows re-voting takes a replacement.
rating.recorded — A thread rating was recorded or changed.
search.query.before — The parsed search terms, before the query runs. The scope is not filterable.
search.results — A page of results, already permission-filtered in SQL. A plugin may reorder or drop; adding a row here would add one the viewer may not see.
feed.items — The items of a feed, rendered as a guest. Anything added is public.
sitemap.entries — One chunk of the sitemap.
metadata.page — Title, description and social card for a page.
admin.navigation — The admin panel’s section links, so a plugin page can be reached.
settings.saved — Board settings changed. Carries the keys, never the values.
task.run.before — A scheduled task is about to run.
task.run.after — A scheduled task finished, with its outcome and duration.
cache.invalidated — A cache tag was invalidated.
plugin.enabled — A plugin was enabled — including this one, which is how it learns it is on.
plugin.disabled — A plugin was disabled, by an operator or by the host after repeated failures. Carries the reason.
UI regions
Regions are not theme slots. A theme owns its slots; a region is an explicit
"plugins may add something here" point that a theme chooses to render, so the theme
keeps control of where plugin output appears and the plugin keeps control of what it
is. Several plugins contributing to one region compose by concatenation, in the same
deterministic order as hooks.
Region
What it is handed
header.notice
The viewer.
index.footer
The viewer.
postbit.badges
The viewer, the post id and the author id.
postbit.footer
The viewer, the post id and the author id.
thread.header
The viewer, the thread id and the thread author’s id.
profile.panel
The viewer and the profile’s member id.
admin.dashboard
The viewer.
header.notice — Directly below the board header, above the page body. Board-wide notices.
index.footer — The bottom of the board index, below the statistics block.
postbit.badges — Beside a post author’s name. Runs once per post on every thread page — the most expensive region on the board, and the one to keep trivial.
postbit.footer — Below a post body, above its actions.
thread.header — Above the first post of a thread, below its title. Runs once per thread page, so unlike postbit.* it can afford to read from the plugin’s own tables.
profile.panel — A panel on a member’s profile, below the standard fields.
admin.dashboard — A card on the admin dashboard. Only rendered for administrators.