How to Create Virtual Pages in WordPress

This guide shows you how to create virtual pages in WordPress that are dynamically generated by your plugin.

Thumbnail of the blog post

As a WordPress plugin developer, you may need to create custom pages “on the fly” that are not stored in the database. This guide shows you how to programmatically create virtual pages in WordPress that are dynamically generated by your plugin.

Why would I need this?

WordPress is a powerful CMS and therefore offers lots of ways to get content on your site! However, if you are developing complex WordPress plugins those may not always be enough.

Imagine this: You want to build a ticketing system as a WordPress plugin and you want each ticket to have a page within your WordPress site for the customer or staff to view.

The schema for the URL would be something like this:{id}

In this case, it might be the best to simply have a PHP template that takes in the id, queries the data from whatever source and dynamically renders the page without having to store anything in the WordPress database.

Step 1: Creating a WordPress plugin

The first step is, of course, creating the WordPress plugin to host our virtual page.

If you already have a plugin, you can skip this step. If not, you can create a new plugin by following these steps:

  • Create a new folder in the wp-content/plugins directory of your WordPress installation.
  • Create a new PHP file in the folder and give it a unique name.
  • Open the PHP file and add the plugin header information at the top. This includes the plugin name, version, author, and description.
  • Save the PHP file and activate the plugin in the WordPress dashboard.

After that the plugin should show up as activated on your dashboard:

Screenshot showing activated plugin

Feel free to go through these steps in the WordPress documentation for more detail.

Step 2: Creating a Rewrite rule

WordPress offers a Rewrite API for exactly this purpose.

You can utilize it to programmatically “redirect” a specific slug in your WordPress installation to your plugin that will then render out the content on this page dynamically.

To achieve this, hook up a function to the init hook that calls the add_rewrite_rule() method.

function ticket_plugin_add_rewrite_rules() {
	    // Slug of your virtual page (e.g. 'ticket').
	    $slug = 'ticket';

		// Add rewrite rule (hook up the virtual page to a slug in WordPress).
		// Do some Regex magic to pass args within the URL for pretty URLs.
			$slug . '/([^/]*)[/]?$',

add_action( 'init', 'ticket_plugin_add_rewrite_rules' );

This example sticks to our example URL schema{id}.

First parameter: The URL

The first parameter represents the slug where our virtual page will be mounted (in this example /ticket). This parameter allows Regex matching. We can use that to allow any variables in the URL as well as GET parameters.

Second parameter: Query variables

The second parameter specifies the query string WordPress receives when the virtual page is loaded.

The query string should contain variables (like in the example). Those so called “query variables” are what WordPress uses under the hood to determine what content to load. WordPress has a lot of built-in ones but plugins can have their own query variables as well (like here).

More on query variables:

In this example, that’s useful to map the part of the URL that is the ticket ID to a query var ticket-id that we can access on the virtual page to know what specific ticket needs to be rendered.

Step 3: Defining a custom query variable

To tell WordPress about our custom query variable ticket-id we need to register it using the query_vars hook:

function ticket_plugin_add_query_vars( $vars ) {
  $vars[] = 'ticket-id';
  return $vars;

add_filter( 'query_vars', 'ticket_plugin_add_query_vars' );

In this example, this is just telling WordPress to recognize our ticket-id query variable.

Step 4: Creating the virtual page’s template

Now we need to set up the PHP template that renders the actual content when a user hits our virtual page.

To tell WordPress we want to use a custom template file for this page as opposed to a template from our theme, we can use the template_redirect hook in WordPress.

function ticket_plugin_template_redirect() {
  if ( get_query_var( 'ticket-id' ) ) {
    include_once plugin_dir_path( __FILE__ ) . 'templates/ticket-page-template.php';

add_action( 'template_redirect', 'ticket_plugin_template_redirect' );

Don’t forget to create the template file within your plugin’s directory. In this example, I added a dedicated templates folder as well.

Blueprint for /wp-content/plugins/ticket-plugin/templates/ticket-page-template.php:

 * Template Name: My Virtual Page

// Include WordPress header (if you want to).

// Get the ticket ID from our query var.
// You can call a filter/function here to get the data for the ticket.
$ticket_id = get_query_var( 'ticket-id' );

// HTML output here.
// For demonstration purposes, I only output the ticket ID here.
echo $ticket_id;

// Include WordPress footer (if you want to).

Step 5: Flushing the rewrite rules

Flushing rewrite rules is an important step when creating custom routes or virtual pages in a WordPress plugin.

When WordPress loads, it parses the URL and checks it against the rewrite rules to determine what content to display. When you create a new custom route or virtual page in your plugin, you need to flush the rewrite rules to ensure that WordPress recognizes the new route or page.

Flushing the rewrite rules rebuilds the URL structure and updates the internal cache, making the new content available for display. Failure to flush rewrite rules can result in 404 errors or the inability to access the new content. To flush the rewrite rules, you can use the flush_rewrite_rules() function in your plugin code. It’s important to note that this function should only be used sparingly, as it can be resource-intensive and slow down your site if used too frequently.

Therefore, I recommend using it in combination with the plugin activation hook (executed when the user activates your plugin on the WordPress dashboard).

function ticket_plugin_activate() {
	// Flush the rewrite rules.
	// Slow and resource heavy, therefore only called on activation and deactivation.

	__DIR__ . '/ticket-plugin.php', // Main plugin file.

All set! 🥳

You should now be able to reach your virtual page under the slug you specified.

In case you get a 404 error, just head to the WordPress dashboard and deactivate and active the plugin again. This will flush the rewrite rules again, possibly registering your virtual page if it wasn’t yet registered.

I created an example WordPress plugin based on this guide that can be found on GitHub. In case you want to see all code pieces brought together, feel free to check it out: