---
title: Features REST route
summary: Read and save Companion's features, their switches and their settings, with lanternwp/v1/features.
url: https://lanternwp.com/developers/features-api
updated: 2026-10-30
---

# Features REST route

LanternWP Companion's features (quick view, product badges, bot protection and the rest) are read and saved through one REST route, `lanternwp/v1/features`, part of the [public API](public-api.md). It is what Companion's own Features screen (**Appearance → LanternWP → Features**) uses, so a script, a deployment tool or another plugin can set a store's features the same way. The names of the features, their groups and their fields come from the route itself; never hard-code a list.

Every request needs a user who can manage WooCommerce (`manage_woocommerce`, which shop managers have). Without one, the route answers `401` to a visitor who is not signed in and `403` to one who is.

## Read the features

```text
GET /wp-json/lanternwp/v1/features
GET /wp-json/lanternwp/v1/features?details=product-badges
```

The answer has the groups in their order and every feature. `details` names the feature whose settings you show: only its fields come with their `notes`, which can take queries of the store's products; without it, a field that has notes says `null`.

```json
{
  "groups": [
    { "id": "cards", "label": "Product cards", "description": "On every product card: the catalog, search and related products.", "features": ["quick-view", "product-swatches", "hover-image", "product-badges"] }
  ],
  "features": [
    {
      "id": "product-badges",
      "group": "cards",
      "name": "Product badges",
      "description": "Bestseller and New badges on product cards and the product page.",
      "handbook": "https://lanternwp.com/docs/product-badges",
      "switch": { "on": true, "default": true },
      "states": [{ "kind": "on", "label": "On", "text": "", "url": "", "link": "" }],
      "needsSetup": false,
      "restore": false,
      "place": null,
      "details": true,
      "fields": [
        { "id": "new", "type": "toggle", "label": "Mark recently published products as New", "help": "", "default": false, "value": false, "fieldset": "New badge", "when": null, "notes": ["Now on Linen Shirt and 3 more."] },
        { "id": "new-days", "type": "number", "label": "Days", "help": "…", "default": 30, "value": 30, "min": 1, "max": 9999, "fieldset": "New badge", "when": "new", "notes": [] }
      ],
      "links": [
        { "title": "On each product", "text": "Show, Hide or Automatic for each badge, in Product data → General.", "url": "…", "label": "Products" }
      ]
    }
  ]
}
```

| Key | What it is |
|---|---|
| `switch` | The feature's switch, or `null` for a feature without one (the contact form, which is on where its block stands) |
| `states` | What the feature's state line says: `on`, `off`, `setup` (it needs something before it can work, with a link to where that is set) or `removed` (the store removed its block from the templates named) |
| `needsSetup` | Whether a state is `setup` or `removed` |
| `restore` | Whether the feature's block can be put back in templates (see below) |
| `place` | Where the feature is set further, such as **Products → Product sets**: `label`, and `url` or `null` when that place has no address of its own (a product's tab); `null` for none |
| `details` | Whether the Features screen has details for the feature: it has `fields` or `links` |
| `fields` | The feature's settings beyond its switch, in the order they show. `type` is `toggle`, `number` (with `min` and `max`), `select` (with `options`, each `value` and `label`), `text` or `secret`. `when` names a toggle of the same feature that shows the field while it is on; `fieldset` is the label of the fields shown together; `notes` says what the saved value does in the store now, such as the products a badge rule marks: a list for the feature `details` names, an empty list for a field without notes, `null` otherwise |
| `links` | What the feature uses that is set elsewhere, as it is now, with a link there: `title`, `text` (such as `On`), `url` and the link's `label`. Back-in-stock emails' double opt-in is the confirmation email in **WooCommerce → Settings → Emails**, so it is a link, not a field |

A `secret` field, such as the Turnstile secret key, never comes back: its `value` is always empty and `hasValue` says whether one is saved.

## Save

One request saves switches and fields, as the screen's Save button does:

```text
POST /wp-json/lanternwp/v1/features
```

```json
{
  "switches": { "quick-view": true, "product-badges": true },
  "fields": { "product-badges": { "new": true, "new-days": 14 } },
  "details": "product-badges"
}
```

- `switches` maps feature ids to `true` or `false`; `fields` maps feature ids to their fields' new values by field id.
- What the request leaves out stays as it is. Send a secret only to change it; an empty text or secret clears it.
- `details` is as in `GET`: the answer's fields of that feature come with their notes.
- A switch or field the store does not have refuses the whole request with `400` and saves nothing.
- Every value its field accepts is saved. The others are not, and `errors` says why, by `<feature>.<field>`, in the order the fields show: a number out of its range, a value not among a select's options, a Turnstile secret key Cloudflare does not accept (asked before it is saved; while Cloudflare cannot be reached, the key is saved).

The answer is `200` with `errors` and the features as saved, in the shape `GET` gives:

```json
{
  "errors": { "bot-protection.secret-key": "Cloudflare does not accept this secret key. Copy it again from your Turnstile widget." },
  "groups": [],
  "features": []
}
```

`errors` is `{}` when everything was saved.

## Put a block back in templates

A feature whose block Block Hooks place (quick view, product badges, swatches and the others) shows `removed` while the store has removed its block from a saved template. This puts it back where it was removed, and leaves the rest of those templates as they are:

```text
POST /wp-json/lanternwp/v1/features/<feature>/restore
```

It also needs a user who can edit the theme's templates (`edit_theme_options`). The answer has the titles of the templates that have the block again (`restored`), those that could not be saved (`failed`) and the features after it, as `GET` gives them (send `details` as in `GET` for their notes). A feature without a block answers `404`.

## From PHP

In a plugin, the same request without HTTP:

```php
$request = new WP_REST_Request( 'POST', '/lanternwp/v1/features' );
$request->set_header( 'content-type', 'application/json' );
$request->set_body( wp_json_encode( array( 'switches' => array( 'quick-view' => true ) ) ) );
$response = rest_do_request( $request );
```

It runs as the current user, so it needs one who can manage WooCommerce.
