Skip to content

Setting Types ​

Settings are the fields the merchant fills in the editor's settings panel. You declare them as fields with @setting on the component's type in etc/components/<Template>.graphqls (Creating Components). Each setting becomes a prop of the same name, and the field's type picks the control.

graphql
type VendorPicks @component(…) {
  "Shown above the list"
  title: LocalizedText! @setting(label: "Title", default: "Our picks")
}

Declare settings with !: a stored setting is never null.

@setting arguments ​

ArgumentWhat it does
labelField label in the settings panel (required)
defaultThe value a new node starts with, and the value the server fills in when a node has none. See Defaults.
min, max, stepRange of a number. See Int, Float.
sourceOptions from code for a String field. See Select.
labelFieldThe sub-field that titles each item of a list. See Array.
dependsOnShows the setting only while other settings have given values. See Dependent settings.
tabThe group the setting is shown in. See Grouping settings.
controlAn editor control the field type does not pick, one added to the settings panel by an editor extension

The string above a field is its description: help text behind a ? icon next to the label.

Types ​

Field typeField in the editordefault asValue in props
String, TextOne-line input""String
TextareaMulti-line input""String
RichTextRich text editor (bold, links, lists)""HTML string
LocalizedTextInput with a Translations dialog""Object of texts per locale; resolve it in a provider
LocalizedTextareaMulti-line input with Translations""Same as LocalizedText
LocalizedRichTextRich text with Translations""Same as LocalizedText, HTML texts
Int, FloatSlider with a number boxnumberNumber
BooleanOn/off switchtrue / falseBoolean
enumDropdown"value"The chosen option's value
String with sourceDropdown, options from code"value"The chosen option's value
ColorSwatch with a color picker and the design's palette""CSS color (#rrggbb or rgba(…)), or empty
ImageMedia gallery picker with upload and crop""Path under pub/media; turn it into a URL in a provider
UrlLink input""Store path or absolute URL; turn it into a link in a provider
[Input!]! (a list of an input type)Sortable list of items, each with its own fields[]Array of objects

A field type that no control handles is an error when the definitions are loaded.

String, Textarea ​

graphql
"Shown under the button"
note: Textarea! @setting(label: "Note", default: "")

Use plain String / Textarea for values that are codes rather than shopper-facing text: SKUs, anchors, cookie names, CSS. For text the shopper reads, use the Localized… types.

RichText ​

The value is HTML. Render it with v-html.

graphql
content: RichText! @setting(label: "Content", default: "<p>Write something.</p>")

LocalizedText, LocalizedTextarea, LocalizedRichText ​

Text that can be translated per store view without a separate design per language. The editor shows the base field (Text, Textarea or RichText) for the default text, and a Translations button where the merchant adds a text per locale code. The stored value is an object:

json
{ "default": "Mid-Season Sale", "de_DE": "Sale zur Saisonmitte", "fr_FR": "Soldes de mi-saison" }
graphql
title: LocalizedText! @setting(label: "Title", default: "Mid-Season Sale")

Declare the default as a plain string; the editor wraps it.

A component with a Localized… setting must have a provider that turns the object into a string with OOCS\LynxComponent\Helper\LocalizedText::get(). It picks the store view's locale, falls back to default, and passes the result through Magento's __(), so a default text that a language pack already translates ("Sign In", "Contact Us") is translated without the merchant entering anything.

php
$props['title'] = $this->localizedText->get($props['title']);

The generated props type already declares title: string. A full provider is on Component Providers.

Int, Float ​

graphql
limit: Int! @setting(label: "Products", default: 8, min: 1, max: 24, step: 1)

min, max and step default to 0, 500 and 1. The value is clamped to the range.

Boolean ​

graphql
show_title: Boolean! @setting(label: "Show Title", default: true)

Every on/off setting is a Boolean, not a select with Yes and No.

Select ​

Static options: an enum. The options follow the order of its values, and @label gives each one its label. Value names are written in capitals and stored in lowercase (MD stores "md"); add @value when the stored value differs, for example when it starts with a digit.

graphql
enum VendorSize {
  SM @label(text: "Small")
  MD @label(text: "Medium")
}

enum VendorRatio {
  AUTO @label(text: "Auto")
  V16_9 @label(text: "16:9") @value(text: "16_9")
}

size: VendorSize! @setting(label: "Size", default: "md")
ratio: VendorRatio! @setting(label: "Ratio", default: "auto")

Enum names are global: prefix yours, or reuse a shared one (Align, Justify, TextAlign, Rounded). Another module can add an option with extend enum VendorSize { LG @label(text: "Large") }, so a component maps values with a fallback.

Options from code: a String field with source, the class of an object implementing OOCS\LynxComponent\Api\OptionSourceInterface. The registry creates it and calls its toOptionArray() only when it builds the editor's configuration.

graphql
attribute: String! @setting(label: "Attribute", default: "sku", source: "Vendor\\Module\\Model\\Config\\Source\\ProductAttribute")
php
<?php
declare(strict_types=1);

namespace Vendor\Module\Model\Config\Source;

use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory;
use OOCS\LynxComponent\Api\OptionSourceInterface;

class ProductAttribute implements OptionSourceInterface
{
    public function __construct(
        private readonly CollectionFactory $collectionFactory,
    ) {
    }

    /**
     * @return array<int, array{value: string, label: string}>
     */
    public function toOptionArray(): array
    {
        $collection = $this->collectionFactory->create()
            ->addFieldToFilter('frontend_label', ['notnull' => true])
            ->setOrder('frontend_label', 'ASC');

        $options = [];
        foreach ($collection as $attribute) {
            $options[] = [
                'value' => (string) $attribute->getAttributeCode(),
                'label' => (string) $attribute->getFrontendLabel(),
            ];
        }

        return $options;
    }
}
  • Implement Lynx's OptionSourceInterface, not Magento\Framework\Data\OptionSourceInterface. The registry ignores the Magento one.
  • Name the class itself, without \Proxy: it is created only for the editor.
  • To reuse one source class with different arguments, declare a virtualType of it in etc/di.xml and name the virtual type in source, as OOCS_LynxCatalog does for its image attribute list (ImageAttribute.graphqls).
  • source also works on a sub-field of an array setting.

Color ​

graphql
"Empty uses the default"
color: Color! @setting(label: "Text Color", default: "")

The picker offers the colors of the current design first. Keep the default empty and fall back to a theme token, so the component follows the design until the merchant picks a color:

vue
<h2 :class="color ? '' : 'text-base-content'" :style="color ? { color } : undefined">{{ text }}</h2>

Image ​

graphql
image: Image! @setting(label: "Image", default: "")

The value is a path relative to pub/media, such as wysiwyg/sale/hero.jpg, so a layout works on every environment and store. Your provider turns it into a URL of the current store with OOCS\LynxThemeComponent\Helper\MediaUrl::url(), which leaves an absolute URL or a path starting with / unchanged:

php
$props['image'] = $this->mediaUrl->url((string) $props['image']);

Url ​

graphql
cta_url: Url! @setting(label: "Button URL", default: "sale")

The value is a store path (about-us, /checkout/cart) or an absolute URL. The editor reports an invalid value only when it has a scheme and cannot be parsed. Your provider turns a path into a link of the current store view with OOCS\LynxThemeComponent\Helper\StoreLink::path(). It returns a root-relative path that includes the store code when Add Store Code to URLs is on, and leaves #anchors, mailto:, tel: and absolute URLs unchanged:

php
$props['cta_url'] = $this->storeLink->path((string) $props['cta_url']);

Render links with Inertia's Link. Lynx sends links to another origin (mailto:, another site) to the browser, so you do not need to tell internal and external links apart. Use a plain <a> only for an internal link that must reload the page.

MediaUrl and StoreLink live in OOCS_LynxThemeComponent; add it to your module's <sequence> and composer.json when you use them.

Array ​

A list the merchant can add to, remove from and reorder. The setting is a list of an input type, and each field of the input is a sub-field with its own @setting(label: …).

graphql
input VendorLinksItem {
  label: LocalizedText! @setting(label: "Label")
  url: Url! @setting(label: "URL")
  new_tab: Boolean! @setting(label: "Open in New Tab")
}

links: [VendorLinksItem!]! @setting(label: "Links", default: [], labelField: "label")
  • labelField names the sub-field whose value titles each item in the list.
  • Sub-fields take any type above, source included. A sub-field shows its label only: its description is not displayed, and dependsOn does not apply.
  • Declared defaults are filled in for top-level settings only. Stored items are not completed with sub-field defaults, so read a sub-field with a fallback in the provider ($item['url'] ?? '').
  • A new item starts with false for a Boolean, min (or 0) for a number, an empty list for a nested list, and the sub-field's default (or an empty string) for the rest.
  • A non-empty default is a list of items:
graphql
links: [VendorLinksItem!]! @setting(label: "Links", default: [{label: "About Us", url: "about-us", new_tab: false}], labelField: "label")

In the provider, loop over the items to resolve their Localized…, Image and Url fields:

php
foreach ($props['links'] as $i => $link) {
    $props['links'][$i]['label'] = $this->localizedText->get($link['label'] ?? '');
    $props['links'][$i]['url'] = $this->storeLink->path((string) ($link['url'] ?? ''));
}

The component receives an array of objects shaped like the input, typed as Lynx.VendorLinksItem[].

Defaults ​

Give every setting a default. It is the single source of the default value:

  • Editor: a new node is saved with every default. A node saved before you added a setting gets its default when the merchant opens it.
  • Server: before your provider and component run, Lynx fills in the default of every setting the node lacks, including nodes your code inserts into a layout. A provider reads $props['limit'] directly, and settings are required in the generated props type.

Write the default as the value it stores: a number for Int and Float, true or false for Boolean, a list for a list ([] when empty), and a string for every other type, Localized… ones included.

An empty string default is fine for text, image, URL and color settings where empty has a meaning ("no image", "use the default color").

Dependent settings ​

dependsOn works like <depends> in Magento's system.xml: the setting is shown only while the listed settings have the given values.

graphql
# show_label is on, and display is card or list
label: LocalizedText! @setting(label: "Label", default: "", dependsOn: {show_label: "true", display: ["card", "list"]})
  • Several fields are combined with AND; an array of values for one field means any of them.
  • Values compare as strings, so a Boolean is written "true" or "false".
  • The panel updates as the merchant edits, before saving.
  • When a setting is hidden, saving removes it from the node, so the server renders it with its default. Your component always receives a value.

Grouping settings ​

A component with many settings can group them with tab. The settings panel then shows one row per group, as the Theme tab does: a click opens the group's settings underneath, and the first group is open already.

graphql
logo_size: VendorLogoSize! @setting(label: "Logo Size", default: "md", tab: "Header")
  • A setting without tab goes to General, shown first. The other groups follow in the order they first appear in the type.
  • When no setting names a tab, the panel lists the settings without groups.
  • One Save button covers every group, and dependsOn works across groups.
  • A group whose settings are all hidden by dependsOn is not shown.