Topics
The solution comes in three parts. The first step is updating the rewrite rules to map a url to an internal value. Next, we map this internal value to a template. Finally, we map this internal template to the theme template.
Most WordPress installations take advantage of URL rewriting on the part of the web server. The goal is to rewrite all blog page requests to index.php. From there, WordPress examines the original URL to determine what content to send. The WordPress rewrite rules allow us to modify this URL. In our case, we want to map a custom page to query variable that we are going to pass into index.php, sending it down the $wp_query.
add_filter('generate_rewrite_rules', 'custom_page_generate_rewrite_rules');
function custom_page_generate_rewrite_rules($wp_rewrite) {
$custom_page_rules = array(
'survey' => 'index.php?custom_page=survey',
);
$wp_rewrite->rules = $custom_page_rules + $wp_rewrite->rules;
}Now that we are able to map a url to the $wp_query, we need to resolve that into a template. For this, we utilize the template redirect of WordPress. This allows our plugin to resolve the request into a template call and exit before wordpress attempts to resolve the request.
add_action('template_redirect', 'custom_page_template_redirect');
fnction custom_page_template_redirect() {
global $wp_query;
$custom_page = $wp_query->query_vars['custom_page'];
if ($custom_page == 'survey') {
include(ABSPATH.'wp-content/plugins/custom_page/survey_page.php');
exit;
}
}For the final piece, we will take a look at just one of the custom pages, survey_page.php. The template redirect hands off the request processing to this page and exits. It is up to this page to interact with the existing theme and display our custom page. The only catch is that the theme must utilize the the_content() template function of WordPress. Otherwise, the proper hooks will not be called and our page will not display correctly.
Fetching the proper file to use from the existing theme is very simple. To do so, we resolve the page template first, single post template second, and finally default to the index.
if (file_exists(TEMPLATEPATH.'/page.php')) {
include(get_page_template());
}
elseif (file_exists(TEMPLATEPATH.'/single.php')) {
include(get_single_template());
}
else {
include(TEMPLATEPATH.'/index.php');
}Updating the content involves inserting filters onto the_title and the_content. However, we only want these filters to be called during the_loop. Surpressing the filters from get_header, get_sidebar, and get_footer will prevent the custom filters from overrideing page template content.
add_action('get_header', 'custom_page_remove_filters');
add_action('get_sidebar', 'custom_page_remove_filters');
add_action('get_footer', 'custom_page_remove_filters');
function custom_page_remove_filters() {
remove_filter('the_title', 'custom_page_title');
remove_filter('the_content', 'custom_page_content');
}
add_action('loop_start', 'custom_page_add_filters');
function custom_page_add_filters() {
add_filter('the_title', 'custom_page_title');
add_filter('the_content', 'custom_page_content');
}I went ahead and put all of this together into a plugin on this site. You can see it in action with the following pages:
I have also attached the source code for the plugin as a full example. You can download it here.

