﻿### [WPForms REST API and Abilities API Reference](https://wpforms.com/developers/wpforms-rest-api/)

**Published:** May 12, 2026
**Author:** Umair Majeed

**Content:**

WPForms 1.9.9 introduced a REST API built on top of the WordPress Abilities API, and WPForms 1.10.2 expanded it with opt-in write support. You can list forms, fetch form configuration, retrieve and search entries, and pull form statistics, and, once write access is enabled, create forms, add and update fields, and update safe form settings. Every ability works from any HTTP client, the command line, your own PHP code, or AI assistants that speak the Model Context Protocol (MCP).

If you searched for “WPForms REST API” and landed here, this is it. There is no separate REST API; the Abilities API integration *is* how WPForms exposes its data over HTTP.

## What Is the Abilities API

The Abilities API is a WordPress core feature added in WordPress 6.9. It lets plugins declare individual capabilities (called *abilities*) with a name, an input schema, an output schema, and a permission callback. WordPress then exposes every registered ability automatically over the REST API at `/wp-json/wp-abilities/v1/abilities//run` and to MCP-compatible AI clients through the official MCP adapter plugin.

WPForms registers a set of abilities under the `wpforms/` namespace. The read abilities have been available since WPForms 1.9.9. WPForms 1.10.2 added a set of write abilities that are disabled by default (see [Enabling Write Access](#enabling-write-access) below). Each ability runs the same WPForms capability checks used in the admin (`wpforms_current_user_can()`), so the REST and MCP surfaces inherit the existing permission model rather than introducing a new one.

**Requirements**:

- WordPress 6.9 or later
- WPForms Lite or Pro 1.9.9 or later for the read abilities, or 1.10.2 or later for the write abilities (some abilities are Pro only; see the reference table below)
- For MCP clients: the [wordpress/mcp-adapter](https://github.com/WordPress/mcp-adapter) plugin

## Calling an Ability

Every ability can be invoked through two transports. The result is identical; pick whichever fits the calling environment.

### REST API

Send an authenticated request to `/wp-json/wp-abilities/v1/abilities//run`. The HTTP method depends on the ability: read abilities use `GET`, and write abilities use `POST`. Calling an ability with the wrong method returns `405 Method Not Allowed`.

**Note:** Read abilities (`GET`) take their parameters as bracketed query string fields under the `input` key, for example `input[limit]=10&input[status]=publish`. URL-encode the brackets if your client does not do it automatically (`%5B` for `[`, `%5D` for `]`). Write abilities (`POST`) take the same parameters as a JSON object under an `input` key in the request body. The PHP transport handles both the same way: pass an associative array to `execute()`.

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/list-forms/run?input%5Blimit%5D=10&input%5Bstatus%5D=publish"
```

### PHP

From any plugin, theme, or custom code that runs after the `wp_abilities_api_init` action has fired, fetch the ability with `wp_get_ability()` and call its `execute()` method. Pass the input as an associative array of parameters (the same names listed in each ability’s parameters table).

```

$ability = wp_get_ability( 'wpforms/list-forms' );

if ( $ability ) {
    $result = $ability->execute(
        [
            'limit'  => 10,
            'status' => 'publish',
        ]
    );

    if ( is_wp_error( $result ) ) {
        // Handle error.
        return;
    }

    // $result['forms']  array of form summaries
    // $result['total']  total count (integer)
}
```

## Authentication

The REST transport uses standard WordPress authentication. The recommended method for external clients is **Application Passwords**, which are built into WordPress core and do not require any extra plugin.

### Generating an Application Password

1. Sign in as the WordPress user whose permissions the calls should run under.
2. Go to **Users » Profile** and scroll to the **Application Passwords** section.
3. Enter a name for the integration and click **Add New Application Password**.
4. Copy the generated password. WordPress only shows it once.

For a deeper reference, including REST endpoints for managing application passwords programmatically, see the WordPress core team’s [Application Passwords Integration Guide](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/).

### Sending the Credentials

Pass the username and application password as HTTP Basic authentication on every request.

#### With curl

Set your site URL as an environment variable so the examples in this doc are runnable as-is:

```

export WP_SITE="https://your-wordpress-site.com"
```

Then add the `-u` flag to every curl command. The flag’s value is your username, followed by a colon, followed by your application password (no spaces).

#### With Postman, Insomnia, or other clients

Set the request’s **Auth** type to **Basic Auth** and provide your username and application password. The client handles the encoding for you.

#### Setting the header manually

Send your credentials as a base64-encoded `Authorization` header:

Combine your username and application password into a single string, separated by a colon (no spaces), and base64-encode the result using a tool of your choice (`base64` command, an online encoder, or your language’s standard library). Send the encoded value on every request as the `Authorization` header, in the form `Authorization: Basic `.

The request examples in the rest of this doc omit the authentication flag for readability. Add the `-u` flag (or the `Authorization` header) to every real request, or the API returns `401 Unauthorized`.

## Enabling Write Access

The read abilities are always available. The write abilities (`wpforms/create-form`, `wpforms/add-field`, `wpforms/update-field`, and `wpforms/update-form-settings`) are disabled by default and must be turned on before they will run. There are two ways to enable them, and the filter always has the final say.

### Enabling Writes in Code

Enable writes programmatically with the `wpforms_integrations_abilities_allow_write` filter:

```

add_filter( 'wpforms_integrations_abilities_allow_write', '__return_true' );
```

The filter overrides the no-code toggle in both directions. Returning `true` enables writes even when the toggle is off, and returning `false` disables them even when the toggle is on. This keeps existing developer setups working unchanged.

### Enabling Writes Without Code

Site owners can enable writes from **WPForms » Tools » AI MCP** with the **Enable MCP Write Access** toggle, which persists to the `ai-mcp-write-enabled` key in the `wpforms_settings` option. The filter above takes precedence over this toggle. For the full walkthrough, see [Using WPForms With AI Assistants](https://wpforms.com/docs/using-wpforms-with-ai-assistants/).

**Note:** While writes are disabled, the write abilities are hidden from MCP discovery and any attempt to run one returns `403` with the code `wpforms_writes_disabled`. Input validation runs before the write gate, so malformed input returns a `400` even when writes are off.

## Permissions

Each ability checks a specific WPForms capability before executing. Failed checks return a `WP_Error` with HTTP status `403`.

AbilityCapability`wpforms/list-forms``view_forms``wpforms/get-form``view_form_single``wpforms/get-form-stats` (Lite)`view_form_single``wpforms/get-form-stats` (Pro)`view_entries_form_single``wpforms/get-entry-summaries``view_entries_form_single``wpforms/get-entry``view_entry_single``wpforms/search-entries` (with `form_id`)`view_entries_form_single``wpforms/search-entries` (without `form_id`)`view_entries`The write abilities added in WPForms 1.10.2 enforce the same capability model. `wpforms/create-form` requires the capability to create forms, and `wpforms/add-field`, `wpforms/update-field`, and `wpforms/update-form-settings` require the capability to edit the target form. A failed check returns a `WP_Error` with HTTP status `403`. Write abilities also require write access to be enabled, as described above.

## Ability Reference

The read abilities are idempotent and safe to call repeatedly. The write abilities, added in WPForms 1.10.2, create or modify form data and require write access to be enabled first. Errors are returned as `WP_Error` objects serialized to JSON with `code`, `message`, and `data.status` fields.

**Note:** The example requests below omit the authentication flag for readability. Each call must be authenticated, or it returns `401 Unauthorized`. Add the `-u` flag (or send the equivalent `Authorization: Basic` header) to every curl command. See the Authentication section for details.

### wpforms/list-forms

List forms with summary metadata. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`status`stringNo`publish`Form status. One of `publish`, `draft`, `trash`.`limit`integerNo`20`Maximum number of forms to return. Range 1 to 100.`offset`integerNo`0`Number of forms to skip.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/list-forms/run?input%5Blimit%5D=10&input%5Bstatus%5D=publish"
```

#### Example Response

```

{
  "forms": [
    {
      "id": 123,
      "title": "Contact Form",
      "status": "publish",
      "created": "2026-01-27 10:00:00",
      "modified": "2026-02-15 14:30:00",
      "author": 1
    }
  ],
  "total": 5
}
```

### wpforms/get-form

Retrieve a single form, including a curated subset of its settings and, optionally, its field configuration. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form to retrieve.`include_fields`booleanNo`true`Include the form’s field configuration in the response.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/get-form/run?input%5Bform_id%5D=123&input%5Binclude_fields%5D=true"
```

#### Example Response

```

{
  "id": 123,
  "title": "Contact Form",
  "status": "publish",
  "created": "2026-01-27 10:00:00",
  "modified": "2026-02-15 14:30:00",
  "author": 1,
  "settings": {
    "form_title": "Contact Form",
    "form_desc": "Get in touch with us",
    "submit_text": "Submit",
    "ajax_submit": true,
    "honeypot": true,
    "antispam": true
  },
  "fields": [
    {
      "id": 1,
      "type": "text",
      "label": "Name",
      "description": "",
      "required": true,
      "size": "medium"
    }
  ]
}
```

The `settings` object returned by this ability is a curated, non-sensitive subset (title, description, submit text, AJAX submit flag, honeypot, anti-spam). Notification, confirmation, and integration settings are not exposed.

### wpforms/get-form-stats

Return submission statistics for a form. The response shape differs between Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/get-form-stats/run?input%5Bform_id%5D=123"
```

#### Lite Response

```

{
  "form_id": 123,
  "entries_available": false,
  "message": "Entry statistics require WPForms Pro. Upgrade to access detailed form submission data."
}
```

#### Pro Response

```

{
  "form_id": 123,
  "total_entries": 156,
  "unread_entries": 12,
  "starred_entries": 8,
  "entries_available": true
}
```

### wpforms/get-entry-summaries

Paginated list of entry summaries for a single form. Pro only.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form to fetch entries for.`status`stringNo`""`One of `partial`, `abandoned`, `spam`, `trash`. Empty returns all completed entries.`type`stringNo`""`One of `read`, `unread`, `starred`.`include_fields`booleanNo`false`Include each entry’s field values in the response.`limit`integerNo`20`Maximum number of entries to return. Range 1 to 100.`offset`integerNo`0`Number of entries to skip.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/get-entry-summaries/run?input%5Bform_id%5D=123&input%5Btype%5D=unread&input%5Blimit%5D=20"
```

#### Example Response

```

{
  "entries": [
    {
      "id": 456,
      "form_id": 123,
      "date": "2026-02-15 14:32:10",
      "status": "",
      "viewed": false,
      "starred": true
    }
  ],
  "total": 15,
  "form_id": 123
}
```

### wpforms/get-entry

Retrieve a single entry by ID, including all field values. Pro only.

#### Parameters

NameTypeRequiredDefaultDescription`entry_id`integerYesNoneThe ID of the entry to retrieve.`include_fields`booleanNo`true`Include the entry’s field values in the response.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/get-entry/run?input%5Bentry_id%5D=456"
```

#### Example Response

```

{
  "id": 456,
  "form_id": 123,
  "date": "2026-02-15 14:32:10",
  "modified": "2026-02-15 15:00:00",
  "status": "",
  "viewed": true,
  "starred": false,
  "ip_address": "192.168.1.100",
  "fields": [
    {
      "id": 1,
      "name": "Name",
      "value": "John Doe",
      "type": "text"
    }
  ]
}
```

> The IP address in the response can be masked. Add `add_filter( 'wpforms_abilities_mask_ip_address', '__return_true' )` to your site’s code; when enabled, IPv4 addresses appear with their last three octets replaced by asterisks (for example, `***.***.***.100`).

### wpforms/search-entries

Search entries across one form or all forms with full-text, field-specific, status, and date range filters. Pro only.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerNoNoneRestrict the search to a single form. Omit to search across all forms.`search`stringNo`""`Full-text query matched against all entry fields.`field_id`integerNoNoneRestrict the search to a specific field ID. Use with `field_value`.`field_value`stringNoNoneExact value to match in the field specified by `field_id`.`date_from`stringNoNoneStart of date range, format `YYYY-MM-DD`.`date_to`stringNoNoneEnd of date range, format `YYYY-MM-DD`.`status`stringNo`""`One of `partial`, `abandoned`, `spam`, `trash`.`type`stringNo`""`One of `read`, `unread`, `starred`.`include_fields`booleanNo`true`Include entry field values in the response.`limit`integerNo`20`Maximum number of entries per page. Range 1 to 100.`page`integerNo`1`Page number. Note that this ability uses page-based pagination, unlike `list-forms` and `get-entry-summaries`, which use `offset`.`orderby`stringNo`date`One of `entry_id`, `date`, `status`.`order`stringNo`DESC`One of `ASC`, `DESC`.#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/search-entries/run?input%5Bform_id%5D=5&input%5Bsearch%5D=john%40example.com&input%5Bpage%5D=1&input%5Blimit%5D=20"
```

#### Example Response

```

{
  "entries": [
    {
      "id": 142,
      "form_id": 5,
      "date": "2026-02-15 14:32:10",
      "status": "",
      "viewed": false,
      "starred": true,
      "fields": [
        { "id": 1, "name": "Name",  "value": "John Doe",         "type": "text"  },
        { "id": 2, "name": "Email", "value": "john@example.com", "type": "email" }
      ]
    }
  ],
  "total": 47,
  "total_pages": 5,
  "page": 1,
  "limit": 20
}
```

The abilities below were added in WPForms 1.10.2. All of them require write access to be enabled (see [Enabling Write Access](#enabling-write-access)), except `wpforms/describe-editing-schema`, which is a read ability used to discover what the write abilities accept. The write abilities run as `POST` requests over REST, or through `execute()` in PHP.

### wpforms/describe-editing-schema

Return the field types and form settings that the write abilities accept on this site. Call this first, because the available field types depend on the license level. Available in Lite and Pro.

**Note:** Over REST this ability is a `GET` and requires a non-empty input object. Calling it with no parameters returns `400` with the message `input is not of type object`. Include at least an empty bracketed key, for example `input%5B%5D=`. In PHP, pass an empty array to `execute()`.

#### Example Request

```

curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/describe-editing-schema/run?input%5B%5D="
```

#### Example Response (truncated)

```

{
  "field_types": [
    {
      "type": "text",
      "label": "Single Line Text",
      "properties": [
        { "key": "label",         "schema": { "type": "string" } },
        { "key": "required",      "schema": { "type": "boolean" } },
        { "key": "size",          "schema": { "type": "string", "enum": ["small", "medium", "large"] } },
        { "key": "placeholder",   "schema": { "type": "string" } },
        { "key": "default_value", "schema": { "type": "string" } }
      ],
      "required_props": []
    },
    {
      "type": "select",
      "label": "Dropdown",
      "properties": [
        { "key": "choices", "schema": { "type": "array", "items": { "type": "object", "properties": { "label": { "type": "string" }, "value": { "type": "string" } }, "required": ["label"] } } },
        { "key": "style",   "schema": { "type": "string", "enum": ["classic", "modern"] } }
      ],
      "required_props": []
    }
  ],
  "form_settings": [
    { "key": "form_title",  "schema": { "type": "string" }, "description": "The form title." },
    { "key": "form_desc",   "schema": { "type": "string" }, "description": "The form description." },
    { "key": "submit_text", "schema": { "type": "string" }, "description": "The submit button label." }
  ]
}
```

On Pro, `field_types` lists `text`, `textarea`, `email`, `number`, `select`, `radio`, `checkbox`, `name`, `phone`, `date-time`, and `file-upload`. On Lite, the set is the base eight: `text`, `textarea`, `email`, `number`, `select`, `radio`, `checkbox`, and `name`. The slugs match the builder, so the `Dropdown` field is `select` and the `Date / Time` field is `date-time`.

#### Field Properties

The `create-form`, `add-field`, and `update-field` abilities accept these field properties. Each property applies only to the field types that declare it, as reported by `describe-editing-schema`.

PropertyTypeApplies ToDescription`label`stringAll typesThe field label.`description`stringAll typesThe field description.`required`booleanAll typesWhether the field is required.`size`stringAll typesField size. One of `small`, `medium`, `large`.`label_hide`booleanAll typesHide the field label on the frontend.`placeholder`string`text`, `textarea`, `email`, `number`, `phone`Placeholder text.`default_value`string`text`, `textarea`, `email`, `number`, `phone`Default value.`choices`array`select`, `radio`, `checkbox`Ordered list of `{ "label": "...", "value": "..." }` objects. `value` is optional and derives from the label when omitted.`style`string`select`, `file-upload`Display style. One of `classic`, `modern`.`input_columns`string`radio`, `checkbox`Column layout. One of `""`, `inline`, `2`, `3`.`extensions`string`file-upload`Comma-separated list of allowed file extensions.`max_size`integer`file-upload`Maximum file size. Minimum 1.`max_file_number`integer`file-upload`Maximum number of files. Minimum 1.`media_library`boolean`file-upload`Store uploads in the WordPress Media Library.### wpforms/create-form

Create a new form with an optional set of fields and settings, in a single call. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`title`stringYesNoneThe form title.`fields`arrayNoNoneInitial fields. Each item needs a `type` plus any field properties for that type.`settings`objectNoNoneInitial settings. One or more of `form_title`, `form_desc`, `submit_text`.**Note:** The input schema sets `additionalProperties` to `false`, so only `title`, `fields`, and `settings` are accepted at the top level. A top-level `description` key is rejected with `400`; set the form description through `settings.form_desc` instead.

#### Example Request

```

$ability = wp_get_ability( 'wpforms/create-form' );

$result = $ability->execute(
    [
        'title'  => 'Job Application',
        'fields' => [
            [ 'type' => 'name',   'label' => 'Your Name', 'required' => true ],
            [ 'type' => 'email',  'label' => 'Email',      'required' => true ],
            [ 'type' => 'select', 'label' => 'Position',   'choices' => [ [ 'label' => 'Baker' ], [ 'label' => 'Cashier' ] ] ],
        ],
        'settings' => [
            'form_title'  => 'Job Application',
            'form_desc'   => 'Apply to join our team.',
            'submit_text' => 'Apply Now',
        ],
    ]
);
```

#### Example Response

```

{
  "form_id": 1102,
  "fields": [
    { "id": 0, "type": "name" },
    { "id": 1, "type": "email" },
    { "id": 2, "type": "select" }
  ]
}
```

### wpforms/add-field

Add a new field to an existing form. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form to add the field to.`type`stringYesNoneThe field type slug. Call `describe-editing-schema` for the available types.Pass any field properties for the chosen type alongside `form_id` and `type` (see the Field Properties table above).

#### Example Request

```

$ability = wp_get_ability( 'wpforms/add-field' );

$result = $ability->execute(
    [
        'form_id'  => 1102,
        'type'     => 'phone',
        'label'    => 'Phone',
        'required' => true,
    ]
);
```

#### Example Response

```

{
  "form_id": 1102,
  "field_id": 6,
  "type": "phone"
}
```

**Note:** Requesting a field type that is not available on the site, such as a Pro type on Lite or an unsupported type, returns `422` with the code `wpforms_field_type_unavailable`.

### wpforms/update-field

Update properties of an existing field. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form that contains the field.`field_id`integerYesNoneThe ID of the field to update.Pass the field properties you want to change alongside `form_id` and `field_id` (see the Field Properties table above). Properties you omit are left unchanged. When you send `choices`, the new list is merged by index onto the existing choices.

#### Example Request

```

$ability = wp_get_ability( 'wpforms/update-field' );

$result = $ability->execute(
    [
        'form_id'     => 1102,
        'field_id'    => 6,
        'required'    => true,
        'placeholder' => 'e.g. 555-123-4567',
    ]
);
```

#### Example Response

```

{
  "form_id": 1102,
  "field_id": 6,
  "updated": ["required", "placeholder"],
  "ignored": []
}
```

### wpforms/update-form-settings

Update a form’s safe settings. The v1 whitelist is `form_title`, `form_desc`, and `submit_text`. Available in Lite and Pro.

#### Parameters

NameTypeRequiredDefaultDescription`form_id`integerYesNoneThe ID of the form to update.`settings`objectYesNoneOne or more of `form_title`, `form_desc`, `submit_text`.#### Example Request

```

$ability = wp_get_ability( 'wpforms/update-form-settings' );

$result = $ability->execute(
    [
        'form_id'  => 1102,
        'settings' => [
            'submit_text' => 'Send Application',
        ],
    ]
);
```

#### Example Response

```

{
  "form_id": 1102,
  "updated": ["submit_text"],
  "ignored": []
}
```

Keys that are not in the whitelist are returned in the `ignored` array rather than causing an error. For example, sending `ajax_submit` leaves it unchanged and lists it under `ignored`.

## Using WPForms With MCP Clients

The read abilities are registered with `mcp.public` set to `true`, so MCP-compatible AI clients (Claude Desktop, Cursor, and others) discover them automatically once the WordPress site is connected through the [wordpress/mcp-adapter](https://github.com/WordPress/mcp-adapter) plugin. The write abilities and `wpforms/describe-editing-schema` are exposed to MCP only when write access is enabled; while writes are off, their `mcp.public` flag is `false` and they are hidden from MCP discovery, so an assistant may report that it cannot find the WPForms write tools. Once a site is connected and write access is on, abilities appear in the client’s tool list under the **WPForms** category and respect the same permission callbacks used by the REST API.

## Discovering Abilities Programmatically

To enumerate the abilities available on a site instead of hard-coding their IDs:

```

# List all registered abilities on the site
curl "$WP_SITE/wp-json/wp-abilities/v1/abilities"

# Fetch the schema for a single ability
curl "$WP_SITE/wp-json/wp-abilities/v1/abilities/wpforms/list-forms"
```

The response includes each ability’s ID, label, description, category, and full input and output schemas, which is enough information to build a generic client without prior knowledge of the WPForms-specific surface.

The write abilities appear in this index on any site running WPForms 1.10.2 or later, whether or not write access is enabled. Their `mcp.public` flag reflects the write-access state, however: it is `true` only when writes are enabled, which is why MCP clients see the write tools only after write access is turned on.

## Extending the Field Registry

The field types and properties that the write abilities accept are defined in an extensible registry. Addons can register their own field types and properties through two filters, so a custom or addon field type can become available to `create-form`, `add-field`, and `update-field`.

- `wpforms_integrations_abilities_field_registry_types` registers new field types. Each type declares its availability through a `requires` value, which is the string `pro`, an addon slug, or a callable, and can specify its required properties.
- `wpforms_integrations_abilities_field_registry_properties` contributes new property definitions that field types can reference.

```

add_filter(
    'wpforms_integrations_abilities_field_registry_types',
    function ( $types ) {
        // Register one or more field types here, then return the list.
        return $types;
    }
);
```

## Related Links

- [Using WPForms With AI Assistants](https://wpforms.com/docs/using-wpforms-with-ai-assistants/), the no-code guide for connecting an AI assistant
- [Introducing the WordPress Abilities API](https://developer.wordpress.org/news/2025/11/introducing-the-wordpress-abilities-api/) (developer.wordpress.org)
- [wordpress/mcp-adapter](https://github.com/WordPress/mcp-adapter) on GitHub
- [Model Context Protocol](https://modelcontextprotocol.io/)

**Categories:** Tutorials, Extending

---

