Generated by php flatbb hooks:list on 2026-09-30. Do not edit by hand.

Filters receive ($value, array $ctx) and return the new value (or null to keep it). Events receive (null, array $ctx).
Regions are filters named region.<position> whose value is HTML (or an array for list regions). Inline regions run inside loops: no database queries in their callbacks.

HookKindDescriptionWhere
account.after_logineventAfter a successful login.app/account.php
account.after_registereventAfter a new account was created.app/account.php
account.login_challengefilterAfter the password was checked: return a URL to send the user to a second step (two-factor) instead of signing them in; the plugin finishes with login_pending_user() + login_user().app/account.php
account.login_validatefilterAdd errors to a sign-in attempt before the password is checked (ctx: username); a non-empty list refuses it.app/account.php
account.register_validatefilterAdd validation errors to registration.app/account.php
admin.actioneventAfter an admin action was logged (ctx: user_id, ip, action, target, detail). The security plugin subscribes here.core/security.php
admin.category_after_saveeventAfter a category was created or edited (ctx: id, data); plugins keep their own per-category options here.app/admin_content.php
admin.category_savefilterFilter category data before saving.app/admin_content.php
admin.plugin_opsfilterExtra buttons on a plugin row.app/admin_system.php
admin.plugin_settings.beforefilterHTML at the top of a plugin settings drawer (ctx: id, manifest).app/admin_system.php
admin.settings_fieldsfilterAdd a settings section: $value['myid'] = ['Label', ['key' => [type, label, help, options, min, max]]]. Each section is a tab in Admin → Settings.app/admin.php
admin.settings_savefilterFilter settings before they are saved.app/admin.php, app/admin_ai.php
admin.tooleventHandle a custom tool action.app/admin_system.php
admin.toolsfilterAdd rows to Admin → Tools.app/admin_system.php
admin.user_savedeventAfter an admin edited a user.app/admin.php
ai.requestfilterFilter: a request to the shared AI connection before it is sent (value: system, user, opts; ctx: purpose = the calling plugin). Change it, or return an array with an error key to refuse it (quotas, redaction).core/ai.php
ai.responseeventEvent: after each attempt on an AI connection (ctx: purpose, connection = 1 for the main one, 2 and 3 for the backups, model, usage [in, out] tokens, ok). A request that falls back fires it once per connection tried. For plugins that count or log usage.core/ai.php
api.<action>filterHandle /api/<action>; return an array to respond as JSON.app/api.php
app.booteventEvery request after plugins are loaded. Preload data here.core/boot.php
auth.login.afterfilterHTML below the sign-in form.app/views/login.php
auth.register.afterfilterHtml under the registration form (social sign-up buttons).app/views/register.php
composer.valuesfilterFilter the values a composer opens with: title, body, category_id, tags for a new topic (ctx mode new), body for a reply (ctx mode reply, topic). Used by drafts plugins.app/topic.php, app/views/topic.php
cron.jobsfilterFilter the list of scheduled jobs.core/cron.php
editor.emojifilterapp/views/editor.php
editor.helpfilterapp/views/editor.php
editor.optionsfilterapp/views/editor.php
feed.topicsfilterapp/api.php
icon.pathsfilterAdd SVG icons: name => path markup.core/icons.php
import.doneeventcore/import.php
import.reseteventcore/import.php
link.previewablefilterFilter: whether a link may get a preview card (bool; ctx url, host). Return false to keep a link a link, e.g. when your plugin shows its own player for that site.core/links.php
mail.sendfiltercore/helpers.php
markdown.afterfilterRendered HTML of a post.core/markdown.php
markdown.beforefilterMarkdown source before rendering.core/markdown.php
markdown.excerptfilterFilter: the Markdown a plain excerpt is made from (ctx: max): search index, page descriptions, notifications, feeds. Strip content not every reader may see.core/markdown.php
menus.knownfilterFilter: the list regions an admin edits under Admin → Appearance → Menus (region => name). Add your plugin's own list region to make its items editable.core/hook.php
migrate.after_importeventcore/migrate.php
notification.after_createeventAfter a notification was stored.app/notification.php
notification.after_create_manyeventAfter notify_many() stored the same notification for many members (ctx: user_ids, from_user_id, kind, topic_id, post_id).app/notification.php
notification.before_createfilterFilter/veto a notification (return null to skip). Also runs once per recipient inside notify_many() (ctx: many = true): no database queries.app/notification.php
notification.kindsfilterIcon and verb per notification kind (kind => [icon, verb]); plugins register their own kinds.app/views/notifications.php
notifications.rowsfilterFilter rows on the notifications page.app/notification.php
page.before_outputfilterThe whole HTML document before it is sent. Use for page-level placeholder replacement.core/render.php
page.optionsfilterFilter the page() options (left/right columns, class, description, canonical, robots, breadcrumbs, title_full = the complete <title> text).core/render.php
permissions.knownfilterAdd permission keys shown in Admin → Groups.app/admin.php
plugin.after_install_zipeventcore/plugin.php
plugin.settings_savedeventAfter plugin settings were saved (ctx: id).app/admin_system.php, app/admin_theme.php
points.after_changeeventAfter points were added or removed (ctx: user_id, delta, reason, ref_id, balance).core/points.php
points.before_changefilterFilter or veto a points change (return false to block).core/points.php
points.iconfilterThe icon name of a points history line (ctx: reason, delta). Runs inside lists: no database access.core/points.php
points.reasonsfilterRegister point reason codes: $value['checkin'] = 'Daily check-in'. Labels show in the private history.core/points.php
points.ref_urlfiltercore/points.php
points.rulesfiltercore/points.php
post.after_deleteeventAfter a reply was deleted or restored.app/topic.php
post.after_likeeventAfter a like toggle.app/topic.php
post.after_saveeventAfter a post was created or edited. A new reply that waits in the review queue fires it on approval.app/topic.php
post.before_savefilterNew reply data (body, reply_to_id) before insert.app/topic.php
post.before_updatefilterReply body before an edit is saved.app/topic.php
region.<action>filtercore/hook.php, core/render.php
region.admin.category.fieldshtml regionExtra fields at the end of the category editor (ctx: category)app/admin_content.php
region.admin.dashboard.cardslist regionAdmin dashboard cards (list)app/admin.php
region.admin.menulist regionAdmin menu items (list)app/admin_ui.php
region.admin.plugins.tabslist regionThe tab row of Admin → Plugins: Installed plus what plugins add (list; ctx: active)app/admin_system.php
region.admin.themes.tabslist regionapp/admin_theme.php
region.auth.login.extrahtml regionInside the sign-in formapp/views/login.php
region.auth.register.extrahtml regionInside the registration formapp/views/register.php
region.body.endhtml regionBefore </body> (scripts)app/views/layout.php
region.composer.extrahtml regionExtra fields inside the post/reply formapp/views/topic.php, app/views/topic_form.php
region.composer.modeslist regionEditor mode tabs above the toolbar (list: label, icon, cmd, class): Write and Preview; a visual editor adds its ownapp/views/editor.php
region.composer.toolbarlist regionEditor toolbar buttons (list)app/views/editor.php
region.footer.lefthtml regionFooter, leftapp/views/layout.php
region.footer.linkslist regionFooter links (list)app/views/layout.php
region.footer.righthtml regionFooter, rightapp/views/layout.php
region.headhtml regionInside <head> (meta tags, analytics)app/views/layout.php
region.header.lefthtml regionHeader, right of the logoapp/views/layout.php
region.header.navlist regionHeader navigation links (list)app/views/layout.php
region.header.rightlist regionHeader, right side: search, new topic, language, theme, notifications, account menu (list)core/render.php
region.header.right.after_searchhtml regionHeader, right of the search boxcore/render.php
region.header.right.before_searchhtml regionHeader, left of the search boxcore/render.php
region.header.user_menulist regionThe account list: the header dropdown shows all of it, the sidebar member card the "you" items as shortcuts (list: label, url, icon, count, group "you" or "site", weight, card => false keeps one out of the card)core/render.php
region.header.user_menu.labelshtml regionUser dropdown, under the name next to the group label: short labels such as the level (ctx: user)app/views/user_menu.php
region.header.user_menu.statslist regionUser dropdown numbers strip (list: label, value, url; ctx: user)app/views/user_menu.php
region.main.afterhtml regionBelow the main content on every pageapp/views/layout.php
region.main.beforehtml regionAbove the main content on every pageapp/views/layout.php
region.main.categorieslist regionCategory bar above the list tabs: All + top-level categories (list)app/home.php
region.main.tabslist regionTabs above topic lists (list)app/home.php
region.main.toolbarhtml regionRight of the list tabs; its New Topic button is hidden on wide screens where the member card shows its ownapp/home.php
region.member.actionslist regionButtons of a member: the sidebar member card (New Topic first), the topic author card, the profile card and the account menu, under its numbers (list: label, url, icon, primary, count, title, done, weight; post: the button POSTs to url with the CSRF token and back = the current path; ctx: user, self, place cardauthor
region.member.labelshtml regionShort labels after the group label, wherever the core shows a member: the sidebar member card, the topic author card, the account menu and the profile (ctx: user, self, place cardauthor
region.member.sectionslist regionSections beside the lists on a profile, one card each: badges, a calendar, a showcase (list: title, html, url, link, weight; ctx: user, self, place profile)app/views/profile.php
region.member.statslist regionNumbers of a member, in the sidebar member card, the topic author card, the account menu strip and the profile card (list: label, value, url, sub, progress 0..1 draws a bar, weight; ctx: user, self, place cardauthor
region.points.actionslist regionWays to earn on the /points page, as tiles (list: label, title, sub, url, icon, primary, done; ctx: user)app/points.php
region.points.summarylist regionShort facts next to the balance on the /points page, such as the level (list: label, sub, progress 0..1, url; ctx: user)app/points.php
region.post.actionslist regionPost action buttons (list, loop, no DB)app/views/post.php
region.post.afterinline region (loop, no DB)After each post (loop, no DB)app/views/post.php
region.post.beforeinline region (loop, no DB)Before each post (loop, no DB)app/views/post.php
region.post.content_afterinline region (loop, no DB)After each post body (loop, no DB)app/views/post.php
region.post.metainline region (loop, no DB)Post meta line, after the time: level, title, badges (loop, no DB)app/views/post.php
region.post.nameinline region (loop, no DB)Post name row, after the username, OP and group labels: a short label such as the level (loop, no DB)app/views/post.php
region.sidebar.left.bottomhtml regionLeft column, bottomapp/views/sidebar_left.php
region.sidebar.left.navlist regionLeft column navigation links, one per plugin (list: label, url, icon, badge, active, weight; group "community", "tools" or your own id with group_label; past setting nav_visible links the rest fold under More)app/views/sidebar_left.php
region.sidebar.left.tophtml regionLeft column, topapp/views/sidebar_left.php
region.sidebar.right.bottomhtml regionRight column, bottomapp/views/sidebar_right.php
region.sidebar.right.cardslist regionRight column card stack (list)core/render.php
region.sidebar.right.tophtml regionRight column, topapp/views/sidebar_right.php
region.topic.actionslist regionTopic page action buttons (list)app/views/topic.php
region.topic.headerhtml regionTopic page, below the title blockapp/views/topic.php
region.topic.no_accesshtml regionTopic page when a plugin refused the visitor, under the reasonapp/topic.php
region.topic.replies_afterhtml regionAfter the post stream, before the reply boxapp/views/topic.php
region.topic.sidebar.bottomhtml regionTopic page right column, bottomapp/views/sidebar_topic.php
region.topic.sidebar.cardslist regionTopic page right column cards (list)app/topic.php
region.topic.sidebar.tophtml regionTopic page right column, topapp/views/sidebar_topic.php
region.topic_list.afterhtml regionAfter the topic list, before paginationapp/views/topic_list.php
region.topic_list.beforehtml regionBetween the list tabs and the topic rows (announcements, notices)app/views/topic_list.php
region.topic_list.item.afterinline region (loop, no DB)After each topic row (loop, no DB)app/views/topic_rows.php
region.topic_list.item.metainline region (loop, no DB)In each topic row meta line (loop, no DB)app/views/topic_rows.php
region.topic_list.item.title_suffixinline region (loop, no DB)After each topic title (loop, no DB)app/views/topic_rows.php
region.user.moderate.optionshtml regionapp/moderation.php
region.user.profile.actionshtml regionapp/views/profile.php
region.user.profile.afterhtml regionSections inside the profile card, under the numbers: badges (ctx: user, self)app/views/profile.php
region.user.profile.cardslist regionCards under the profile card (list)app/user.php
region.user.profile.labelshtml regionProfile card, after the group label: short labels such as a custom title (ctx: user, self)app/views/profile.php
region.user.profile.metainline region (loop, no DB)Profile card details, after Joined / Seen / website: short items with an icon (ctx: user, self)app/views/profile.php
region.user.profile.statslist regionProfile card numbers (list of label, value, url, sub; progress 0..1 draws a bar across the card)app/user.php
region.user.profile.tabslist regionProfile page tabs (list)app/user.php
region.user.settings.tabslist regionSettings page menu, a section list on phones (list): id => [label, group accountpreferences
regions.knownfilterRegister extra regions for Admin → Widgets.core/hook.php
review.decidedeventEvent: a moderator approved (status 1) or rejected (status 2) an item of the review queue (ctx: id, status, by). On approval the usual topic.after_save / post.after_save fire as well.app/review.php
review.heldeventEvent: a topic or reply was put in the review queue (ctx: id, kind, topic_id, post_id, user_id, reason).app/review.php
review.holdfilterFilter: why a new topic or reply waits in the review queue (value: the reason so far, an English text shown through t(), or empty; ctx: kind topicreply, user, category, text). Return a reason to hold it. Administrators and moderators are never held.
router.not_foundfilterNo route matched (ctx: path). Return a URL (301) or [url, code] to redirect instead of the 404 page; return nothing to let it 404. Also the place to record misses.core/router.php
router.routesfilterFilter: the route table (path => handler). Replace a core address (a portal at /); admin, sign-in, settings, setup and API addresses cannot be taken over. Admin → Plugins lists the takeovers.core/router.php
schema.installeventAfter core tables are created/upgraded.core/schema.php
search.resultsfilterapp/search.php
security.cspfilterContent Security Policy directives (name => list of sources): add the CDNs your plugin loads scripts, styles or fonts from.core/security.php
seo.sitemapfilterGET /sitemap.xml: return the XML to serve instead of the built-in sitemap (an index, paged files); return nothing to keep the default.app/api.php
topic.accessfilterapp/topic.php
topic.after_actioneventAfter pin/lock/move/restore (ctx: topic_id, action).app/topic.php
topic.after_deleteeventAfter a topic was soft-deleted.app/moderation.php, app/topic.php
topic.after_saveeventAfter a topic was created or edited (ctx: topic_id, post_id, new). A new topic that waits in the review queue fires it when a moderator approves it, so the topic is public by then.app/topic.php
topic.before_savefilterNew topic data (category_id, user_id, title, body, tags) before insert.app/topic.php
topic.can_replyfilterapp/topic.php
topic.category_autofilterFilter: whether a plugin will pick the category of a new topic (bool). True makes the category optional in the composer; return true only when topic.category_missing can actually answer (configured, within limits).app/topic.php
topic.category_missingfilterFilter: a new topic arrived without a category (value 0; ctx: title, body, user). Return a category id to file it there (an AI pick, say); the writer must be allowed to post in it. Runs before the default category.app/topic.php
topic.postsfilterFilter the posts of the current page (batch-loaded, attach extra data here).app/topic.php
topic.titlefilterFilter the escaped title html of a topic in lists and on the topic page (ctx: topic, where listpage); loop, no DB.
topic.viewfilterFilter the topic row shown on the topic page (ctx: posts).app/topic.php
topic_list.queryfilterFilter the conditions of every topic list (front page, category, tag, unread): value ["where" => conditions on alias t, "params"]; append to both. Ctx: where, join.app/home.php
topic_list.rowsfilterFilter topic rows of any list (batch-loaded, attach extra data here).app/home.php
upgrade.after_applyeventcore/upgrade.php
upload.after_saveeventEvent: a file was saved under uploads/ (ctx: kind attachmentavatar
upload.before_savefilterFilter: return a message to refuse an uploaded attachment, or change the file at ctx tmp in place (ctx: kind, user, tmp, name, ext, mime, size, is_image).core/upload.php
upload.urlfilterFilter: the address of an uploaded file (ctx: path), to serve it from a CDN or an object store. Runs for every avatar on a page: no database access.core/upload.php
user.after_deleteeventapp/moderation.php
user.after_moderateeventapp/moderation.php, app/review.php
user.after_renameeventAfter a username changed (ctx: user_id, old, new, by). Old profile URLs redirect automatically.core/auth.php
user.after_saveeventAfter the profile was saved.core/render.php, app/user.php
user.avatar_afterfilterHTML placed inside the avatar, over its lower corner (ctx: user, size in px); the wrapper is positioned, so use position:absolute. Runs inside lists: no database access.core/render.php
user.before_savefilterProfile fields before saving.app/user.php
user.canfilterFilter: whether the signed-in member may do something (bool; ctx: permission, user, group), after the group decided. Guests and admins never reach it.core/auth.php
user.email_changedeventAfter a member changed their email address in Settings → Email, or restored the old one from the notice link (ctx: user_id, old, new, restored).app/user.php
user.levelfilterFilter: the level of a member as a number (ctx: user), read by user_level(). Answered by the plugin that keeps levels. Runs inside lists: no database access.core/auth.php
user.link_afterfilterHTML appended after every rendered username link (ctx: user, class; class is "profile-name" on the profile header). Runs inside lists: no database access.core/render.php, app/views/card_newest.php, app/views/profile.php
user.logout_everywhereeventAfter every session of a user was invalidated (ctx: user_id).core/auth.php
user.prefs_savefilterPreferences array before saving.app/user.php
user.profile_tabfilterHTML for a custom profile tab (ctx: user, tab).app/user.php
user.settings_posteventPOST handler for a custom settings tab.app/user.php
user.settings_tabfilterExtra HTML for a settings tab.app/user.php