| name | fluent-wp-client |
| description | Typed WordPress REST API client for TypeScript. Use when implementing features that read or write WordPress content (posts, pages, media, terms, users, comments, settings), work with custom post types or taxonomies, authenticate against WordPress, parse Gutenberg blocks, hydrate post relations, call WordPress Abilities endpoints, design AI/MCP/agent tools from WordPress schemas, or migrate from node-wpapi. Triggers: "WordPress API", "WP REST", "WordPress client", "fluent-wp-client", "posts API", "pages API", "media upload", "custom post type", "CPT", "taxonomy", "Gutenberg blocks", "WP auth", "JWT WordPress", "cookie nonce", "WPAPI", "node-wpapi", "WordPress abilities", "WordPress AI tools", "MCP tools", "agent tools", "WordPress schema tools".
|
fluent-wp-client
Runtime-agnostic TypeScript client for the WordPress REST API with typed CRUD, auth,
relation, ability, and block-parsing APIs. Works in Node, Bun, Deno, and browsers.
Quick start
1. Install
npm install fluent-wp-client
2. Create a client
import { WordPressClient } from 'fluent-wp-client';
const wp = new WordPressClient({
baseUrl: 'https://example.com',
});
3. Read content
const posts = await wp.content('posts').list({ perPage: 10 });
const post = await wp.content('posts').item('hello-world');
const pages = await wp.content('pages').listAll();
4. Create content (requires auth)
const wp = new WordPressClient({
baseUrl: 'https://example.com',
auth: { username: 'admin', password: 'xxxx xxxx xxxx xxxx' },
});
const draft = await wp.content('posts').create({
title: 'New post',
content: '<p>Body content.</p>',
status: 'draft',
});
Authentication
Choose the auth strategy that fits the runtime and use case.
Application password (Basic auth)
Server-to-server or CLI scripts.
const wp = new WordPressClient({
baseUrl: 'https://example.com',
auth: { username: 'admin', password: 'xxxx xxxx xxxx xxxx' },
});
JWT token
User-scoped sessions with the JWT Authentication plugin.
const wp = new WordPressClient({
baseUrl: 'https://example.com',
auth: { token: 'eyJhbG...' },
});
const { token } = await wp.loginWithJwt({
username: 'admin',
password: 'secret',
});
const authed = new WordPressClient({
baseUrl: 'https://example.com',
auth: { token },
});
Pre-built Authorization header
When the header value is already constructed externally.
const wp = new WordPressClient({
baseUrl: 'https://example.com',
authHeader: 'Bearer eyJhbG...',
});
Cookie + nonce (browser)
For WordPress front-end JavaScript where the user is already logged in.
const wp = new WordPressClient({
baseUrl: window.location.origin,
auth: {
nonce: window.wpApiSettings.nonce,
credentials: 'include',
},
});
const me = await wp.users().me();
Request-aware signing (HMAC / custom)
For auth schemes that sign each request individually.
const wp = new WordPressClient({
baseUrl: 'https://example.com',
authHeaders: ({ method, url, body }) => ({
Authorization: computeHmacSignature({ method, url: url.toString(), body }),
}),
});
Per-request auth override
Override client-level auth for a single call.
const { data } = await wp.request({
endpoint: '/wp-json/wp/v2/posts',
auth: { token: 'different-token' },
});
Core resource APIs
Posts
const posts = await wp.content('posts').list({ categories: [3], perPage: 20 });
const allPosts = await wp.content('posts').listAll({ status: 'publish' });
const paginated = await wp.content('posts').listPaginated({ page: 2, perPage: 10 });
const post = await wp.content('posts').item(42);
const bySlug = await wp.content('posts').item('hello-world');
const created = await wp.content('posts').create({ title: 'Title', status: 'draft' });
const updated = await wp.content('posts').update(created.id, { status: 'publish' });
await wp.content('posts').delete(created.id, { force: true });
Pages
const pages = await wp.content('pages').list({ perPage: 50 });
const allPages = await wp.content('pages').listAll();
const page = await wp.content('pages').item(10);
const bySlug = await wp.content('pages').item('about');
const created = await wp.content('pages').create({ title: 'About us', status: 'draft' });
await wp.content('pages').update(created.id, { status: 'publish' });
await wp.content('pages').delete(created.id, { force: true });
Categories and tags
const categories = wp.terms('categories');
const tags = wp.terms('tags');
const cats = await categories.listAll();
const cat = await categories.item('technology');
const created = await categories.create({ name: 'News' });
await categories.update(created.id, { description: 'Latest news' });
await categories.delete(created.id, { force: true });
const allTags = await tags.listAll();
const tag = await tags.create({ name: 'featured' });
Comments
const comments = await wp.comments().list({ post: 42 });
const comment = await wp.comments().create({
post: 42,
content: 'Great article!',
status: 'approve',
});
await wp.comments().update(comment.id, { content: 'Updated comment.' });
await wp.comments().delete(comment.id, { force: true });
Media
const items = await wp.media().list({ mediaType: 'image' });
const item = await wp.media().item(55);
const url = wp.media().getImageUrl(item, 'medium');
const media = await wp.media().upload({
file: imageBlob,
filename: 'cover.jpg',
mimeType: 'image/jpeg',
title: 'Cover image',
alt_text: 'A book cover',
});
await wp.media().update(media.id, { caption: 'New caption' });
await wp.media().delete(media.id, { force: true });
Users
const users = await wp.users().list({ roles: ['author'] });
const user = await wp.users().item(1);
const me = await wp.users().me();
const created = await wp.users().create({
username: 'newauthor',
email: 'author@example.com',
password: 'securepass',
roles: ['author'],
});
await wp.users().update(created.id, { first_name: 'Jane' });
await wp.users().delete(created.id, { reassign: 1, force: true });
Settings
const settings = await wp.settings().get();
await wp.settings().update({ title: 'New Site Title' });
Custom post types and taxonomies
Use content(resource) and terms(resource) for any CPT or custom taxonomy
registered with a rest_base.
const books = wp.content('books');
const list = await books.list({ perPage: 20 });
const allBooks = await books.listAll();
const paged = await books.listPaginated({ page: 2, perPage: 10 });
const book = await books.item(7);
const bySlug = await books.item('my-book');
const created = await books.create({ title: 'New Book', status: 'draft' });
await books.update(created.id, { status: 'publish' });
await books.delete(created.id, { force: true });
const genres = wp.terms('genre');
const list = await genres.list({ perPage: 100 });
const all = await genres.listAll();
const genre = await genres.item('sci-fi');
const created = await genres.create({ name: 'Science Fiction' });
await genres.update(created.id, { name: 'Sci-Fi' });
await genres.delete(created.id, { force: true });
CPT typing
import type { WordPressCustomPost } from 'fluent-wp-client';
type Book = WordPressCustomPost<{
type: 'book';
acf?: { acf_subtitle?: string; acf_summary?: string };
}>;
const books = await wp.content<Book>('books').list();
For full CPT/taxonomy patterns and namespace routing, read
references/custom-endpoints.mdx.
Gutenberg block parsing
Parse serialized block markup into structured block trees.
const blocks = await wp.content('posts').item('hello-world').getBlocks();
const pageBlocks = await wp.content('pages').item('about').getBlocks();
const posts = await wp.content('posts').list({ perPage: 5 });
const firstBlocks = await posts[0].getBlocks();
const content = await wp.content('posts').item('hello-world').getContent();
import { parseWordPressBlocks } from 'fluent-wp-client';
const parsed = await parseWordPressBlocks(content.raw);
getBlocks() requires context=edit (authenticated with edit capabilities).
For custom parser configuration and CPT block parsing, read
references/gutenberg-content.mdx.
Embed and extraction
Request embedded data with embed: true or selective embed: ['author', 'wp:term'], then use typed extraction helpers.
import {
getEmbeddedAuthor,
getEmbeddedTerms,
getEmbeddedFeaturedMedia,
getEmbeddedParent,
getEmbeddableLinkKeys,
} from 'fluent-wp-client';
const post = await wp.content('posts').item('hello-world', { embed: true });
getEmbeddedAuthor(post);
getEmbeddedTerms(post, 'category');
getEmbeddedTerms(post, 'post_tag');
getEmbeddedFeaturedMedia(post);
getEmbeddedParent(post);
const keys = getEmbeddableLinkKeys(post);
Embed keys: author, wp:term, wp:featuredmedia, up, replies, acf:post, acf:term.
Schema Discovery
Before writing mutations against an unfamiliar resource or ability, call .describe() to fetch its live JSON Schema, then convert with z.fromJSONSchema() and pass to create(), update(), or .inputSchema().
wp.content(resource).describe() — create, update, item schemas
wp.terms(resource).describe() — same for taxonomies
wp.ability(name).describe() — input and output schemas
wp.explore() — full catalog of all resources and abilities at once
See docs/schema-discovery.mdx for examples.
AI and MCP tool design
When designing AI, MCP, or agent tools from WordPress data, read
references/agent-tool-design.mdx before proposing package-level factories or adapter
code. Prefer using fluent-wp-client as the schema/discovery layer while the
application owns concrete tool names, permission policy, approval flow, and result
formatting.
WordPress Abilities API
Inspect and execute server-registered abilities at /wp-json/wp-abilities/v1.
const abilities = await wp.getAbilities();
const ability = await wp.getAbility('myplugin/sync-data');
const categories = await wp.getAbilityCategories();
const result = await wp.executeGetAbility<{ title: string }>('test/get-site-title');
const posted = await wp.executeRunAbility('test/update-option', { value: 'new' });
const deleted = await wp.executeDeleteAbility('test/delete-option');
import { z } from 'zod';
const sync = wp
.ability<{ mode: string }, { synced: number }>('myplugin/sync-data')
.inputSchema(z.object({ mode: z.enum(['full', 'delta']) }))
.outputSchema(z.object({ synced: z.number() }));
const output = await sync.run({ mode: 'full' });
For the complete abilities API surface, read
references/abilities.mdx.
Unified resource builders
The 3.0 API centers on content(resource), terms(resource), and first-class resource clients.
const posts = await wp.content('posts').list({ perPage: 10, page: 1 });
const post = await wp.content('posts').item('hello-world');
const created = await wp.content('posts').create({ title: 'New', status: 'draft' });
await wp.content('posts').update(created.id, { title: 'Updated' });
await wp.content('posts').delete(created.id, { force: true });
const books = await wp.content('books').list({ perPage: 5 });
const genres = await wp.terms('genre').list({ perPage: 50 });
const media = await wp.media().list({ perPage: 20 });
const comments = await wp.comments().list({ post: 42 });
const { data } = await wp.request<{ author: { id: number; name: string } }>({
endpoint: '/wp-json/my-plugin/v1/authors/7',
method: 'GET',
});
Use request() whenever the endpoint lives outside the standard wp/v2 resource families.
Meta and ACF fields
Native WordPress meta
const post = await wp.content('posts').create({
title: 'Post with meta',
status: 'draft',
meta: {
custom_field: 'value',
numeric_field: 42,
array_field: ['a', 'b', 'c'],
},
});
ACF fields
const post = await wp.content('posts').create({
title: 'Post with ACF',
status: 'draft',
acf: {
subtitle: 'My subtitle',
priority_score: 85,
related_posts: [10, 20],
},
});
Both meta and acf are extensible Record<string, unknown> fields.
All Zod schemas use .passthrough() so custom plugin fields pass through.
Response validation
WordPress validates request payloads upstream. When local validation is needed, validate in application code with Zod or discovered JSON Schemas.
import { z } from 'zod';
const postSchema = z.object({ id: z.number(), slug: z.string(), status: z.string() });
const created = postSchema.parse(
await wp.content('posts').create({ title: 'Validated', status: 'draft' })
);
const bookSchema = z.object({
id: z.number(),
slug: z.string(),
type: z.literal('book'),
});
const rawBook = await wp.content('books').item('typed-book');
const book = bookSchema.parse(rawBook);
Low-level transport
Use request() for full control over method, body, headers, and response metadata.
const { data, response } = await wp.request<{ synced: boolean }>({
endpoint: '/wp-json/my-plugin/v1/sync',
method: 'POST',
body: { mode: 'full' },
});
const posts = await wp.content('posts').listPaginated({ perPage: 10, page: 1 });
Pagination
- WordPress caps
per_page at 100.
.list() methods return one page of results.
.listAll() methods auto-paginate through every page internally.
.listPaginated() methods return { data, total, totalPages, page, perPage }.
const all = await wp.content('posts').listAll();
const page = await wp.content('posts').listPaginated({ page: 3, perPage: 25 });
console.log(`Page ${page.page} of ${page.totalPages} (${page.total} total)`);
Error handling
All runtime failures throw WordPressClientError (or a subclass). Use instanceof to narrow to specific error types:
import { WordPressClientError, WordPressHttpError } from 'fluent-wp-client';
try {
await wp.content('posts').item(999999);
} catch (error) {
if (error instanceof WordPressHttpError) {
console.log(error.status);
console.log(error.wpCode);
console.log(error.wpMessage);
console.log(error.responseBody);
}
if (error instanceof WordPressClientError) {
console.log(error.kind);
console.log(error.retryable);
}
}
Exported Zod schemas
Reuse or extend the built-in schemas for your own validation:
postSchema, pageSchema, mediaSchema, categorySchema, authorSchema,
commentSchema, settingsSchema, abilitySchema, abilityCategorySchema,
abilityAnnotationsSchema, baseWordPressSchema, contentWordPressSchema,
embeddedMediaSchema, wordPressErrorSchema, postWriteBaseSchema,
updatePostFieldsSchema.
All schemas use .passthrough() so custom fields (ACF, meta, plugin data) survive parsing.
Exported types
Entity types
WordPressPost, WordPressPage, WordPressMedia, WordPressCategory, WordPressTag,
WordPressAuthor, WordPressComment, WordPressSettings, WordPressAbility,
WordPressAbilityCategory, WordPressCustomPost<TExtra>, WordPressPostBase,
WordPressBase, WordPressContent
Auth types
BasicAuthCredentials, JwtAuthCredentials, HeaderAuthCredentials,
CookieNonceAuthCredentials, WordPressAuthConfig, WordPressAuthHeaders,
WordPressAuthHeadersProvider, ResolvableWordPressAuth<TContext>
Filter types
PostsFilter, PagesFilter, MediaFilter, CategoriesFilter, TagsFilter,
UsersFilter, CommentsFilter, PaginationParams
Operation types
PaginatedResponse<T>, FetchResult<T>, DeleteOptions, UserDeleteOptions,
WordPressDeleteResult, WordPressWritePayload, TermWriteInput, UserWriteInput,
ContentResourceClient<T, TCreate, TUpdate>, TermsResourceClient<T, TCreate, TUpdate>
Block types
WordPressParsedBlock, WordPressBlockParser, WordPressContentRecord<T>
Reference docs
Consult these when deeper guidance is needed for a specific topic:
| Reference | When to read |
|---|
| references/usage.mdx | Full method reference, all API styles side by side, CRUD walkthrough, filter and pagination detail |
| references/gutenberg-content.mdx | Rendered vs raw content, block parsing workflows, custom parser setup, CPT block parsing |
| references/custom-endpoints.mdx | CPT/taxonomy patterns, low-level custom endpoint requests, and per-request headers |
| references/abilities.mdx | Ability metadata, direct execution helpers, fluent builder with schemas, exported ability schemas |
| references/schema-discovery.mdx | .describe(), explore(), z.fromJSONSchema() integration and examples |