Coming from BuddyPress / BuddyBoss
A translation guide for developers who already know BuddyPress or BuddyBoss. BuddyNext solves the same problems - profiles, an activity stream, groups, connections, private messaging - but it is a standalone plugin with its own architecture, its own permission model, and a REST-first contract. Nothing from the bp_* / BP_* namespace exists at runtime. This page maps the concepts you know to the BuddyNext equivalents so you can port a customization without re-learning the whole codebase. Where a deep-dive page already covers a topic (the Nav API, child-theme overrides, roles and capabilities), this page summarizes and points you there.
The one-paragraph mental model
Section titled “The one-paragraph mental model”There is no BuddyPress to detect, depend on, or extend. BuddyNext boots itself at plugins_loaded:15 through BuddyNext\Core\Plugin::init() and fires buddynext_loaded when it is ready. Services live in a DI container reached through buddynext_service( 'key' ). Permissions flow through one function, buddynext_can(). The frontend is 100% REST under buddynext/v1 - there is no admin-ajax.php surface. Navigation, templates, and capabilities are all registered declaratively through hooks and filters, the same seams BuddyNext Pro itself uses.
1. Architecture and bootstrapping
Section titled “1. Architecture and bootstrapping”BuddyPress is a component framework: you call bp_is_active( 'groups' ) to check a component, subclass BP_Component to add one, and bootstrap through bp_loaded / bp_init. BuddyNext has no component framework and no global $bp object. Features are folders under includes/{Feature}/, each binding a service into the container, and feature availability is a toggle resolved by FeatureRegistry.
| BuddyPress / BuddyBoss | BuddyNext | Notes |
|---|---|---|
bp_is_active( 'groups' ) |
buddynext_feature_enabled( 'spaces' ) |
Global helper (buddynext.php). Or buddynext_service( 'features' )->is_enabled( $slug ) from FeatureRegistry. |
buddypress() / global $bp |
buddynext_service( 'key' ) |
DI container lookup. Throws on an unbound key, so guard optional features with the container’s has(). |
BP_Component subclass |
A feature folder includes/{Feature}/ + a container binding |
See the Architecture Overview page for the five-layer model and the canonical feature-module shape. |
bp_loaded / bp_init |
buddynext_loaded |
Fired once in Plugin::init() (includes/Core/Plugin.php) after all services are registered and wired. |
bp_core_get_user_domain() and friends |
PageRouter::profile_url(), people_url(), spaces_url(), … |
Always build links with the PageRouter::*_url() helpers; hub slugs are admin-configurable. |
function_exists( 'buddypress' ) guard |
none needed | BuddyNext has no soft dependency to detect. Your addon depends on BuddyNext directly. |
Two boot extension seams matter, both inside register_services():
buddynext_register_servicesfires before core bindings - use it to add a brand-new service. Core bindings registered afterward win over anything you bind here.buddynext_services_registeredfires after all core bindings but before anything is resolved - rebind a key here to replace a core service with a subclass. The container resolves lazily, so the late rebind takes effect.
There is no bp-custom.php
Section titled “There is no bp-custom.php”BuddyPress auto-loads wp-content/plugins/bp-custom.php very early so you can register components and override functions before the suite boots. BuddyNext has no equivalent auto-loaded file. Put your customizations in a small mu-plugin or your theme’s functions.php and attach them to BuddyNext hooks. Because every seam fires at plugins_loaded:15 or later, hook your registrations on plugins_loaded (or later) and they will be in place before any check runs:
// mu-plugin or theme functions.php - the bp-custom.php replacement.add_action( 'plugins_loaded', function () { if ( ! function_exists( 'buddynext_service' ) ) { return; // BuddyNext not active. } // Register nav items, capabilities, services, template overrides here.}, 20 );2. Navigation
Section titled “2. Navigation”BuddyPress builds member and group nav imperatively with bp_core_new_nav_item() and bp_core_new_subnav_item(), called inside a bp_setup_nav callback against the global $bp member. BuddyNext replaces this with a declarative Nav API: every surface (profile tabs, space tabs, the left rail, sub-nav, metric pills) registers items into a single NavRegistry, which gates, orders, dedupes, and nests them per request. You register on the one-time buddynext_register_nav action (you receive the registry) or filter a resolved surface (for example buddynext_rail_items for the left rail).
A profile tab - before (BuddyPress) and after (BuddyNext):
// BEFORE - BuddyPressfunction my_profile_tab() { bp_core_new_nav_item( array( 'name' => __( 'Events', 'my-textdomain' ), 'slug' => 'events', 'position' => 80, 'screen_function' => 'my_events_screen', 'default_subnav_slug' => 'events', ) );}add_action( 'bp_setup_nav', 'my_profile_tab' );// AFTER - BuddyNextadd_action( 'buddynext_register_nav', function ( \BuddyNext\Nav\NavRegistry $registry ) { $registry->register( array( 'id' => 'events', 'surface' => 'profile', // profile | space 'layer' => 'primary', // primary | metric | rail | context 'label' => __( 'Events', 'my-textdomain' ), 'icon' => 'calendar', // an icon slug from assets/icons/ 'priority' => 80, // A primary tab needs a clean route (url) and/or a render panel. 'url' => fn( \BuddyNext\Nav\NavContext $c ): string => trailingslashit( \BuddyNext\Core\PageRouter::profile_url( $c->subject_id ) ) . 'events/', 'render' => fn( \BuddyNext\Nav\NavContext $c ): void => my_render_events_panel( $c->subject_id ), // Optional: gate visibility, badge a count - both lazy callables that // see the live NavContext (subject_id, viewer_id, is_self(), ...). 'condition' => fn( \BuddyNext\Nav\NavContext $c ): bool => $c->is_self(), 'count' => fn( \BuddyNext\Nav\NavContext $c ): int => my_event_count( $c->subject_id ), ) );} );A left-rail menu item - filter the resolved rail instead of registering on a surface:
// AFTER - BuddyNext: inject a left-rail link.add_filter( 'buddynext_rail_items', function ( array $items, string $hub ): array { $items[] = array( 'key' => 'help-center', 'label' => __( 'Help Center', 'my-textdomain' ), 'icon' => 'help-circle', 'url' => home_url( '/help/' ), 'show' => true, 'order' => 90, ); return $items;}, 10, 2 );Order is deterministic: items sort by priority then registration order, with optional before / after anchors against another item’s id. Sub-nav is expressed by giving a child item a parent set to the parent’s id (see includes/Nav/Providers/ProfileNav.php for the built-in Network tab with Connections / Followers / Following children). For the full item contract, the four layers, gating, count resolution, and the buddynext_nav_items / buddynext_context_nav filters, see the Nav API page.
3. Template overrides
Section titled “3. Template overrides”BuddyPress ships templates under bp-templates/, resolved through bp_get_template_part() and the template stack so a theme can override any file from its own buddypress/ directory. BuddyNext keeps the same theme-override idea with a three-plan loader (BuddyNext\Core\TemplateLoader) and the buddynext_get_template() helper.
| BuddyPress / BuddyBoss | BuddyNext |
|---|---|
bp_get_template_part( 'members/single/home' ) |
buddynext_get_template( 'feed/home.php', $vars ) |
{theme}/buddypress/{relative}.php override |
{child-theme}/buddynext/{relative}.php override |
bp_locate_template() stack |
TemplateLoader::locate() three-plan resolution |
bp_before_member_body etc. template hooks |
buddynext_before_template / buddynext_after_template (fired around every template) |
The resolution order is:
1. {active-child-theme}/buddynext/{relative}.php2. {parent-theme}/buddynext/{relative}.php3. {plugin}/templates/{relative}.php (the default)So to override the home feed, copy templates/feed/home.php from the plugin to {your-child-theme}/buddynext/feed/home.php and edit the copy. Templates receive their data as pre-extracted variables (the loader imports only valid identifier keys) and must never run $wpdb queries - data comes from a service. For the full override workflow, the variable contract, and which templates are safe to override, see the Child Theme Template Overrides page.
4. REST API
Section titled “4. REST API”BuddyPress exposes routes under buddypress/v1 and still leans on admin-ajax.php for much of its frontend. BuddyNext is REST-first and admin-ajax-free: every template, frontend script, admin script, and block view-script talks to buddynext/v1 (free) or buddynext-pro/v1 (pro). A CI gate (bin/check-rest-boundary.sh) enforces that no wp_ajax_* handler, ajaxurl, or check_ajax_referer() is used on the frontend.
| Concept | BuddyPress route | BuddyNext route(s) |
|---|---|---|
| Activity stream | buddypress/v1/activity |
buddynext/v1/feed/home, feed/explore, feed/counts; CRUD on buddynext/v1/posts, posts/{id} |
| Members directory | buddypress/v1/members |
buddynext/v1/members; buddynext/v1/search/members |
| Member relationship state | (friends component) | buddynext/v1/users/{id}/follow/status, connect, block, account-type |
| XProfile fields | buddypress/v1/xprofile/fields |
buddynext/v1/profile-fields, profile-fields/{id}, profile-groups; buddynext/v1/me/profile |
| Groups | buddypress/v1/groups |
buddynext/v1/spaces, spaces/{id}, spaces/{id}/members, space-categories |
| Private messages | buddypress/v1/messages |
No buddynext/v1 DM namespace - DM is the WPMediaVerse engine (see section 7) |
| Notifications | buddypress/v1/notifications |
buddynext/v1/me/notifications, me/notifications/{id}/read, me/notification-prefs |
| Reactions / comments | (activity favorites / comments) | buddynext/v1/reactions, reactions/toggle; buddynext/v1/comments, comments/{id} |
| Hashtags | (no core equivalent) | buddynext/v1/hashtags/{slug}, hashtags/trending, hashtags/autocomplete |
Auth is the standard logged-in WordPress REST pattern: cookie plus the wp_rest nonce sent in the X-WP-Nonce header. There are no per-action nonce names and the nonce never travels in the query string. External (non-browser) integrations authenticate with Application Passwords over HTTP Basic. Every route declares a permission_callback and most reads/writes resolve buddynext_can() for authorization. The success body is the resource payload directly (no success/data wrapper); errors are a WP_Error carrying code, message, and data.status. For the full envelope, pagination model (cursor for timelines, page-numbered for bounded lists), and ceilings, see the REST Contract page and the per-resource REST pages.
5. Capabilities
Section titled “5. Capabilities”BuddyPress authorizes with bp_current_user_can() plus WordPress role capabilities (and BuddyBoss layers its own member-type and moderation caps on top). BuddyNext registers no custom WordPress roles. Every authorization decision flows through one function:
buddynext_can( int $user_id, string $capability, array $context = array() ): bool| BuddyPress / BuddyBoss | BuddyNext |
|---|---|
bp_current_user_can( 'bp_moderate' ) |
buddynext_can( get_current_user_id(), 'buddynext-moderation/review-queue' ) |
Group role check (groups_is_user_admin()) |
buddynext_can( $uid, 'buddynext-spaces/manage-settings', array( 'space_id' => $id ) ) |
add_role() / mapped WP caps |
Community role in user meta (bn_community_role) + per-space role in bn_space_members |
| Member types as roles | A community role is one of member, moderator, admin, owner (a hierarchy); member types (section 6) are a separate labeling system |
Never call current_user_can() against a BuddyNext capability or read bn_community_role directly - route it through buddynext_can() so all four resolution layers (WP admin, role map, explicit grant, the buddynext_user_can filter) and the space-ban short-circuit apply.
The free catalog holds 22 capabilities registered through the WordPress Abilities API (WP 6.9+), plus two space-scoped ones resolved by dedicated per-space methods:
buddynext-profile/edit-own buddynext-spaces/create buddynext-connections/followbuddynext-profile/edit-any buddynext-spaces/join buddynext-connections/connectbuddynext-profile/view buddynext-spaces/join-gated buddynext-moderation/reportbuddynext-feed/create-post buddynext-spaces/post buddynext-moderation/review-queuebuddynext-feed/delete-own-post buddynext-spaces/moderate buddynext-moderation/issue-strikebuddynext-feed/delete-any-post buddynext-spaces/manage-settings buddynext-moderation/suspend-userbuddynext-feed/pin-post buddynext-spaces/deletebuddynext-feed/schedule-post buddynext-comments/create (space-scoped, per-space methods) buddynext-moderate-space buddynext-manage-spaceTo add a capability you filter buddynext_abilities (register the slug) and buddynext_role_map (give it a minimum role); to override one decision you filter buddynext_user_can. For the four-layer resolution model, the role hierarchy, explicit grants, and the filter seams, see the Roles and Capabilities page.
6. Hooks cheat-sheet
Section titled “6. Hooks cheat-sheet”The action you hooked in BuddyPress has a BuddyNext counterpart with a different name and, in several cases, a different argument order. The signatures below are verified against the live source (do_action call sites in includes/); the do_action() and apply_filters() call sites in includes/ are the authoritative inventory of every hook the plugin fires. Trust these over any older curated list.
| BuddyPress / BuddyBoss | BuddyNext | Signature (verified) |
|---|---|---|
bp_activity_add / bp_activity_posted_update |
buddynext_post_created |
( $post_id, $user_id, $type ) |
bp_activity_deleted_activities |
buddynext_post_deleted |
( $post_id, $user_id ) |
friends_friendship_requested |
buddynext_connection_requested |
( $connection_id, $requester_id, $recipient_id, $note ) |
friends_friendship_accepted |
buddynext_connection_accepted |
( $connection_id, $requester_id, $recipient_id ) |
friends_friendship_rejected |
buddynext_connection_declined |
( $connection_id, $requester_id, $recipient_id ) |
friends_friendship_withdrawn |
buddynext_connection_withdrawn |
( $connection_id, $requester_id, $recipient_id ) |
friends_friendship_deleted |
buddynext_connection_removed |
( $user_a, $user_b ) |
| (follow add-on) | buddynext_user_followed |
( $follower_id, $following_id ) |
| (follow add-on) | buddynext_user_unfollowed |
( $follower_id, $following_id ) |
groups_create_group |
buddynext_space_created |
( $space_id, $owner_id ) |
groups_join_group |
buddynext_space_member_joined |
( $space_id, $user_id, $role ) |
groups_leave_group |
buddynext_space_member_left |
( $space_id, $user_id ) |
groups_remove_member |
buddynext_space_member_removed |
( $space_id, $user_id, $removed_by ) |
bp_set_member_type |
buddynext_member_type_assigned |
( $user_id, $new_slug, $old_slug ) |
bp_remove_member_type |
buddynext_member_type_removed |
( $user_id, $removed_slug ) |
| (activity favorite) | buddynext_reaction_added |
( $object_type, $object_id, $user_id, $emoji ) |
bp_activity_comment_posted |
buddynext_comment_created |
( $comment_id, $object_type, $object_id, $user_id ) |
bp_moderation report flow |
buddynext_report_created |
( $report_id, $object_type, $object_id, $reporter_id ) |
Watch the argument order: several BuddyNext space and engagement hooks lead with the object id, not the user id. buddynext_space_member_joined is ( $space_id, $user_id, $role ), buddynext_reaction_added is ( $object_type, $object_id, ... ), and buddynext_comment_created / buddynext_report_created both carry an $object_type + $object_id pair (reactions and comments are polymorphic across posts and other objects). There are also member-type lifecycle hooks with no BuddyPress equivalent - buddynext_member_type_created and buddynext_member_type_deleted. The Hooks Overview and per-domain hooks pages list the complete set.
7. Where DM, forums, and gamification live (the companion model)
Section titled “7. Where DM, forums, and gamification live (the companion model)”BuddyPress bundles messaging and leans on bbPress for forums and third-party plugins for points. BuddyNext keeps its core lean and delegates these to first-party companion plugins, each integrated through a Layer 1 bridge (includes/Bridges/). The bridge boots on the buddynext_load_bridges seam at plugins_loaded:25, gated on its feature toggle and self-guarded by class_exists(), so BuddyNext degrades gracefully when a companion is absent.
| Capability | Companion plugin | Where the engine lives |
|---|---|---|
| Direct messaging | WPMediaVerse | WPMediaVerseBridge. DM tables (mvs_conversations, mvs_messages, …) and the DM REST engine belong to WPMediaVerse; BuddyNext is the UI layer only. There is no buddynext/v1 DM namespace. |
| Forums / discussions | Jetonomy | JetonomyBridge. Surfaces a Discussions tab on the profile and space surfaces (via buddynext_register_nav) and a left-rail item (via buddynext_rail_items); Jetonomy owns the jt_* data. |
| Points, badges, levels | WB Gamification | GamificationBridge. Listens to WB Gamification award/level hooks and reflects them into BuddyNext (for example the profile Achievements tab). |
If you are porting a customization that touched BuddyPress messages, bbPress, or a points plugin, target the companion’s own API for the data and use the relevant BuddyNext bridge hook for the integration touch-point. For how a bridge normalizes external data into BuddyNext and the full list of bridges (including the Pro-plan ones), see the Integration Bridges page; for how companions are installed, see The Companion Install Model.
Notes and gotchas
Section titled “Notes and gotchas”- No
$bpglobal, no component detection. Your addon depends on BuddyNext directly. Guard only onfunction_exists( 'buddynext_service' )if you ship a plugin that can run without it. - Build links with
PageRouter::*_url(). Hub slugs are admin-configurable; a hand-rolled/members/{id}/path can break on a renamed slug. - Hook on
plugins_loadedor later. Every seam (buddynext_register_nav,buddynext_abilities, the service seams) fires atplugins_loaded:15+, so register there. There is no earlybp-custom.phpload step. - Argument order changed. Re-check every space, reaction, comment, and report hook signature against the table above before porting a listener - a wrong
accepted_argsor positional assumption fails silently. - Frontend is REST, not ajax. Do not add a
wp_ajax_*handler for a frontend feature; the REST boundary is CI-gated. Register a route under your own namespace or extend through the documented seams.

