Skip to content

Components (plugins, themes, modules)

Previewcapability-gated

The components resource is the differentiating surface of the managed.dev API — the dynamic application layer that generic PaaS deliberately won’t touch and other WP hosts expose only bluntly. It models plugins, themes, and (on other runtimes) modules as one typed resource: you install, activate, update, and remove them per environment, each call returns a job, and every action is gated by both a scope and a runtime capability.

A component is a unit of the application layer addressed by kind and slug:

{
"id": "comp_01J9...",
"kind": "plugin", // plugin | theme | module
"slug": "wordpress-seo",
"name": "Yoast SEO",
"version": "23.4",
"status": "active", // active | inactive
"update_available": "23.5",
"site_id": "site_01J7...",
"env_id": "env_01J8..."
}

Components are environment-scoped by default: a plugin can be active on staging but not production, so calls live under /v1/sites/{siteID}/environments/{envID}/components. A site-level alias resolves to the production environment as a convenience.

Every components call is checked twice. First the scope on your key — for example wp.plugins:write to install a plugin. Then the runtime capability the route requires — components.plugins. A key can hold a scope it can never use, because the capability simply isn’t present on a runtime that has no plugins.

GET …/{envID}/components  

wp.plugins:read
Parameter Type Required Description
kind string no Filter by plugin, theme, or module. Omit to list all kinds.
status string no Filter by active or inactive.
limit integer no Page size. See pagination.
cursor string no Opaque cursor from a prior next_cursor.
List active plugins on staging
curl https://api.managed.dev/v1/sites/site_01J7.../environments/env_01J8.../components?kind=plugin&status=active \
-H "Authorization: Bearer mfk_live_9aF2..." \
-H "Forge-Version: 2026-06-23"
200 response
{
"data": [
{ "id": "comp_01J9...", "kind": "plugin", "slug": "woocommerce",
"version": "9.1.2", "status": "active", "update_available": null },
{ "id": "comp_01J9...", "kind": "plugin", "slug": "wordpress-seo",
"version": "23.4", "status": "active", "update_available": "23.5" }
],
"pagination": { "next_cursor": "eyJ0...", "has_more": false },
"request_id": "req_01J9..."
}

POST …/{envID}/components  

wp.plugins:write

Returns 202 Accepted with a job — installs are asynchronous. Send an Idempotency-Key so a retried request never installs twice.

Parameter Type Required Description
kind string yes plugin, theme, or module.
slug string yes The component slug, e.g. wordpress-seo.
source string no registry (default), or a signed artifact reference.
version string no A pinned version, or latest (default).
activate boolean no Activate immediately after install. Defaults to false.

PATCH …/{envID}/components/{id}  

wp.plugins:write

Patch a component to update its version or flip its status between active and inactive. Returns 202 with a job.

DELETE …/{envID}/components/{id}  

wp.plugins:write

Deactivates and removes the component. Returns 202 with a job.

POST …/{envID}/components:batch  

wp.plugins:write

Apply a set of component actions to one environment in a single call — the typed equivalent of a fleet plugin update. Each item carries its own action; the call returns one job whose result reports per-item outcomes.

Request body
{
"operations": [
{ "action": "update", "kind": "plugin", "slug": "woocommerce", "version": "latest" },
{ "action": "update", "kind": "plugin", "slug": "wordpress-seo", "version": "latest" },
{ "action": "deactivate", "kind": "plugin", "slug": "hello-dolly" }
]
}

worked example — install a plugin on staging

Section titled “worked example — install a plugin on staging”

Install Yoast SEO on a staging environment, then watch the job stream to completion.

  1. Confirm the capability. Check the site advertises components.plugins before you branch — see capability discovery.

  2. Send the install. A scope-pinned key, an idempotency key, and a 202 back.

    Install Yoast on staging
    curl -X POST https://api.managed.dev/v1/sites/site_01J7.../environments/env_01J8.../components \
    -H "Authorization: Bearer mfk_live_9aF2..." \
    -H "Forge-Version: 2026-06-23" \
    -H "Idempotency-Key: 6f1c-44a0-9e2b" \
    -H "Content-Type: application/json" \
    -d '{ "kind": "plugin", "slug": "wordpress-seo", "source": "registry", "version": "latest", "activate": true }'
    202 response
    HTTP/1.1 202 Accepted
    Location: /v1/jobs/job_01J9...
    X-RateLimit-Limit: 100
    X-RateLimit-Remaining: 99
    {
    "data": {
    "id": "job_01J9...", "type": "component.install", "status": "queued",
    "created_at": "2026-06-23T18:04:11.412Z",
    "resource": { "type": "component", "kind": "plugin", "slug": "wordpress-seo",
    "site_id": "site_01J7...", "env_id": "env_01J8..." }
    },
    "request_id": "req_01J9..."
    }
  3. Tail the job. Follow it to succeeded over SSE, or ETag long-poll from CI. See async jobs for both paths.

    Live-tail the install
    curl -N https://api.managed.dev/v1/jobs/job_01J9.../stream \
    -H "Authorization: Bearer mfk_live_9aF2..."

The same call against a static site returns 409 capability.unsupported — there are no plugins to install. That’s the capability gate doing its job.