Skip to content

A theme can replace the plugin’s page templates without editing the plugin. Copy the plugin’s file into your theme at the path shown below, keep the filename, and edit your copy. The override survives plugin updates. Every lookup checks the child theme first, then the parent theme, then falls back to the plugin’s own copy.

Written against version 4.3.0.

Plugin file Theme path Rendered on
templates/edit.php yourtheme/bp-member-blog/edit.php The post composer (new post and edit), on every platform.
templates/posts.php yourtheme/bp-member-blog/posts.php A member’s Published, Drafts or Pending list, on the dashboard and on every profile.
templates/peepso/edit.php yourtheme/buddypress-member-blog/peepso/edit.php The PeepSo wrapper around the composer.
templates/writer-profile.php yourtheme/buddypress-member-blog/writer-profile.php The writer profile at /author/{name}/.
templates/topic-hub.php yourtheme/buddypress-member-blog/topic-hub.php Topic hubs at /category/{slug}/ and /tag/{slug}/.
(none shipped) yourtheme/buddypress-member-blog/emails/{id}.php The body of one email. See Emails.
Pro: templates/series-landing-page.php yourtheme/buddypress-member-blog-pro/series-landing-page.php (Pro) A series landing page.

Other Pro templates (group posts, publishing calendar, scheduled posts, analytics, co-author selector, invitation list) are not theme-overridable; change them through their hooks.

The composer and the post list are loaded by bp_member_blog_load_template() from the bp-member-blog/ directory of your theme. Change that directory name with a filter:

add_filter( 'bp_member_blog_template_dir', function () {
return 'templates/member-blog'; // yourtheme/templates/member-blog/posts.php
} );

Everything else is looked up under buddypress-member-blog/ in your theme, and that name is fixed.

  • One posts.php now renders all three lists (Published, Drafts, Pending) on every platform. The separate draft-posts.php, pending-posts.php and the PeepSo copies of all three are gone. An old override of one of those files is no longer loaded; move your changes into posts.php.
  • PeepSo uses the same composer as everyone else. peepso/edit.php only wraps edit.php in PeepSo’s container, so override bp-member-blog/edit.php to change the form itself.
  • Template parts (parts/post-card.php, parts/empty-state.php) and the bpmb_get_template_part() / bpmb_template_part_path pair no longer exist. Cards are drawn by bpmb_post_cards(); restyle them with CSS tokens (see Styling).

The shipped posts.php is one call:

<?php
defined( 'ABSPATH' ) || exit;
$bpmb_list = bpmb_member_home()->list_context();
echo bpmb_member_home()->post_list( (int) $bpmb_list['user_id'], (string) $bpmb_list['tab'], (string) $bpmb_list['mode'] );

list_context() gives you user_id (the home owner), tab (posts, drafts or pending) and mode. post_list() returns the toolbar, the cards, the pager and the empty state, with the query, co-authored posts and permissions already applied. An override can wrap or reorder that output without rebuilding any of it:

<?php
defined( 'ABSPATH' ) || exit;
$bpmb_list = bpmb_member_home()->list_context();
if ( 'posts' === $bpmb_list['tab'] ) {
echo '<p class="my-list-intro">' . esc_html__( 'Everything this writer has published.', 'my-theme' ) . '</p>';
}
echo bpmb_member_home()->post_list( (int) $bpmb_list['user_id'], (string) $bpmb_list['tab'], (string) $bpmb_list['mode'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- renderer escapes.

Hooks around the list, which usually make an override unnecessary:

Hook Type Arguments
bp_member_blog_before_posts action string $status, int $user_id
bpmb_posts_toolbar action string $post_status (publish, draft or pending)
bp_member_blog_posts_per_page filter int $per_page

To add or remove whole tabs on the member home, use bpmb_member_home_tabs; see Extending.

edit.php is long. Before copying it, check whether a hook does the job; an override has to be merged by hand every time the plugin changes the form.

Every hook below receives int $post_id (0 for a new post):

Hook Where
bp_post_before_title, bp_post_after_title Around the title.
bp_post_before_content, bp_post_after_content Around the editor.
bp_post_before_category, bp_member_blog_before_category, bp_post_after_category Around the categories.
bpmb_composer_panel_collaboration The Collaboration panel of the settings sidebar (Pro adds co-authors here).
bp_post_before_tag, bp_post_after_tag Around the tags.
bp_post_before_featured_image, bp_post_after_featured_image Around the featured image.
bpmb_composer_panel_details The Details panel of the settings sidebar.
bp_post_before_seo, bp_post_after_seo Around the SEO fields.
bp_post_after_publishing_options After the publishing options.
bpmb_composer_panel_publishing The Publishing panel of the settings sidebar.
bp_post_before_submit_button Before the action buttons.

The filter bp_member_blog_post_content (string $content) changes the content loaded into the editor when a post is opened.

Add a field without touching edit.php:

add_action( 'bp_post_after_content', function ( $post_id ) {
$value = $post_id ? get_post_meta( $post_id, '_my_subtitle', true ) : '';
?>
<div class="bp-member-blog-field">
<label for="my-subtitle"><?php esc_html_e( 'Subtitle', 'my-plugin' ); ?></label>
<input type="text" id="my-subtitle" name="my_subtitle" value="<?php echo esc_attr( $value ); ?>" />
</div>
<?php
} );

Saving the value is your code’s job, for example on save_post.

The plugin can take over two core archive URLs. Both are switches on the Features tab of the Member Blog settings (Writer profiles and Topic hubs), and both have a filter for code.

/author/{name}/ becomes the writer profile

Section titled “/author/{name}/ becomes the writer profile”

src/Frontend/AuthorArchive.php swaps in writer-profile.php on template_include (priority 20). With BuddyPress active, the member profile stays canonical: the writer profile prints a <link rel="canonical"> pointing at it. There is no redirect.

/**
* @param bool $enabled Defaults to the Writer profiles switch.
* @param int $user_id The writer being viewed.
*/
add_filter( 'bpmb_takeover_author_archive', function ( $enabled, $user_id ) {
return $enabled && user_can( $user_id, 'edit_posts' );
}, 10, 2 );

Return false and the theme’s author.php renders again. bpmb_writer_url() keeps pointing at the same URL either way.

Hook Type Arguments
bpmb_writer_profile_per_page filter int $per_page (default 12), int $user_id
bpmb_before_writer_profile action int $user_id
bpmb_after_writer_profile action int $user_id

Paging uses the main query, so /author/jane/page/2/ works.

Category and tag archives become topic hubs

Section titled “Category and tag archives become topic hubs”

src/Frontend/TermArchive.php swaps in topic-hub.php for the taxonomies in bpmb_topic_hub_taxonomies (default category and post_tag).

/**
* Keep hubs for categories and hand tag archives back to the theme.
*
* @param bool $enabled Defaults to the Topic hubs switch.
* @param \WP_Term $term The term being viewed.
*/
add_filter( 'bpmb_takeover_term_archive', function ( $enabled, $term ) {
return $enabled && 'category' === $term->taxonomy;
}, 10, 2 );
Hook Type Arguments
bpmb_topic_hub_per_page filter int $per_page (default 12), \WP_Term $term
bpmb_before_topic_hub action \WP_Term $term
bpmb_after_topic_hub action \WP_Term $term

When a listing shortcode has nothing to show, it prints one designed empty state instead of a blank gap:

<div class="bpmb-empty bpmb-shortcode-empty-state">
<span class="bpmb-empty__icon">...</span>
<h3 class="bpmb-empty__title">...</h3>
<p class="bpmb-empty__text">...</p>
<div class="bpmb-empty__actions">...</div>
</div>

Target .bpmb-empty.bpmb-shortcode-empty-state to style shortcode empty states without touching other empty states. When the list is empty because of how the shortcode was set up (for example, no topic given or nothing featured yet), users with manage_options also see a <p class="bpmb-admin-hint"> note under it explaining the fix. Visitors never see that note.

  1. Draw cards through the plugin. Use bpmb_post_cards() or bpmb_member_home()->post_list() rather than your own card markup, so your list matches every other list and keeps the right action buttons.
  2. Ask, do not re-check. To know whether the viewer may edit a post, call bpmb_access()->can_edit( $post_id ).
  3. Link writers with bpmb_writer_url( $user_id ), not get_author_posts_url().
  4. Keep the class names. Button geometry is enforced on .bpmb-follow-btn, .bpmb-clap-btn and .bpmb-bookmark-btn. Restyle with CSS tokens instead.
  5. Do not query in a template. Ask the service that owns the data. For example, Pro’s group listing uses Buddypress_Member_Blog_Pro_Groups::group_posts_query( $group_id ), which applies the member scope, the bpmb_group_posts_query_args filter and cache priming.

The full list of hooks, with the file and line each is fired from, is in the Hook Reference.