> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluid.app/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For new direct REST integrations, use the v2026-04 surfaces. The @fluid-app FairShare SDK continues to use its own published public-v2025-06 contract.
> Authenticate with the header Authorization: Bearer <token>; public storefront read endpoints require no auth.
> Lists use cursor pagination via the page[cursor] and page[limit] query params; follow meta.pagination.next_cursor until it is null.
> When the same operation exists on more than one surface, use the newest: dated API versions are newer than unversioned ones, and later dates win (v2026-04 > v2025-06 > unversioned v0/v1.1). Fall back to a legacy or unversioned operation only when no newer versioned equivalent exists — the company-v0 notes below list the known superseded operations. /api/company/v1 and /api/v1/... paths are documented in no spec here and must never be used (/api/v1.1/... is distinct and documented in company-v0). Use page/per_page offset pagination only where a spec documents it — in practice the unversioned company-v0 admin surface; every versioned surface uses cursor pagination.
> The OpenAPI specs under api-reference/ are the authoritative contracts; prefer them over prose when in doubt. api-reference/storefront-v2026-04.yaml covers the v2026-04 storefront surface (/api/v202604/... paths); api-reference/auth-v0.yaml covers the unversioned auth surface (/api/... paths — authentication, MFA, social auth, and token exchange); api-reference/checkout-v2026-04.yaml covers the v2026-04 checkout surface (/api/checkout/v2026-04/... paths — carts, cart auth, discounts, items, subscriptions, orders, enrollments, and store config); api-reference/public-v2025-06.yaml covers the Public SDK surface used by the @fluid-app FairShare SDK, including its parallel cart lifecycle, browser integrations, versioned payment callbacks, unversioned public utilities, and the cart price-override operation; api-reference/payment-v2026-04.yaml covers the v2026-04 payment gateway admin surface (/api/payment/v2026-04/... paths, bearer-authenticated — gateway CRUD, gateway purchase/authorize/$0-verify, transaction list/show and capture/void/credit, and merchant payment configuration); api-reference/payments-v2026-04.yaml covers the v2026-04 cart payment surface (/api/payments/v2026-04/carts/{cart_token}/... paths, authenticated by the cart token in the path with no bearer — payment-method selection, VGS card tokenization, 3D Secure verification, and PayPal/Braintree/Klarna/Apple Pay flows); api-reference/commerce-v2026-04.yaml covers the v2026-04 commerce order-editing surface (/api/v202604/orders/{order_id}/edits paths, bearer-authenticated — post-checkout order edits that atomically insert items and add adjustments/discounts, with an optional dry-run preview); api-reference/webhooks-v0.yaml covers the unversioned webhooks surface (/api/... paths — webhook registration, delivery payloads, callback registrations, company events, and webhook/callback schemas); api-reference/company-v0.yaml covers the legacy unversioned company admin surface (/api/... paths, bearer-authenticated — company settings and management, customers, users, roles, subscription plans, subscription bundles, subscriptions, media, pages, catch-ups, inventory levels, domains, agreements, and admin order actions). company-v0 caveats: it is the legacy v0 admin contract and its lists use flat page/per_page offset pagination, which is expected there despite the general cursor-pagination rule; where an operation exists in both company-v0 and a versioned spec, prefer the versioned spec — the subscriptions lifecycle (list/create/show/update, cancel, pause, reactivate, resume, retry, skip, failed-cycle-waiver, discounts) and subscription bundles are superseded by checkout-v2026-04, and company pages/media CRUD plus the public pages, categories, products, and media list endpoints are superseded by storefront-v2026-04. Subscription plan management (/api/subscription_plans, resource-wrapped {"subscription_plan": {...}} bodies) exists only in company-v0. api-reference/analytics-v2026-04.yaml covers the v2026-04 Home dashboard analytics surface (/api/v202604/analytics/dashboard/... paths, bearer-authenticated — read-only endpoints for the Home > Overview, Home > Live, and Home > Field tabs, each accepting an optional country ISO alpha-2 query param that scopes aggregations to a single country).
> api-reference/analytics-v0.yaml covers the unversioned analytics surface that backs the fluid-admin Traffic tab (/api/analytics/... and /api/analytics/traffic/... paths, bearer-authenticated — the legacy shares/views/visitors summary plus traffic overview, ranked campaigns, sources, geographies, flows, and per-rep breakdown, all sharing one reporting-period contract).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.
> Portal Definition authoring edits and synchronizes the portal JSON resource graph. Widget Package authoring builds either a company-owned or Droplet-owned Remote DOM package. These are separate contracts; do not imply that one defines the other.
> For Widget Package worker code, use only @fluid-app/portal-sdk/widgets/worker. Use only the Portal Definition and Widget Package workflows and public entry points documented here; do not infer support for undocumented surfaces.
> Every portal function and declarative capability used by a widget must appear in that widget's uses array. Use the same typed function value in uses; do not invent capability-name strings.
> Widget styling must use the portal's semantic theme variables for colors, typography, spacing, radii, borders, focus, and charts whenever a token represents the visual decision. Do not create a separate light or dark palette or duplicate theme controls as widget properties.
> Prefer worker-safe Fluid UI components exported by @fluid-app/portal-sdk/widgets/worker when they fit the interaction. When no exported component fits, use semantic HTML, accessible behavior, and the portal theme variables.
> A Portal Definition push updates the remote working definition. A portal version is an immutable snapshot, and activation is a separate live release operation.

# Writing settings schema

> Shape config/settings_schema.json so the theme panel renders every control and groups your text styles into presets.

`config/settings_schema.json` declares everything a merchant can change about your theme. The editor renders it as the theme panel, and infers structure from it — from IDs, types, labels, and a few reserved keys.

Most of that inference goes into typography. A text preset is one font-size setting plus the font and weight it uses, and the editor groups each into a single card so a merchant edits **H1** rather than three unrelated controls, then applies **H1** to content from the rich-text toolbar.

A schema that passes `fluid theme lint` can still render controls the panel cannot draw, produce a flat list where you expected presets, or produce presets that resolve to nothing. This page covers the shape that works.

For the linking mechanics behind presets, see [linked CSS presets](/themes/linked-css-variable-presets). For the wider settings flow, see [root theme configuration](/themes/root-theme-configuration). For settings inside a section's `{% schema %}`, see [theme schemas](/themes/schema-components).

## Setting types the theme panel renders

The theme panel renders a narrower set of types than a section schema does. Anything outside this list renders as a red **Unsupported setting type** message in the panel.

| Category            | Types                                                   |
| ------------------- | ------------------------------------------------------- |
| Text                | `text`, `textarea`, `url`                               |
| Numbers and choices | `range`, `number`, `select`, `radio`, `checkbox`        |
| Color               | `color`, `color_background`                             |
| Typography          | `font_picker`, `font_weight`                            |
| Media               | `image`, `image_picker`, `video_picker`, `media_picker` |

Two of these behave differently from the section-schema equivalents documented in [theme schemas](/themes/schema-components):

* **Use `font_picker` for every font.** The editor also treats `font` and `font_family` as fonts when resolving a preset, so a reference to one still links — but neither renders a control in the theme panel, leaving a merchant unable to change it.
* **`header` is dropped, not rendered.** The panel discards any setting without an `id` and a `label`, which every header entry lacks.

For the full set of types available inside a section's `{% schema %}`, see [theme schemas](/themes/schema-components).

## Name the group `typography`

Only a group named exactly `typography` becomes preset cards. Any other name renders a flat list, however the settings inside it are shaped.

```json theme={null}
{
  "name": "typography",
  "settings": [ ... ]
}
```

`Typography`, `type`, and `text` all miss. The match is exact and case-sensitive.

<Warning>
  The editor drops any setting without an `id`, a `type`, and a `label`. That includes every `{"type": "header"}` entry, so headers do not separate settings in the theme panel. Use separate groups instead.
</Warning>

## Declare fonts and weights once

Give each typeface one `font_picker`, and each step in your weight scale its own setting.

```json theme={null}
[
  {
    "name": "fonts",
    "settings": [
      {
        "type": "font_picker",
        "id": "font_family_heading",
        "label": "Heading font",
        "default": "Reckless Neue"
      },
      {
        "type": "font_picker",
        "id": "font_family_body",
        "label": "Body font",
        "default": "Basis Grotesque"
      }
    ]
  },
  {
    "name": "font_weights",
    "settings": [
      {
        "type": "range",
        "id": "font_weight_normal",
        "label": "Normal",
        "min": 100,
        "max": 900,
        "step": 100,
        "default": 400
      },
      {
        "type": "range",
        "id": "font_weight_bold",
        "label": "Bold",
        "min": 100,
        "max": 900,
        "step": 100,
        "default": 700
      }
    ]
  }
]
```

Fonts and presets may share one group. The editor gathers every font into whichever group declares the first one, so a merchant finds them all in one place.

### Name fonts by role in the ID

A preset's font picker labels each choice from the setting's **ID**, with the `font_family_` prefix removed — not from its label. Most themes label both font settings "Font family", which tells a merchant nothing about which is which.

| Setting ID            | Appears in the preset picker as                |
| --------------------- | ---------------------------------------------- |
| `font_family_heading` | Heading                                        |
| `font_family_body`    | Body                                           |
| `font_family_display` | Display                                        |
| `brand_typeface`      | Falls back to the label — no recognized prefix |

`font_family_heading` and `font_family_body` are also reserved: they are what a preset falls back to when it names no font of its own. Declare at least one of them.

Weights are labelled from their `label`, because "Normal" and "Bold" are already the names a merchant wants.

## Declare each preset

Put size settings in `typography` and point each at a font and a weight.

```json theme={null}
{
  "type": "range",
  "id": "font_size_h1",
  "label": "H1",
  "min": 32,
  "max": 120,
  "step": 1,
  "unit": "px",
  "default": 64,
  "role": "heading",
  "font_family_ref": "font_family_heading",
  "font_weight_ref": "font_weight_bold"
}
```

Set `role` explicitly on every preset. Without it, an ID beginning `font_size_h` reads as a heading and everything else reads as body — a legacy convention that breaks the moment you name a heading preset something else.

Both references are optional and resolve anywhere in the schema, so the font may live in another group. Prefer them over the role fallback: a renamed ID then shows up as a visible break rather than a silent change in meaning.

<Note>
  The two references behave differently when they point at nothing. A `font_family_ref` that resolves to no setting leaves the preset with **no font** — it does not fall back to the role font. A `font_weight_ref` that resolves to nothing **does** fall back to the role weight. A missing weight has a sensible universal default; a missing typeface does not.
</Note>

## Know what counts as a preset

Every setting in `typography` is tested against the same questions. It becomes a preset card only if it is neither a font nor a weight.

| The editor reads it as | When any of these is true                                                                                                                                                         |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A font                 | `role` is `font_family`, or `type` is `font_picker`, `font_family`, or `font`. Only `font_picker` renders a control — see [setting types](#setting-types-the-theme-panel-renders) |
| A weight               | `role` is `font_weight`; `type` is `font_weight`; the **ID** matches `font_weight`, `font-weight`, or `fontweight`; or the **label** contains "font weight"                       |
| A preset               | Everything else with an `id`, `type`, and `label`                                                                                                                                 |

Presets are identified by exclusion, so a size may be typed `range`, `number`, `font_size`, or anything else — older themes that typed sizes inconsistently keep working.

<Warning>
  The label test is why a size setting labelled "Heading font weight scale" never becomes a preset card. Nothing in the schema overrides the classification, so rename the setting instead.
</Warning>

The rule exists because themes overwhelmingly author weights as `range` rather than as a dedicated type. Without it, `font_weight_body` would appear as a preset card sitting between H1 and H2.

## Make preset changes reach existing content

Declare a CSS variable for every font, weight, and size, then read the variables from your CSS. Without this step a merchant's edits change nothing on the page.

```liquid theme={null}
{% style %}
  :root {
    --font_family_heading: {{ settings.font_family_heading }};
    --font_family_body: {{ settings.font_family_body }};
    --font_weight_bold: {{ settings.font_weight_bold }};
    --font_size_h1: {{ settings.font_size_h1 }}px;
  }
{% endstyle %}
```

Name each variable after its setting ID. Any name resolves for reading, but the editor writes and removes `--<setting_id>` when it manages a variable for you, and matching keeps the two conventions from diverging.

### Read the preset's own variable first

The editor also maintains a variable per preset property, written the first time a merchant applies that preset to content:

```css theme={null}
--font_size_h1_font_family: var(--font_family_heading);
--font_size_h1_font_weight: var(--font_weight_bold);
```

Content styled as **H1** records `var(--font_size_h1_font_family)` — named for the preset, not for the font it happens to use today. When a merchant repoints H1 to a different font, that one declaration changes and every piece of content styled as H1 follows.

Write your own CSS the same way, with the target as a fallback:

```css theme={null}
h1 {
  font-family: var(--font_size_h1_font_family, var(--font_family_heading));
  font-weight: var(--font_size_h1_font_weight, var(--font_weight_bold));
  font-size: var(--font_size_h1);
}
```

<Tip>
  Reading only `var(--font_family_heading)` is the most common reason a merchant reports that they changed a preset and nothing happened. The panel looks correct, rich-text content updates, and your own headings keep the old font.
</Tip>

## Expect the editor to write to your schema

The editor edits `config/settings_schema.json` in place. Leave these alone:

| What appears                                          | Written when                                                                                                                                                            |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `generated_for` on a setting                          | The editor created that setting. Only settings carrying this key can be removed through the panel — one you authored is never deleted, because your Liquid may read it. |
| New entries in every font picker's `options`          | A merchant added a Google font or uploaded a font file. It becomes selectable on every font in the theme.                                                               |
| `font_family_ref` or `font_weight_ref` on a preset    | A merchant repointed that preset.                                                                                                                                       |
| `--<preset_id>_font_family` in `layouts/theme.liquid` | A preset was applied to content.                                                                                                                                        |

Merchants can also add fonts from the panel with **Add font**, which appends one more `font_picker` alongside your heading and body fonts. Presets can then point at it like any other font.

<Warning>
  Never strip `generated_for` by hand. It is the only thing separating a setting the editor may remove from one it must not.
</Warning>

## Troubleshoot presets

| What you see                                              | Why                                                         | Fix                                                                 |
| --------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------- |
| Typography renders as a flat list                         | The group is not named exactly `typography`                 | Rename the group                                                    |
| A size setting never becomes a card                       | Its ID or label matches the weight rules                    | Rename the setting                                                  |
| A card shows a size control and nothing else              | Its references point at IDs that do not exist               | Correct the IDs, or drop the references and let `role` resolve them |
| Two fonts are indistinguishable in a preset's picker      | Both are labelled "Font family" and their IDs carry no role | Rename the IDs to `font_family_<role>`                              |
| A merchant changes a preset and your headings do not move | Your CSS reads the font variable directly                   | Read the preset variable first, with the target as fallback         |
| Section headers are missing from the panel                | The editor drops settings without an `id` and `label`       | Use separate groups                                                 |

## Checklist

* Every setting uses a type the theme panel renders, and fonts use `font_picker`
* The preset group is named exactly `typography`
* Every preset sets `role` and `unit`
* Font IDs carry their role, and at least one of `font_family_heading` or `font_family_body` exists
* Every `font_family_ref` and `font_weight_ref` points at an ID that exists
* No size setting's ID or label contains "font weight"
* `layouts/theme.liquid` declares a variable for every font, weight, and size
* Your CSS reads preset variables with the target variable as a fallback
