WordPress REST API: A Complete Guide for Developers
Muhammad Bilal Azhar
Co-Founder & Technical Lead · Google Cloud Certified Professional
WordPress REST API: A Complete Guide for Developers
The WordPress REST API lets you interact with WordPress programmatically. It's the foundation for headless WordPress and enables building modern frontends while keeping WordPress as a content backend.
What Is the REST API?
The REST API exposes WordPress data via HTTP endpoints. Instead of rendering HTML, you get JSON data.
Example:
curl https://example.com/wp-json/wp/v2/posts
Returns:
[
{
"id": 1,
"title": {
"rendered": "Hello World"
},
"content": {
"rendered": "
Welcome to WordPress...
"
},
"excerpt": {
"rendered": "
A short excerpt...
"
},
"date": "2026-02-05T10:00:00",
"author": 1,
"categories": [1, 3],
"tags": [2, 5],
"_links": { ... }
}
]
Core Endpoints
Discovery
Start here to see all available routes:
GET /wp-json/
Posts
GET /wp-json/wp/v2/posts # List posts
GET /wp-json/wp/v2/posts/{id} # Single post
POST /wp-json/wp/v2/posts # Create post
PUT /wp-json/wp/v2/posts/{id} # Update post
DELETE /wp-json/wp/v2/posts/{id} # Delete post
Pages
GET /wp-json/wp/v2/pages # List pages
GET /wp-json/wp/v2/pages/{id} # Single page
POST /wp-json/wp/v2/pages # Create page
PUT /wp-json/wp/v2/pages/{id} # Update page
DELETE /wp-json/wp/v2/pages/{id} # Delete page
Categories & Tags
GET /wp-json/wp/v2/categories # List categories
GET /wp-json/wp/v2/tags # List tags
Users
GET /wp-json/wp/v2/users # List users
GET /wp-json/wp/v2/users/{id} # Single user
GET /wp-json/wp/v2/users/me # Current user (auth required)
Media
GET /wp-json/wp/v2/media # List media
GET /wp-json/wp/v2/media/{id} # Single media item
POST /wp-json/wp/v2/media # Upload media
Comments
GET /wp-json/wp/v2/comments # List comments
POST /wp-json/wp/v2/comments # Create comment
Query Parameters
Filtering Posts
Get 5 posts
/wp-json/wp/v2/posts?per_page=5
Page 2 of results
/wp-json/wp/v2/posts?page=2
Posts in category 3
/wp-json/wp/v2/posts?categories=3
Posts by author 1
/wp-json/wp/v2/posts?author=1
Search posts
/wp-json/wp/v2/posts?search=keyword
Order by date descending
/wp-json/wp/v2/posts?orderby=date&order=desc
Only published posts (default)
/wp-json/wp/v2/posts?status=publish
Include specific posts
/wp-json/wp/v2/posts?include=1,2,3
Exclude specific posts
/wp-json/wp/v2/posts?exclude=4,5,6
Customizing Response
Embed related data (author, featured media, terms)
/wp-json/wp/v2/posts?_embed
Only specific fields
/wp-json/wp/v2/posts?_fields=id,title,excerpt
Posts with specific slug
/wp-json/wp/v2/posts?slug=post-name
Authentication
No Auth (Public Data)
Reading public posts, pages, categories works without authentication:
curl https://example.com/wp-json/wp/v2/posts
Application Passwords (WordPress 5.6+)
1. Go to Users → Your Profile
2. Scroll to "Application Passwords"
3. Generate a password
curl -u "username:xxxx xxxx xxxx xxxx" \
https://example.com/wp-json/wp/v2/posts \
-X POST \
-H "Content-Type: application/json" \
-d '{"title":"My New Post","content":"Content here","status":"draft"}'
JWT Authentication (Plugin Required)
Install a JWT plugin like JWT Authentication for WP REST API:
Get token
curl -X POST https://example.com/wp-json/jwt-auth/v1/token \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"pass"}'
Use token
curl https://example.com/wp-json/wp/v2/posts \
-H "Authorization: Bearer YOUR_TOKEN"
OAuth (Plugin Required)
For third-party applications, use OAuth 1.0a or 2.0 plugins.
Working with Data
Creating a Post
// JavaScript/Node.js
const response = await fetch('https://example.com/wp-json/wp/v2/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('username:app_password'),
},
body: JSON.stringify({
title: 'My New Post',
content: '
Post content here
',
status: 'publish',
categories: [1, 3],
tags: [5, 7],
}),
});
const post = await response.json();
console.log('Created post:', post.id);
Updating a Post
const response = await fetch('https://example.com/wp-json/wp/v2/posts/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + btoa('username:app_password'),
},
body: JSON.stringify({
title: 'Updated Title',
content: 'New content here',
}),
});
Uploading Media
const formData = new FormData();
formData.append('file', imageFile);
formData.append('title', 'My Image');
formData.append('alt_text', 'Description of image');
const response = await fetch('https://example.com/wp-json/wp/v2/media', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('username:app_password'),
},
body: formData,
});
Custom Endpoints
Registering Custom Routes
// In your plugin or theme functions.php
add_action('rest_api_init', function() {
register_rest_route('myplugin/v1', '/featured-posts', [
'methods' => 'GET',
'callback' => 'get_featured_posts',
'permission_callback' => '__return_true',
]);
});
function get_featured_posts($request) {
$posts = get_posts([
'meta_key' => 'featured',
'meta_value' => '1',
'posts_per_page' => 10,
]);
$data = [];
foreach ($posts as $post) {
$data[] = [
'id' => $post->ID,
'title' => $post->post_title,
'excerpt' => get_the_excerpt($post),
'link' => get_permalink($post),
];
}
return new WP_REST_Response($data, 200);
}
Access at: /wp-json/myplugin/v1/featured-posts
With Parameters
register_rest_route('myplugin/v1', '/posts-by-author/(?P\d+)', [
'methods' => 'GET',
'callback' => 'get_posts_by_author',
'permission_callback' => '__return_true',
'args' => [
'author_id' => [
'required' => true,
'validate_callback' => function($param) {
return is_numeric($param);
},
],
],
]);
function get_posts_by_author($request) {
$author_id = $request->get_param('author_id');
// Fetch and return posts
}
Protected Routes
register_rest_route('myplugin/v1', '/private-data', [
'methods' => 'GET',
'callback' => 'get_private_data',
'permission_callback' => function() {
return current_user_can('edit_posts');
},
]);
Extending Default Endpoints
Adding Custom Fields to Response
add_action('rest_api_init', function() {
register_rest_field('post', 'reading_time', [
'get_callback' => function($post) {
$content = get_post_field('post_content', $post['id']);
$word_count = str_word_count(strip_tags($content));
return ceil($word_count / 200) . ' min read';
},
'schema' => [
'description' => 'Estimated reading time',
'type' => 'string',
],
]);
});
Now every post response includes reading_time.
Including ACF Fields
add_action('rest_api_init', function() {
register_rest_field('post', 'acf_fields', [
'get_callback' => function($post) {
return get_fields($post['id']);
},
]);
});
Building Headless WordPress
React/Next.js Example
// lib/api.js
const API_URL = process.env.WORDPRESS_API_URL;
export async function getPosts() {
const res = await fetch(${API_URL}/wp-json/wp/v2/posts?_embed);
const posts = await res.json();
return posts.map(post => ({
id: post.id,
title: post.title.rendered,
excerpt: post.excerpt.rendered,
content: post.content.rendered,
date: post.date,
slug: post.slug,
featuredImage: post._embedded?.['wp:featuredmedia']?.[0]?.source_url,
author: post._embedded?.['author']?.[0]?.name,
categories: post._embedded?.['wp:term']?.[0],
}));
}
export async function getPostBySlug(slug) {
const res = await fetch(
${API_URL}/wp-json/wp/v2/posts?slug=${slug}&_embed
);
const posts = await res.json();
return posts[0];
}
Next.js Page
// app/blog/[slug]/page.tsx
import { getPostBySlug, getPosts } from '@/lib/api';
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function Post({ params }) {
const post = await getPostBySlug(params.slug);
return (
);
}
Common Issues
CORS Errors
Add to functions.php or a plugin:
add_action('rest_api_init', function() {
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
add_filter('rest_pre_serve_request', function($value) {
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
return $value;
});
}, 15);
Authentication Issues
- Ensure SSL (HTTPS) for production
- Check Application Password format (spaces matter)
- Verify user has required capabilities
Missing Data
- Use
_embedto include related data
- Check if custom post types expose to REST
- Register REST fields for custom data
Performance Considerations
API Response Time
WordPress REST API can be slow:
- Database queries per request
- Plugin overhead
- No built-in caching
Solutions
Server-side caching:
add_action('rest_api_init', function() {
// Cache API responses
header('Cache-Control: public, max-age=300');
});
CDN/Edge caching:
- Cloudflare API caching
- Fastly
- AWS CloudFront
Static Site Generation:
Fetch at build time, not runtime:
// Next.js ISR
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export const revalidate = 60; // Rebuild every 60 seconds
FAQ
Q: Is the REST API secure?
Yes, for read operations on public content. Write operations require authentication. Always use HTTPS. See our WordPress security guide →
Q: Can I disable the REST API?
Partially. You can restrict certain endpoints, but core functionality depends on it.
Q: How do I get custom post types via API?
Ensure show_in_rest is true when registering the post type:
register_post_type('book', [
'show_in_rest' => true,
'rest_base' => 'books',
// ... other args
]);
Q: Why are some fields missing?
Not all meta fields are exposed by default. Use register_rest_field() to add them.
Q: What's the best approach for performance?
Static site generation fetches data at build time. See our headless WordPress tutorial →
Conclusion
The WordPress REST API enables:
- Headless WordPress architectures
- Mobile app backends
- Third-party integrations
- Custom admin interfaces
For the best performance, consider building static sites that fetch from the API at build time rather than on each request.
Related guides:
Related Articles
View allHow to Choose a Headless CMS in 2026: Decision Guide
Navigate the headless CMS landscape with this comprehensive decision guide. Compare Sanity, Contentful, Strapi, Payload, and more.
Headless CMS vs Traditional CMS: When to Use Which
Understand the differences between headless and traditional CMS. Learn which approach fits your project based on real requirements.
Headless WordPress in 2026: Is It Worth the Complexity?
Headless WordPress promises the best of both worlds - WordPress's familiar editor with modern frontend performance. But is the complexity worth it? We analyze the real costs.
WordPress Database Explained: A Complete Guide
Understand how WordPress stores data. Tables, relationships, and why this matters for performance and migration.