I have a special situation and need some help achieving the final step.


When I access mydomain.com/category/postname, I get the post as expected.

When I access mydomain.com/category/postname?ajax=1, I get the post html (baked into and including its corresponding page template markup, which is extremely important for this use case), but I get it rendered without its header and footer (I did this on purpose, with a $_GET['ajax']==1 conditional inside the header.php and footer.php files to suppress them).

This all works as expected. However, it leaves me with a new small problem... Browsers won't properly cache content from URLs that end with a GET parameter string.

Therefore, I would like to create a rewrite rule that achieves the following...

mydomain.com/ajax/category/postname becomes mydomain.com/category/postname?ajax=1

Here's from my functions.php:

function dynaload_rewrite() {
  add_rewrite_rule('ajax/(.*)/?', '$matches[1]?ajax=1', 'bottom');
add_action('init', 'dynaload_rewrite');

This results in .htaccess file as follows:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteRule ^ajax/(.*)/? /$matches[1]?ajax=1 [QSA,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress

Expected Result: When I access /ajax/category/postname I want the content from /category/postname to render, minus the header and footer, as they do if I access normally with ?ajax=1

Actual Result: I am receiving the content from my WordPress 404 page, however the header and footer are omitted (so I know that the parameter ajax=1 is being passed correctly, it's just loading the wrong page).

Successful help is appreciated and will be deemed to be impressive. Thank you.


Your external rule doesn't work because you're using a format that only works for internal rules- $matches[1] is essentially nonsense in the context of an .htaccess file. Using the $matches array will only work when rules are parsed by PHP, external rules need to use the standard $1 instead of $matches[1]. Any rule that doesn't point to index.php is interpreted as an external rule and gets added to .htaccess instead of the internal rules array.

That said, I suggest changing things a bit and adding a rewrite endpoint instead of a rule. Your URLs would be:


You could then check if get_query_var('ajax') is set in your templates.

| improve this answer | |
  • Thank you for your reply. It doesn't work with $1 either, though... However I will try your alternate solution and report back – Christin Milloy Dec 16 '15 at 20:28
  • Your solution only partially works. When I add the following: add_rewrite_endpoint( 'ajax', EP_PERMALINK | EP_PAGES | EP_ALL ); I am getting 301's for all posts, and some pages. Instead of what I want which is the content (although I get the content for category archives and certain pages for some reason). – Christin Milloy Dec 16 '15 at 20:57
  • have you flushed rewrite rules? – Milo Dec 16 '15 at 21:00
  • Yes. I should also mention that I am using "Custom Structure" which is /learning-library/%postname%/, and that I had to type category into the "Category Base" in order to get category archive pages to work at all (even though category is supposed to be the default). Setting that, then re-flushing, makes it so that when I request /ajax/1/ I am getting correct behaviour on category archive pages, correct behaviour on basic pages (including those in subfolders), but I am getting 301's on learning-library/postname every time. In other words everything works now except blog posts. – Christin Milloy Dec 16 '15 at 21:05
  • I'm still getting 301's for anything under /learning-library/slugname/ajax/1 that's pointing to the vanilla /learning-library/slugname. Does anyone know where those 301's are coming from and why my custom endpoint isn't picking up on blog posts? – Christin Milloy Dec 21 '15 at 17:04

This works to capture the category and post_name then changes the template rendered.

if (!class_exists('WPTestPluginEndpoint')):

    class WPTestPluginEndpoint
        const ENDPOINT_QUERY_NAME = 'ajax';

        // WordPress hooks

        public function init()
            add_filter('query_vars', array($this, 'add_query_vars'), 0);
            add_action('parse_request', array($this, 'sniff_requests'), 0);
            add_action('init', array($this, 'add_endpoint'), 0);

        // Add public query vars

        public function add_query_vars($vars)
            $vars[] = 'ajax';
            $vars[] = 'cat';
            $vars[] = 'post_name';

            return $vars;

        // Add API Endpoint

        public function add_endpoint()
            add_rewrite_rule('^ajax/([^/]*)/([^/]*)/?', 'index.php?ajax=1&cat=$matches[1]&post_name=$matches[2]', 'top');

            flush_rewrite_rules(false); //// <---------- REMOVE THIS WHEN DONE

        // Sniff Requests

        public function sniff_requests($wp_query)
            global $wp;

            if (isset($wp->query_vars['ajax'])) {

                $_REQUEST ['ajax'] = $wp->query_vars['ajax'];
                $_REQUEST ['cat'] = $wp->query_vars['cat'];
                $_REQUEST ['post_name'] = $wp->query_vars['post_name'];

                $this->handle_request(); // handle it

        // Handle Requests

        protected function handle_request()
            // Control the template used

            add_filter('template_include', function ($original_template) {

                $args = array(
                    'cat' => $_REQUEST['cat'],
                    'post_name' => $_REQUEST['post_name'],


                return get_template_directory() . '/single.php';

    $wptept = new WPTestPluginEndpoint();

endif; // WPTestPluginEndpoint

In your template

if ( ! isset( $_REQUEST['ajax'] ) ) get_header(); ?>

<?php if ( ! isset( $_REQUEST['ajax'] ) ) get_footer(); ?>
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.