WP REST API (v1 – Deprecated)

Working with Posts

Back in the Getting Started guide we used posts to demonstrate how to work with the API, but only touched on some of the details. Let’s take a more detailed look at the Post API.

Before We Start

This guide assumes you have a basic knowledge of the API, as well as the prerequisites noted in the Getting Started guide. If you haven’t already, make sure you read that guide to ensure you know the basics.

This guide also assumes that you know how to send requests given how to use them, so the examples will be HTTP requests. I recommend reading the cURL manual or using a higher level tool if you don’t know how to wrangle cURL.

The examples also pretend that your JSON base URL (wp-json in the main WP directory) is located at /, which is probably not the case. For example, if your base URL is http://example.com/wp-json and the example request is GET /posts, you should should actually send the following:

GET /wp-json/posts HTTP/1.1
Host: example.com

Higher level HTTP clients can usually handle this for you.

Post Entities and Collections

As mentioned previously, we can send a GET request to the main posts route to get posts:

GET /posts HTTP/1.1

This returns a list of Post objects. From a technical point of view, each object is called an Entity (in this case, Post Entities), while the endpoint returns a Collection (here, a Post Collection).

Post Entities have certain defined properties, although which are set depend on the context. For example, with embedded entities certain properties may have different values; while the post parent entity is embedded, the parent field inside this is only the ID.

Collection Views

Post Collections can be modified by using various query string parameters. One that you may already know is the page parameter. This parameter is used for pagination, and while you can set it manually, it’s also sent via the Link headers to point you to the next and previous pages. A context parameter is also available that we’ll talk about later.

You can also change the post type via the type parameter. It’s recommended that you check the available post types before doing this, as plugins may change what’s available.

The last parameter is the filter parameter. This gives you full access to the WP_Query parameters, to alter the query to your liking. Depending on the level of access you have, not all parameters will be available, so check the schema for the available parameters. A good assumption to make is that anything you can put in a query on the site itself (such as ?s=... for searches) will be available. You can specify filter parameters in a request using array-style URL formatting.

Creating and Editing Posts

The main posts route also has a creation endpoint:

POST /posts HTTP/1.1
Content-Type: application/json; charset=utf-8

{
    "title": "My Post Title"
}

Here we send a Post entity that we’ve created locally to the server. These Post entities are the same as Post entities served up by the server, but excluding some fields which are immutable. For example, the ID and timezone fields cannot be changed.

When editing posts, it’s helpful to pass the edit context along with your request. This gives you extra data useful for editing, such as the unfiltered content and title. This is not required, however it is recommended, as the normal content and title fields have been filtered to correct HTML (such as via the wpautop function), making them less than optimal for editing.

You can also pass an If-Unmodified-Since header when editing posts with the previous modification date to ensure that you don’t accidentally overwrite edits made in the meantime. Note that while dates in the modified field of the post are in RFC3339 format, this header requires the use of RFC1123 (similar to RFC822). (Sorry about that, but the HTTP standard requires it.)

Custom Post Types

Custom post types can be queried via the main post routes (/posts and children) when they have been made public. The type can be set via the type query parameter.

Before working with custom post types, you should first check that the API has support for the post type that you want to work with. This data is available via the read-only APIs at /posts/types:

GET /posts/types HTTP/1.1

This should return a list of the available types:

{
    "post": {
        "name": "Posts",
        "slug": "post",
        "description": "",
        "labels": {
            "name": "Posts",
            "singular_name": "Post",
            "add_new": "Add New",
            "add_new_item": "Add New Post",
            "edit_item": "Edit Post",
            "new_item": "New Post",
            "view_item": "View Post",
            "search_items": "Search Posts",
            "not_found": "No posts found.",
            "not_found_in_trash": "No posts found in Trash.",
            "parent_item_colon": null,
            "all_items": "All Posts",
            "menu_name": "Posts",
            "name_admin_bar": "Post"
        },
        "queryable": true,
        "searchable": true,
        "hierarchical": false,
        "meta": {
            "links": {
                "self": "http:\/\/example.com\/wp-json\/posts\/types\/post",
                "archives": "http:\/\/example.com\/wp-json\/posts"
            }
        }
    },
    "page": {
        "name": "Pages",
        "slug": "page",
        "description": "",
        "labels": {
            "name": "Pages",
            "singular_name": "Page",
            "add_new": "Add New",
            "add_new_item": "Add New Page",
            "edit_item": "Edit Page",
            "new_item": "New Page",
            "view_item": "View Page",
            "search_items": "Search Pages",
            "not_found": "No pages found.",
            "not_found_in_trash": "No pages found in Trash.",
            "parent_item_colon": "Parent Page:",
            "all_items": "All Pages",
            "menu_name": "Pages",
            "name_admin_bar": "Page"
        },
        "queryable": false,
        "searchable": true,
        "hierarchical": true,
        "meta": {
            "links": {
                "self": "http:\/\/example.com\/wp-json\/posts\/types\/page"
            }
        }
    },
    "attachment": {
        "name": "Media",
        "slug": "attachment",
        "description": "",
        "labels": {
            "name": "Media",
            "singular_name": "Media",
            "add_new": "Add New",
            "add_new_item": "Add New Post",
            "edit_item": "Edit Media",
            "new_item": "New Post",
            "view_item": "View Attachment Page",
            "search_items": "Search Posts",
            "not_found": "No posts found.",
            "not_found_in_trash": "No posts found in Trash.",
            "parent_item_colon": null,
            "all_items": "Media",
            "menu_name": "Media",
            "name_admin_bar": "Media"
        },
        "queryable": true,
        "searchable": true,
        "hierarchical": false,
        "meta": {
            "links": {
                "self": "http:\/\/example.com\/wp-json\/posts\/types\/attachment",
                "archives": "http:\/\/example.com\/wp-json\/posts?type=attachment"
            }
        }
    }
}

The meta.links.archives value gives a nicer way to access given post type archives for HATEOAS supporters and should always be used rather than manually setting the type parameter directly, as CPTs may create their own route structure instead. The labels fields should also always be used when displaying the field, as these are run through WordPress’ translations.

A similar API exists for post statuses at /posts/statuses:

{
    "publish": {
        "name": "Published",
        "slug": "publish",
        "public": true,
        "protected": false,
        "private": false,
        "queryable": true,
        "show_in_list": true,
        "meta": {
            "archives": "http:\/\/example.com\/wp-json\/posts"
        }
    },
    "future": {
        "name": "Scheduled",
        "slug": "future",
        "public": false,
        "protected": true,
        "private": false,
        "queryable": false,
        "show_in_list": true,
        "meta": [
        ]
    },
    "draft": {
        "name": "Draft",
        "slug": "draft",
        "public": false,
        "protected": true,
        "private": false,
        "queryable": false,
        "show_in_list": true,
        "meta": [
        ]
    },
    "pending": {
        "name": "Pending",
        "slug": "pending",
        "public": false,
        "protected": true,
        "private": false,
        "queryable": false,
        "show_in_list": true,
        "meta": [
        ]
    },
    "private": {
        "name": "Private",
        "slug": "private",
        "public": false,
        "protected": false,
        "private": true,
        "queryable": false,
        "show_in_list": true,
        "meta": [
        ]
    }
}

Next Steps

You should now understand more advanced usage of the post-related APIs, and be able to implement a fully compliant client for the API. You might now want to take a look at the other APIs, or look at documentation on the specifics.