Add A Profile Tab
Add a custom tab to a member’s profile page using the jetonomy_profile_tabs filter - no template override needed. The tab appears next to Posts, Replies, and Votes; clicking it loads a separate route that you register alongside the tab.
How Profile Tabs Work
Section titled “How Profile Tabs Work”When Jetonomy renders a profile page it builds an ordered map of tabs, then passes it through jetonomy_profile_tabs before rendering the tab bar. Each entry in the map is a slug keyed to a label and URL. The posts tab is active when no sub-tab is in the URL; all other tabs are matched by slug against the current URL.
Rendering rule: every non-empty entry with a label and url key is rendered. There is no minimum count - even a single tab will render. To remove a built-in tab, unset its slug from the map before returning it.
Content rule: a custom tab is just a link. Clicking it navigates to the URL you specify. Content for that URL must come from a separate route and template registered via jetonomy_template_map (see Template Overrides).
The jetonomy_profile_tabs Filter
Section titled “The jetonomy_profile_tabs Filter”apply_filters( 'jetonomy_profile_tabs', $tabs, $user, $is_own )Parameters
| Parameter | Type | Description |
|---|---|---|
$tabs |
array |
Ordered map: slug => ['label' => string, 'url' => string]. Built-ins: posts, replies, votes, bookmarks (own only), drafts (own only). |
$user |
WP_User |
The profile owner. |
$is_own |
bool |
true when the viewing user is the profile owner. |
Return: array - the modified tab map.
Source: templates/views/user-profile.php
Built-in slugs and active-state rule:
The posts tab is active when the URL is the bare profile URL (no sub-tab segment). All other tabs are active when their slug appears as the jetonomy_tab query var. Setting a tab’s URL to your own route means Jetonomy’s active-tab logic will not apply to it - your template is responsible for highlighting the tab.
Step 1: Register a Template for the Tab Content
Section titled “Step 1: Register a Template for the Tab Content”Register a new route via jetonomy_template_map and point it to a PHP template in your plugin or theme. The template receives the user login from get_query_var( 'jetonomy_slug' ).
add_filter( 'jetonomy_template_map', function( array $map ): array { // Absolute path bypasses the theme-override check - correct for plugin templates. $map['profile-portfolio'] = MY_PLUGIN_DIR . 'templates/profile-portfolio.php'; return $map;} );A minimal template for the route:
<?php$login = get_query_var( 'jetonomy_slug' );$user = get_user_by( 'login', $login );
if ( ! $user ) { wp_redirect( home_url( '/' ) ); exit;}?><div class="jt-app"> <?php \Jetonomy\Template_Loader::partial( 'header' ); ?> <div class="jt-container"> <h2><?php echo esc_html( $user->display_name ); ?> - Portfolio</h2> <?php // Your custom content here. ?> </div></div>Step 2: Register the Rewrite Rule
Section titled “Step 2: Register the Rewrite Rule”Teach Jetonomy’s router about the new URL pattern. The slug captured in $matches[1] is passed as jetonomy_slug so the template can look up the user.
add_action( 'init', function() { $settings = get_option( 'jetonomy_settings', [] ); $base = $settings['base_slug'] ?? 'community';
add_rewrite_rule( '^' . preg_quote( $base, '^' ) . '/u/([^/]+)/portfolio/?$', 'index.php?jetonomy_route=profile-portfolio&jetonomy_slug=$matches[1]', 'top' );} );After adding this code, flush permalinks once: Settings → Permalinks → Save Changes, or via WP-CLI:
wp --path="/path/to/wordpress" rewrite flushStep 3: Add the Tab
Section titled “Step 3: Add the Tab”Hook into jetonomy_profile_tabs and append your tab. Build the URL from the $user parameter so you never need to look up the user again.
add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array { $settings = get_option( 'jetonomy_settings', [] ); $base = $settings['base_slug'] ?? 'community';
$tabs['portfolio'] = [ 'label' => __( 'Portfolio', 'my-plugin' ), 'url' => home_url( "/{$base}/u/{$user->user_login}/portfolio/" ), ];
return $tabs;}, 10, 3 );Complete Example
Section titled “Complete Example”The following block can be placed in your theme’s functions.php or in a site-specific mu-plugin. Replace MY_PLUGIN_DIR with the correct path if you are not working inside a plugin.
/** * Portfolio tab on member profiles. * * 1. Registers a /community/u/{login}/portfolio/ URL. * 2. Loads a custom template for that URL. * 3. Adds a "Portfolio" tab to the profile tab bar. */
// Step 1 - template.add_filter( 'jetonomy_template_map', function( array $map ): array { // Point to a template file in your plugin or theme. $map['profile-portfolio'] = get_stylesheet_directory() . '/jetonomy/views/profile-portfolio.php'; return $map;} );
// Step 2 - rewrite rule.add_action( 'init', function() { $settings = get_option( 'jetonomy_settings', [] ); $base = $settings['base_slug'] ?? 'community';
add_rewrite_rule( '^' . preg_quote( $base, '^' ) . '/u/([^/]+)/portfolio/?$', 'index.php?jetonomy_route=profile-portfolio&jetonomy_slug=$matches[1]', 'top' );} );
// Step 3 - tab.add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array { $settings = get_option( 'jetonomy_settings', [] ); $base = $settings['base_slug'] ?? 'community';
$tabs['portfolio'] = [ 'label' => __( 'Portfolio', 'my-plugin' ), 'url' => home_url( "/{$base}/u/{$user->user_login}/portfolio/" ), ];
return $tabs;}, 10, 3 );Removing or Reordering Built-in Tabs
Section titled “Removing or Reordering Built-in Tabs”Remove a tab: unset it from the map before returning.
add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array { // Remove the Votes tab from all profiles. unset( $tabs['votes'] );
// Remove Bookmarks from other people's profiles (it is already hidden // by the template for non-owners, but removing it here prevents the // URL from loading via direct navigation). if ( ! $is_own ) { unset( $tabs['bookmarks'] ); }
return $tabs;}, 10, 3 );Reorder tabs: PHP arrays preserve insertion order, so rebuild the map in the sequence you want.
add_filter( 'jetonomy_profile_tabs', function( array $tabs, WP_User $user, bool $is_own ): array { // Move Replies before Posts. $replies = $tabs['replies'] ?? null; if ( $replies ) { unset( $tabs['replies'] ); // Re-insert at the front using array union. $tabs = [ 'replies' => $replies ] + $tabs; } return $tabs;}, 10, 3 );Relabel a tab:
add_filter( 'jetonomy_profile_tabs', function( array $tabs ): array { if ( isset( $tabs['posts'] ) ) { $tabs['posts']['label'] = __( 'Topics', 'my-plugin' ); } return $tabs;}, 10, 3 );- Flush permalinks after changing any rewrite rule. In a plugin, call
flush_rewrite_rules()on activation and deactivation hooks - never on every request. - The
jetonomy_slugandjetonomy_routequery vars are already registered by Jetonomy’s router; you do not need to add them toquery_vars. - If your tab must only appear for logged-in users, check
is_user_logged_in()inside the filter before appending the tab. - See Template Overrides for calling partials (
header,breadcrumb) correctly inside your custom template so theme overrides are respected.

