Hooks Overview
BuddyNext overviewFree + ProGet BuddyNext →
BuddyNext is built to be extended through WordPress actions and filters. This page covers how hooks are named, the split between actions and filters, the difference between hooks BuddyNext provides and hooks it consumes, priority conventions, and which hook domain is documented on which reference page. Read this before any of the per-domain hook pages (27-33).

Overview / Contract
Section titled “Overview / Contract”The source of truth is the code
Section titled “The source of truth is the code”Grep the source before you rely on a signature. The hook surface is large and it moves; these pages document it, but the do_action() / apply_filters() call site is what actually runs. Where a page here and the live code disagree, the code wins - and please file it, because the doc is then a bug.
This guide documents the hooks BuddyNext fires itself. It does not cover core WordPress hooks BuddyNext listens to, nor the actions and filters published by sibling plugins (Jetonomy, WPMediaVerse, Career Board, WBGamification) that BuddyNext consumes through its Bridges - those belong to those plugins’ contracts. Pro adds its own surface on top, documented on the Pro and Integration Hooks page.
Naming
Section titled “Naming”Two prefixes are in use, and they mean different things:
| Prefix | Scope | Where it lives |
|---|---|---|
buddynext_* |
Public extension surface - the seams third parties hook | Services, listeners, controllers, and templates |
bn_* |
Internal plumbing - not part of the public contract | A small number of admin-internal hooks (bn_admin_hub_sections, bn_admin_hub_tab_placement, bn_admin_hub_default_icon_map, bn_admin_hub_pages) and one Pro filter (bn_membership_onetime_is_lifetime) |
Author your integrations against buddynext_* hooks. The bn_* prefix matches the database table prefix and the CSS prefix, but as a hook prefix it marks internal admin wiring that may change without notice.
Within buddynext_*, the surface divides into two families:
buddynext_part_*- the template-part theming seam, and the largest family by a wide margin. It is documented in full on its own page (see Hooks: Template Parts).- Everything else under
buddynext_*- the domain event and data-shaping hooks: social graph, feed, spaces, profiles, members, notifications, moderation, search, and so on. These are split across the per-domain pages described below.
These pages are a guide, not an exhaustive index. The per-domain pages cover the hooks an integrator actually reaches for. A number of narrower seams are fired but not listed - navigation (
buddynext_rail_items,buddynext_nav_items,buddynext_mobile_nav_items,buddynext_user_links, documented instead on the Navigation API page), the template loader (buddynext_template_locations,buddynext_before_template,buddynext_after_template), the theme tokens (buddynext_css_vars,buddynext_css_vars_dark), and per-template_before/_afteractions. If you need a seam that is not on these pages, grep for it: it very likely exists.
One hazard worth knowing before you hook anything
Section titled “One hazard worth knowing before you hook anything”A handful of BuddyNext hooks are fired from more than one call site. WordPress passes a listener only the arguments the firing site supplied, so a typed callback registered for the full signature raises an ArgumentCountError if any site fires with fewer arguments.
buddynext_space_updated and buddynext_member_unsuspended used to be fired with fewer arguments from one site; every call site now passes the full documented signature. The one hook that still differs by site is buddynext_ability_granted: Pro’s Stripe webhook fires it with two arguments and Free’s access webhook with three (see Hooks: Pro and Integration). The same hazard applies to the filters buddynext_max_following and buddynext_max_connections, which the app config route applies with the cap alone while the write paths also pass the user ID.
Defend against it by giving trailing parameters defaults rather than trusting accepted_args:
// Safe: survives a call site that supplies fewer arguments than documented.add_action( 'buddynext_ability_granted', static function ( int $user_id, string $ability, string $source = '' ): void { // ... }, 10, 3);The hook with differing call sites is called out on its own page.
Actions vs filters - the practical distinction
Section titled “Actions vs filters - the practical distinction”The split is not arbitrary. It tells you what the hook is for.
- An action (
do_action) is a notification that something happened. BuddyNext does not use the return value. Use actions to react to events: award points, write an analytics row, send an external webhook, sync to a CRM. Example:buddynext_post_createdfires after a post is saved. - A filter (
apply_filters) asks you to shape a value and return it. BuddyNext uses what you return. Use filters to change behavior or markup: rerank a feed, add a profile field type, raise a limit, inject markup into a template part. Example:buddynext_post_pin_limitlets Pro raise the pinned-post cap from 1 to 3.
Note: A filter callback must return a value. Returning nothing (or the wrong type) from a filter can break the surface that depends on it. An action callback’s return value is ignored.
Provided vs consumed
Section titled “Provided vs consumed”BuddyNext sits on both sides of the hook system, and the distinction matters when you decide where to put your code.
- Provided (BuddyNext fires, you listen): all of the roughly 950 distinct hook names Free and Pro fire (about 320 of them template-part hooks). These are the seams you build on. Free ships no
docs/specs/HOOKS.md; the code is the contract - the per-domain hooks pages (25-33) and the livedo_action()/apply_filters()call sites are the reference for the integration-grade actions. - Consumed (a sibling plugin fires, BuddyNext listens): BuddyNext’s Bridges hook events owned by other plugins - for example
mvs_message_sent(WPMediaVerse),jetonomy_after_create_post(Jetonomy),wcb_job_created(Career Board), andwb_gamification_badge_awarded(WBGamification). You do not hook these through BuddyNext; you hook them on the plugin that fires them. They are listed here only so you know which direction a given event flows.
Note: Some hooks are deliberately fired by Free as a seam that only Pro consumes today. For example
buddynext_feed_order_byis the documented SQL-level feed-rerank seam; Pro ships an affinity ranker but reaches it by a container rebind rather than this filter, leaving the filter open for third-party use.
Priorities
Section titled “Priorities”BuddyNext follows standard WordPress priority rules. Default priority is 10. Lower numbers run earlier; higher numbers run later.
The priorities that are part of the contract (do not fight them):
| Hook point | Priority | Why |
|---|---|---|
plugins_loaded -> Free boot (Plugin::init) |
15 | After addons (10), before Pro (20) |
plugins_loaded -> Pro boot |
20 | After Free, so Free’s services exist |
plugins_loaded -> Bridge classes |
25 | After both Free and Pro |
buddynext_safeguard_check -> Pro AI moderation |
20 | Runs after Free’s keyword safeguard (priority 10) |
buddynext_notification_created -> Pro push dispatch |
20 | Runs after the in-app notification is written |
When you hook a domain event, default priority is almost always correct. Raise the priority number only when your callback depends on another callback having already run (the AI moderation example above), and lower it only when you must run before BuddyNext’s own listener.
Hook domain map (pages 27-33)
Section titled “Hook domain map (pages 27-33)”The buddynext_* domain hooks (everything except the template-part family) are documented per domain. Use this map to jump to the right page.
| Page | Domain | Representative hooks |
|---|---|---|
| 27 | Feed, posts, reactions, comments | buddynext_post_created, buddynext_post_updated, buddynext_post_deleted, buddynext_post_shared, buddynext_reaction_added, buddynext_comment_created, buddynext_feed_order_by |
| 28 | Members, profiles, fields, social graph | buddynext_user_followed, buddynext_user_unfollowed, buddynext_connection_requested, buddynext_connection_accepted, buddynext_member_approved, buddynext_member_type_assigned, buddynext_profile_field_render |
| 29 | Spaces | buddynext_space_created, buddynext_space_member_joined, buddynext_space_member_left, buddynext_space_member_removed, buddynext_space_join_approved, buddynext_can_join_space, buddynext_space_can_view_roster |
| 30 | Notifications and email | buddynext_notification_created, buddynext_notification_prefs_*, the email-template and digest seams |
| 31 | Moderation, auth, trust | buddynext_report_created, buddynext_user_warned, buddynext_user_suspended, buddynext_appeal_submitted, buddynext_safeguard_check, buddynext_registration_pending |
| 32 | Search, hashtags, sidebar, admin | buddynext_search_query_args, buddynext_hashtag_*, the sidebar-widget and admin-hub filters |
| 33 | Pro and integration | Pro hooks (buddynext_pro_subscription_created, buddynext_outbound_webhook_limit) and the integration bridge seams |
The template-part family (buddynext_part_*, 314 hooks across 81 parts) is documented separately on the Hooks: Template Parts page (26), because it is a different kind of seam - presentation rather than events - and dominates the surface by count.
Examples
Section titled “Examples”Reacting to an event with add_action
Section titled “Reacting to an event with add_action”buddynext_post_created is a domain action. It fires after a post is saved, with the post ID, author ID, and a type string. Use it to do something in response - here, log every new post.
add_action( 'buddynext_post_created', function ( int $post_id, int $user_id, string $type ): void { // $type is one of: text, photo, file, link, poll, announcement, // activity, media, discussion, job, share. // Need the full post? Re-fetch by ID - the hook intentionally // passes only the identifiers. $post = buddynext_service( 'post_service' )->get( $post_id );
error_log( sprintf( 'BuddyNext: user %d created a %s post (#%d).', $user_id, $type, $post_id ) ); }, 10, // default priority 3 // this action passes 3 arguments);Note: Domain event actions pass identifiers, not hydrated objects, on purpose - it keeps the event cheap to fire. When you need more fields, re-fetch through the domain service (
buddynext_service( 'post_service' )->get( $post_id )).
Shaping a value with add_filter
Section titled “Shaping a value with add_filter”buddynext_post_pin_limit is a domain filter. BuddyNext defaults the pinned-post cap to 1 and uses whatever you return. Here, raise it to 3, the most BuddyNext allows (this is the same seam Pro uses to lift the cap).
add_filter( 'buddynext_post_pin_limit', function ( int $limit ): int { return 3; });Always declare the argument count on add_action when the hook passes more than one argument (the fourth argument to add_action), and always return from a filter callback.
Notes / gotchas
Section titled “Notes / gotchas”- The code is the contract. Free ships no
docs/specs/HOOKS.md; an earlier revision of this page cited one as a “locked” source of truth, and it does not exist. Use the per-domain hooks pages (25-33) and the livedo_action()/apply_filters()call sites, cross-checked againstaudit/manifest.json’shooks_firedentries for the file and line where a hook actually fires (the manifest can lag the code, so the call site wins). - Do not hook
bn_*as a public seam. Those hooks are internal admin wiring. - Free/Pro boundary. Pro never calls Free code directly - it extends Free through container rebinds, class inheritance, or these hooks. If you are writing a Pro-style extension, prefer the same three mechanisms. Pro’s own hook surface (events it emits, such as
buddynext_pro_subscription_created) is listed on the Pro and Integration Hooks page (33). - Consumed hooks live on the other plugin. To react to a direct message, hook
mvs_message_senton WPMediaVerse, not a BuddyNext hook. To react to a Jetonomy discussion, hookjetonomy_after_create_post. BuddyNext’s bridges already do this internally; your code should target the source plugin’s hook directly.

