Coming from BuddyPress / BuddyBoss
Free & ProA 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
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
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
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
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 - BuddyPress
function 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 - BuddyNext
add_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
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}.php
2. {parent-theme}/buddynext/{relative}.php
3. {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
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
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/follow
buddynext-profile/edit-any buddynext-spaces/join buddynext-connections/connect
buddynext-profile/view buddynext-spaces/join-gated buddynext-moderation/report
buddynext-feed/create-post buddynext-spaces/post buddynext-moderation/review-queue
buddynext-feed/delete-own-post buddynext-spaces/moderate buddynext-moderation/issue-strike
buddynext-feed/delete-any-post buddynext-spaces/manage-settings buddynext-moderation/suspend-user
buddynext-feed/pin-post buddynext-spaces/delete
buddynext-feed/schedule-post buddynext-comments/create
(space-scoped, per-space methods) buddynext-moderate-space
buddynext-manage-space
To 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
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)
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
- 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.