> ## 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.
> Never use /api/company/v1 or /api/v1 paths, page/per_page params, or offset pagination — they are legacy.
> 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).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.

# Build theme schemas

> Define section settings, blocks, presets, resource selectors, and reusable option groups for Fluid's visual editor.

A schema describes the controls and content structure that Fluid exposes in the visual editor.
Place valid JSON between `{% schema %}` and `{% endschema %}` in a section or standalone block.

```liquid theme={null}
{% schema %}
{
  "name": "Featured collection",
  "settings": [],
  "blocks": [],
  "presets": [
    { "name": "Featured collection" }
  ]
}
{% endschema %}
```

The schema tag does not render HTML.
Your Liquid markup reads the values that an editor selects.

## Define a section schema

A section schema can use these top-level fields:

| Field         | Purpose                                      |
| ------------- | -------------------------------------------- |
| `name`        | Display name in the editor                   |
| `tag`         | Wrapper element used for the section         |
| `class`       | Class added to the wrapper                   |
| `settings`    | Controls for one section instance            |
| `blocks`      | Repeatable child content definitions         |
| `max_blocks`  | Maximum combined number of blocks            |
| `presets`     | Initial content and picker variants          |
| `enabled_on`  | Template types where the section is allowed  |
| `disabled_on` | Template types where the section is excluded |

Use either `enabled_on` or `disabled_on` to constrain placement:

```json theme={null}
{
  "name": "Product highlights",
  "enabled_on": {
    "templates": ["product"]
  },
  "settings": [],
  "presets": [
    { "name": "Product highlights" }
  ]
}
```

## Define a setting

Every setting needs a supported `type`.
Every value-producing setting needs a unique, non-empty `id` within its settings array.
Use the ID to read the saved value:

```liquid theme={null}
<section style="background: {{ section.settings.background }}">
  <h2>{{ section.settings.heading }}</h2>
</section>

{% schema %}
{
  "name": "Announcement",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Free delivery on orders over $75"
    },
    {
      "type": "color",
      "id": "background",
      "label": "Background",
      "default": "#e7f0ff"
    }
  ],
  "presets": [
    { "name": "Announcement" }
  ]
}
{% endschema %}
```

Common setting fields include:

| Field                        | Purpose                                   |
| ---------------------------- | ----------------------------------------- |
| `type`                       | Editor control or resource selector       |
| `id`                         | Stable key used in Liquid and saved data  |
| `label`                      | Label shown to editors                    |
| `default`                    | Initial value                             |
| `options`                    | Choices for selection controls            |
| `min`, `max`, `step`, `unit` | Numeric control constraints               |
| `content`                    | Text displayed by a `header` setting      |
| `visible_if`                 | Conditions that control editor visibility |

Keep IDs stable after a theme ships.
Changing an ID disconnects the schema from existing saved values.

## Choose a setting type

Use the following value controls in section and block schemas.
Where an alias is shown, use one spelling consistently within a theme.

### Text and URL

| Type                    | Use               |
| ----------------------- | ----------------- |
| `text`, `plaintext`     | Short plain text  |
| `textarea`              | Longer plain text |
| `rich_text`, `richtext` | Formatted content |
| `html`, `html_textarea` | HTML content      |
| `url`                   | A link            |

### Numbers and choices

| Type       | Use                    |
| ---------- | ---------------------- |
| `number`   | Numeric input          |
| `range`    | Bounded numeric slider |
| `select`   | Dropdown choice        |
| `radio`    | Visible single choice  |
| `checkbox` | Boolean choice         |

A range should define its bounds and increment:

```json theme={null}
{
  "type": "range",
  "id": "columns",
  "label": "Columns",
  "min": 2,
  "max": 5,
  "step": 1,
  "unit": "items",
  "default": 3
}
```

Use `label` and `value` for each explicit select or radio choice:

```json theme={null}
{
  "type": "select",
  "id": "alignment",
  "label": "Alignment",
  "options": [
    { "label": "Left", "value": "left" },
    { "label": "Center", "value": "center" }
  ],
  "default": "left"
}
```

### Visual and media

| Type                        | Use            |
| --------------------------- | -------------- |
| `color`, `color_background` | Color value    |
| `font`, `font_picker`       | Font choice    |
| `image`, `image_picker`     | Image choice   |
| `video_picker`              | Video choice   |
| `media_picker`              | Media choice   |
| `text_alignment`            | Text alignment |

### Layout

| Type               | Use                            |
| ------------------ | ------------------------------ |
| `media_fit`        | Media fit behavior             |
| `corner_radius`    | Per-corner radius values       |
| `padding`          | Spacing values                 |
| `border`           | Border configuration           |
| `gradient_overlay` | Gradient overlay configuration |

Composite controls can return structured values.
Inspect the selected value before you interpolate one directly into CSS.

### Organization

Use `header` to divide a long settings panel:

```json theme={null}
{
  "type": "header",
  "content": "Card appearance"
}
```

A header organizes controls.
It does not create a value that you read in Liquid.

### Resource selectors

Fluid resolves the following setting types into Liquid resource values:

| Type              | Resolved value      |
| ----------------- | ------------------- |
| `product`         | One product         |
| `variant`         | One product variant |
| `collection`      | One collection      |
| `category`        | One category        |
| `enrollment_pack` | One enrollment pack |
| `media`           | One medium          |
| `link_list`       | One navigation menu |
| `product_list`    | Several products    |

The saved identifier resolves to a resource value before Liquid reads the setting:

```json theme={null}
{
  "type": "product",
  "id": "featured_product",
  "label": "Featured product"
}
```

```liquid theme={null}
{% assign featured_product = section.settings.featured_product %}
{% if featured_product %}
  <a href="{{ featured_product.url }}">{{ featured_product.title }}</a>
{% endif %}
```

Use `product_list` when the editor should choose several products.
Iterate the resolved value:

```liquid theme={null}
{% for item in section.settings.featured_products %}
  <a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
```

```json theme={null}
{
  "type": "product_list",
  "id": "featured_products",
  "label": "Featured products"
}
```

## Define inline blocks

Inline blocks belong to one section.
A block definition needs `type` and `name` when it defines settings.

```json theme={null}
{
  "name": "Testimonials",
  "max_blocks": 8,
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "limit": 8,
      "settings": [
        {
          "type": "textarea",
          "id": "quote",
          "label": "Quote",
          "default": "The daily essentials fit naturally into my routine."
        },
        {
          "type": "text",
          "id": "author",
          "label": "Author",
          "default": "Jordan Lee"
        }
      ]
    }
  ]
}
```

Block settings must be an array.
Duplicate block types are ambiguous and produce a validation warning.
Duplicate setting IDs produce an error.

Render inline blocks from `section.blocks`:

```liquid theme={null}
{% for block in section.blocks %}
  <blockquote {{ block.fluid_attributes }}>
    <p>{{ block.settings.quote }}</p>
    <cite>{{ block.settings.author }}</cite>
  </blockquote>
{% endfor %}
```

Standalone block references contain only `type`.
Use `@theme` to accept public standalone blocks and `@app` to accept compatible extension blocks.
See [blocks and components](/themes/blocks-and-components) for rendering and nesting patterns.

## Add presets

A preset can provide a display `name`, picker `category`, initial `settings`, and initial `blocks`.

```json theme={null}
{
  "presets": [
    {
      "name": "Customer stories",
      "category": "Social proof",
      "settings": {
        "heading": "Loved by our community"
      },
      "blocks": [
        {
          "type": "testimonial",
          "settings": {
            "quote": "The setup was quick and the results were easy to understand.",
            "author": "Morgan Ruiz"
          }
        }
      ]
    }
  ]
}
```

Use preset values that match IDs declared in the section and block schemas.
A preset does not add new setting definitions.

## Reuse option groups

Use an option group when several section or block settings should share a choice list defined by the root theme.
This keeps choices such as type scales consistent across the editor.

First, add `option_group` metadata to settings in `config/settings_schema.json`:

```json theme={null}
[
  {
    "name": "Typography",
    "settings": [
      {
        "type": "range",
        "id": "text_small",
        "label": "Small text size",
        "min": 10,
        "max": 24,
        "step": 1,
        "default": 14,
        "option_group": {
          "id": "type_scale",
          "label": "Small",
          "value": "text-small"
        }
      },
      {
        "type": "range",
        "id": "text_large",
        "label": "Large text size",
        "min": 18,
        "max": 48,
        "step": 1,
        "default": 28,
        "option_group": {
          "id": "type_scale",
          "label": "Large",
          "value": "text-large"
        }
      }
    ]
  }
]
```

Each matching `option_group.id` contributes one choice.
The editor uses `option_group.label` as the choice label and `option_group.value` as its saved value.
Duplicate values are collapsed to the first matching choice.

Then set `options` to the plain option-group ID:

```json theme={null}
{
  "type": "select",
  "id": "heading_size",
  "label": "Heading size",
  "options": "type_scale",
  "default": "text-large"
}
```

Use only letters, numbers, `_`, and `-` in an option-group ID.
If the group is missing or malformed, the editor cannot resolve choices for the setting.
An explicit `options` array continues to work and takes precedence over option-group resolution.

<Warning>
  Do not use a Liquid expression or an object in `options` to reference an option group.
  Use the plain group ID, such as `"type_scale"`.
</Warning>

See [root theme configuration](/themes/root-theme-configuration) for the rest of the settings flow.

## Validate before publishing

Before you publish a schema:

* Confirm that the schema body is valid JSON.
* Give each value-producing setting a unique, non-empty ID.
* Use only supported setting types.
* Define block `settings` as an array.
* Give inline blocks a `name`.
* Confirm that preset values match declared IDs and block types.
* Confirm that named standalone blocks exist.
* Test default and edited values in the visual editor.

Run `fluid theme lint --json` to catch schema and reference problems before you push.
See the [CLI guide](/themes/cli) for the complete local workflow.
