Everything else in a WordPress install is a post, so the first instinct with a BuddyPress group is a WP_Query against some post type. There is not one. Groups, their metadata and their membership live in three tables of their own, and nothing in the posts, postmeta or term tables knows they exist.
// returns nothing — there is no 'group' post type
$groups = new WP_Query( array( 'post_type' => 'group' ) );
// the tables, for when SQL really is the answer
// wp_bp_groups id, creator_id, name, slug, status, date_created
// wp_bp_groups_groupmeta group_id, meta_key, meta_value
// wp_bp_groups_members group_id, user_id, is_admin, is_mod, is_banned
if ( bp_has_groups( array( 'type' => 'active', 'per_page' => 20 ) ) ) {
while ( bp_groups() ) {
bp_the_group();
echo esc_html( bp_get_group_name() );
}
}
$group = groups_get_group( array( 'group_id' => 17 ) );
groups_update_groupmeta( 17, 'region', 'aegean' );
$region = groups_get_groupmeta( 17, 'region' );
The consequences are mostly things that quietly do nothing. get_post_meta() returns empty and groups_get_groupmeta() is the equivalent; there is no taxonomy support, so categorising groups means a meta key and a join you write yourself; and because no post exists, a group never appears in site search, in an RSS feed, in a sitemap plugin, or in anything that iterates registered post types. The one that causes real damage is the prefix: on multisite the BuddyPress tables are global by default, so they carry the base prefix rather than the per-site one, and a direct query built on $wpdb->prefix reads the wrong table on every site except the first. bp_core_get_table_prefix() is the function that knows. What the separation buys is speed — member and group listings are indexed queries against narrow tables rather than a three-way join through postmeta — and the price is that nothing written for posts works on them.