Publishing WordPress Posts in Bulk with a Custom REST API Endpoint

Published:

After looking into ways to publish to WordPress remotely, most approaches still revolve around XML-RPC.

The problem is that XML-RPC is not exactly a comfortable choice from a security standpoint. A lot of bots routinely scan the /xmlrpc.php path, which is why many themes and optimization plugins disable it by default. Some firewalls even recommend putting that URL on a blacklist.

If XML-RPC is off the table, the practical alternative is to expose a publishing endpoint yourself.

You can do that by installing the WPCode plugin and adding the following PHP snippet, or by placing it directly into functions.php:

<?php
// rest初始化
add_action('rest_api_init', function () {

  // 注册API接口,地址参考:https://www.krjojo.com/wp-json/api/publish
  register_rest_route('api', 'publish', [

    // 接收post提交
    'methods' => 'POST',
    'callback' => function ($request) {

      // 验证Token密码,可以自行修改
      if ((getallheaders()['Token'] ?? '') !== 'VH6AudNa%8Z*TYSg') return;

      // 获取json参数并发布文章
      $jsonData = file_get_contents("php://input");
      $post_id  = wp_insert_post(json_decode($jsonData, true));

      // 返回发布成功或失败
      if (is_wp_error($post_id)) {
        return [
          'code' => 400,
          'id' => 0,
          'msg' => $post_id->get_error_message(),
        ];
      } else {
        return [
          'code' => 200,
          'id' => $post_id,
          'msg' => 'ok',
        ];
      }
    },
    'permission_callback' => function () {
      return true;
    }
  ], 1);
});

Both the API route and the token can be changed to whatever you want.

A useful detail here is that publishing is done through WordPress’s official wp_insert_post() function. That means the normal internal hooks and dependencies still run as expected. Meta fields are also checked and written properly, including data such as likes or view counts.

Because the request body is JSON, the endpoint is also quite flexible. It is not limited to creating new posts—you can use it to update existing posts as well. The available fields and behavior are documented in the official wp_insert_post reference:

https://developer.wordpress.org/reference/functions/wp_insert_post/

Here is a Python example for publishing a post:

import requests

url = 'https://www.krjojo.com/wp-json/api/publish'
headers = {
    'Token': 'VH6AudNa%8Z*TYSg',        # 上面的Token
    'Content-Type': 'application/json'  # json格式数据
}
data = {
    'post_author': 1,                   # 作者id,一般是站长
    'post_date': "2024-10-08 20:40:34", # 发布时间
    'post_content': "这是内容",
    'post_title': "这是标题",
    "post_status": "publish",           # publish代表已发布,默认为 draft 草稿
    "post_category": [20, 8537],        # 分类id
    "tags_input": ["标签","测试"]        # 可以多个标签,自动识别同名
}

# 发送POST请求
response = requests.post(url, json=data, headers=headers)

# 检查响应状态码
if response.status_code == 200:
    if response.json()['code'] == 200:
        print('发布成功,文章id:', response.json()['id'])
    else :
        print('发布失败:', response.json()['msg'])
        exit()
else:
    print('请求失败,状态码:', response.status_code)
    exit()

Categories need a bit of special handling. Since WordPress categories can share the same name and can also have parent-child relationships, using category IDs directly is the reliable option.

If the goal is bulk publishing, there is not much more to do—just wrap the request logic in a for loop.

In practice, this makes large-scale imports fairly straightforward. Importing 50,000 posts this way is entirely manageable.

Post import result