A WordPress REST API client for JavaScript
This is a client for the WordPress REST API. It is under active development, and should be considered beta software. More features are in progress, and issues are welcome if you find something that doesn't work!
wpapi
is designed to work with WP-API v2 beta 1 or higher. If you use a prior version of the beta, some commands will not work. The latest beta is always recommended!
Index:
Purpose
This library is designed to make it easy for your Node.js application to request specific resources from a WordPress install. It uses a query builder-style syntax to let you craft the request being made to the WP-API endpoints, then returns the API server's response to your application as a JavaScript object.
Installation
To use the library, install it with npm:
npm install --save wpapi
Then, within your application's script files, require
the module to gain access to it:
var WP = require( 'wpapi' );
This library requires Node.js version 0.12 or above; 4.0 or higher is recommended.
This library is designed to work in the browser as well, via a build system such as Browserify or Webpack; alternatively, the files in the browser/
folder of the release archives are pre-built UMD modules that can be added to a page using a regular <script>
tag or required via AMD or CommonJS module systems. In the absence of a module system, the UMD modules will export the browser global variable WPAPI
, which can be used in place of require( 'wpapi' )
in the examples below.
Using The Client
The module is a constructor, so you can create an instance of the API client bound to the endpoint for your WordPress install:
var WP = require( 'wpapi' );
var wp = new WP({ endpoint: 'http://src.wordpress-develop.dev/wp-json' });
Once an instance is constructed, you can chain off of it to construct a specific request. (Think of it as a query-builder for WordPress!)
We support requesting posts using either a callback-style or promise-style syntax:
wp.posts().get(function( err, data ) {
if ( err ) {
}
});
wp.posts().then(function( data ) {
}).catch(function( err ) {
});
The wp
object will have endpoint handler methods for every endpoint that ships with the default WordPress REST API plugin.
Self-signed (Insecure) HTTPS Certificates
In a case where you would want to connect to a HTTPS WordPress installation that has a self-signed certificate (insecure), you will need to force a connection by placing the following line before you make any wp
calls.
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
Auto-Discovery
It is also possible to leverage the capability discovery features of the API to automatically detect and add setter methods for your custom routes, or routes added by plugins.
To utilize the auto-discovery functionality, call WP.discover()
with a URL within a WordPress REST API-enabled site:
var apiPromise = WP.discover( 'http://my-site.com' );
If auto-discovery succeeds this method returns a promise that will be resolved with a WP client instance object configured specifically for your site. You can use that promise as the queue that your client instance is ready, then use the client normally within the .then
callback.
Custom Routes will be detected by this process, and registered on the client. To prevent name conflicts, only routes in the wp/v2
namespace will be bound to your instance object itself. The rest can be accessed through the .namespace
method on the WP instance, as demonstrated below.
apiPromise.then(function( site ) {
site.posts().then(function( posts ) {
console.log( posts );
});
site.namespace( 'myplugin/v1' ).authors()
.then(function( authors ) { });
var myplugin = site.namespace( 'myplugin/v1' );
myplugin.authors()
.id( 7 )
.then(function( author ) { });
});
Bootstrapping
If you are building an application designed to interface with a specific site, it is possible to sidestep the additional asynchronous HTTP calls that are needed to bootstrap the client through auto-discovery. You can download the root API response, i.e. the JSON response when you hit the root endpoint such as your-site.com/wp-json
, and save that JSON file locally; then, in
your application code, just require in that JSON file and pass the routes property into the WP
constructor or the WP.site
method.
Note that you must specify the endpoint URL as normal when using this approach.
var apiRootJSON = require( './my-endpoint-response.json' );
var site = new WP({
endpoint: 'http://my-site.com/wp-json',
routes: apiRootJSON.routes
});
site.namespace( 'myplugin/v1' ).authors()...
To create a slimmed JSON file dedicated to this particular purpose, see the Node script lib/data/generate-endpoint-request.js, which will let you download and save an endpoint response to your local project.
In addition to retrieving the specified resource with .get()
, you can also .create()
, .update()
and .delete()
resources:
Creating Posts
To create posts, use the .create()
method on a query to POST (the HTTP verb for "create") a data object to the server:
var wp = new WP({
endpoint: 'http://your-site.com/wp-json',
username: 'someusername',
password: 'password'
});
wp.posts().create({
title: 'Your Post Title',
content: 'Your post content',
status: 'publish'
}).then(function( response ) {
console.log( response.id );
})
This will work in the same manner for resources other than post
: you can see the list of required data parameters for each resource on the WP REST API Documentation Website.
Updating Posts
To create posts, use the .update()
method on a single-item query to PUT (the HTTP verb for "update") a data object to the server:
var wp = new WP({
endpoint: 'http://your-site.com/wp-json',
username: 'someusername',
password: 'password'
});
wp.posts().id( 2501 ).update({
title: 'A Better Title',
status: 'publish'
}).then(function( response ) {
console.log( response );
})
This will work in the same manner for resources other than post
: you can see the list of required data parameters for each resource on the WP REST API Documentation Website.
Requesting Different Resources
A WP instance object provides the following basic request methods:
wp.posts()...
: Request items from the /posts
endpointswp.pages()...
: Start a request for the /pages
endpointswp.types()...
: Get Post Type collections and objects from the /types
endpointswp.comments()...
: Start a request for the /comments
endpointswp.taxonomies()...
: Generate a request against the /taxonomies
endpointswp.tags()...
: Get or create tags with the /tags
endpointwp.categories()...
: Get or create categories with the /categories
endpointwp.statuses()...
: Get resources within the /statuses
endpointswp.users()...
: Get resources within the /users
endpointswp.media()...
: Get Media collections and objects from the /media
endpoints
All of these methods return a customizable request object. The request object can be further refined with chaining methods, and/or sent to the server via .get()
, .create()
, .update()
, .delete()
, .headers()
, or .then()
. (Not all endpoints support all methods; for example, you cannot POST or PUT records on /types
, as these are defined in WordPress plugin or theme code.)
Additional querying methods provided, by endpoint:
- posts
wp.posts()
: get a collection of posts (default query)wp.posts().id( n )
: get the post with ID nwp.posts().id( n ).revisions()
: get a collection of revisions for the post with ID nwp.posts().id( n ).revisions( rn )
: get revision rn for the post with ID n
- pages
wp.pages()
: get a collection of page itemswp.pages().id( n )
: get the page with numeric ID nwp.pages().path( 'path/str' )
: get the page with the root-relative URL path path/str
wp.pages().id( n ).revisions()
: get a collection of revisions for the page with ID nwp.pages().id( n ).revisions( rn )
: get revision rn for the page with ID n
- comments
wp.comments()
: get a collection of all public commentswp.comments().id( n )
: get the comment with ID n
- taxonomies
wp.taxonomies()
: retrieve all registered taxonomieswp.taxonomies().taxonomy( 'taxonomy_name' )
: get a specific taxonomy object with name taxonomy_namewp.taxonomies().taxonomy( 'taxonomy_name' ).terms()
: get all terms for taxonomy taxonomy_namewp.taxonomies().taxonomy( 'taxonomy_name' ).term( termIdentifier )
: get the term with slug or ID termIdentifier from the taxonomy taxonomy_name
- categories
wp.categories()
: retrieve all registered categorieswp.categories().id( n )
: get a specific category object with id n
- tags
wp.tags()
: retrieve all registered tagswp.tags().id( n )
: get a specific tag object with id n
- types
wp.types()
: get a collection of all registered public post typeswp.types().type( 'cpt_name' )
: get the object for the custom post type with the name cpt_name
- statuses
wp.statuses()
: get a collection of all registered public post statuses (if the query is authenticated—will just display "published" if unauthenticated)wp.statuses().status( 'slug' )
: get the object for the status with the slug slug
- users
wp.users()
: get a collection of registered userswp.users().id( n )
: get the user with ID n (does not require authentication if that user is a published author within the blog)wp.users().me()
: get the authenticated user's record
- media
wp.media()
: get a collection of media objects (attachments)wp.media().id( n )
: get media object with ID n
For security reasons, methods like .revisions()
and .users()
require the request to be authenticated.
Filtering Collections
Queries against collection endpoints (like wp.posts()
, which maps to endpoint/posts/
) can be filtered to specify a subset of posts to return. Many of the WP_Query values are available by default, including tag
, author_name
, page_id
, etc; even more parameters are available to filter byif you authenticate with the API using either Basic Auth or OAuth. You can continue to chain properties until you call .then
, .get
, .post
, .put
, or .delete
on the request chain.
Example queries:
wp.posts().filter( 'author_name', 'jadenbeirne' ).get();
wp.posts().filter({
category_name: 'islands',
tag: [ 'clouds', 'sunset' ]
}).get();
wp.posts().category( 7 ).tag( 'music' ).get();
wp.posts().author( 'williamgibson' ).get();
wp.posts().author( 42 ).get();
wp.posts().author( 42 ).author( 'frankherbert' ).get();
wp.posts().perPage( 20 )...
wp.posts().perPage( 20 ).page( 2 )...
wp.posts()
.author( 'jadenbeirne' )
.perPage( 5 )
.tag( 'fiction' )
.get();
Filtering Shortcut Methods
The following methods are shortcuts for filtering the requested collection down by various commonly-used criteria:
.category( category )
: find posts in a specific category.tag( tag )
: find posts with a specific tag.taxonomy( name, term )
: find items with a specific taxonomy term.search( searchString )
: find posts containing the specified search term(s).author( author )
: find posts by a specific author, designated either by name or by ID.name( slug )
: find the post with the specified slug.slug( slug )
: alias for .name()
.year( year )
: find items published in the specified year.month( month )
: find items published in the specified month, designated by the month index (1–12) or name (e.g. "February").day( day )
: find items published on the specified day
Uploading Media
Files may be uploaded to the WordPress media library by creating a media record using the .media()
collection handler.
If you wish to associate a newly-uploaded media record to a specific post, you must use two calls: one to first upload the file, then another to associate it with a post. Example code:
wp.media()
.file( '/path/to/the/image.jpg' )
.create({
title: 'My awesome image',
alt_text: 'an image of something awesome',
caption: 'This is the caption text',
description: 'More explanatory information'
})
.then(function( response ) {
var newImageId = response.id;
return wp.media().id( newImageId ).update({
post: associatedPostId
});
})
.then(function( response ) {
console.log( 'Media ID #' + response.id );
console.log( 'is now associated with Post ID #' + response.post );
});
If you are uploading media from the client side, you can pass a reference to a file input's file list entry in place of the file path:
wp.media()
.file( document.getElementById( 'file-input' ).files[0] )
.create()...
Custom Routes
Support for Custom Post Types is provided via the .registerRoute
method. This method returns a handler function which can be assigned to your site instance as a method, and takes the same namespace and route string arguments as rest_register_route
:
var site = new WP({ endpoint: 'http://www.yoursite.com/wp-json' });
site.myCustomResource = site.registerRoute( 'myplugin/v1', '/author/(?P<id>)' );
site.myCustomResource().id( 17 );
The string (?P<id>)
indicates that a level of the route for this resource is a dynamic property named ID. By default, properties identified in this fashion will not have any inherent validation. This is designed to give developers the flexibility to pass in anything, with the caveat that only valid IDs will be accepted on the WordPress end.
You might notice that in the example from the official WP-API documentation, a pattern is specified with a different format: this is a regular expression designed to validate the values that may be used for this capture group.
var site = new WP({ endpoint: 'http://www.yoursite.com/wp-json' });
site.myCustomResource = site.registerRoute( 'myplugin/v1', '/author/(?P<id>\\d+)' );
site.myCustomResource().id( 7 );
site.myCustomResource().id( 'foo' );
Adding the regular expression pattern (as a string) enabled validation for this component. In this case, the \\d+
will cause only numeric values to be accepted.
NOTE THE DOUBLE-SLASHES in the route definition here, however: '/author/(?P<id>\\d+)'
This is a JavaScript string, where \
must be written as \\
to be parsed properly. A single backslash will break the route's validation.
Each named group in the route will be converted into a named setter method on the route handler, as in .id()
in the example above: that name is taken from the <id>
in the route string.
The route string 'pages/(?P<parentPage>[\d]+)/revisions/(?P<id>[\d]+)'
would create the setters .parentPage()
and id()
, permitting any permutation of the provided URL to be created.
To permit custom parameter support methods on custom endpoints, a configuration object may be passed to the registerRoute
method with a mixins
property defining any functions to add:
site.handler = site.registerRoute( 'myplugin/v1', 'collection/(?P<id>)', {
mixins: {
myParam: function( val ) {
return this.param( 'my_param', val );
}
}
});
This permits a developer to extend an endpoint with arbitrary parameters in the same manner as is done for the automatically-generated built-in route handlers.
Re-utilizing existing mixins (like .search()
) on custom routes will be supported in the near future.
Setter method naming for named route components
In the example above, registering the route string '/author/(?P<id>\\d+)'
results in the creation of an .id()
method on the resulting resource handler:
site.myCustomResource().id( 7 );
If a named route component (e.g. (?P<id>\\d+)
, above) is in snake_case
, then that setter will be converted to camelCase instead, as with some_part
below:
site.myCustomResource = site.registerRoute( 'myplugin/v1', '/resource/(?P<some_part>\\d+)' );
site.myCustomResource().somePart( 7 );
Non-snake_cased route parameter names will be unaffected.
Embedding data
Note: This section applies only to the WP-API v2 betas and above; the initial 1.0 release of the API embedded data by default.
Data types in WordPress are interrelated: A post has an author, some number of tags, some number of categories, etc. By default, the API responses will provide pointers to these related objects, but will not embed the full resources: so, for example, the "author"
property would come back as just the author's ID, e.g. "author": 4
.
This functionality provides API consumers the flexibility to determine when and how they retrieve the related data. However, there are also times where an API consumer will want to get the most data in the fewest number of responses. Certain resources (author, comments, tags, and categories, to name a few) support embedding, meaning that they can be included in the response if the _embed
query parameter is set.
To request that the API respond with embedded data, simply call .embed()
as part of the request chain:
wp.posts().id( 2501 ).embed()
...
This will include an ._embedded
object in the response JSON, which contains all of those embeddable objects:
{
"_embedded": {
"author": [ ],
"replies": [ ],
"http://v2.wp-api.org/attachment": [ ],
"http://v2.wp-api.org/term": [
[ {}, {} ],
[ {} ],
],
"http://v2.wp-api.org/meta": [ ]
}
}
For more on working with embedded data, check out the WP-API documentation.
Working with Paged Response Data
WordPress sites can have a lot of content—far more than you'd want to pull down in a single request. The API endpoints default to providing a limited number of items per request, the same way that a WordPress site will default to 10 posts per page in archive views. The number of objects you can get back can be adjusted by calling the perPage
method, but many servers will return a 502 error if too much information is requested in one batch.
To work around these restrictions, paginated collection responses are augmented with a _paging
property. That property contains some useful metadata:
total
: The total number of records matching the provided querytotalPages
: The number of pages available (total
/ perPage
)next
: A WPRequest object pre-bound to the next page of resultsprev
: A WPRequest object pre-bound to the previous page of resultslinks
: an object containing the parsed link
HTTP header data (when present)
The existence of the _paging.links.prev
and _paging.links.next
properties can be used as flags to conditionally show or hide your paging UI, if necessary, as they will only be present when an adjacent page of results is available.
You can use the next
and prev
properties to traverse an entire collection, should you so choose. For example, this snippet will recursively request the next page and concatenate it with existing results, in order to build up an array of every post on your site:
getAll( request ) {
return request.then(function( response ) {
if ( ! response._paging || ! response._paging.next ) {
return response;
}
return Promise.all([
response,
getAll( response._paging.next )
]).then(function( responses ) {
return _.flatten( responses );
});
});
}
getAll( wp.posts() ).then(function( allPosts ) { });
Be aware that this sort of unbounded recursion can take a very long time: if you use this technique in your application, we strongly recommend caching the response objects in a local database rather than re-requesting from the WP remote every time you need them.
You can also use a .page(pagenumber)
method on calls that support pagination to directly get that page.
Authentication
You must be authenticated with WordPress to create, edit or delete resources via the API. Some WP-API endpoints additionally require authentication for GET requsts in cases where the data being requested could be considered private: examples include any of the /users
endpoints, requests where the context
query parameter is true
, and /revisions
for posts and pages, among others.
This library currently supports basic HTTP authentication. To authenticate with your WordPress install,
- Download and install the Basic Authentication handler plugin on your target WordPress site. (Note that the basic auth handler is not curently available through the plugin repository: you must install it manually.)
- Activate the plugin.
- Specify the username and password of an authorized user (a user that can edit_posts) when instantiating the WP request object:
var wp = new WP({
endpoint: 'http://www.website.com/wp-json',
username: 'someusername',
password: 'thepasswordforthatuser'
});
Now any requests generated from this WP instance will use that username and password for basic authentication if the targeted endpoint requires it.
As an example, wp.users().me()
will automatically enable authentication to permit access to the /users/me
endpoint. (If a username and password had not been provided, a 401 error would have been returned.)
Manually forcing authentication
Because authentication may not always be set when needed, an .auth()
method is provided which can enable authentication for any request chain:
wp.posts().id( 817 ).auth().get(...
This .auth
method can also be used to manually specify a username and a password as part of a request chain:
wp.posts().id( 817 ).auth( 'mcurie', 'nobel' ).get(...
This will override any previously-set username or password values.
Authenticate all requests for a WP instance
It is possible to make all requests from a WP instance use authentication by setting the auth
option to true
on instantiation:
var wp = new WP({
endpoint:
username:
password:
auth: true
});
Cookie authentication
When the library is loaded from the frontend of the WP-site you are querying against, you can utilize the build in Cookie authentication supported by WP REST API.
First localize your scripts with an object with root-url and nonce in your theme's functions.php
or your plugin::
function my_enqueue_scripts() {
wp_enqueue_script( 'app', get_template_directory_uri() . '/assets/dist/bundle.js', array(), false, true );
wp_localize_script( 'app', 'WP_API_Settings', array(
'endpoint' => esc_url_raw( get_json_url() ),
'nonce' => wp_create_nonce( 'wp_json' ) )
);
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );
And then use this nonce when initializing the library:
var WP = require( 'wpapi' );
var wp = new WP({
endpoint: window.WP_API_Settings.endpoint,
nonce: window.WP_API_Settings.nonce
});
SECURITY WARNING
Please be aware that basic authentication sends your username and password over the wire, in plain text. We only recommend using basic authentication in production if you are securing your requests with SSL.
More robust authentication methods will hopefully be added; we would welcome contributions in this area!
API Documentation
In addition to the above getting-started guide, we have automatically-generated API documentation. More user-oriented documentation, including a more in-depth overview of available endpoint and filter methods, will be added to this README in the near future.
Issues
If you identify any errors in this module, or have an idea for an improvement, please open an issue. We're excited to see what the community thinks of this project, and we would love your input!
Contributing
We welcome contributions large and small. See our contributor guide for more information.