WooCommerce My Account Tab Generator
Add a tab to the WooCommerce My Account page: the rewrite endpoint, the query var, the menu item in the right position and the content callback.
<?php
/**
* The "Rewards" tab on the My Account page.
*/
/**
* Registers the endpoint. This is what makes /my-account/rewards/ resolve.
*/
function my_store_add_endpoint() {
add_rewrite_endpoint( 'rewards', EP_ROOT | EP_PAGES );
}
add_action( 'init', 'my_store_add_endpoint' );
/**
* Tells WordPress the endpoint is a query var it should keep.
*
* @param array $vars Public query vars.
* @return array
*/
function my_store_query_vars( $vars ) {
$vars[] = 'rewards';
return $vars;
}
add_filter( 'query_vars', 'my_store_query_vars', 0 );
/**
* Adds the menu item.
*
* @param array $items Menu items, keyed by endpoint.
* @return array
*/
function my_store_menu_items( $items ) {
$new = array(
'rewards' => __( 'Rewards', 'my-store' ),
);
// Logout belongs at the end, so lift it off, append, and put it back.
$logout = array();
if ( isset( $items['customer-logout'] ) ) {
$logout['customer-logout'] = $items['customer-logout'];
unset( $items['customer-logout'] );
}
return array_merge( $items, $new, $logout );
}
add_filter( 'woocommerce_account_menu_items', 'my_store_menu_items' );
/**
* The browser title on that tab.
*
* @param string $title Current title.
* @return string
*/
function my_store_endpoint_title( $title ) {
return __( 'Your rewards', 'my-store' );
}
add_filter( 'woocommerce_endpoint_rewards_title', 'my_store_endpoint_title' );
/**
* Prints the tab content.
*
* The hook name carries the endpoint slug, so renaming the slug renames this
* hook too.
*/
function my_store_endpoint_content() {
echo '<p>' . esc_html__( 'Everything you have earned so far.', 'my-store' ) . '</p>';
// Your content here.
}
add_action( 'woocommerce_account_rewards_endpoint', 'my_store_endpoint_content' );
/**
* Registers the endpoint and rewrites the rules once, on activation.
*
* Without this the URL is a 404 until someone opens Settings, Permalinks.
*/
function my_store_activate() {
my_store_add_endpoint();
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'my_store_activate' );
/**
* Clears the rule again so the URL stops resolving.
*/
function my_store_deactivate() {
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'my_store_deactivate' );
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
Add your own tab to WooCommerce My Account. The generator writes all four pieces that have to agree: the rewrite endpoint, the query var, the menu item and the content hook.
How to use
- Pick the endpoint slug first. It becomes the URL, the menu key and half of two hook names, so changing it later means changing all of them.
- Put the tab before Logout. Logout belongs at the end of the list, and appending blindly puts your tab underneath it.
- Choose a template file if a theme should be able to override the content.
wc_get_template()is how WooCommerce does its own tabs. - Leave the activation flush on. A new endpoint is a 404 until the rewrite rules are rebuilt.
- If the tab 404s after you change the slug, open Settings, Permalinks and save. That rebuilds the rules by hand.
Example
The menu item, inserted before Logout rather than after it:
function acme_menu_items( $items ) {
$new = array(
'rewards' => __( 'Rewards', 'acme' ),
);
$logout = array();
if ( isset( $items['customer-logout'] ) ) {
$logout['customer-logout'] = $items['customer-logout'];
unset( $items['customer-logout'] );
}
return array_merge( $items, $new, $logout );
}
add_filter( 'woocommerce_account_menu_items', 'acme_menu_items' );
The array key has to match the endpoint slug exactly. A mismatch gives you a menu item that links nowhere, or a working URL with no link to it.
Pitfalls
flush_rewrite_rules()on every request is expensive. Call it on activation, never oninit.- Four things use the slug: the endpoint, the query var, the menu key and the content hook. Three out of four gives you a tab that half works.
- Appending the item without moving Logout puts your tab after it, which every customer reads as the end of the list.
- The endpoint only exists on the My Account page.
EP_ROOT | EP_PAGESis what makes it work on the page WooCommerce assigned. - The content hook prints inside the account wrapper. Echoing a full page template gives you nested markup.
- Plugins that cache pages will cache the tab. Content that varies per customer needs to be excluded from caching.
- Deactivating without a flush leaves the rewrite rule behind until permalinks are saved again.
- A slug that collides with an existing WooCommerce endpoint, such as
ordersordownloads, silently overrides it.
Compatibility
add_rewrite_endpoint() is WordPress core and has been stable since 3.4. woocommerce_account_menu_items, woocommerce_account_<endpoint>_endpoint and woocommerce_endpoint_<endpoint>_title have been stable since WooCommerce 2.6 and still apply in WooCommerce 8 and 9. My Account has no block equivalent, so these hooks remain the way in. The generated code targets PHP 7.0 and up, and the tool runs entirely in your browser.
Frequently asked questions
Why is my tab a 404?
How do I put the tab in a specific position?
Can a theme override the content?
wc_get_template(). A theme can then place the same path under its own woocommerce folder.How do I restrict the tab to some customers?
$items unchanged for everyone else in the menu filter, and check again in the content callback. The URL is guessable, so both checks matter.