Skip to main content

wordpress_client 10.0.0 copy "wordpress_client: ^10.0.0" to clipboard
wordpress_client: ^10.0.0 copied to clipboard

A library to interact with the Wordpress REST API. Supports most of the common endpoints and all of the CRUD operations on the endpoints.

WordPress Client

Pub Version License Stars
Dart Flutter WordPress
A strongly typed WordPress client for Dart & Flutter: REST, GraphQL and the Abilities API.

Features #

  • Typed REST client for 30+ endpoints (posts, pages, media, users, comments, taxonomies, blocks, templates, navigation, widgets, settings, themes, ...) with full CRUD. See Supported REST Methods.
  • Typed results: WordpressResponse<T> is a success or a failure; failures classify into a sealed WordpressFailure.
  • Auth: Application Passwords, JWT (two plugins), or your own IAuthorization. Credentials never follow the client to another origin.
  • Resilience: opt-in retries with backoff and Retry-After.
  • Caching: GET cache with per-resource invalidation and ETag revalidation. Custom stores supported.
  • Pagination: listPages / listAll, plus parallel page fetching.
  • GraphQL (WPGraphQL): queries, mutations, partial results, GET, pagination, batching, persisted queries.
  • Abilities API (WordPress 6.9+).
  • Extensibility: middlewares, Dio interceptors, custom endpoints, raw requests, polling streams.
  • Testable: MockWordpressAdapter serves canned responses, no live site needed.

Install #

dependencies:
  wordpress_client: ^10.0.0

Requires Dart >=3.0.0.

Quick start #

import 'package:wordpress_client/wordpress_client.dart';

// From the site root (resolves <site>/wp-json/wp/v2)
final client = WordpressClient.forSite(
  siteUrl: Uri.parse('https://example.com'),
);

// Or from the REST base URL
final same = WordpressClient(
  baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'),
);

final response = await client.posts.list(ListPostRequest(perPage: 10));

switch (response) {
  case WordpressSuccessResponse():
    print(response.data.map((p) => p.title?.rendered)); // List<Post>
  case WordpressFailureResponse():
    print('${response.code}: ${response.message}');
}

Create the client once and reuse it. Call client.dispose() when done.

Configure #

final client = WordpressClient.forSite(
  siteUrl: Uri.parse('https://example.com'),
  bootstrapper: (b) => b
      .withDefaultAuthorization(
        WordpressAuth.appPassword(user: 'admin', appPassword: 'xxxx xxxx xxxx'),
      )
      .withRequestTimeout(const Duration(seconds: 20))
      .withRetryPolicy(const RetryPolicy.standard())
      .withCache(ttl: const Duration(minutes: 5))
      .withDefaultUserAgent('my-app/1.0')
      .build(),
);

More: Bootstrap and Configuration, Authorization.

Read, create, update, delete #

final post = (await client.posts.retrieve(RetrievePostRequest(id: 1))).dataOrThrow();

await client.posts.create(CreatePostRequest(
  title: 'Hello',
  content: '<p>First post</p>',
  status: 'draft',
));

await client.posts.update(UpdatePostRequest(id: 1, title: 'Renamed'));
await client.posts.delete(DeletePostRequest(id: 1, force: true));

Write requests need credentials (withDefaultAuthorization, or authorization: on the request).

Handle results #

final r = await client.posts.list(ListPostRequest());

final posts = r.dataOrThrow();                         // throws on failure
final safe = r.dataOrNull();                           // null on failure
final count = r.map(onSuccess: (s) => s.data.length, onFailure: (_) => 0);

if (r case WordpressFailureResponse(:final failure)) {
  switch (failure) {
    case NetworkFailure(:final kind): print('network: $kind');       // timeout, connection, cancelled
    case AuthFailure(:final reason):  print('auth: $reason');
    case ApiFailure(:final statusCode, :final error): print('$statusCode ${error?.code}');
    case DecodeFailure():             print('bad response body');
    case MiddlewareFailure():         print('a middleware aborted it');
    case ClientFailure(:final type):  print('client: $type');
  }
}

More: Error Handling and Responses.

Query fluently #

final res = await client.posts.query
    .withSearch('flutter')
    .withCategories([3, 7])
    .withOrder(Order.desc)
    .withPerPage(20)
    .execute();

final bySlug = await client.pages.extensions.findBySlug('about'); // Page?

More: Fluent Queries, Interfaces and Extensions.

Paginate #

// Everything in one response (use maxItems to cap it)
final all = await client.posts.listAll(ListPostRequest(), maxItems: 1000);

// Lazily, one response per page
await for (final page in client.posts.listPages(ListPostRequest(), perPage: 50)) {
  // page is a WordpressResponse<List<Post>>; a failing page ends the stream
}

// Pages in parallel
final merged = await client.parallel
    .list<Post, ListPostRequest>(
      interface: client.posts,
      requestBuilder: () => [
        for (var p = 1; p <= 10; p++)
          ParallelRequest(page: p, request: ListPostRequest(page: p, perPage: 50)),
      ],
    )
    .merge();

More: Pagination and Finders, Parallel Requests.

Retries #

Off by default. Retries timeouts, connection errors and 429/502/503/504, only for idempotent methods (never POST/PATCH).

b.withRetryPolicy(const RetryPolicy(maxAttempts: 4, baseDelay: Duration(seconds: 1)))

// override for one raw request
client.raw(WordpressRequest(
  method: HttpMethod.get,
  url: RequestUrl.relative('posts'),
  retryPolicy: RetryPolicy.none,
));

More: Retries.

Cache #

b.withCache(
  ttl: const Duration(minutes: 5),
  ttlFor: (url) => url.path.endsWith('/categories') ? const Duration(hours: 1) : null,
)

GET responses are cached per URL, query and credential. A write evicts only the resource it touched. Expired entries with an ETag are revalidated with a 304. More: Caching.

GraphQL (WPGraphQL) #

final res = await client.graphql.query<List<Map<String, dynamic>>>(
  document: r'query($n: Int!) { posts(first: $n) { nodes { id title } } }',
  variables: {'n': 5},
  parseData: (d) => (d['posts']['nodes'] as List).cast<Map<String, dynamic>>(),
);

// Keep data and errors together (partial results)
final result = await client.graphql.execute<Map<String, dynamic>>(
  document: '{ viewer { name } }',
  parseData: (d) => d,
  requireAuth: true,
);
if (result case WordpressSuccessResponse(:final data)) {
  print(data.data); print(data.errors);
}

Also mutate, useGet, paginate / queryAll, batch, persisted queries and registered queries. More: GraphQL.

Abilities API (WordPress 6.9+) #

final abilities = (await client.abilities.list(category: 'site')).dataOrThrow();
final out = await client.abilities.run('my-plugin/get-user-info', input: {'user_id': 1});

Requires an authenticated user. More: Abilities.

Middlewares #

client.registerMiddleware(
  DelegatedMiddleware(
    onRequestDelegate: (req) async {
      print('-> ${req.method.name} ${req.extra[WordpressRequest.metaUrl]}');
      return req;
    },
    onResponseDelegate: (res) async {
      print('<- ${res.code} in ${res.duration.inMilliseconds}ms');
      return res;
    },
  ),
);

More: Middlewares.

Custom endpoints and raw requests #

// Any URL on the client's origin, untyped
final raw = await client.raw(WordpressRequest(
  method: HttpMethod.get,
  url: RequestUrl.relative('posts'),
  queryParameters: {'per_page': 1},
));
print(raw.code); print(raw.data);

Typed custom endpoints: Using Custom Requests. Polling streams: Streaming and Polling.

Test your code #

import 'package:dio/dio.dart';
import 'package:wordpress_client/testing.dart';
import 'package:wordpress_client/wordpress_client.dart';

final adapter = MockWordpressAdapter()
  ..reply('GET', '/posts', MockResponse.paged([{'id': 1}], total: 1, totalPages: 1))
  ..reply('POST', '/posts', MockResponse.wpError(statusCode: 403, code: 'rest_cannot_create'));

final client = WordpressClient.fromDioInstance(
  baseUrl: Uri.parse('https://example.com/wp-json/wp/v2'),
  instance: Dio()..httpClientAdapter = adapter,
);

expect(adapter.callCount(method: 'GET', path: '/posts'), 1);

More: Testing.

Security notes #

  • A request is only sent to the client's own origin (scheme, host and port). Anything else fails with RequestErrorType.originMismatch.
  • reconfigure(baseUri: ...) to a different origin drops the default credentials and discovery data. Pass new credentials in the same call.
  • Cache keys hold a SHA-256 digest of the Authorization header, never the header itself.

Documentation #

Everything is in the Wiki:

Start Requests Behaviour Extend
Usage Sending Requests Retries Middlewares
Bootstrap and Configuration Fluent Queries Caching Using Custom Requests
Authorization Pagination and Finders Error Handling and Responses Raw Requests
Supported REST Methods Parallel Requests Events and Statistics Integration with Storage Packages
API Changelog GraphQL, Abilities Streaming and Polling Testing

Contributing #

Bugs and feature requests go to the issue tracker. PRs are welcome.

License #

MIT


If this package helps you, consider supporting its development:

Buy Me A Coffee

38
likes
0
points
2.46k
downloads

Documentation

Documentation

Publisher

verified publisherarunprakashg.com

Weekly Downloads

A library to interact with the Wordpress REST API. Supports most of the common endpoints and all of the CRUD operations on the endpoints.

Repository (GitHub)
View/report issues

Topics

#wordpress #parallel #crud #rest

Funding

Consider supporting this project:

www.buymeacoffee.com

License

unknown (license)

Dependencies

collection, cookie_jar, crypto, dio, dio_cookie_manager, http_parser, meta, path

More

Packages that depend on wordpress_client