The activity stream is not posts. It has its own table, its own class and its own template loop, so every habit built around WP_Query — pre_get_posts, fields => ids, the post and meta caches, meta_query — applies to none of it, and the arguments are quietly ignored rather than rejected.
// none of these mean anything: activities are not a post type
new WP_Query( array( 'post_type' => 'activity', 'posts_per_page' => 20 ) );
// the API that exists
if ( bp_has_activities( array(
'per_page' => 20,
'page' => 1,
'scope' => 'groups',
'user_id' => $user_id,
'filter' => array( 'action' => 'activity_update' ),
'display_comments' => false,
) ) ) {
while ( bp_activities() ) { bp_the_activity();
echo bp_get_activity_content_body();
}
}
Pagination is per_page and page; max caps the total rather than the page, which is the argument to reach for on a widget that should never show more than ten items. display_comments defaults to threaded, and threading costs an extra query per top-level item plus the assembly on top — turning it off is the single largest saving available on a sidebar stream. Structured filtering is limited to what the filter array accepts: action, object, the two id fields and user id. Anything beyond that means calling BP_Activity_Activity::get() and writing the clause yourself, at which point you are writing SQL against bp_activity and the comment should say so.