If you're an advanced developer or just want the TL;DR version of this, skip to the code.

Create a Post in WordPress

For many, creating WordPress posts and pages is a simple matter of using the built-in editor. But if you’re working on a more advanced project – perhaps you need to automatically generate pages (or posts) to act as views or to restrict users from accessing the dashboard.

If that’s the case, then it may be better to programmatically create a post in WordPress.

Case in point: I’ve been working on a project where all user profile management (save for administrators) has to occur outside of the dashboard. This means that when the theme is installed, it needs to automatically generate pages that support this functionality.

It’s not terribly difficult, but it’s important to make sure that you aren’t overwriting existing pages, that you’re properly setting post attributes, and that you’re properly assigning templates when necessary.

Here’s a simple example of how to programmatically create a post in WordPress.

Identify The Basics

The WordPress API allows you to define a wide array of options when manually inserting pages. Odds are, you’ll need more in some cases than in others.

For the most part, I usually roll with the following parameters:

  • comment_status
  • ping_status
  • post_author
  • post_name
  • post_title
  • post_status
  • post_type

I’ll show these in more detail later, but one of the most important parts in programmatically creating a post in WordPress is to identify exactly what parameters are relevant to your needs.

Make notes and then plan to incorporate them.

Check For an Existing Post

If you’re building a product from the ground up, then the likelihood of a post already existing isn’t high, but if you’re writing a plugin or you’re going to be working with an existing setup, then it’s best to make sure that a post exists before trying to do anything else.

Luckily, WordPress makes it easy to check if a post exists. Of course, I’m making an assumption here: If you’re going to be creating a post then you’re likely going to be defining a title so the easiest way to check if a post exists, at least in this case, is checking to see if the title already exists.

Easy:

if( null == get_page_by_title( $title ) ) {
    // Create the page
} else {
    // The page exists
} // end if

I’ll fill this in detail momentarily, but it’s important to handle the case where a page exists. Either throw an error, prevent the page from being created, halt executed, register a notice, or a set a flag.

Generally speaking, I’m a fan of registering an admin_notice, thought hat’s a bit out of scope for what I want to cover here. So, later in this article, I’m going to set a flag that will allow you to react appropriately.

Apply The Default Settings

At this point, I usually define the default settings for the post. The list I use is above but it often depends on three variables each of which may come from an input field or be manually set in the code. For this example, I’ll be following the later:

I’ll need the slug that I wish to use for the post URL:

$slug = 'example-post';

I’ll need the ID of the user that will be attributed for creating the post. ‘1’ is a safe bet as it’s usually the administrator but it’s also common to use the ID of the current user:

$author_id = 1;

And I’ll need to set the title of the post:

$title = 'My Example Post';

At this point, you define the array of options for creating a post:

array(
	'comment_status'	=>	'closed',
	'ping_status'		=>	'closed',
	'post_author'		=>	$author_id,
	'post_name'		=>	$slug,
	'post_title'		=>	$title,
	'post_status'		=>	'publish',
	'post_type'		=>	'post'
)

The parameters should be clear, but here’s what I’ve done: I’m going to create a post that does not accept comments or pings (this is arbitrary – you can mark these as open). The page has been created by the administrator and will have the slug and the title that I defined above. The post will be immediately published when the function fires and I’m obviously creating a post (instead of, say, a page).

Hook It Into WordPress

To wrap this up, there’s one more thing that you need to account for when inserting a post into the database and that is that the wp_insert_post function will return the ID of the post once it has created it.

As such, I normally like to initialize a variable that I return to indicate whether or not the creation of the new post was successful.

Something like this:

// Initialize the post ID to -1. This indicates no action has been taken.
$post_id = -1;

// Setup the author, slug, and title for the post
$author_id = 1;
$slug = 'example-post';
$title = 'My Example Post'

// If the page doesn't already exist, then create it
if( null == get_page_by_title( $title ) ) {

	// Set the page ID so that we know the page was created successfully
	$post_id = wp_insert_post(
		array(
			'comment_status'	=>	'closed',
			'ping_status'		=>	'closed',
			'post_author'		=>	$author_id,
			'post_name'		=>	$slug,
			'post_title'		=>	$title,
			'post_status'		=>	'publish',
			'post_type'		=>	'post'
		)
	);

// Otherwise, we'll stop and set a flag
} else {

    // Arbitrarily use -2 to indicate that the page with the title already exists
    $post_id = -2;

} // end if

I’ll show this function in its entirety when we take a look at the final function, but I find it useful to attach this function to the after_setup_theme action to ensure that the post is created when WordPress sets up the theme.

Programmatically Create a Post in WordPress

This is the TL;DR version of all of the code covered in this article. It’s meant for those that want to see everything tied together or those that skipped the rest of the article.

/**
 * A function used to programmatically create a post in WordPress. The slug, author ID, and title
 * are defined within the context of the function.
 *
 * @returns -1 if the post was never created, -2 if a post with the same title exists, or the ID
 *          of the post if successful.
 */
function programmatically_create_post() {

	// Initialize the page ID to -1. This indicates no action has been taken.
	$post_id = -1;

	// Setup the author, slug, and title for the post
	$author_id = 1;
	$slug = 'example-post';
	$title = 'My Example Post';

	// If the page doesn't already exist, then create it
	if( null == get_page_by_title( $title ) ) {

		// Set the post ID so that we know the post was created successfully
		$post_id = wp_insert_post(
			array(
				'comment_status'	=>	'closed',
				'ping_status'		=>	'closed',
				'post_author'		=>	$author_id,
				'post_name'		=>	$slug,
				'post_title'		=>	$title,
				'post_status'		=>	'publish',
				'post_type'		=>	'post'
			)
		);

	// Otherwise, we'll stop
	} else {

    		// Arbitrarily use -2 to indicate that the page with the title already exists
    		$post_id = -2;

	} // end if

} // end programmatically_create_post
add_filter( 'after_setup_theme', 'programmatically_create_post' );

The various calls to this function would look like this:

$post_id = programmatically_create_post()
if( -1 == $post_id || -2 == $post_id ) {
   // The post wasn't created or the page already exists
} // end if

Resources in the Codex

I’ve presented a relatively simple case for what’s needed to create a post in WordPress programmatically. There’s much more than you can do so I definitely recommend reviewing the following Codex articles:

Category:
Tips
Tags:

Join the conversation! 63 Comments

  1. Always good to see others talking about this stuff!

    One thing, WordPress will add a default date, but it won’t default the ‘post_date_gmt’ time.

    Also for semantics I’d recommend calling $page_id $post_id, since your inserting a post type of ‘posts’. I use wp_insert_post() often and it gets confusing when inserting different posts types and keeping track of the returned ID.

  2. Wow, how timely! I’m working on a project right now and updating a whole bunch of posts, very apt and helpful – thanks

  3. Found this after Googling it. :)

    What a win…Thanks!

  4. Hello i followed your tutorial
    and i was succesfull in creating a 1 page
    But i want to create multiple pages programmatically
    When i add the same code again i get the 500 Internal Server Error
    is there any was!??

  5. I don’t think you noted this, so excuse me if I missed it.

    The default post type on get_page_by_title is ‘page’, so if you’re checking for posts, you need to use get_page_by_title( $title, 'OBJECT', 'post' ). So, line 19 would be:

    if( null == get_page_by_title( $title, 'OBJECT', 'post' ) ) {

  6. Is ‘after_setup_theme’ filter?
    Much times we want to create post in some category or add some tags to it. In this situations you can use wp_set_post_terms(). For example:
    $cat_slug = ‘my-category’;
    if ($post_id > 0)
    {
    wp_set_post_terms($post_id, $cat_slug, ‘category’);
    }
    Anyway, nice article, thanks Tom!

  7. Great Post!!!
    I am stuck on where should I keep the function I wrote to create an admin page programmatically.
    My requirement is that I needed to create a wp-admin page programmatically so that no manual steps are involved. which I have done.
    The problem I am facing now is:
    1. where in my project should I put this function so that this wp-admin page gets created before I actually call the page?

    Thanks in advance.

    • This should occur in functions.php and you may be looking for the add_menu_page hook depending on how you’ve setup your function.

      • Thanks Tom!
        To elaborate, I have a wordpress site created on which I have added a menu “My Transactions”. Now, when I load the site and click on the above menu, I am getting a 404 which I assume the wp-admin page didnt get created.

        Below is the function I have.

        //create the wp-admin page programmatically
        function createAdminPage(){
        $title=”myTitle”;
        if( null == get_page_by_title($title)) {
        // Create the page
        global $user_ID;
        $page[‘post_type’] = ‘page’;
        $page[‘post_content’] = ‘Put your page content here’;
        $page[‘post_parent’] = 32441;//dashboard page
        $page[‘post_author’] = $user_ID;
        $page[‘post_status’] = ‘publish’;
        $page[‘post_title’] = $title;
        $page[‘_wp_page_template’] = “page-admn.php”;
        $page = apply_filters(‘yourplugin_add_new_page’, $page, ‘teams’);
        $pageid = wp_insert_post ($page);
        if ($pageid == 0) { /* Add Page Failed */ }
        } else {
        // The page exists
        } // end if
        }

        //call the above function
        add_action(‘admin_init’,’createAdminPage’);

        I tried putting this on functions.php but no luck.
        Any idea on whats wrong?
        Thanks in advance.

        • I can’t provide much support beyond this, but if you look at the code that I’ve provided above, it uses the after_setup_theme hook.

          If you look at the documentation that I provided, it also references the add_menu_page page book.

          Based on the code you’re showing me, I’d go with the former, rather than the latter.

  8. Have you ever had an issue where the wp_insert_post does not call action edit_post or save_post? This is causing posts to not be visible unless I call the edit post page from the admin screen by the post ID that was returned. Once, just viewing the post it shows in post list and is now viewable, so it seems that the action edit_post fixes the issue, but I’m not sure how to call it from outside of WP.

    Any ideas?

    • I’ve not personally experienced this, but I have had seen other experience similar issues especially in the context of working with meta data.

      Of course, I know that doesn’t really help with what you’re talking about.

      That said, this could have to do with how you’re hooking your function. Make sure you review this article – I hope this helps!

  9. How about setting different post_content for each post?
    Could it be possible with a php file which contains the logic?
    Something like this:
    ‘post_content’ => ‘posts-content.php’

    And inside that you would do, if(‘post_title’ == ‘Home’) { echo ‘Default home page content’};

    • It can be done, but you’d have to set up quite a few conditionals to properly handle it, which is really more of the scope of another post (rather than a really long comment :).

      That said, you’re on the right track, but rather than using `posts-content.php`, you’d need to use something like `file_get_contents` or another function in order to read the contents of the file into a string, then apply it to the `post_content` attribute.

      • I already figured it out, I basically created a file that I needed and included it before the foreac. But file_get_contents would maybe be a better approach, Thanks! :)

  10. Tom,
    I want to create a script that lives in my Uni web server area and then creates a bunch of posts for my doctorate blog on wordpress.com. I have a spreadsheet/CSV file, with research paper details including harvard refernce, a summary/critique, a link to the paper and various flags about the papers usefulness, possibly even categories or tags.

    Your tutorial page is great and I would like to use the code to loop through the csv file. However, as a newbie with WP API am I missing something? Is there an assumption about access control I am missing? Where should I supply credentials to wordpress.com as my blog administer or is there a part of wordpress.com where I would store this script and therefore have write access by default?

    • Hey Paul — all of this code is meant to be executed on a server that you managed with a self-hosted installation. From there, you have access to the theme files and plugin files that allow you to read the files on the server and process them as you’ve mentioned.

      WordPress.com is a much more locked down environment so you don’t have that kind of flexibility.

      Hope this helps!

  11. Tom,

    Thanks! For some reason while working off of WP.org’s wp_insert_post() I was running into trouble. After look around a but and using your code it worked.

  12. Looking for something similar, maybe you could help. I need front-end users to be able to click a button and have a new page created, but the page needs to be a duplicate of an existing page.

  13. Hi Tom,
    Thanks for the very nice post.
    I have a dumb question though. I now know how to add your code to the functions.php file; I know the code for how to call the function(as you show in the post), but I don’t know where I should initiate the call? Inside the post editor or something? Please help, thanks!

    • I now know how to add your code to the functions.php file; I know the code for how to call the function(as you show in the post), but I don’t know where I should initiate the call?

      Normally, the best way to do this is to grab a copy of functions.php via your FTP client, make the changes on your local machine, then upload the file back to the root of your theme.

      This can be done within the WordPress dashboard using the editor, but I don’t recommend it. If you mess something up, you’ll be locked out of your dashboard.

      And really, be very careful when editing functions.php.

  14. Hi,
    Don’t know why, my first comment wasn’t published.

    First, thank you for the tutorial.
    I have a page template which call a function to create the post.
    The post is created.
    I can’t made the post opened automatically right after the creation, how to do it ?

    Thanks

  15. Hi Tom, thanks for your great post(s).
    I try to profit of a bit of your time asking if you can help me with this scenario:

    In a multisite installation inserting a NEW post in main site should propagate the wp_insert_post() in a bunch of sites of the network. the array of IDs where it has to be done come from a custom_meta of this last new post ( the admin decides where to spread the new post using a multiselect).
    It looks like I’m on the right way and everything is quite ok ( except that it has to be multilanguage -WPML- as well but that’s another trouble and it also looks like almost solved).
    the problem is that I’m not getting the expected values on get_post_meta() which returns empty…I try to write my code and see if you can handle this…

    function relatePostByLanguages($post_id){
    if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
            return;
        if( defined( 'DOING_AJAX' ) && DOING_AJAX )
            return;
    //etc 
    $termid = get_post_meta( $post_id, '_termid', true ); //check if I'm new post or updating an existing one
        if ($termid == '') {
            ///IT'S A NEW POST
            update_post_meta( $post_id, '_termid', 'update' );//next time it will be an update
             $thisNewPost=get_post($post_id);
             $current=get_current_blog_id();
             $post_to_insert = array(
                          'post_title'    => $thisNewPost->post_title,
                          'post_content'  => $thisNewPost->post_content,
                          'post_status'   => $thisNewPost->post_status,
                          'post_author'   => $thisNewPost->post_author,
                          'post_type'	  => $thisNewPost->post_type,
                          'post_name'     => $thisNewPost->post_name,
                        );
                $siti=get_post_meta($thisNewPost->ID,'AS_domini');
               /*that's the array of IDS coming from a metabox multiselect on this post auto-saved() 
             but it's empty. I tried also '$post_id' instead of  '$thisNewPost->ID' but no luck*/
              remove_action( 'save_post', 'relatePostByLanguages' );//avoid looping in here
              for($k=0;$k<count($siti);$k++){
    //of course this array being empty there's no loop, if I use a filled '$siti' array everything is ok
                   switch_to_blog($siti[$k]);  
                   wp_insert_post( $post_to_insert );
                  /// other stuff related with translations.....
                    }
               switch_to_blog($current);
             ///END OF THE GAME----
              add_action( 'save_post', 'relatePostByLanguages' );
              }
    }
    add_action( 'save_post', 'relatePostByLanguages' );
    

    So…I’d really appreciate your help.
    Thanks in advance

    • Hey Andrea – thanks for the comment and for sharing the code. Unfortunately, it’s a bit harder for me to provide any guidance on this code as I typically don’t work with multisite environments; however, there are a few things that I think could be done in order to try to make this a bit more readable:

      1. Move the DOING_AUTOSAVE and etc. checks in a separate function so that the four-line block can be consolidated into a two line block
      2. Use get_post_status rather than get_post_meta to check on the status of a post (you presumably only want to be working with posts that aren’t private or drafts).
      3. Don’t try to setup remove_action and add_action to the same function that’s calling it. There are times in which this is the best route, but this creates code that’s hard to trace.
      4. Going from 0 to count( $siti ) is a little unclear because I don’t know what the AS_domini meta data refers to.

      Based on what I think is trying to happen (which is to update an existing post to all of the sites on a network), then I’d setup a couple of functions to do this:

      1. Load all of the sites on a network
      2. For each site on the network, check to see if the given post exists
      3. If the post does not exists, insert it; otherwise, don’t do anything

      Hope this helps!

  16. Everywhere I saw that post creation returns post ID

    I wanted to send email of page URL (permalink) to the group as soon as it is created programmatically.

    Post will be created automatically from external parameters so there should be no human intervention in sending that URL. (e.g. my program finds city temperature dropped and created a post of it)

  17. Nice post Tom,

    But what if we do want to have a featured image with that post!

    i think wp_insert_post does not support that functionality

  18. I think maybe your function should return the post id at the end… not seeing that?

    • There are times where you may want to return a post ID, there are times where the post is created and you don’t really need to return the post ID. It all depends on what you need to do with whatever happens after the post is created.

      In the final lines of code that are shared in this post, you’re right that it expects the post ID to be returned so that you can evaluate the results properly. You may not always need to do that, though.

  19. I am not a WP expert. What i would like to know is that I have a seperate PHP file and i want to add a WP post.

    Reading this article, it does not say anything about which files you need to use in order to insert post.

    On http://codex.wordpress.org/Function_Reference/wp_insert_post

    I read: wp_insert_post() is located in wp-includes/post.php.

    So i would just edit post.php, change my code and save it under another name let´s say my_post_page.php ?

    I would just like to have a seperate PHP page, with code to manually add posts from an daily updated XML file and also to order it val a value (amount) column

    • I would just like to have a seperate PHP page, with code to manually add posts from an daily updated XML file and also to order it val a value (amount) column

      Generally, I like to tell people to treat the core code as an abstraction. Don’t mess with it, change it, or copy it and include it in your own theme or plugin because the side effects can be detrimental to other code.

      Instead, if you want to create a new post from the content of another file, then you’ll need to setup a function to read the contents of the file, parse it however is necessary (read the XML file and parse the content out of it, read the contents of the file and sanitize it to remove any unnecessary markup or potentially malicious code, etc.) and then use the wp_insert_post function to write the contents of the file into a new post in the database.

      Hope this helps!

  20. Hi Tom

    I have a similar question to Daniel above. I am trying to write a php script to take in an xml file and then create posts on that content.

    Your article here is very useful as a starting point but if I’m guessing that this function needs to be called from an already logged in user?

    In other words, if I just have a php file and I include the correct wp bits and pieces and an external service sends an xml file, will that work if there is nothing logged in?

    I am actually guilty now of one of my pet peeves, that is to ask without trying. I will give it a try to see myself, but on the chance that this won’t work for my requirement I’m hoping you or someone commenting here can point me to some doc / tutorial.

    Thanks a mil..

    John

    • Your article here is very useful as a starting point but if I’m guessing that this function needs to be called from an already logged in user?

      This really depends on how it’s setup.

      If you’re running the script from within WordPress, then you need to be using an account that has permissions; however, it is possible to write to the WordPress database when you aren’t logged in using a different approach but this will require you to have a bit of knowledge of the database, making safe, sanitized queries, and things like that.

      Another thing you may want to look into using is WP-CLI.

      I am actually guilty now of one of my pet peeves, that is to ask without trying. I will give it a try to see myself, but on the chance that this won’t work for my requirement I’m hoping you or someone commenting here can point me to some doc / tutorial.

      Either way, I hope the information above has helped and I do wish you luck in what you’re doing :).

  21. Further to my comment earlier. I have tested and this function will post the new post even for a “not logged in” user.

    This makes it awesome for automated machine to machine code, but obviously means you have to validate the authenticity of whoever is making the connection outside of wordpress.

    • This makes it awesome for automated machine to machine code, but obviously means you have to validate the authenticity of whoever is making the connection outside of wordpress.

      Ah, I’m glad to hear it :).

      But yes, you need to verify the connection if it’s coming outside of WordPress. Honestly, I’d say you should also verify the connection even if it’s coming from within WordPress simply as a security precaution.

      Some may say that’s over the top but I’d rather be safe than not.

  22. in get_page_by_title( $title,OBJECT,’post’) ,

    you must specify the 3rd parameter to post_type=”post”

  23. In general, awesome post.

    But please, please, please modify so that the resulting post id (or -1 or -2) gets returned. It bothers me that the block comment at the top says that it returns the value and there is no return.

    Thanks.

    (long time follower, first time commenter)

    • This is such an old post, I really should update it. I need to include the code in a gist and there’s a lot I’ve learned since I originally wrote this post.

      I’ll try to make a note to come back to this sometime very soon and update the post.

      Thank you for the comment and, more importantly, thanks so much for the comment. Glad to have you along, Peter!

  24. Hi there,

    I don’t understand what array in your code is responsible for “the content”? I’m a noob, so don’t scream immediately. I just wanted to add something like this:

    array(

    ‘comment_status’ => ‘closed’,

    ‘ping_status’ => ‘closed’,

    ‘post_author’ => $author_id,

    ‘post_name’ => $slug,

    ‘post_title’ => $title,

    ‘post_content’ => ‘read this post about’, $title, (how to do it properly?)

    ‘post_status’ => ‘publish’,

    ‘post_type’ => ‘post’

    )

    In this example, I tried to add “auto generated” content in the beginning of the post. However I’m a noob, so I’m not sure how to do that properly. Also, if it’s possible, how to “auto update” the post? In my case I have issues with “Post Author” contact button – it disappears. I need to “edit the post” + “click update” then the button appears again. Maybe it’s possible to do it here?

    • I’m a noob, so don’t scream immediately.

      I won’t and FWIW I think it’s a shame you feel this way when asking those involved with WordPress a question. :(

      Not all of us are going to respond like that because not all of us are like that :).

      In this example, I tried to add “auto generated” content in the beginning of the post.

      Do so something like this, you’ll need to do string concatenation. To make the example a bit easier to read, I’m going to assume you have a function that returns the string you want to prefix. Then you can do this:

      $prefix = $this->get_content_prefix();

      $content = $prefix . $title

      Then you take the content and for your array:

      array( 'post_content' => $content );

      Obviously, I’ve kept it short for readability but this should take care of it.

  25. I managed to incorporate some of these functions on a plugin i build mysefl thanks to your tutorial. Thanks a lot :)

  26. Hi there Tom,

    This thread is from a long time ago. But addresses something I’ve been trying to do. Does this work with WordPress 4.8? So, far I am not having much luck.

    Thanks,

    Dylan

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.