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
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
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
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
// my-plugin/templates/profile-portfolio.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
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 flush
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
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
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 );
Notes
- 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.