GeneratorsWooCommerceMy Account Endpoint

My Account Endpoint Generator

A new tab in the customer's My Account area — the endpoint, the menu entry, the content, and the once-only rewrite-rule flush wired to activation so the tab doesn't 404 on first load.

loyalty-points-account-endpoint.php
Saved just now
The tab
/my-account/loyalty-points/
Content
<?php
// Generated with WP CodeKit — powered by GrowQuest (https://growquest.io).
/**
 * Plugin Name:       Loyalty Points account tab
 * Description:       Adds the loyalty-points endpoint to My Account.
 * Version:           1.0.0
 * Requires PHP:      7.4
 * Requires Plugins:  woocommerce
 * Text Domain:       acme
 */

defined( 'ABSPATH' ) || exit;

/**
 * Register the /my-account/loyalty-points endpoint.
 */
function acme_add_endpoint() {
	add_rewrite_endpoint( 'loyalty-points', EP_ROOT | EP_PAGES );
}
add_action( 'init', 'acme_add_endpoint' );

/**
 * Tell WooCommerce's own query-var map about it.
 *
 * @param array $vars Existing query vars.
 * @return array
 */
function acme_query_vars( $vars ) {
	$vars['loyalty-points'] = 'loyalty-points';

	return $vars;
}
add_filter( 'woocommerce_get_query_vars', 'acme_query_vars' );

/**
 * Add it to the My Account menu.
 *
 * @param array $items Existing items, keyed by endpoint slug, in display order.
 * @return array
 */
function acme_menu_item( $items ) {
	$new_items = array();

	foreach ( $items as $key => $label ) {
		$new_items[ $key ] = $label;

		if ( 'orders' === $key ) {
			$new_items['loyalty-points'] = __( 'Loyalty Points', 'acme' );
		}
	}

	return $new_items;
}
add_filter( 'woocommerce_account_menu_items', 'acme_menu_item' );

/**
 * Render the endpoint content.
 */
function acme_endpoint_content() {
	echo wp_kses_post( wpautop( __( 'You have earned 0 points so far. Points are added automatically when an order is marked complete.', 'acme' ) ) );
}
add_action( 'woocommerce_account_loyalty-points_endpoint', 'acme_endpoint_content' );

/**
 * Flag a rewrite-rule flush for the next full page load — endpoints
 * registered on init are not live until the rules are rebuilt once.
 */
function acme_flag_flush() {
	update_option( 'acme_flush_rules', 1 );
}
register_activation_hook( __FILE__, 'acme_flag_flush' );

/**
 * Flush once, after the endpoint above has actually been registered.
 */
function acme_maybe_flush() {
	if ( ! get_option( 'acme_flush_rules' ) ) {
		return;
	}

	delete_option( 'acme_flush_rules' );
	flush_rewrite_rules();
}
add_action( 'wp_loaded', 'acme_maybe_flush' );

register_deactivation_hook( __FILE__, 'flush_rewrite_rules' );

About this generator

My Account Endpoint Generator Online

Add a WooCommerce My Account endpoint — a Loyalty Points tab, a Downloads-style archive, a support page — with all four pieces wired together: add_rewrite_endpoint(), the woocommerce_get_query_vars entry, the woocommerce_account_menu_items insertion and the woocommerce_account_{slug}_endpoint content callback. The rewrite-rule flush is handled on activation, not on every page load.

You pick which existing item the new tab sits after, and the generator writes the rebuild loop that puts it exactly there while preserving the order of everything else. Free to use, no account, nothing uploaded.

Reviewed Output tested on WordPress 6.8 · PHP 8.3

Why the My Account Endpoint Generator beats a four-snippet copy-paste

Every tutorial on this splits the job across four snippets from four different pages, and the one people always miss is the flush. An endpoint registered on init does not exist in the rewrite rules until they are rebuilt once, so the tab appears in the sidebar and then 404s when clicked — which reads like a broken URL rather than a missing flush. This generator writes all four pieces, plus the activation-time flush, in one file.

The flush is wired to activation, not to init

A register_activation_hook() callback sets a flag option; a wp_loaded callback flushes once and deletes the flag. That ordering matters — flushing during activation would run before your init registration, so the rules would rebuild without the endpoint in them.

Deactivation cleans up after itself

register_deactivation_hook( __FILE__, 'flush_rewrite_rules' ) is emitted too, so the endpoint's rules are dropped when the plugin is switched off instead of lingering in the rewrite array.

The query var WooCommerce actually reads

Registering the rewrite endpoint alone is not enough on a My Account page. The generator also adds the slug to woocommerce_get_query_vars, which is the map WooCommerce consults when deciding which endpoint template to render.

Exact placement in the account menu

Choose the item your tab follows — Dashboard, Orders, Downloads, Addresses, Payment methods, Account details — or place it immediately before Log out. The generator writes the rebuild loop that inserts it there rather than appending and hoping.

Core endpoint slugs are refused

dashboard, orders, downloads, edit-address, payment-methods, edit-account, customer-logout and lost-password are all rejected with a rename fix, because re-registering one overwrites core’s own page.

Static text or a real callback

Choose static content and you get a translated, wp_kses_post()-escaped paragraph. Choose a callback and you get a stub with get_current_user_id() already in hand, which is the value almost every account page needs first.

How does the My Account Endpoint Generator work?

Four steps. Name the tab, place it, decide what it renders, then export and flush.

  1. 01

    Name the tab

    Set the menu label shoppers see in the sidebar and the slug that becomes the URL segment after /my-account/. Set the function prefix and text domain alongside it.

  2. 02

    Place it in the menu

    Pick which existing item the tab should follow, or place it just before Log out. The generated filter rebuilds the array in order rather than appending blindly.

  3. 03

    Choose the content

    Either write static text, which is emitted as a translated and escaped paragraph, or switch to a callback stub if the page needs to query anything for the logged-in customer.

  4. 04

    Export, then flush once

    Copy the snippet or download the plugin. If you pasted it into functions.php rather than activating a plugin, visit Settings → Permalinks and save once — that rebuilds the rewrite rules the endpoint needs.

Worked example — a Loyalty Points tab after Orders

The registration, the query var and the menu insertion. The content callback and the flush helpers are omitted here for length.

function acme_add_endpoint() {
	add_rewrite_endpoint( 'loyalty-points', EP_ROOT | EP_PAGES );
}
add_action( 'init', 'acme_add_endpoint' );

function acme_query_vars( $vars ) {
	$vars['loyalty-points'] = 'loyalty-points';

	return $vars;
}
add_filter( 'woocommerce_get_query_vars', 'acme_query_vars' );

function acme_menu_item( $items ) {
	$new_items = array();

	foreach ( $items as $key => $label ) {
		$new_items[ $key ] = $label;

		if ( 'orders' === $key ) {
			$new_items['loyalty-points'] = __( 'Loyalty Points', 'acme' );
		}
	}

	return $new_items;
}
add_filter( 'woocommerce_account_menu_items', 'acme_menu_item' );

The content hook takes the slug verbatim, so this endpoint renders from woocommerce_account_loyalty-points_endpoint — hyphens and all. That is WooCommerce’s own naming, not a typo, and the generator emits it to match your slug exactly.

WooCommerce My Account endpoints — frequently asked questions

The questions that come up when adding a tab to the customer account area.

Why does my new My Account tab return a 404?

The rewrite rules have not been rebuilt since the endpoint was registered. Go to Settings → Permalinks and click Save Changes once — you do not need to alter anything. If the code lives in a plugin, the generated activation flag handles this automatically on activation; if it lives in functions.php, there is no activation event, so the manual save is the fix.

What do EP_ROOT and EP_PAGES mean in add_rewrite_endpoint()?

They are the endpoint mask — the set of URL types the endpoint is attached to. EP_PAGES attaches it to pages, which is what the My Account page is; EP_ROOT attaches it to the site root. Combining them with the bitwise OR, as EP_ROOT | EP_PAGES, covers both and is what WooCommerce uses for its own account endpoints.

Why is my tab visible in the menu but blank when opened?

The content hook name did not match. WooCommerce renders woocommerce_account_{slug}_endpoint, using the slug verbatim, so a slug of loyalty-points needs woocommerce_account_loyalty-points_endpoint. It is also worth checking that the slug was added to woocommerce_get_query_vars, without which WooCommerce does not recognise the endpoint on the account page at all.

How do I control where the tab appears in the menu?

The woocommerce_account_menu_items filter receives an ordered associative array. Appending your item puts it after Log out, which is almost never what you want. Rebuild the array in a foreach loop and insert your key after the item it should follow — that is what this generator writes, based on the position you choose.

Should I flush rewrite rules on every page load?

No. flush_rewrite_rules() regenerates and re-saves the entire rewrite array, which is measurable work on a busy store and is entirely wasted after the first run. The correct pattern is to flush once, on activation — or, as the generated code does, set a flag on activation and flush on the next wp_loaded, after the endpoint itself has been registered on init.

Can I restrict a My Account endpoint to certain customers?

Yes, inside the content callback. Everything under My Account already requires a logged-in user, so the useful checks are get_current_user_id() for who is looking, current_user_can() for a capability, or wc_get_customer_order_count() for a purchase-history condition. The menu item itself can be hidden by returning early from the woocommerce_account_menu_items filter for users who should not see the tab.

Where do I paste the code from the My Account Endpoint generator?

You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.

Why isn't my new my account endpoint showing up, or why do I get a 404?

Almost always stale rewrite rules. WordPress caches the URL routing table, so anything that adds URLs is invisible until that cache is rebuilt. Go to Settings → Permalinks and press Save Changes — you do not need to alter anything, just saving the page flushes the rules. Never call flush_rewrite_rules() on every page load; it is an expensive write and belongs in an activation hook.

Can I use the generated code in client work or a commercial plugin?

Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.

The other WooCommerce generators in the library, plus the WordPress tools most often used alongside this one.