{"id":617,"date":"2026-10-13T08:01:41","date_gmt":"2026-10-13T08:01:41","guid":{"rendered":"https:\/\/keyworth.co\/blog\/wordpress-api-publish-posts"},"modified":"2026-10-13T08:01:45","modified_gmt":"2026-10-13T08:01:45","slug":"wordpress-api-publish-posts","status":"publish","type":"post","link":"https:\/\/keyworth.co\/blog\/wordpress-api-publish-posts","title":{"rendered":"How to Publish WordPress Posts Programmatically with the REST API"},"content":{"rendered":"<p>To publish posts programmatically with the WordPress REST API, you send an authenticated POST request to the <code>\/wp\/v2\/posts<\/code> endpoint. The request body, formatted as JSON, must include a title, content, and a status like &#8216;publish&#8217;, &#8216;draft&#8217;, or &#8216;future&#8217;. Authentication is best handled using Application Passwords for server-side scripts. This method allows for automating content workflows, scheduling posts, and integrating external content sources directly into your WordPress site, transforming content management from a manual task into a scalable, automated workflow.<\/p>\n<figure class=\"article-image\"><img src=\"https:\/\/kzvpvjjbqvmgluaudrhi.supabase.co\/storage\/v1\/object\/public\/article-images\/6b5ecc78-b40a-4f46-ab2d-ae30611eb043\/inline-0.jpg\" alt=\"Close-up of colorful code displayed on a computer screen within an editor window\" loading=\"lazy\" style=\"width:100%;aspect-ratio:16\/9;object-fit:cover;\" decoding=\"async\"><figcaption>Close-up of colorful code displayed on a computer screen within an editor window <span class=\"image-attribution\" style=\"font-size:0.75em;color:#888;\">(Photo by Antonio Batini\u0107 on pexels)<\/span><\/figcaption><\/figure>\n<h2>Understanding the WordPress REST API Fundamentals<\/h2>\n<p>The WordPress REST API is a developer-oriented interface for interacting with your site&#8217;s content. It provides a structured, predictable way to create, read, update, and delete WordPress content using standard HTTP methods. Instead of clicking buttons in the admin dashboard, you send requests to specific URLs, known as endpoints. The default route for the API is typically <code>https:\/\/yourdomain.com\/wp-json\/wp\/v2\/<\/code>. All data is sent and received in JSON (JavaScript Object Notation), a lightweight and human-readable format. For anyone looking to automate content, the primary endpoint of interest is <code>\/wp\/v2\/posts<\/code>. Sending a request to this URL is the programmatic equivalent of clicking &#8220;Add New Post&#8221;. The entire WordPress content model, from posts and pages to users and comments, is accessible through these endpoints, as detailed in the official REST API handbook. This direct access is what enables services to offer powerful features like direct publishing to WordPress blogs.<\/p>\n<h2>Comparing Authentication Methods for API Access<\/h2>\n<p>Before you can create a post, you must prove to WordPress who you are. The API offers several ways to handle authentication, each suited for different scenarios. The choice of authentication method is a critical security decision that defines who can access your site and from where. While options like Basic Auth exist, they are not recommended for production environments due to security risks. For most programmatic publishing, <strong>Application Passwords<\/strong> are the modern, secure, and recommended standard. Third-party services might use OAuth 2.0, but this is often too complex for simple scripts. A detailed breakdown can be found within the <a href=\"https:\/\/developer.wordpress.org\/rest-api\/\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">WordPress developer resources on authentication<\/a>.<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>Best Use Case<\/th>\n<th>Pros<\/th>\n<th>Cons<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Application Passwords<\/td>\n<td>Server-to-server scripts, cron jobs, CI\/CD pipelines<\/td>\n<td>Easy to generate, built into WordPress Core (since 5.6), individually revocable.<\/td>\n<td>Transmitted over HTTP (requires HTTPS), tied to a user account&#8217;s permissions.<\/td>\n<\/tr>\n<tr>\n<td>Basic Auth<\/td>\n<td>Local development testing ONLY<\/td>\n<td>Simple to implement (username:password).<\/td>\n<td>Highly insecure; sends credentials in plaintext with every request. Must not be used in production.<\/td>\n<\/tr>\n<tr>\n<td>JWT (JSON Web Tokens)<\/td>\n<td>Headless WordPress, Single Page Applications (SPAs)<\/td>\n<td>Stateless, can be used by client-side JavaScript, granular permissions.<\/td>\n<td>Requires a dedicated plugin; adds complexity for token management and expiration.<\/td>\n<\/tr>\n<tr>\n<td>OAuth 2.0<\/td>\n<td>Third-party applications needing user permission<\/td>\n<td>Industry standard for delegated authorization; users do not share credentials directly.<\/td>\n<td>Very complex to implement; requires a plugin and a multi-step token exchange process. Overkill for internal scripts.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>How to Create a New Post with a <code>curl<\/code> Command<\/h2>\n<p><code>curl<\/code> is a command-line tool for transferring data with URLs. It&#8217;s the most direct way to test the WordPress REST API. To create a post, you&#8217;ll send a <code>POST<\/code> request to the <code>\/wp\/v2\/posts<\/code> endpoint. The request must include an <code>Authorization<\/code> header and a JSON payload with the post&#8217;s data.<\/p>\n<p>First, you need an Application Password. Here is how to create one:<\/p>\n<ol>\n<li>Log in to your WordPress admin dashboard.<\/li>\n<li>Go to Users > Profile.<\/li>\n<li>Scroll down to the &#8220;Application Passwords&#8221; section.<\/li>\n<li>Enter a name for your new application (e.g., &#8220;My Publishing Script&#8221;) and click &#8220;Add New Application Password&#8221;.<\/li>\n<li>WordPress will generate a password like <code>abcd efgh ijkl mnop qrst uvwx<\/code>. Copy this immediately; it will not be shown again.<\/li>\n<\/ol>\n<p>This password is used with your username for authentication. You&#8217;ll combine them as <code>username:password<\/code> and Base64 encode them.<\/p>\n<p>Here is a complete <code>curl<\/code> example to create a new draft post:<\/p>\n<p>Replace <code>YOUR<em>BASE64<\/em>ENCODED_STRING<\/code> with your Base64-encoded <code>username:password<\/code> string. A successful request will return a <code>201 Created<\/code> status code and the JSON object of the newly created post.<\/p>\n<figure class=\"article-image\"><img src=\"https:\/\/kzvpvjjbqvmgluaudrhi.supabase.co\/storage\/v1\/object\/public\/article-images\/6b5ecc78-b40a-4f46-ab2d-ae30611eb043\/inline-1.jpg\" alt=\"Computer screen showing Python code and command line output for API authentication\" loading=\"lazy\" style=\"width:100%;aspect-ratio:16\/9;object-fit:cover;\" decoding=\"async\"><figcaption>Computer screen showing Python code and command line output for API authentication <span class=\"image-attribution\" style=\"font-size:0.75em;color:#888;\">(Photo by \u0421\u0430\u0448\u0430 \u0410\u043b\u0430\u043b\u044b\u043a\u0438\u043d on pexels)<\/span><\/figcaption><\/figure>\n<h2>Publishing Posts with Python and JavaScript (Node.js)<\/h2>\n<p>While <code>curl<\/code> is great for testing, you&#8217;ll likely use a higher-level language for real automation. Python and JavaScript are popular choices.<\/p>\n<h3>Python Example using <code>requests<\/code><\/h3>\n<p>Python&#8217;s <code>requests<\/code> library simplifies making HTTP requests. The process mirrors the <code>curl<\/code> command: set headers, create the JSON payload, and send a POST request.<\/p>\n<h3>JavaScript (Node.js) Example using <code>axios<\/code><\/h3>\n<p>For Node.js environments, <code>axios<\/code> is a common promise-based HTTP client. The logic is identical: prepare authentication headers and post the JSON data.<\/p>\n<h2>Managing Post Status, Scheduling, and Updates<\/h2>\n<p>Programmatic publishing isn&#8217;t just about creating new posts; it&#8217;s about managing the entire content lifecycle. The REST API gives you full control over a post&#8217;s state.<\/p>\n<p>The <code>status<\/code> field in your JSON payload is the key. You can set it to:<\/p>\n<ul>\n<li><code>publish<\/code>: Makes the post immediately live.<\/li>\n<li><code>draft<\/code>: Saves the post as a draft.<\/li>\n<li><code>pending<\/code>: Marks the post for review.<\/li>\n<li><code>private<\/code>: Publishes the post privately, visible only to privileged users.<\/li>\n<li><code>future<\/code>: Schedules the post to be published at a later time.<\/li>\n<\/ul>\n<p>To schedule a post, you must set <code>status<\/code> to <code>future<\/code> and also provide a <code>date<\/code> field with a future date in ISO 8601 format (e.g., <code>2026-12-25T10:30:00<\/code>). WordPress&#8217;s cron system will automatically publish the post at the specified time.<\/p>\n<p>To update an existing post, you send a <code>POST<\/code> request to its specific endpoint: <code>\/wp\/v2\/posts\/<id><\/code>. The JSON payload only needs to include the fields you want to change. For example, to change a post&#8217;s title and publish a draft, you would send <code>{ \"title\": \"New Updated Title\", \"status\": \"publish\" }<\/code> to <code>https:\/\/yourdomain.com\/wp-json\/wp\/v2\/posts\/123<\/code>.<\/p>\n<blockquote><p>The choice of authentication method isn&#8217;t just a technical detail; it&#8217;s a security decision that defines who can access your site and from where.<\/p><\/blockquote>\n<h2>Assigning Taxonomies: Categories and Tags Programmatically<\/h2>\n<p>A post without categories or tags is an organizational dead end. You can assign them during creation or with an update request. The API expects an array of integer IDs for the <code>categories<\/code> and <code>tags<\/code> fields.<\/p>\n<p>To find the ID for a specific category or tag, you can either inspect the URL when editing it in the WordPress admin (it will contain <code>tag_ID=XX<\/code>) or query the API endpoints <code>\/wp\/v2\/categories<\/code> and <code>\/wp\/v2\/tags<\/code> to get a full list with their corresponding IDs.<\/p>\n<p>Here\u2019s how you would include them in your JSON payload:<\/p>\n<p>Passing an empty array <code>[]<\/code> will remove all associated categories or tags from the post.<\/p>\n<h2>Working with Custom Post Types and Custom Fields (Meta)<\/h2>\n<p>Many sophisticated WordPress sites rely on Custom Post Types (CPTs) for content like &#8216;Events&#8217;, &#8216;Products&#8217;, or &#8216;Portfolio Items&#8217;. The REST API supports these out of the box, provided they were registered correctly. Each CPT gets its own endpoint, typically following the pattern <code>\/wp\/v2\/<cpt_slug><\/code>. For a &#8216;product&#8217; CPT, the endpoint would be <code>\/wp\/v2\/products<\/code>.<\/p>\n<p>Similarly, custom fields (post meta) are accessible via the <code>meta<\/code> key in your JSON payload. For this to work, the custom field must be explicitly registered to be shown in the REST API. This is a security measure to prevent accidental data exposure.<\/p>\n<p>You would add this code to your theme&#8217;s <code>functions.php<\/code> file or a custom plugin:<\/p>\n<p>Once registered, you can set its value in your API request:<\/p>\n<p><code>\"meta\": { \"my_subtitle\": \"An engaging subtitle for the post\" }<\/code><\/p>\n<p>This functionality is essential for any headless WordPress implementation or advanced content integration.<\/p>\n<h2>Best Practices for Security and Error Handling<\/h2>\n<p>Using the REST API introduces a new entry point into your website, so security is paramount. Likewise, strong error handling makes your scripts reliable.<\/p>\n<p><strong>Security Best Practices:<\/strong><\/p>\n<ol>\n<li><strong>Enforce HTTPS:<\/strong> Never send credentials over an unencrypted connection. Use a valid SSL certificate.<\/li>\n<li><strong>Use Application Passwords:<\/strong> Avoid using your primary user password. Application passwords can be individually revoked without affecting your main login.<\/li>\n<li><strong>Principle of Least Privilege:<\/strong> Create a dedicated API user with the minimum role required (e.g., &#8216;Editor&#8217; or &#8216;Author&#8217;, not &#8216;Administrator&#8217;).<\/li>\n<li><strong>Secure Credential Storage:<\/strong> Do not hardcode API keys or passwords in your scripts. Use environment variables or a secure vault service. Many guides cover how to manage application secrets securely.<\/li>\n<\/ol>\n<p><strong>Handling API Responses:<\/strong><br \/>\nYour script should always check the HTTP status code of the API response. Key codes include:<\/p>\n<ul>\n<li><strong>201 Created:<\/strong> Success. The post was created. The full post object is in the response body.<\/li>\n<li><strong>400 Bad Request:<\/strong> Your JSON is likely malformed or a required field is missing.<\/li>\n<li><strong>401 Unauthorized:<\/strong> Authentication failed. Check your username and application password.<\/li>\n<li><strong>403 Forbidden:<\/strong> Authentication worked, but the user lacks permissions (e.g., an &#8216;Author&#8217; trying to edit another user&#8217;s post).<\/li>\n<\/ul>\n<p>The response body for an error code will contain a JSON object with a specific <code>code<\/code> and <code>message<\/code> explaining what went wrong.<\/p>\n<blockquote><p>An API error message is not a failure; it&#8217;s a specific instruction from WordPress on what to fix in your next request.<\/p><\/blockquote>\n<figure class=\"article-image\"><img src=\"https:\/\/kzvpvjjbqvmgluaudrhi.supabase.co\/storage\/v1\/object\/public\/article-images\/6b5ecc78-b40a-4f46-ab2d-ae30611eb043\/inline-2.jpg\" alt=\"Person typing Java code on a laptop, with another screen displaying colorful code\" loading=\"lazy\" style=\"width:100%;aspect-ratio:16\/9;object-fit:cover;\" decoding=\"async\"><figcaption>Person typing Java code on a laptop, with another screen displaying colorful code <span class=\"image-attribution\" style=\"font-size:0.75em;color:#888;\">(Photo by Mikhail Nilov on pexels)<\/span><\/figcaption><\/figure>\n<h2>Strategies for Bulk Publishing and Content Synchronization<\/h2>\n<p>Publishing one post is simple, but what about migrating 1,000 articles or synchronizing an external product database? Sending thousands of API requests in a rapid-fire loop can overload your server or trigger rate limits imposed by your host or security plugins. Smart strategies are needed for bulk operations.<\/p>\n<ul>\n<li><strong>Implement a Queue:<\/strong> Instead of making API calls directly, push publishing jobs to a queue (using tools like Redis or RabbitMQ). A separate worker process can then pull jobs from the queue and execute them at a controlled pace.<\/li>\n<li><strong>Pace Your Requests:<\/strong> If a queue is overkill, introduce a simple delay (e.g., <code>sleep(1)<\/code> in PHP or <code>setTimeout<\/code> in JavaScript) between each API call to avoid hammering the server.<\/li>\n<li><strong>Transactional Logic:<\/strong> For multi-step processes like creating a post and then uploading its images, design your script to handle failures at any step without leaving orphaned data.<\/li>\n<\/ul>\n<p>These are the same principles that power large-scale content platforms. For instance, services that offer <strong>auto-publishing<\/strong> capabilities use a strong <strong>API<\/strong>-driven backend to syndicate content to thousands of sites. This approach is versatile, enabling automated blog publishing not just for WordPress, but for platforms like Shopify as well. Mastering these programmatic techniques provides complete control over your content pipeline. For those looking to bypass the development and maintenance of such systems, you can <a href=\"https:\/\/keyworth.co\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">explore our automated content platform<\/a>.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<h3>How do I find the ID for a category or tag?<\/h3>\n<p>A simple method is to navigate to <code>Posts > Categories<\/code> (or <code>Tags<\/code>) in your WordPress admin. Hover your mouse over the category you need, and look at the link preview in the bottom of your browser. The URL will contain a string like <code>tag_ID=27<\/code>. That number, 27, is the ID. Alternatively, a <code>GET<\/code> request to the <code>\/wp\/v2\/categories<\/code> endpoint will return a JSON array of all categories and their IDs.<\/p>\n<h3>Can I upload images and set a featured image via the REST API?<\/h3>\n<p>Yes, this is a two-step process. First, you must upload the image file by sending a <code>POST<\/code> request to the <code>\/wp\/v2\/media<\/code> endpoint with the proper headers. This request is more complex than a simple text post. A successful upload will return a media object containing a new ID. Second, you create or update your post and set the <code>featured_media<\/code> field to this new media ID.<\/p>\n<h3>What are the most common permission errors when publishing?<\/h3>\n<p>The most frequent permission error is <code>rest<em>forbidden<\/code>, which returns a <code>403 Forbidden<\/code> status code. This almost always means the user account tied to your Application Password does not have the necessary capabilities. For creating posts, the user needs the <code>publish<\/em>posts<\/code> capability, which the &#8216;Editor&#8217; and &#8216;Administrator&#8217; roles have by default. &#8216;Author&#8217; can create posts but not edit others&#8217;.<\/p>\n<h3>Is the REST API enabled by default in WordPress?<\/h3>\n<p>Yes, the REST API is a core feature and has been enabled by default since WordPress version 4.7. You can typically find it at <code>https:\/\/yourdomain.com\/wp-json\/<\/code>. However, some security plugins or hosting providers may disable it or restrict access. If you get a 404 error on the endpoint, you should check your security plugin settings or contact your host.<\/p>\n<h2>Related reading<\/h2>\n<ul>\n<li><a href=\"https:\/\/keyworth.co\/blog\/wordpress-seo-content-publishing-a-hands-on-tutorial\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">WordPress SEO Content Publishing: A Hands-On Tutorial<\/a>.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Automate WordPress content workflows by publishing posts programmatically. Use Application Passwords to secure API access and manage post status.<\/p>\n","protected":false},"author":1,"featured_media":616,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-617","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/posts\/617","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/comments?post=617"}],"version-history":[{"count":1,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/posts\/617\/revisions"}],"predecessor-version":[{"id":618,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/posts\/617\/revisions\/618"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/media\/616"}],"wp:attachment":[{"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/media?parent=617"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/categories?post=617"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/keyworth.co\/blog\/wp-json\/wp\/v2\/tags?post=617"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}