Skip to content

Hooks & Filters Reference

Action hooks and filters are spread across four source files: includes/bpsp-functions.php, includes/bpsp-gamification-hooks.php, includes/class-bpsp-database.php, and public/class-buddypress-sticky-post-public.php.

2.3.8 removal note: bpsp_before_handle_sticky_posts was fired by the AJAX pin handler that was removed in 2.3.8. Do not use it — it will never fire. The REST API (POST /buddypress/v1/activity/{id}/sticky) replaced the AJAX handler; use bpsp_after_pin_post for post-pin logic.


Roles allowed to pin posts globally. Default: array( 'administrator' ).

This is a legacy fallback filter. For new integrations, configure allowed roles in Permissions → Pin to own profile in the admin panel. The filter runs last in bpsp_user_can_pin() for back-compat with older code.

add_filter( 'bpsp_allow_user_roles', function ( $roles ) {
$roles[] = 'editor';
return $roles;
} );

Fires after the plugin reads the list of pinned activity IDs and before it passes them to the activity query. $component is 'activity' or 'groups'; $group_id is set when called in a group context.

Parameters: ( array|string $ids, string $component, int $group_id )

add_filter( 'bpsp_get_sticky_posts', function ( $ids, $component, $group_id ) {
// Remove any activities older than 60 days from the pinned list.
return array_filter( (array) $ids, function ( $id ) {
$activity = bp_activity_get_specific( array( 'activity_ids' => $id ) );
if ( empty( $activity['activities'] ) ) {
return false;
}
return ( time() - strtotime( $activity['activities'][0]->date_recorded ) ) < 60 * DAY_IN_SECONDS;
} );
}, 10, 3 );

Override whether a specific activity is considered pinned. Return null to fall through to the default logic (database or options check). Return true or false to short-circuit.

Parameters: ( bool|null $is_pinned, int $activity_id )

add_filter( 'bpsp_is_post_pinned', function ( $is_pinned, $activity_id ) {
// Force a specific activity to always appear as pinned.
if ( 42 === $activity_id ) {
return true;
}
return $is_pinned;
}, 10, 2 );

Fires inside bpsp_add_sticky_post() before the pin is written. Return false to block the pin. Return true (default) to continue. Returning false skips both the options/user-meta write and the DB-table write — the database wrapper filter (bpsp-functions-wrapper.php) also calls it.

Parameters: ( bool $continue, string $component, int $post_id, string $user_role, int $group_id )

// Block pinning activities older than 30 days.
add_filter(
'bpsp_before_add_sticky_post',
function ( $continue, $component, $post_id, $user_role, $group_id ) {
$activity = new BP_Activity_Activity( $post_id );
if ( ! empty( $activity->date_recorded )
&& ( time() - strtotime( $activity->date_recorded ) ) > 30 * DAY_IN_SECONDS ) {
return false;
}
return $continue;
},
10,
5
);

Same shape as bpsp_before_add_sticky_post but for unpins. Return false to block the unpin (useful for “lock pins for 24 hours after creation”).

Parameters: ( bool $continue, string $component, int $post_id, string $user_role, int $group_id )


alter_bpsp_get_pin_post_label / alter_bpsp_get_unpin_post_label

Section titled “alter_bpsp_get_pin_post_label / alter_bpsp_get_unpin_post_label”

Change the pin and unpin button labels. The same text can be set without code from Appearance → Button Labels in the admin panel; use the filter only when you need conditional logic.

Parameters: ( string $label )

add_filter( 'alter_bpsp_get_pin_post_label', fn() => __( 'Feature this post', 'mytd' ) );
add_filter( 'alter_bpsp_get_unpin_post_label', fn() => __( 'Unfeature', 'mytd' ) );

The array of action buttons rendered inside the activity entry for the current activity. By default the plugin appends one button (pin or unpin, depending on pin state). Add custom buttons or remove the default one here.

Parameters: ( array $tools, int $activity_id )

Each item in $tools is an array with keys: icon (FontAwesome class string), title (string), action (string), class (array of CSS class strings).


The HTML string used as the “Pinned” ribbon/badge on a pinned activity. Returned value is output via wp_kses_post().

Parameters: ( string $html )

add_filter( 'bpsp_activity_pinned_tag', function () {
return '<span class="custom-pinned-badge">Featured</span>';
} );

Wraps every get_option() call made through bpsp_options(). Filters any stored plugin option value before the plugin uses it. $option_id is the raw option name (e.g., 'bpsp_activity_sticky_posts').

Parameters: ( mixed $value, string $option_id )


The full sidebar tab registry for the plugin’s admin page. Tab entries from add-on modules (e.g., Pro) inject themselves here. The shape of each entry mirrors BPSP_Admin::get_tabs().

Parameters: ( array $tabs )

add_filter( 'bpsp_admin_tabs', function ( $tabs ) {
$tabs['my-tab'] = array(
'label' => 'My Tab',
'icon' => 'dashicons-admin-generic',
'group' => 'settings',
);
return $tabs;
} );

// Fired by bpsp_add_sticky_post() after a successful pin write.
// Also fired by the DB-table storage path in bpsp-functions-db.php.
do_action( 'bpsp_after_pin_post', $post_id, $component, $type, $group_id );
// Fired by bpsp_delete_sticky_post() after a successful unpin write.
do_action( 'bpsp_after_unpin_post', $post_id, $component, $type, $group_id );
// Fired by bpsp_log_sticky_action() after a log row is written.
do_action( 'bpsp_sticky_action_logged', $log_entry );
// Fired by the hourly expiration cron after it auto-unpins expired posts.
do_action( 'bpsp_posts_expired', $expired_posts );
// Fired inside bpsp_posts_expired for each individual expired post.
do_action( 'bpsp_sticky_post_expired', $post_id, $pinned_by, $component, $group_id );

Parameter types for pin/unpin lifecycle hooks:

  • $post_idint activity ID
  • $componentstring 'activity' or 'groups'
  • $typestring 'user' or 'administrator'
  • $group_idint group ID, 0 when not in a group

These fire from bpsp_after_pin_post via the listener in includes/bpsp-gamification-hooks.php. Use them for gamification integrations (GamiPress, myCRED, AutomatorWP, BadgeOS).

// Any pin, any scope.
do_action( 'bpsp_user_pinned_activity', $user_id, $post_id, $component, $type, $group_id );
// Sitewide activity stream pins.
do_action( 'bpsp_user_pinned_activity_stream', $user_id, $post_id, $type );
do_action( 'bpsp_user_pinned_own_activity', $user_id, $post_id ); // personal pin
do_action( 'bpsp_admin_pinned_activity', $user_id, $post_id ); // sitewide admin pin
// Group pins.
do_action( 'bpsp_user_pinned_group_activity', $user_id, $post_id, $group_id, $type );
do_action( 'bpsp_group_admin_pinned_activity', $user_id, $post_id, $group_id ); // pinner is group admin
do_action( 'bpsp_group_mod_pinned_activity', $user_id, $post_id, $group_id ); // pinner is group mod
do_action( 'bpsp_group_member_pinned_activity',$user_id, $post_id, $group_id ); // pinner is regular member

// First ever pin for this user.
do_action( 'bpsp_user_first_pin', $user_id, $post_id, $component );
// Fires on every pin that crosses a milestone (5, 10, 25, 50, 100).
do_action( 'bpsp_user_pin_milestone', $user_id, $pin_count, $component );
// Fires only for the specific milestone crossed.
do_action( 'bpsp_user_pin_milestone_5', $user_id, $component );
do_action( 'bpsp_user_pin_milestone_10', $user_id, $component );
do_action( 'bpsp_user_pin_milestone_25', $user_id, $component );
do_action( 'bpsp_user_pin_milestone_50', $user_id, $component );
do_action( 'bpsp_user_pin_milestone_100', $user_id, $component );
// Fires for every pin; $activity_obj->type is the BP activity type
// (e.g., 'activity_update', 'bp_docs_edited').
do_action( "bpsp_user_pinned_{$activity_obj->type}", $user_id, $post_id, $activity_obj );

Each pin action has a matching unpin hook with the same argument shape:

do_action( 'bpsp_user_unpinned_activity', $user_id, $post_id, $component, $type, $group_id );
do_action( 'bpsp_user_unpinned_activity_stream', $user_id, $post_id, $type );
do_action( 'bpsp_user_unpinned_own_activity', $user_id, $post_id );
do_action( 'bpsp_admin_unpinned_activity', $user_id, $post_id );
do_action( 'bpsp_user_unpinned_group_activity', $user_id, $post_id, $group_id, $type );

These are the public API for add-ons or other plugins to call directly.

// Check if a specific activity is pinned.
$is_pinned = bpsp_is_post_pinned( $activity_id );
// Get the array of pinned activity IDs for a context.
$ids = bpsp_get_sticky_posts( 'activity' ); // sitewide / user profile
$ids = bpsp_get_sticky_posts( 'groups', $group_id ); // specific group
// Pin or unpin an activity.
// $user_role: 'administrator' for sitewide admin pins, any other string for personal pins.
$ok = bpsp_add_sticky_post( $component, $activity_id, $user_role, $group_id );
$ok = bpsp_delete_sticky_post( $component, $activity_id, $user_role, $group_id );
// Authorization helpers.
// bpsp_user_can_pin() — preferred, context-free, works in REST and AJAX.
// bpsp_user_can_posts() — deprecated 2.3.9; relied on BP front-end globals.
bpsp_user_can_pin( $activity ); // BP_Activity_Activity or ID
bpsp_user_can_unpin( $activity ); // more permissive — original pinner always passes
bpsp_user_can_pin_in_group( $user_id, $group_id ); // honours group_pin_scope setting
// Write an activity log entry (respects 'enable_activity_log' setting).
bpsp_log_sticky_action( 'pin', $activity_id, $user_id, $component, $group_id );
// Per-user pin count helpers.
$count = bpsp_get_user_pin_count( $user_id, $component, $type, $group_id );
$lifetime = bpsp_get_user_lifetime_pins( $user_id );

  • REST API — fires the same hooks on REST writes.
  • WP-CLI — fires the same hooks on CLI writes.
  • Gamification — wiring myCRED / GamiPress / AutomatorWP.