# Content Editors

Content editors define the merchant-editable content fields for your section. Each editor maps to a specific input widget in the Instant Site Editor — text inputs, image uploaders, toggles, dropdown selects, and more.

### Overview <a href="#overview" id="overview"></a>

All content editors are created using factory functions from `@lightspeed/crane-api`:

```typescript
import { content } from '@lightspeed/crane-api';
```

| Type                                      | Factory                      | Purpose                            |
| ----------------------------------------- | ---------------------------- | ---------------------------------- |
| [`INPUTBOX`](#inputbox)                   | `content.inputbox()`         | Single-line text input             |
| [`TEXTAREA`](#textarea)                   | `content.textarea()`         | Multi-line text input              |
| [`BUTTON`](#button)                       | `content.button()`           | Button with text, link, and action |
| [`IMAGE`](#image)                         | `content.image()`            | Image upload                       |
| [`TOGGLE`](#toggle)                       | `content.toggle()`           | Boolean on/off switch              |
| [`SELECTBOX`](#selectbox)                 | `content.selectbox()`        | Dropdown selection                 |
| [`DECK`](#deck)                           | `content.deck()`             | Collection of repeatable cards     |
| [`MENU`](#menu)                           | `content.menu()`             | Menu items list                    |
| [`NAVIGATION_MENU`](#navigation-menu)     | `content.navigationMenu()`   | Navigation menu (header)           |
| [`LOGO`](#logo)                           | `content.logo()`             | Logo (text or image)               |
| [`DIVIDER`](#divider)                     | `content.divider()`          | Visual separator in the editor     |
| [`INFO`](#info)                           | `content.info()`             | Informational text in the editor   |
| [`PRODUCT_SELECTOR`](#product-selector)   | `content.productSelector()`  | Product picker                     |
| [`CATEGORY_SELECTOR`](#category-selector) | `content.categorySelector()` | Category picker                    |

{% hint style="info" icon="tag" %}
**Label Convention**

All `label`, `placeholder`, and `description` properties must be translation keys starting with `$` (e.g., `'$label.content.title'`). These are resolved from your [translation files](/site-themes/develop-site-themes/sections/settings/translations.md).
{% endhint %}

### INPUTBOX <a href="#inputbox" id="inputbox"></a>

Single-line text input for short content like titles and headings.

#### Properties <a href="#properties" id="properties"></a>

| Property      | Type     | Required | Description                          |
| ------------- | -------- | -------- | ------------------------------------ |
| `label`       | `string` | Yes      | Translation key for the editor label |
| `placeholder` | `string` | Yes      | Translation key for placeholder text |
| `defaults`    | `object` | No       | Default values                       |

#### Defaults <a href="#defaults" id="defaults"></a>

| Property | Type     | Required | Description                          |
| -------- | -------- | -------- | ------------------------------------ |
| `text`   | `string` | Yes      | Default text value (translation key) |

#### Factory <a href="#factory" id="factory"></a>

```typescript
content.inputbox({
  label: '$label.content.title',
  placeholder: '$label.content.title_placeholder',
  defaults: { text: '$label.defaults.title' },
})
```

### TEXTAREA <a href="#textarea" id="textarea"></a>

Multi-line text input for longer content like descriptions and paragraphs.

#### Properties <a href="#properties-1" id="properties-1"></a>

| Property      | Type     | Required | Description                          |
| ------------- | -------- | -------- | ------------------------------------ |
| `label`       | `string` | Yes      | Translation key for the editor label |
| `placeholder` | `string` | Yes      | Translation key for placeholder text |
| `defaults`    | `object` | No       | Default values                       |

#### Defaults <a href="#defaults-1" id="defaults-1"></a>

| Property | Type     | Required | Description                          |
| -------- | -------- | -------- | ------------------------------------ |
| `text`   | `string` | Yes      | Default text value (translation key) |

#### Factory <a href="#factory-1" id="factory-1"></a>

```typescript
content.textarea({
  label: '$label.content.description',
  placeholder: '$label.content.description_placeholder',
  defaults: { text: '$label.defaults.description' },
})
```

### BUTTON <a href="#button" id="button"></a>

Button with configurable text, action type, and target. The button action type determines which additional fields are required.

#### Properties <a href="#properties-2" id="properties-2"></a>

| Property   | Type     | Required | Description                          |
| ---------- | -------- | -------- | ------------------------------------ |
| `label`    | `string` | Yes      | Translation key for the editor label |
| `defaults` | `object` | No       | Default values                       |

#### Defaults <a href="#defaults-2" id="defaults-2"></a>

| Property     | Type     | Required | Description                            |
| ------------ | -------- | -------- | -------------------------------------- |
| `title`      | `string` | Yes      | Default button text (translation key)  |
| `buttonType` | `string` | Yes      | Action type (see table below)          |
| `link`       | `string` | No       | URL for `HYPER_LINK` type              |
| `linkTarget` | `string` | No       | Link target (e.g., `_blank`)           |
| `email`      | `string` | No       | Email address for `MAIL_LINK` type     |
| `phone`      | `string` | No       | Phone number for `TEL_LINK` type       |
| `tileId`     | `string` | No       | Section ID for `SCROLL_TO_TILE` type   |
| `categoryId` | `number` | No       | Category ID for `GO_TO_CATEGORY` types |

#### Button Types <a href="#button-types" id="button-types"></a>

| Type                  | Required Fields | Description                     |
| --------------------- | --------------- | ------------------------------- |
| `HYPER_LINK`          | `link`          | Navigate to a URL               |
| `MAIL_LINK`           | `email`         | Open email client               |
| `TEL_LINK`            | `phone`         | Open phone dialer               |
| `SCROLL_TO_TILE`      | `tileId`        | Scroll to a section on the page |
| `GO_TO_CATEGORY`      | `categoryId`    | Navigate to a store category    |
| `GO_TO_CATEGORY_LINK` | `categoryId`    | Link to a store category        |
| `GO_TO_STORE`         | —               | Navigate to the store page      |
| `GO_TO_STORE_LINK`    | —               | Link to the store page          |
| `GO_TO_PAGE`          | —               | Navigate to a specific page     |

#### Factory <a href="#factory-2" id="factory-2"></a>

```typescript
content.button({
  label: '$label.content.cta_button',
  defaults: {
    title: '$label.defaults.cta',
    buttonType: 'HYPER_LINK',
    link: 'https://example.com',
  },
})
```

{% hint style="warning" %}
**Validation**

Crane validates that the required fields are present based on `buttonType`. \
For example, `link` is required when `buttonType` is `HYPER_LINK`, and `email` is required when `buttonType` is `MAIL_LINK`. Missing required fields produce build errors.
{% endhint %}

### IMAGE <a href="#image" id="image"></a>

Image upload with optional static flag. Images support multiple resolutions for responsive rendering.

#### Properties <a href="#properties-3" id="properties-3"></a>

| Property   | Type      | Required | Description                                                                                                                  |
| ---------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `label`    | `string`  | Yes      | Translation key for the editor label                                                                                         |
| `static`   | `boolean` | No       | If `true`, marks the image as static (non-editable by merchants). Must match the corresponding design editor's `static` flag |
| `defaults` | `object`  | No       | Default values                                                                                                               |

#### Defaults <a href="#defaults-3" id="defaults-3"></a>

| Property    | Type     | Required | Description                                                                |
| ----------- | -------- | -------- | -------------------------------------------------------------------------- |
| `imageData` | `object` | Yes      | Image data with `set` (image URLs by resolution) and optional `borderInfo` |

The `imageData.set` object must contain at least one key from: `ORIGINAL`, `WEBP_LOW_RES`, `WEBP_HI_2X_RES`, `MOBILE_WEBP_LOW_RES`, `MOBILE_WEBP_HI_RES`. Each value has `url` (required), `width` (optional), and `height` (optional).

#### Factory <a href="#factory-3" id="factory-3"></a>

```typescript
content.image({
  label: '$label.content.hero_image',
})
```

{% hint style="warning" %}
Validation

If both a content `IMAGE` editor and a design `IMAGE` editor reference the same field, their `static` property values must match. Mismatches produce the error: *"Both content and design editor need to have same value for attribute static."*
{% endhint %}

### TOGGLE <a href="#toggle" id="toggle"></a>

Boolean on/off switch for enabling or disabling features.

#### Properties <a href="#properties-4" id="properties-4"></a>

| Property      | Type     | Required | Description                                |
| ------------- | -------- | -------- | ------------------------------------------ |
| `label`       | `string` | Yes      | Translation key for the editor label       |
| `description` | `string` | Yes      | Translation key for the toggle description |
| `defaults`    | `object` | No       | Default values                             |

#### Defaults <a href="#defaults-4" id="defaults-4"></a>

| Property  | Type      | Required | Description          |
| --------- | --------- | -------- | -------------------- |
| `enabled` | `boolean` | Yes      | Default toggle state |

#### Factory <a href="#factory-4" id="factory-4"></a>

```typescript
content.toggle({
  label: '$label.content.show_overlay',
  description: '$label.content.show_overlay_description',
  defaults: { enabled: true },
})
```

### SELECTBOX <a href="#selectbox" id="selectbox"></a>

Dropdown selection with predefined options.

#### Properties <a href="#properties-5" id="properties-5"></a>

| Property      | Type     | Required | Description                          |
| ------------- | -------- | -------- | ------------------------------------ |
| `label`       | `string` | Yes      | Translation key for the editor label |
| `placeholder` | `string` | Yes      | Translation key for placeholder text |
| `description` | `string` | Yes      | Translation key for description text |
| `options`     | `array`  | Yes      | Array of selectable options (min 1)  |
| `defaults`    | `object` | No       | Default values                       |

Each option in `options`:

| Property | Type     | Required | Description                          |
| -------- | -------- | -------- | ------------------------------------ |
| `value`  | `string` | Yes      | Option value (must not be empty)     |
| `label`  | `string` | Yes      | Translation key for the option label |

#### Defaults <a href="#defaults-5" id="defaults-5"></a>

| Property | Type     | Required | Description                                                     |
| -------- | -------- | -------- | --------------------------------------------------------------- |
| `value`  | `string` | Yes      | Default selected value (must match one of the `options` values) |

#### Factory <a href="#factory-5" id="factory-5"></a>

```typescript
content.selectbox({
  label: '$label.content.alignment',
  placeholder: '$label.content.alignment_placeholder',
  description: '$label.content.alignment_description',
  options: [
    { value: 'left', label: '$label.options.left' },
    { value: 'center', label: '$label.options.center' },
    { value: 'right', label: '$label.options.right' },
  ],
  defaults: { value: 'center' },
})
```

{% hint style="warning" %}
**Validation**

In [showcases](/site-themes/develop-site-themes/sections/showcases.md), selectbox default values must match one of the options defined in the content editor. Invalid option values produce the error: *"Option value is not defined in content.ts options."*
{% endhint %}

### DECK <a href="#deck" id="deck"></a>

Collection of repeatable cards. Merchants can add, remove, and reorder cards. Each card contains its own set of content editors.

#### Properties <a href="#properties-6" id="properties-6"></a>

| Property         | Type      | Required | Description                               |
| ---------------- | --------- | -------- | ----------------------------------------- |
| `label`          | `string`  | Yes      | Translation key for the editor label      |
| `addButtonLabel` | `string`  | Yes      | Translation key for the "Add card" button |
| `maxCards`       | `integer` | Yes      | Maximum number of cards (min 1)           |
| `cards`          | `object`  | Yes      | Card configuration (see below)            |
| `defaults`       | `object`  | No       | Default values                            |

#### Card Configuration <a href="#card-configuration" id="card-configuration"></a>

The `cards` object has the following structure:

```typescript
cards: {
  defaultCardContent: {
    label: string;       // Translation key for the card label
    settings: Record<string, ContentEditor>;  // Editors within each card
  }
}
```

The `settings` record can contain any content editor type except `MENU`, `NAVIGATION_MENU`, or `LOGO`. Each card gets a copy of these settings.

{% hint style="info" %}
**Nested `DECK`**

Card settings may include a `DECK` editor – slides with nested link decks. Nesting is limited to **one level**: a top-level `DECK` can contain cards whose settings have a `DECK`, but that inner `DECK`’s card settings cannot contain another `DECK` (max depth 2). Exceeding the limit produces a validation error.
{% endhint %}

#### Factory <a href="#factory-6" id="factory-6"></a>

```typescript
content.deck({
  label: '$label.content.slides',
  addButtonLabel: '$label.content.add_slide',
  maxCards: 10,
  cards: {
    defaultCardContent: {
      label: '$label.content.slide',
      settings: {
        slide_image: content.image({ label: '$label.content.slide_image' }),
        slide_title: content.inputbox({
          label: '$label.content.slide_title',
          placeholder: '$label.content.slide_title_placeholder',
          defaults: { text: '$label.defaults.slide_title' },
        }),
      },
    },
  },
})
```

{% hint style="warning" %}
**Validation**

In [showcases](/site-themes/develop-site-themes/sections/showcases.md), deck card settings are validated against the `defaultCardContent.settings` definition. Settings keys that don't exist in the deck definition produce errors.
{% endhint %}

### MENU <a href="#menu" id="menu"></a>

Menu items list. The `label` property is optional for this editor.

#### Properties <a href="#properties-7" id="properties-7"></a>

| Property   | Type     | Required | Description                          |
| ---------- | -------- | -------- | ------------------------------------ |
| `label`    | `string` | No       | Translation key for the editor label |
| `defaults` | `object` | No       | Default values                       |

#### Defaults <a href="#defaults-6" id="defaults-6"></a>

| Property | Type    | Required | Description         |
| -------- | ------- | -------- | ------------------- |
| `items`  | `array` | No       | Array of menu items |

Each menu item:

| Property              | Type      | Required | Description                                           |
| --------------------- | --------- | -------- | ----------------------------------------------------- |
| `id`                  | `string`  | Yes      | Unique identifier                                     |
| `title`               | `string`  | No       | Display text                                          |
| `type`                | `string`  | No       | Action type (same as button types, plus `GO_TO_PAGE`) |
| `link`                | `string`  | No       | URL (for `HYPER_LINK`)                                |
| `email`               | `string`  | No       | Email (for `MAIL_LINK`)                               |
| `phone`               | `string`  | No       | Phone (for `TEL_LINK`)                                |
| `tileIdForScroll`     | `string`  | No       | Section ID (for `SCROLL_TO_TILE`)                     |
| `pageIdForNavigate`   | `string`  | No       | Page ID (for `GO_TO_PAGE`)                            |
| `showStoreCategories` | `boolean` | No       | Show category submenu                                 |
| `isSubmenuOpened`     | `boolean` | No       | Submenu expanded state                                |
| `categoryId`          | `number`  | No       | Category ID (for `GO_TO_CATEGORY`)                    |

#### Factory <a href="#factory-7" id="factory-7"></a>

```typescript
content.menu({
  label: '$label.content.footer_links',
  defaults: {
    items: [
      { id: 'about', title: 'About', type: 'HYPER_LINK', link: '/about' },
      { id: 'contact', title: 'Contact', type: 'MAIL_LINK', email: 'info@example.com' },
    ],
  },
})
```

### NAVIGATION\_MENU <a href="#navigation-menu" id="navigation-menu"></a>

Navigation menu for site headers. Similar to MENU but without a configurable label — it is always rendered as the main navigation.

#### Properties <a href="#properties-8" id="properties-8"></a>

| Property   | Type     | Required | Description    |
| ---------- | -------- | -------- | -------------- |
| `defaults` | `object` | No       | Default values |

#### Defaults <a href="#defaults-7" id="defaults-7"></a>

Same structure as [#menu](#menu "mention") defaults.

#### Factory <a href="#factory-8" id="factory-8"></a>

```typescript
content.navigationMenu({
  defaults: {
    items: [
      { id: 'home', title: 'Home', type: 'GO_TO_PAGE', pageIdForNavigate: 'home' },
      { id: 'shop', title: 'Shop', type: 'GO_TO_STORE' },
    ],
  },
})
```

{% hint style="info" %}
**Mandatory for Headers**

The `NAVIGATION_MENU` editor is mandatory for sections with type `HEADER`. It must be defined with the key `menu`. See [Sections](/site-themes/develop-site-themes/sections.md)
{% endhint %}

### LOGO <a href="#logo" id="logo"></a>

Logo element supporting both text and image modes.

#### Properties <a href="#properties-9" id="properties-9"></a>

| Property   | Type     | Required | Description                          |
| ---------- | -------- | -------- | ------------------------------------ |
| `label`    | `string` | No       | Translation key for the editor label |
| `defaults` | `object` | No       | Default values                       |

#### Defaults <a href="#defaults-8" id="defaults-8"></a>

| Property    | Type     | Required | Description                                                                |
| ----------- | -------- | -------- | -------------------------------------------------------------------------- |
| `logoType`  | `string` | No       | `"TEXT"` or `"IMAGE"`                                                      |
| `text`      | `string` | No       | Default logo text (translation key)                                        |
| `imageData` | `object` | No       | Default logo image (same structure as [#image](#image "mention") defaults) |

#### Factory <a href="#factory-9" id="factory-9"></a>

```typescript
content.logo({
  label: '$label.content.store_logo',
  defaults: { logoType: 'TEXT', text: '$label.defaults.store_name' },
})
```

{% hint style="info" %}
**Mandatory for Headers**

The `LOGO` editor is mandatory for sections with type `HEADER`. It must be defined with the key `logo`. See [Sections](/site-themes/develop-site-themes/sections.md)
{% endhint %}

### DIVIDER <a href="#divider" id="divider"></a>

Visual separator in the editor UI. Does not render any content in the storefront — it is purely for organizing the editor panel.

#### Properties <a href="#properties-10" id="properties-10"></a>

| Property   | Type     | Required | Description                           |
| ---------- | -------- | -------- | ------------------------------------- |
| `label`    | `string` | Yes      | Translation key for the divider label |
| `defaults` | `object` | No       | Default values (empty, type only)     |

#### Factory <a href="#factory-10" id="factory-10"></a>

```typescript
content.divider({
  label: '$label.content.advanced_section',
})
```

### INFO <a href="#info" id="info"></a>

Informational text displayed in the editor UI. Like the `DIVIDER`, this does not render in the storefront — it provides guidance to merchants.

#### Properties <a href="#properties-11" id="properties-11"></a>

| Property      | Type     | Required | Description                              |
| ------------- | -------- | -------- | ---------------------------------------- |
| `label`       | `string` | Yes      | Translation key for the info title       |
| `description` | `string` | Yes      | Translation key for the info description |
| `button`      | `object` | No       | Optional link button in the editor       |
| `defaults`    | `object` | No       | Default values                           |

The `button` object:

| Property | Type     | Required | Description                     |
| -------- | -------- | -------- | ------------------------------- |
| `label`  | `string` | Yes      | Translation key for button text |
| `link`   | `string` | Yes      | URL (must be a valid URL)       |

#### Defaults <a href="#defaults-9" id="defaults-9"></a>

| Property | Type     | Required | Description                         |
| -------- | -------- | -------- | ----------------------------------- |
| `text`   | `string` | No       | Default info text (translation key) |
| `button` | `object` | No       | `{ title?: string, link?: string }` |

#### Factory <a href="#factory-11" id="factory-11"></a>

```typescript
content.info({
  label: '$label.content.help_info',
  description: '$label.content.help_description',
  button: { label: '$label.content.learn_more', link: 'https://docs.example.com' },
})
```

### PRODUCT\_SELECTOR <a href="#product-selector" id="product-selector"></a>

Product picker allowing merchants to select products from their store catalog.

#### Properties <a href="#properties-12" id="properties-12"></a>

| Property      | Type      | Required | Description                          |
| ------------- | --------- | -------- | ------------------------------------ |
| `label`       | `string`  | Yes      | Translation key for the editor label |
| `maxProducts` | `integer` | Yes      | Maximum number of products (1–32)    |
| `defaults`    | `object`  | No       | Default values (empty, type only)    |

#### Factory <a href="#factory-12" id="factory-12"></a>

```typescript
content.productSelector({
  label: '$label.content.featured_products',
  maxProducts: 8,
})
```

### CATEGORY\_SELECTOR <a href="#category-selector" id="category-selector"></a>

Category picker allowing merchants to select store categories.

#### Properties <a href="#properties-13" id="properties-13"></a>

| Property        | Type      | Required | Description                          |
| --------------- | --------- | -------- | ------------------------------------ |
| `label`         | `string`  | Yes      | Translation key for the editor label |
| `maxCategories` | `integer` | Yes      | Maximum number of categories (1–32)  |
| `defaults`      | `object`  | No       | Default values (empty, type only)    |

#### Factory <a href="#factory-13" id="factory-13"></a>

```typescript
content.categorySelector({
  label: '$label.content.featured_categories',
  maxCategories: 6,
})
```

### Showcase Default Factories <a href="#showcase-default-factories" id="showcase-default-factories"></a>

When defining [showcase](/site-themes/develop-site-themes/sections/showcases.md) configurations, you need to provide default values for content fields independently of the editor definition. The `content.default.*()` factories create these standalone defaults:

```typescript
import { content } from '@lightspeed/crane-api';

// Used in showcase files, not in content.ts
content.default.inputbox({ text: '$label.showcase.title' });
content.default.textarea({ text: '$label.showcase.description' });
content.default.button({ title: '$label.showcase.cta', buttonType: 'HYPER_LINK', link: '/' });
content.default.image({ imageData: { set: { ORIGINAL: { url: '/assets/hero.webp' } } } });
content.default.toggle({ enabled: false });
content.default.selectbox({ value: 'left' });
```

These factories auto-inject the `type` discriminant. See [Showcases](/site-themes/develop-site-themes/sections/showcases.md) for usage in showcase files.

### Complete Example <a href="#complete-example" id="complete-example"></a>

```typescript
// settings/content.ts
import { content } from '@lightspeed/crane-api';

export default {
  title: content.inputbox({
    label: '$label.content.title',
    placeholder: '$label.content.title_placeholder',
    defaults: { text: '$label.defaults.title' },
  }),
  description: content.textarea({
    label: '$label.content.description',
    placeholder: '$label.content.description_placeholder',
    defaults: { text: '$label.defaults.description' },
  }),
  hero_image: content.image({
    label: '$label.content.hero_image',
  }),
  cta_button: content.button({
    label: '$label.content.cta_button',
    defaults: {
      title: '$label.defaults.cta',
      buttonType: 'HYPER_LINK',
      link: 'https://example.com',
    },
  }),
  show_overlay: content.toggle({
    label: '$label.content.show_overlay',
    description: '$label.content.show_overlay_desc',
    defaults: { enabled: true },
  }),
};
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.ecwid.com/site-themes/develop-site-themes/sections/settings/content-editors.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
