Docs

WordPress introspection

Generate and call the WordPress endpoints described by your active plugins.

The generated introspection types the routes published by the plugins active on your WordPress site: the WP_* types and an endpoints descriptor map, built from GET /kizlo/v1/introspect. It carries no request logic. The callable client is assembled from it, and you reach it through context.wordpress in a procedure, event handler, fixture, or other server-side Kizlo code.

Generate the introspection

kizlo init and kizlo create write a stub at <dir>/server/generated/introspection.ts. The first generation replaces it with endpoints fetched from GET /kizlo/v1/introspect on your configured WordPress site.

CommandWhen it writes introspection.ts
kizlo generateOnce, before a build or typecheck
kizlo devOn startup and whenever the WordPress introspection document changes

For the default dir: "src/lib/kizlo", the file lives at src/lib/kizlo/server/generated/introspection.ts. A package without a Kizlo server can set dir: { introspection } and receives <introspection>/introspection.ts instead.

Commit introspection.ts. It gives builds and teammates the same WordPress contract without requiring a live site during installation. The .kizlo/introspection.meta.json cache stays local and is regenerated as needed.

What the stub means

The stub marks the endpoint tree as any so a new project compiles before it has connected to WordPress. It does not mean that WordPress has no routes, and it does not provide endpoint definitions at runtime.

Run kizlo generate before relying on endpoint completion or checking a production build. The generated file replaces any with the routes, inputs, results, and error codes published by the active site.

Read the endpoint tree

Each API ID segment becomes a nested client key. Kizlo camelizes every segment and operation name for the generated TypeScript member.

Introspection declarationGenerated call
API ID kizlo.post-types.post, operation listwordpress.kizlo.postTypes.post.list()
API ID kizlo.post-types.post, operation retrievewordpress.kizlo.postTypes.post.retrieve()
API ID woocommerce.store.cart.addItem, operation createwordpress.woocommerce.store.cart.addItem.create({ body: { id: 42 } })

The input names the three parts of a request. params is interpolated into the URL, query becomes the query string, and body is sent as the payload. An endpoint only declares the parts it has, and a part is required when something inside it is; an endpoint that requires nothing can be called without an argument.

const response = await context.wordpress.kizlo.postTypes.post.retrieve({
  params: { identifier: "hello-world" },
})

if (response.error) {
  context.logger.error(response.error.message)
  throw response.error
}

return response.data

An identifier on a managed post type or taxonomy accepts a numeric ID or slug as a string.

Call an endpoint the tree does not have

The file describes one WordPress at one moment. Switch off API access for a post type, deactivate the plugin that serves a route, or generate against a different site, and your code is left calling a path the file no longer has.

The call resolves to a failure instead of throwing, carrying the rest_no_route code that is already in every endpoint's error union. Nothing is sent, so the check you write for any failed request covers it.

const response = await context.wordpress.kizlo.postTypes.post.list()

// response.error.code
// "rest_no_route"

// response.error.message
// No endpoint at "kizlo.postTypes.post.list" in this project's generated client, so nothing was sent.
// Run `kizlo generate` against the WordPress you are connecting to.

To branch before calling, ask the client what it carries: "woocommerce" in context.wordpress.

Override one call

Pass request-specific options as the second argument. They affect this invocation without changing the endpoint definition or transport defaults.

const controller = new AbortController()

await context.wordpress.kizlo.postTypes.post.retrieve(
  { params: { identifier: "42" } },
  {
    headers: { "X-Request-Source": "featured" },
    signal: controller.signal,
    timeout: "5 seconds",
  },
)

See WP_CallOptions for the option types.

Name endpoint shapes

Use the dotted client path to reuse a generated shape without importing the generated file:

import type { WP_EndpointData, WP_EndpointInput, WP_EndpointResult } from "kizlo"

type Post = WP_EndpointData<"kizlo.postTypes.post.retrieve">
type PostListInput = WP_EndpointInput<"kizlo.postTypes.post.list">
type PostResult = WP_EndpointResult<"kizlo.postTypes.post.retrieve">

The reference pages cover WP_EndpointData, WP_EndpointInput, and WP_EndpointResult.

Describe another REST namespace

Kizlo derives controller-backed routes from wp/v2 and wp-site-health/v1. Add another registered namespace when its WordPress controller schemas are enough for your generated client.

wp-content/plugins/acme-kizlo/acme-kizlo.php
add_filter('kizlo_introspection_core_namespaces', static function (array $namespaces): array {
    $namespaces[] = 'acme/v1';

    return $namespaces;
});

The filter changes introspection only and registers no endpoint. Mechanical discovery uses the route paths, controller schemas, and generated names as they are. Use kizlo_register_route_spec when you need curated names, response shapes, error codes, or collision control.

Name a namespace's APIs

An API ID is the literal segments of a path and nothing else, so two namespaces that both serve /products ask for the same ID, and an API belongs to one namespace. Give a namespace a prefix and its routes get names of their own. wp/v2 and wp-site-health/v1 keep the unprefixed names by default.

wp-content/plugins/acme-kizlo/acme-kizlo.php
add_filter('kizlo_introspection_core_api_prefix', static function (string $prefix, string $namespace): string {
    return $namespace === 'acme/v1' ? 'acme' : $prefix;
}, 10, 2);

acme/v1/products is then acme.products, reached as wordpress.acme.products.list(). The prefix heads the schema IDs too, so its records never merge with another namespace's. A prefix that is not a valid API ID is reported and ignored.

Describe a route no controller serves

Discovery reads a response from WP_REST_Controller::get_item_schema(). A closure route, or a route object of another design, publishes no such method, so it is described with an object whose fields are unknown. It stays callable, and the diagnostic says so. Whoever registered the route can read it, so this filter asks them.

wp-content/plugins/acme-kizlo/acme-kizlo.php
add_filter(
    'kizlo_introspection_core_response',
    static function (mixed $properties, ?object $subject, string $namespace, string $route, string $operation): mixed {
        if ($namespace !== 'acme/v1' || !$subject instanceof Acme_Route) {
            return $properties;
        }

        return kizlo_translate_spec_properties($subject->get_item_schema()['properties'], "acme {$route}");
    },
    10,
    5,
);

Return the value you were handed for anything you cannot describe, and the route keeps its opaque object rather than a shape you had to invent. Only a route without a WP_REST_Controller reaches this filter. To add fields to a response a controller already describes, such as ones a response filter attaches at runtime, use kizlo_introspection_core_schema, which is handed the derived properties and the controller behind them.

Add a route the plugin does not own

An integration plugin can add a core or third-party route to the document without registering a new WordPress endpoint. Register its reusable response shapes with kizlo_register_route_schema, then describe each operation with kizlo_register_route_spec.

When the upstream API already exposes WordPress-style schemas, derive the declaration with kizlo_translate_spec_properties or kizlo_translate_spec_schema. Regenerate the introspection after the companion plugin is active, then call the new API ID and operation through the naming rules above.

Add errors to an existing route

When Kizlo already introspects the route, register its handler errors without repeating the input or response contract. kizlo_register_route_errors identifies the route by namespace, readable path, and HTTP method. Path placeholders must match the registered parameter names. Use its collection filter when you build registrations in a loop.

On this page