Your WordPress is not just a website: it's also a data server. Since version 4.7, the WordPress REST API is integrated into the core and exposes posts, pages, users, media, and taxonomies in JSON format, ready to be read or modified from any external application: a mobile app, an automation script, another website, or a custom control panel. It's the technology that powers the block editor, that thousands of integrations use, and the gateway to what's called "headless" WordPress. And yet, many advanced users have never explored it. In this practical guide, we will see what exactly the WordPress REST API is, how its endpoints are organized, how to authenticate securely with Application Passwords, how to read and create content with real examples (from the browser, with JavaScript, and with PHP), how to create your own endpoints, and what security precautions should be taken. All with functional code that you can try on your own website today.
What is the WordPress REST API and what is it for
A REST API is an interface that allows two systems to communicate over HTTP using the classic protocol operations: GET for reading, POST for creating, PUT/PATCH for updating and DELETE for deleting. The response arrives in JSON, a lightweight text format that any language understands. In the case of WordPress, this means that everything you manage from the dashboard (posts, pages, comments, categories, users, media) also has a structured and official "backdoor" for machines.
Real use cases where the WordPress REST API makes a difference:
- Automation: publish or update content from scripts, spreadsheets, or tools like n8n and Make.
- Headless WordPress: use WordPress only as a content manager and build the frontend with React, Vue, or Astro.
- Mobile apps: the official WordPress app works entirely on this API.
- Website integrations: display the latest articles from one website on another, synchronize catalogs, centralize publishing.
- Custom dashboards: dashboards that read and write to WordPress without going through wp-admin.
The API is also the basis for modern integrations with artificial intelligence: plugins like AI Engine, which turns your WordPress into an AI agent, they rely on it to expose content to external assistants.
Main Endpoints: the API map
Everything starts with a URL: https://tudominio.com/wp-json/. Open it in the browser and you will see the complete index of available routes. Core endpoints hang from the namespace wp/v2:
| Endpoint | Content |
|---|---|
/wp-json/wp/v2/posts |
Blog posts |
/wp-json/wp/v2/pages |
Pages |
/wp-json/wp/v2/media |
Media Library |
/wp-json/wp/v2/categories |
Categories |
/wp-json/wp/v2/tags |
Tags |
/wp-json/wp/v2/users |
Users (public data) |
/wp-json/wp/v2/comments |
Comments |
/wp-json/wp/v2/search |
Global search |
Each collection endpoint supports very useful query parameters: ?per_page=5 limits results, ?search=wordpress searches, ?categories=12 filters by category, ?orderby=date&order=asc sorts and ?_fields=id,title,link returns only the fields you request, lightening the response. For a specific item, its ID is added: /wp-json/wp/v2/posts/123.
WordPress JSON responses arrive unformatted, in a single line that is difficult to read. A workflow trick: paste the response into our free JSON formatter and validator and you will have the indented and colored tree to inspect it comfortably.
Pagination: the headers you should know
Collection endpoints return a maximum of 100 items per request (10 by default). To browse large catalogs, the API includes two HTTP headers in each response: X-WP-Total, with the total number of items, and X-WP-TotalPages, with the number of pages available according to your per_page. Simply iterate by adding ?page=2, ?page=3 and so on until the last one is reached. If you request a non-existent page, the API responds with a 400 error with the code rest_post_invalid_page_number, a convenient signal to stop the loop in export or migration scripts.
Authentication with Application Passwords
Reading public content does not require authentication, but creating, editing, or deleting does. Since WordPress 5.6, the integrated and recommended method is Application Passwords (application passwords): app-specific keys, individually revocable, that do not expose your real password.
To create one: go to Users → Profile, scroll down to "Application Passwords", type a descriptive name (for example "Publishing Script") and click create. WordPress will show you a key in the format xxxx xxxx xxxx xxxx xxxx xxxx. Save it in a password manager or an environment variable file, never in the source code.
The key is used with HTTP Basic authentication: username and application password encoded in Base64 in the header Authorization. Essential requirement: the website must be served over HTTPS, because in plain HTTP the credentials would travel exposed.
Quick test from the terminal with curl:
curl -X POST https://tudominio.com/wp-json/wp/v2/posts \
-u "tu_usuario:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{"title":"Borrador desde la API","status":"draft"}'
If the response is a JSON with the ID of the new draft, authentication works. Each permission depends on the user's role: an application password from a subscriber will not be able to publish posts.
Read and create content: examples with JavaScript
Let's see the API in action with fetch, just as you would on an external website or application. First, read the latest blog articles (without authentication, public content):
const API = 'https://tudominio.com/wp-json/wp/v2';
async function ultimasEntradas() {
const res = await fetch(`${API}/posts?per_page=5&_fields=id,title,link,date`);
if (!res.ok) {
throw new Error('Error HTTP ' + res.status);
}
const posts = await res.json();
posts.forEach((post) => {
console.log(`${post.date} — ${post.title.rendered} — ${post.link}`);
});
}
ultimasEntradas();
And now, create a post by authenticating with an Application Password (this must be executed in a server environment or private script, never in your visitors' browser, because it would expose the key):
const usuario = 'tu_usuario';
const clave = 'xxxx xxxx xxxx xxxx xxxx xxxx';
const token = Buffer.from(`${usuario}:${clave}`).toString('base64');
async function crearEntrada() {
const res = await fetch('https://tudominio.com/wp-json/wp/v2/posts', {
method: 'POST',
headers: {
'Authorization': `Basic ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Publicado por la REST API',
content: '<p>Este contenido lo ha creado un script.</p>',
status: 'draft',
categories: [12],
}),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.message || 'Error al crear la entrada');
}
console.log('Creada con ID', data.id, '→', data.link);
}
crearEntrada();
Note the detail of title.rendered when reading: the API returns text fields as objects with the variants rendered (final HTML) and, if you are authenticated with editing permissions and add ?context=edit, also raw (original content).
Create your own endpoints with register_rest_route
The core API covers standard content, but the true potential comes when you expose your own data. With register_rest_route() you can create custom routes under your own namespace. Full example: an endpoint that returns basic site statistics, with a validated parameter and permission control:
<?php
add_action( 'rest_api_init', function () {
register_rest_route( 'biblioweb/v1', '/estadisticas', array(
'methods' => 'GET',
'callback' => 'biblioweb_api_estadisticas',
'permission_callback' => '__return_true', // Endpoint público de solo lectura.
'args' => array(
'tipo' => array(
'default' => 'post',
'sanitize_callback' => 'sanitize_key',
'validate_callback' => function ( $valor ) {
return post_type_exists( $valor );
},
),
),
) );
} );
function biblioweb_api_estadisticas( WP_REST_Request $request ) {
$tipo = $request->get_param( 'tipo' );
$conteo = wp_count_posts( $tipo );
$usuarios = count_users();
return rest_ensure_response( array(
'tipo' => $tipo,
'publicados' => (int) $conteo->publish,
'borradores' => (int) $conteo->draft,
'usuarios' => (int) $usuarios['total_users'],
'generado_en' => current_time( 'mysql' ),
) );
}
After saving this code (in a plugin or snippet), your new endpoint responds at https://tudominio.com/wp-json/biblioweb/v1/estadisticas?tipo=page. Three golden rules: always use your own versioned namespace (biblioweb/v1), define permission_callback explicitly (it is mandatory since WordPress 5.5; for private endpoints use a check like current_user_can( 'edit_posts' )) and validates each input parameter. The complete reference is in the official WordPress REST API handbook.
Security: what to expose and what to protect
The WordPress REST API is secure by design (it only exposes what is already public and requires permissions to write), but it is advisable to adjust some details:
- User enumeration: the endpoint
/wp/v2/userslists authors with published content, which makes it easier for an attacker to know valid usernames. Many security plugins restrict it; it can also be filtered by code to require authentication. - HTTPS mandatory: without an SSL certificate, Application Passwords travel in plain text. There is no reasonable exception to this rule.
- Principle of least privilege: create application passwords with a user whose role has only the permissions the integration needs, and revoke them when they are no longer used.
- Don't disable the API brutally: the block editor and many plugins depend on it. If you want to limit it, require authentication with the filter
rest_authentication_errorsinstead of blocking it completely. - Limit the surface: public custom endpoints should only return data that you don't mind being visible to anyone.
These precautions fit within a general hardening strategy; if you want to review it, we have a guide with tricks to strengthen your WordPress security.
Frequently Asked Questions
Is the WordPress REST API active on my website?
Almost certainly, yes: it comes activated by default since WordPress 4.7 (2016). Check it by visiting tudominio.com/wp-json/: if you see a JSON with your site name and the list of routes, it is operational. If it returns a 404, check your permalinks or any security plugin that might be blocking it.
Is it dangerous to have the API activated?
No more than having the website published. The read API only exposes content that is already public on your website, and every write operation requires authentication and permissions. The two sensible adjustments are to restrict the user list and ensure everything is served over HTTPS. Completely disabling it usually causes more problems than it prevents.
Can I use the API with custom post types and custom fields?
Yes. Custom post types appear in the API if they were registered with show_in_rest => true, and get their own endpoint (for example /wp/v2/proyecto). Custom fields are exposed with register_post_meta() also checking show_in_rest, or with the field meta of the post itself if the plugin that creates them supports it.
What is the difference between the REST API and WPGraphQL?
The REST API is the core standard: each data type has its endpoint and responses have a fixed structure. GraphQL (via the WPGraphQL plugin) allows you to request exactly the fields you need in a single query, which is popular in complex headless projects. For automations, integrations, and most projects, the REST API is simpler, requires no plugins, and is officially supported.
Conclusion
The WordPress REST API turns your website into a programmable platform: a clear index of endpoints under /wp-json/wp/v2/, integrated authentication with Application Passwords over HTTPS and the possibility of extending the map with custom routes using register_rest_route(). With what you've seen in this guide, you can read public content from any application, publish and edit using authenticated scripts, expose custom data with validation and permissions, and do so with appropriate security precautions. The best way to internalize it is practical: open tudominio.com/wp-json/wp/v2/posts?per_page=3 in the browser, pass the response through a JSON formatter to understand its structure, create your first Application Password and launch a draft from curl. In fifteen minutes you will have crossed the door that separates using WordPress from programming with WordPress.