I just similarly struggled with this, and I almost gave up as well. Join me on my journey of discovery! (Or just skip to The Solution)
The Problem:
Usually, when you create a new endpoint, you can fix it not working by simply going to the Permalinks page under Settings > Permalinks and just saving the permalink settings. This, somewhere in the WP code, must call flush_rewrite_rules()
. This feels gross to me, because if your plugin will be used by more than one person (you) or used by non-PHP devs in general, they won't know to go save the Permalink rules after activating your plugin. Your plugin should be ready to go after activation without some strange arcane second step necessary.
WordPress seems to need you to add_rewrite_rule()
on init
every page load, HOWEVER, you also need to then flush_rewrite_rules()
after you initially add the rewrite rule... BUT, as @Rarst says, you should NOT flush_rewrite_rules()
on every page init
.
The Solution:
So, I came up with this technique, and tested it. This technique does not require using set_transient, although you could easily use it as others have.
First, you want your plugin to have an activator. Whether it's a Class or just a function, you should have a function that is called upon activation of your plugin:
(replace generic variables, classes and function names accordingly)
register_activation_hook( __FILE__, 'activate' );
function activate() {
//this function will be called at activation, and then on every init as well:
Plugin_Class_Public::add_endpoint();
//run this at activation ONLY, AFTER setting the endpoint - needed for your endpoint to actually create a query_vars entry:
flush_rewrite_rules();
}
Here is the function to add the endpoint:
public static function add_endpoint() {
//be sure to replace your_endpoint_slug with your own endpoint
add_rewrite_endpoint('your_endpoint_slug', EP_PERMALINK | EP_PAGES);
}
Then, register your endpoint on init by calling the function above:
add_action('init', array('Plugin_Class_Public', 'add_endpoint'));
And be sure to set a hook for template_include:
add_action('template_include', array('Plugin_Class_Public', 'endpoint_output'));
Which will call a function that looks like this:
public static function endpoint_output($template) {
if(!is_home()){ //we probably don't want this to run on our homepage
global $wp;
global $wp_query;
$output = "";
/*note: your_endpoint_slug needs to be replaced here as you did above.
Its value will be set to whatever comes after it in the url,
eg: www.website.com/your_endpoint_slug/some_more/url -
$wp_query->query_vars['your_endpoint_slug'] = "some_more/url"
*/
if (isset($wp_query->query_vars['your_endpoint_slug'])) {
$output = Plugin_Class_Public::generate_output(wp_query->query_vars['your_endpoint_slug']);
echo $output;
exit;
}
//print output
}
//nothing matched our endpoint, or it's the homepage:
return $template;
}
This function builds your output:
public static function generate_output($url_after_endpoint_in_url) {
$output = "";
//do something to generate your desired output and return it
return $output;
}
You can also unregister your endpoint on deactivation by calling flush_rewrite_rules(), to be a bit more tidy, although this doesn't seem to work properly, as the init() function above runs when you deactivate as well- so it's still saving that endpoint. Open to suggestions on how to fix this. Though, once the plugin is deactivated, it won't run the function from template_include anymore, so no real harm done, IMO. You can always save your permalinks to remove this extraneous query_var later.
register_deactivation_hook( __FILE__, 'deactivate' );
function deactivate() {
flush_rewrite_rules();
}
(Note on register_activation/deactivation_hook functions: The first parameter in each of these functions refers to your main plugin file, which is the file in which you have placed the plugin header comment. Usually these two functions will be triggered from within the main plugin file; however, if the functions are placed in any other file, you must update the first parameter to correctly point to the main plugin file.)