I've used Dave Jesch's solution for making virtual pages (from content temporarily saved in a custom database table, updated from an RSS every four hours). See more here: http://davejesch.com/wordpress/wordpress-tech/creating-virtual-pages-in-wordpress/

So far I've verified that the url looks like one of a virtual page (i.e. it's the child of a certain slug and it contains numbers as the first part of the slug, this is specific to my website, of course). I've fetched all the content needed to create a page, from my custom DB table. And I've managed to use a custom template.

What I have yet to solve and need help with, is a solution for adding (faking) custom fields to the page without adding them to the database. There are some values from the custom table that I'd like to use outside of the_content() but within the loop, as you would custom fields.

All the suggestions I've seen for this saves the data as custom fields to the database, to the page id -1, but since the website has quite a lot of visitors and all virtual pages will get the id -1 that can only end badly, plus I don't want to save a bunch of junk in the DB as the content of my custom database will change quite often (they are job ads, so there will be hundreds of them and they will rarely stay in the system for more than a couple of weeks, tops).

So, is there a way to send fake custom fields along with the fake page or is there a better way to do this?


The below code is most of what makes up the function. A few function names are changed to make it less client specific, but on the whole that's most of it. The get_ip_post() function is omitted as it only fetches content from the custom table and returns them as an array.

/*                                          */
/* Virtual page, class and functions.       */
/*                                          */

// Based on: http://davejesch.com/wordpress/wordpress-tech/creating-virtual-pages-in-wordpress/

if (!class_exists('DJVirtualPage'))
    class DJVirtualPage
        private $slug = NULL;
        private $title = NULL;
        private $content = NULL;
        private $author = NULL;
        private $date = NULL;
        private $type = NULL;

        public function __construct($args)
            if (!isset($args['slug']))
                throw new Exception('No slug given for virtual page');
            $this->slug = $args['slug'];
            $this->title = isset($args['title']) ? $args['title'] : '';
            $this->content = isset($args['content']) ? $args['content'] : '';
            $this->author = isset($args['author']) ? $args['author'] : 1;
            $this->date = isset($args['date']) ? $args['date'] : current_time('mysql');
            $this->dategmt = isset($args['date']) ? $args['date'] : current_time('mysql', 1);
                echo $args['post_parent_ID'];
            $this->type = isset($args['type']) ? $args['type'] : 'page';
                echo $this->post_parent_ID;

            add_filter('the_posts', array(&$this, 'virtualPage'));

        // filter to create virtual page content
        public function virtualPage($posts)
            global $wp, $wp_query;

            if (count($posts) == 0 &&
                (strcasecmp($wp->request, $this->slug) == 0 || $wp->query_vars['page_id'] == $this->slug))
                //create a fake post intance
                $post = new stdClass;
                // fill properties of $post with everything a page in the database would have
                $post->ID = -1;                          // use an illegal value for page ID
                $post->post_author = $this->author;       // post author id
                $post->post_date = $this->date;           // date of post
                $post->post_date_gmt = $this->dategmt;
                $post->post_content = $this->content;
                $post->post_title = $this->title;
                $post->post_excerpt = '';
                $post->post_status = 'publish';
                $post->comment_status = 'closed';        // mark as closed for comments, since page doesn't exist
                $post->ping_status = 'closed';           // mark as closed for pings, since page doesn't exist
                $post->post_password = '';               // no password
                $post->post_name = $this->slug;
                $post->to_ping = '';
                $post->pinged = '';
                $post->modified = $post->post_date;
                $post->modified_gmt = $post->post_date_gmt;
                $post->post_content_filtered = '';
                $post->post_parent = 0;
                $post->guid = get_home_url('/' . $this->slug);
                $post->menu_order = 0;
                $post->post_type = $this->type;
                $post->post_mime_type = '';
                $post->comment_count = 0;

                // set filter results
                $posts = array($post);

                // reset wp_query properties to simulate a found page
                $wp_query->is_page = TRUE;
                $wp_query->is_singular = TRUE;
                $wp_query->is_home = FALSE;
                $wp_query->is_archive = FALSE;
                $wp_query->is_category = FALSE;
                $wp_query->query_vars['error'] = '';
                $wp_query->is_404 = FALSE;
            } else { 

            return ($posts);

function isJobAd($url){
        global $virtual_post_id;
        if( strpos( $url, 'lediga-uppdrag/' ) > 0 || strpos( $url, 'ledige-oppdrag/' ) > 0 ){   // Check that we're in the right place, Swedish & Norwegian urls respectively
            $url_parts = explode( '/', $url );
            $slug = end($url_parts);
            $post_id = current(explode("-", $slug));
                $virtual_post_id = $post_id;
                return true;
            } else {
                return false;
        } else {
            return false;

    return true;

function dj_create_virtual() {
    global $current_blog;
    global $ip_slug;

    if( !is_admin() ) {
        $url = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');

        $url_parts = explode( '/', $url );
        $slug = end($url_parts);
        $post_id = current(explode("-", $slug));

        if (isJobAd($url))

            $arr_post = get_ip_post( $post_id );
            if( is_array( $arr_post ) ) {
                $args = array('slug' => $ip_slug . '/' . $slug,
                        'title' => $arr_post['title'],
                        'content' => $arr_post['description'],
                        'type' => 'page',
                        'date' => $arr_post['pubdate']);

                $pg = new DJVirtualPage($args);
            } else {
                echo 'No post found.';
add_action('init', 'dj_create_virtual');

// Using page-ip-post.php as template
add_filter( 'single_template', 'portfolio_page_template', 99 );
function portfolio_page_template( $template ) {

    if ( is_page( -1 )  ) {
        $new_template = locate_template( array( 'page-ip-post.php' ) );
        if ( '' != $new_template ) {
            return $new_template ;

    return $template;
  • Who is Dave Jesch and where is that solution (in code)? Also "(...) , because." isn't really an argument. Could you please elaborate? File an edit, rework your question, add needed detail and code.
    – kaiser
    Apr 22, 2014 at 10:53
  • @kaiser And this is why I shouldn't be doing tricky stuff before lunch ;) The link was the first thing I added as I felt it was required to make sense of the question, but somehow I seem to have deleted it before saving. I hope this makes more sense.
    – Linda H
    Apr 22, 2014 at 15:51
  • 1
    Yes it makes a bit more sense. Now that we know where you got that idea from, please file another edit and show how you actually create that virtual page (in code). If the link is gone, your question will not make any sense anymore. Thanks.
    – kaiser
    Apr 22, 2014 at 15:55
  • @kaiser, thanks for the feedback! I've updated with tons of code. I looked around at how others make virtual pages and I've seen quite a few similar examples, so some of the additions/changes I've made are based on little snippets here and there, but the core class is Dave Jesch's example.
    – Linda H
    Apr 22, 2014 at 16:27

1 Answer 1


If you use get_post_meta in the template, that calls get_metadata with meta type post, which contains the filter get_{$meta_type}_metadata, which in this case will be get_post_metadata. There you can check the key and return whatever data you want.

function wpd_fake_meta( $meta_data, $object_id, $meta_key, $single ){
    if( 'somekey' == $meta_key ){
        return 'somefakevalue';
    return null;
add_filter( 'get_post_metadata', 'wpd_fake_meta', 20, 4 ); 
  • While this didn't exactly solve my problem as I'd hoped (i.e. sending some more info along to the template without having to resort to global variables) it definitely answers my question :D and even if I would have to make one or more global variables to solve it this way too, it does provide a more reusable template. Thanks!
    – Linda H
    Apr 23, 2014 at 12:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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