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
All content editors are created using factory functions from @lightspeed/crane-api:
import { content } from '@lightspeed/crane-api';| Type | Factory | Purpose |
|---|---|---|
INPUTBOX | content.inputbox() | Single-line text input |
TEXTAREA | content.textarea() | Multi-line text input |
BUTTON | content.button() | Button with text, link, and action |
IMAGE | content.image() | Image upload |
TOGGLE | content.toggle() | Boolean on/off switch |
SELECTBOX | content.selectbox() | Dropdown selection |
DECK | content.deck() | Collection of repeatable cards |
MENU | content.menu() | Menu items list |
NAVIGATION_MENU | content.navigationMenu() | Navigation menu (header) |
LOGO | content.logo() | Logo (text or image) |
DIVIDER | content.divider() | Visual separator in the editor |
INFO | content.info() | Informational text in the editor |
PRODUCT_SELECTOR | content.productSelector() | Product picker |
CATEGORY_SELECTOR | content.categorySelector() | Category picker |
💡 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.
INPUTBOX
Single-line text input for short content like titles and headings.
Properties
| 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
| Property | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Default text value (translation key) |
Factory
content.inputbox({
label: '$label.content.title',
placeholder: '$label.content.title_placeholder',
defaults: { text: '$label.defaults.title' },
})TEXTAREA
Multi-line text input for longer content like descriptions and paragraphs.
Properties
| 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
| Property | Type | Required | Description |
|---|---|---|---|
text | string | Yes | Default text value (translation key) |
Factory
content.textarea({
label: '$label.content.description',
placeholder: '$label.content.description_placeholder',
defaults: { text: '$label.defaults.description' },
})BUTTON
Button with configurable text, action type, and target. The button action type determines which additional fields are required.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Translation key for the editor label |
defaults | object | No | Default values |
Defaults
| 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
| 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
content.button({
label: '$label.content.cta_button',
defaults: {
title: '$label.defaults.cta',
buttonType: 'HYPER_LINK',
link: 'https://example.com',
},
})⚠️ Validation
Crane validates that 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.
IMAGE
Image upload with optional static flag. Images support multiple resolutions for responsive rendering.
Properties
| 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
| 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
content.image({
label: '$label.content.hero_image',
})⚠️ 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."
TOGGLE
Boolean on/off switch for enabling or disabling features.
Properties
| 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
| Property | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Default toggle state |
Factory
content.toggle({
label: '$label.content.show_overlay',
description: '$label.content.show_overlay_description',
defaults: { enabled: true },
})SELECTBOX
Dropdown selection with predefined options.
Properties
| 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
| Property | Type | Required | Description |
|---|---|---|---|
value | string | Yes | Default selected value (must match one of the options values) |
Factory
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' },
})⚠️ Validation
In showcases, 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."
DECK
Collection of repeatable cards. Merchants can add, remove, and reorder cards. Each card contains its own set of content editors.
Properties
| 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
The cards object has the following structure:
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 DECK (no nesting), MENU, NAVIGATION_MENU, or LOGO. Each card gets a copy of these settings.
Factory
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' },
}),
},
},
},
})⚠️ Validation
In showcases, deck card settings are validated against the defaultCardContent.settings definition. Settings keys that don't exist in the deck definition produce errors.
MENU
Menu items list. The label property is optional for this editor.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
label | string | No | Translation key for the editor label |
defaults | object | No | Default values |
Defaults
| 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
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
Navigation menu for site headers. Similar to MENU but without a configurable label — it is always rendered as the main navigation.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
defaults | object | No | Default values |
Defaults
Same structure as MENU defaults.
Factory
content.navigationMenu({
defaults: {
items: [
{ id: 'home', title: 'Home', type: 'GO_TO_PAGE', pageIdForNavigate: 'home' },
{ id: 'shop', title: 'Shop', type: 'GO_TO_STORE' },
],
},
})ℹ️ Mandatory for Headers
The NAVIGATION_MENU editor is mandatory for sections with type HEADER. It must be defined with the key menu. See Sections.
LOGO
Logo element supporting both text and image modes.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
label | string | No | Translation key for the editor label |
defaults | object | No | Default values |
Defaults
| 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 defaults) |
Factory
content.logo({
label: '$label.content.store_logo',
defaults: { logoType: 'TEXT', text: '$label.defaults.store_name' },
})ℹ️ Mandatory for Headers
The LOGO editor is mandatory for sections with type HEADER. It must be defined with the key logo. See Sections.
DIVIDER
Visual separator in the editor UI. Does not render any content in the storefront — it is purely for organizing the editor panel.
Properties
| Property | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Translation key for the divider label |
defaults | object | No | Default values (empty, type only) |
Factory
content.divider({
label: '$label.content.advanced_section',
})INFO
Informational text displayed in the editor UI. Like DIVIDER, this does not render in the storefront — it provides guidance to merchants.
Properties
| 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
| Property | Type | Required | Description |
|---|---|---|---|
text | string | No | Default info text (translation key) |
button | object | No | { title?: string, link?: string } |
Factory
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
Product picker allowing merchants to select products from their store catalog.
Properties
| 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
content.productSelector({
label: '$label.content.featured_products',
maxProducts: 8,
})CATEGORY_SELECTOR
Category picker allowing merchants to select store categories.
Properties
| 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
content.categorySelector({
label: '$label.content.featured_categories',
maxCategories: 6,
})Showcase Default Factories
When defining showcase configurations, you need to provide default values for content fields independently of the editor definition. The content.default.*() factories create these standalone defaults:
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 for usage in showcase files.
Complete Example
// 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 },
}),
};