WordPress REST API 是一组 HTTP 接口,允许外部应用程序——无论是 JavaScript 前端、移动应用程序或第三方服务——直接通过 HTTP 读取、写入和管理 WordPress 数据,以 JSON 格式交换信息。在 WordPress 4.7 中正式推出,它已成为 Headless WordPress 架构和单页应用程序的骨干,这些应用程序纯粹将 WordPress 用作内容后端。

本指南从基本原理开始引导你完成 WordPress REST API:理解概念、探索核心端点、从 JavaScript 和 PHP 调用 API、创建自定义路由,以及在上线之前应用安全最佳实践。

什么是 WordPress REST API 以及它如何工作?

REST(表示状态转移)是一种用于 Web API 的架构风格,它依赖于标准 HTTP 谓词——GET、POST、PUT、PATCH 和 DELETE——进行通信。WordPress 通过公开网站上所有类型内容的接口实现这种风格:文章、页面、用户、媒体、类别、标签、自定义文章类型等。

当客户端向 WordPress API 端点发送 HTTP 请求时,服务器验证身份验证(如果需要)、处理请求并以 JSON 形式返回数据。每个端点都位于 /wp-json/ 基本路径下。例如:

关键优势是前端和后端的完全解耦。你可以构建 React、Vue、Next.js 或 Flutter 前端,并仅将 WordPress 用于通过 API 访问的内容存储和管理——这是一种称为 Headless WordPress 的模式。

你应该了解的核心端点

主要命名空间是 wp/v2,它涵盖所有内置内容类型。下面的表格总结了最常用的端点:

Endpoint Method Description Auth Required
/wp/v2/posts GET List published posts No
/wp/v2/posts POST Create a new post Yes
/wp/v2/pages GET List published pages No
/wp/v2/users GET List users (public authors) Partial
/wp/v2/media GET / POST List or upload media files POST: Yes
/wp/v2/categories GET List categories No
/wp/v2/tags GET List tags No
/wp/v2/comments GET / POST List or create comments POST: Sometimes

你可以通过访问 https://yoursite.com/wp-json/ 来发现你的特定安装上可用的每个端点,它返回一个 JSON 发现文档,列出所有已注册的路由和命名空间——包括插件添加的那些。

使用 JavaScript 调用 API(Fetch API)

使用本地 Fetch API 从 WordPress REST API 使用 JavaScript 获取数据很简单。下面的示例检索五个最新文章并记录其标题和 URL:

// Fetch the 5 most recent posts
async function getLatestPosts() {
  const response = await fetch(
    'https://yoursite.com/wp-json/wp/v2/posts?per_page=5&_fields=id,title,link,excerpt'
  );

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  const posts = await response.json();

  posts.forEach(post => {
    console.log(post.id, post.title.rendered, post.link);
  });
}

getLatestPosts();

Notice the _fields query parameter — it instructs WordPress to return only the specified fields rather than the full 30+ field object, significantly reducing payload size and improving performance.

Useful Query Parameters

The REST API supports a rich set of standard query parameters for filtering, sorting, and paginating results:

// Filter posts by category, sorted alphabetically
const url = new URL('https://yoursite.com/wp-json/wp/v2/posts');
url.searchParams.set('categories', '5,8');
url.searchParams.set('per_page', '10');
url.searchParams.set('orderby', 'title');
url.searchParams.set('order', 'asc');
url.searchParams.set('_fields', 'id,title,link,date');

const res = await fetch(url.toString());
const data = await res.json();

Authentication with Application Passwords

Endpoints that modify data — creating, editing, or deleting content — require authentication. WordPress supports several methods, but the recommended approach for most integrations is Application Passwords, introduced in WordPress 5.6.

Generating an Application Password

  1. Go to WordPress Admin > Users > Your Profile.
  2. Scroll down to the "Application Passwords" section.
  3. Enter a name for your application (e.g. "My Headless App") and click "Add New Application Password".
  4. WordPress generates a password shown only once — copy and store it securely immediately.
// Create a post using Basic Auth with Application Password
const username = 'your_username';
const appPassword = 'xxxx xxxx xxxx xxxx xxxx xxxx';

const credentials = btoa(`${username}:${appPassword.replace(/\s/g, '')}`);

const response = await fetch('https://yoursite.com/wp-json/wp/v2/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Basic ${credentials}`
  },
  body: JSON.stringify({
    title: 'New Post via API',
    content: 'Post content goes here...',
    status: 'draft'
  })
});

const newPost = await response.json();
console.log('Created post ID:', newPost.id);

Security Tip: Never embed Application Passwords directly in frontend JavaScript — anyone can inspect the source or network requests and extract them. Store credentials on the server side and have your frontend communicate through a backend proxy endpoint. Also generate a separate Application Password per integration and revoke it immediately when no longer needed.

Calling the API from PHP

When consuming the WordPress REST API from PHP — whether in a plugin, a theme, or an external application — you have two good options: WordPress's own HTTP API functions (when inside a WP context) or plain cURL (for any PHP environment):

<?php
// Option 1: WordPress HTTP API (recommended inside WP)
$response = wp_remote_get(
    'https://yoursite.com/wp-json/wp/v2/posts?per_page=5',
    array(
        'timeout' => 15,
        'headers' => array( 'Accept' => 'application/json' )
    )
);

if ( is_wp_error( $response ) ) {
    error_log( 'API Error: ' . $response->get_error_message() );
    return false;
}

$posts = json_decode( wp_remote_retrieve_body( $response ), true );
foreach ( $posts as $post ) {
    echo $post['id'] . ': ' . $post['title']['rendered'] . "\n";
}

// Option 2: cURL (any PHP environment)
$ch = curl_init( 'https://yoursite.com/wp-json/wp/v2/posts?per_page=5' );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_HTTPHEADER, [ 'Accept: application/json' ] );
curl_setopt( $ch, CURLOPT_USERAGENT, 'MyApp/1.0' );
curl_setopt( $ch, CURLOPT_TIMEOUT, 15 );
$result = curl_exec( $ch );
curl_close( $ch );

$posts = json_decode( $result, true );
?>

Building Custom REST Routes

The most powerful feature of the WordPress REST API is the ability to register your own routes. This is ideal when you need endpoints tailored to your site's specific data, such as aggregated sales figures, filtered Custom Post Types, or computed results from your own business logic.

<?php
// Register custom routes on rest_api_init
add_action( 'rest_api_init', function() {

    // Public GET route
    register_rest_route( 'myplugin/v1', '/summary', array(
        'methods'             => 'GET',
        'callback'            => 'myplugin_get_summary',
        'permission_callback' => '__return_true',
    ) );

    // Authenticated POST route with argument validation
    register_rest_route( 'myplugin/v1', '/data', array(
        'methods'             => 'POST',
        'callback'            => 'myplugin_save_data',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
        'args' => array(
            'title' => array(
                'required'          => true,
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
            ),
            'amount' => array(
                'required' => true,
                'type'     => 'number',
            ),
        ),
    ) );
} );

// GET callback
function myplugin_get_summary( WP_REST_Request $request ) {
    return rest_ensure_response( array(
        'total_posts'  => wp_count_posts()->publish,
        'total_pages'  => wp_count_posts( 'page' )->publish,
        'total_users'  => count_users()['total_users'],
        'site_name'    => get_bloginfo( 'name' ),
        'generated_at' => current_time( 'mysql' ),
    ) );
}

// POST callback
function myplugin_save_data( WP_REST_Request $request ) {
    $title  = $request->get_param( 'title' );
    $amount = $request->get_param( 'amount' );

    // Process and store data...

    return new WP_REST_Response(
        array( 'success' => true, 'message' => 'Data saved successfully' ),
        201
    );
}
?>

After adding this code you can call https://yoursite.com/wp-json/myplugin/v1/summary immediately. Make sure WordPress Permalinks are not set to "Plain" — the rewrite rules required by /wp-json/ depend on a pretty-permalink structure.

Extending Standard Endpoints with Custom Fields

Sometimes you need to attach extra data to standard endpoint responses without replacing them. register_rest_field lets you inject additional properties into any existing resource type. The example below adds a featured_image_url field to every post response:

<?php
add_action( 'rest_api_init', function() {
    register_rest_field( 'post', 'featured_image_url', array(
        'get_callback' => function( $post_arr ) {
            $id  = get_post_thumbnail_id( $post_arr['id'] );
            if ( ! $id ) return null;
            $img = wp_get_attachment_image_src( $id, 'medium' );
            return $img ? $img[0] : null;
        },
        'schema' => array(
            'description' => 'Medium-size featured image URL',
            'type'        => 'string',
            'format'      => 'uri',
        ),
    ) );
} );
?>

With this in place, every /wp/v2/posts response will include featured_image_url, saving your frontend from making an additional request just to get the thumbnail.

Securing the WordPress REST API

WordPress exposes several public endpoints by default. While convenient for development, unmanaged access can introduce security risks. Here are the most important hardening steps:

1. Hide the Users Endpoint

By default, /wp-json/wp/v2/users exposes usernames to anyone, which can be harvested for brute-force attempts. Remove it for unauthenticated requests:

<?php
add_filter( 'rest_endpoints', function( $endpoints ) {
    if ( ! is_user_logged_in() ) {
        unset( $endpoints['/wp/v2/users'] );
        unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
    }
    return $endpoints;
} );
?>

2. Require Authentication for All Requests

If your site has no need to serve public API data, lock down the entire API for unauthenticated callers:

<?php
add_filter( 'rest_authentication_errors', function( $result ) {
    if ( true === $result || is_wp_error( $result ) ) return $result;
    if ( ! is_user_logged_in() ) {
        return new WP_Error(
            'rest_not_logged_in',
            'Authentication required to use this API.',
            array( 'status' => 401 )
        );
    }
    return $result;
} );
?>

3. Rate Limiting and Abuse Prevention

For publicly accessible APIs, implement rate limiting at the server or CDN layer. Cloudflare Rate Limiting rules are a convenient option — set a threshold of requests per IP per minute and return a 429 response for excess traffic. Combine this with server-side logging to detect unusual access patterns early.

Practical Example: Headless WordPress with React

The following example shows a minimal React component that fetches posts from the WordPress REST API, renders them as a list, and handles pagination using the response headers WordPress provides:

// BlogList.jsx
import { useState, useEffect } from 'react';

const WP_API = 'https://yoursite.com/wp-json/wp/v2';

export default function BlogList() {
  const [posts, setPosts]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [page, setPage]     = useState(1);
  const [totalPages, setTotal] = useState(1);

  useEffect(() => {
    async function load() {
      setLoading(true);
      const res = await fetch(
        `${WP_API}/posts?per_page=10&page=${page}&_fields=id,title,slug,excerpt,date`
      );
      setTotal(parseInt(res.headers.get('X-WP-TotalPages') || '1', 10));
      setPosts(await res.json());
      setLoading(false);
    }
    load();
  }, [page]);

  if (loading) return <p>Loading...</p>;

  return (
    <div>
      {posts.map(p => (
        <article key={p.id}>
          <h2 dangerouslySetInnerHTML={{ __html: p.title.rendered }} />
          <div dangerouslySetInnerHTML={{ __html: p.excerpt.rendered }} />
          <a href={`/blog/${p.slug}`}>Read more →</a>
        </article>
      ))}
      <nav>
        <button onClick={() => setPage(n => n - 1)} disabled={page === 1}>Previous</button>
        <span> Page {page} of {totalPages} </span>
        <button onClick={() => setPage(n => n + 1)} disabled={page === totalPages}>Next</button>
      </nav>
    </div>
  );
}

WordPress automatically sends X-WP-Total (total items) and X-WP-TotalPages (total pages) response headers, making it straightforward to build fully functional pagination UI without any extra calculation on the client side.

Frequently Asked Questions (FAQ)

Is the WordPress REST API enabled by default?

Yes. The WordPress REST API has been enabled by default since WordPress 4.7 with no additional plugins required. You can verify it immediately by visiting https://yoursite.com/wp-json/wp/v2/posts in your browser. If you see a JSON array of posts the API is working. If not, check that your Permalinks are not set to Plain under Settings > Permalinks.

How do I restrict the WordPress REST API from public access?

The most common approach is to add a filter in functions.php using the rest_authentication_errors hook combined with is_user_logged_in(). This returns a 401 error for any unauthenticated request. For more granular control you can remove specific routes via the rest_endpoints filter, or require Application Password authentication on a per-route basis using the permission_callback parameter in register_rest_route.

What is the difference between WP_REST_Request and WP_REST_Response?

WP_REST_Request is the object that holds all incoming data from the client HTTP request including URL parameters, headers, and the request body. WP_REST_Response is the object you construct and return from your callback function to send data back to the client. You control the response data, HTTP status code, and headers through it. Both are always used together inside a custom route callback.

How does the WordPress REST API compare to GraphQL?

The REST API exposes separate HTTP endpoints per resource type such as /wp/v2/posts for posts and /wp/v2/users for users, and each response always includes every available field. GraphQL (via the WPGraphQL plugin for example) uses a single endpoint where the client declares exactly which fields it needs, eliminating over-fetching. For most standard projects and small headless setups the REST API is sufficient. GraphQL becomes more advantageous when you have a complex frontend that queries many different resource types with varying field requirements in a single round trip.

为 WordPress 优化的托管

AsiaGB 托管支持 PHP 8.3、MySQL、LiteSpeed 缓存和 WordPress 工具包——起价 500 泰铢/年。

查看托管计划