SeloraXDEVELOPERS

JSON Extensions

JSON Extensions

JSON extensions are declarative UI trees that the dashboard renders natively. You define your UI as a nested structure of { type, props, children } nodes, and the dashboard maps each node to a real React component. There is no iframe, no JavaScript to host, and no bundle to build.

This is the simplest way to extend the dashboard. Define a component tree in your selorax.config.json or via the API, deploy it, and it renders immediately wherever the target surface loads.

Component Tree Structure

Every node in a JSON extension follows this shape:

{
  "type": "Card",
  "props": {
    "title": "Order Summary"
  },
  "children": [
    {
      "type": "Text",
      "props": {
        "content": "This order has been verified."
      }
    }
  ]
}
FieldTypeRequiredDescription
typestringYesComponent name (must be one of the 74 valid components)
propsobjectNoProperties passed to the component
childrenarrayNoNested child nodes (max depth: 10 levels)

The tree is validated server-side on deploy. Invalid component types, unknown action types, or trees deeper than 10 levels are rejected.

Available Components

There are 74 components grouped by category.

Layout (15 components)

ComponentDescriptionKey Props
CardBordered container with optional titletitle, subtitle, padding (none/sm/md/lg), border
StackFlex containerdirection (horizontal/vertical), gap, align, justify, wrap
GridCSS grid layoutcolumns, gap
DividerHorizontal rulespacing (sm/md/lg)
SeparatorVisual separatorspacing (sm/md/lg)
BoxGeneric containerpadding, background, borderRadius, className
InlineStackHorizontal flex containergap, align, className
BlockStackVertical flex containergap, align, className
InlineGridInline grid layoutcolumns, gap, className
BleedNegative margin containermarginInline, marginBlock, className
LayoutPage-level layout containerclassName
LayoutSectionSection within a Layouttitle, description, oneHalf, oneThird, fullWidth
PageFull page wrappertitle, subtitle, backAction, primaryAction, secondaryActions
ButtonGroupGroup of buttonssegmented, className
CollapsibleExpandable/collapsible containeropen, className

Display (19 components)

ComponentDescriptionKey Props
TextBody textcontent, variant (body/caption/code), weight, color, align
HeadingSection headingcontent, level (1-6)
ImageImage elementsrc, alt, width, height, fit, rounded
BadgeStatus badgecontent, variant (default/success/warning/error/info)
IconIcon elementname, size (sm/md/lg), color
KeyValueLabel-value pairlabel, value
ListOrdered or unordered listitems (array of {label, description, icon, action}), ordered
TableData tablecolumns (array of {key, label, width}), rows
ThumbnailSmall imagesrc, alt, size (sm/md/lg)
BannerAnnouncement bannertitle, content, status (success/warning/critical/info), dismissible
CalloutCardHighlighted cardtitle, content, illustration
EmptyStateEmpty placeholderheading, content, image
TagRemovable tagcontent, label, onRemove
DescriptionListTerm-description listitems (array of {term, description})
MediaCardCard with mediatitle, description, src, alt
FooterHelpFooter help textcontent
AvatarUser avatarname, src, size (sm/md/lg)
VideoThumbnailVideo previewsrc, thumbnailUrl, onClick
ExceptionListError/warning listitems (array of {icon, status, title, description})

Skeleton (4 components)

ComponentDescriptionKey Props
SkeletonBodyTextLoading text lineslines
SkeletonPageLoading pagetitle
SkeletonDisplayTextLoading headingsize (sm/md/lg)
SkeletonThumbnailLoading imagesize (sm/md/lg)

Input (11 components)

ComponentDescriptionKey Props
TextFieldText inputname, label, placeholder, value, type (text/number/email/password/url), required, helpText, error
TextAreaMulti-line textname, label, placeholder, rows, required, helpText, error
SelectDropdown selectname, label, options (array of {label, value}), value, placeholder
CheckboxCheckboxname, label, checked, disabled, helpText
ToggleToggle switchname, label, checked, disabled, helpText
DatePickerDate inputname, label, value, min, max, required
RadioGroupRadio buttonsname, label, options (array of {label, value, description}), value
AutocompleteAutocomplete inputlabel, placeholder, options, value, bind
ColorPickerColor pickerlabel, value, bind
DropZoneFile upload arealabel, accept, helpText
RangeSliderSlider inputlabel, min, max, step, value, bind

Actions (4 components)

ComponentDescriptionKey Props
ButtonClickable buttonlabel, action, variant (primary/secondary/outline/ghost/destructive), size, disabled, loading, icon
LinkText linkcontent, url, external
ActionMenuDropdown action menulabel, items (array of {label, action, icon, destructive})
PageActionsPage-level action barprimaryAction ({label, action, disabled}), secondaryActions

Data and Filters (4 components)

ComponentDescriptionKey Props
IndexTableSelectable data tableheaders, rows, selectable
DataTableSimple data tableheaders, rows, totals
FiltersFilter barfilters, query
PaginationPage navigationhasPrevious, hasNext, onPrevious, onNext

Overlay (5 components)

ComponentDescriptionKey Props
ModalDialog modalid, open, title, primaryAction, secondaryAction
DrawerSide drawerid, open, title
PopoverPopover containertrigger
TooltipHover tooltipcontent
SheetBottom sheettitle, open

Compound (2 components)

ComponentDescriptionKey Props
TabsTab containertabs (array of {value, label, children, icon}), selected
AccordionExpandable sectionsitems (array of {id, title, content, defaultOpen}), multiple

Feedback (3 components)

ComponentDescriptionKey Props
AlertAlert messagetitle, message, variant (info/success/warning/error), dismissible
ProgressProgress barvalue, max, label, variant
SpinnerLoading spinnersize (sm/md/lg), label

Other (4 components)

ComponentDescriptionKey Props
TruncateText truncationcontent, lines
TextStyleStyled textvariation (positive/negative/strong/subdued/code), content
ScrollableScrollable containermaxHeight
OptionListSelectable option listtitle, options, selected, allowMultiple
ComponentDescriptionKey Props
ListboxListboxoptions, selected
ResourceListResource list containerclassName
ResourceItemSingle resource rowname, subtitle, media, action, shortcutActions

Action Types

Actions define what happens when a user interacts with a component (clicks a button, taps a link). Every action prop is an object with a type field.

Navigate to a dashboard page (client-side routing).

{
  "type": "Button",
  "props": {
    "label": "View Orders",
    "action": {
      "type": "navigate",
      "url": "/orders"
    }
  }
}

Open an external URL in a new tab.

{
  "type": "Button",
  "props": {
    "label": "Visit Website",
    "action": {
      "type": "open_link",
      "url": "https://example.com",
      "external": true
    }
  }
}

set_state

Update extension-local state. Combined with template expressions, this enables interactive UIs without JavaScript.

{
  "type": "Button",
  "props": {
    "label": "Toggle Details",
    "action": {
      "type": "set_state",
      "key": "showDetails",
      "value": true
    }
  }
}

call_backend

Send an authenticated HTTP request to your app's backend. The platform generates a signed session JWT and forwards the request. Supports onSuccess and onError follow-up actions.

{
  "type": "Button",
  "props": {
    "label": "Check Fraud Score",
    "action": {
      "type": "call_backend",
      "url": "https://myapp.com/api/fraud-check",
      "method": "POST",
      "body": {
        "order_id": "{{context.order_id}}"
      },
      "onSuccess": {
        "type": "set_state",
        "key": "fraudResult",
        "value": "{{response}}"
      },
      "onError": {
        "type": "set_state",
        "key": "error",
        "value": "Failed to check fraud score"
      }
    }
  }
}

SSRF protection

The url in a call_backend action must use HTTPS and must match the hostname of your app's registered app_url. Requests to private/internal networks are blocked.

selorax_api

Call the SeloraX platform API directly (scoped to the merchant's store). Useful for reading store data or triggering platform actions.

{
  "type": "Button",
  "props": {
    "label": "Load Products",
    "action": {
      "type": "selorax_api",
      "url": "/apps/v1/products",
      "method": "GET",
      "onSuccess": {
        "type": "set_state",
        "key": "products",
        "value": "{{response.data}}"
      }
    }
  }
}

open_modal

Open a Modal component by its id.

{
  "type": "Button",
  "props": {
    "label": "Confirm Action",
    "action": { "type": "open_modal", "id": "confirm-dialog" }
  }
}

close_modal

Close the currently open modal.

{ "type": "close_modal" }

open_drawer and close_drawer

Open or close a Drawer component by its id.

{
  "type": "Button",
  "props": {
    "label": "View Details",
    "action": { "type": "open_drawer", "id": "detail-panel" }
  }
}

State Management

JSON extensions support local state without any JavaScript. State is managed through three mechanisms:

The bind Prop

Input components accept a bind prop that creates a two-way binding to extension state.

{
  "type": "TextField",
  "props": {
    "name": "discount_code",
    "label": "Discount Code",
    "bind": "discountCode"
  }
}

When the user types in this field, state.discountCode is updated automatically.

The set_state Action

Any action can update state:

{
  "type": "set_state",
  "key": "selectedTab",
  "value": "shipping"
}

Template Expressions

Use {{state.xxx}} and {{context.xxx}} to reference state and context values anywhere in props:

{
  "type": "Text",
  "props": {
    "content": "Discount: {{state.discountCode}}"
  }
}

Initial State

Set default state values using the initial_state field in your extension config:

{
  "extension_id": "discount-block",
  "target": "order.detail.block",
  "mode": "json",
  "initial_state": {
    "showDetails": false,
    "discountCode": ""
  },
  "ui": { ... }
}

Template Expressions

Template expressions use the {{...}} syntax and can reference:

ExpressionDescription
{{state.key}}Extension-local state value
{{context.store_id}}Current store ID
{{context.order_id}}Current order ID (on order detail pages)
{{context.product_id}}Current product ID (on product detail pages)
{{context.customer_id}}Current customer ID (on customer detail pages)
{{context.user}}Current dashboard user object
{{settings.key}}Merchant-configured setting value
{{response}}Response from a call_backend or selorax_api action (in onSuccess)
{{response.data}}Response data payload

Conditional Rendering

Use the when or visible prop on any node to conditionally render it based on state:

{
  "type": "Card",
  "props": {
    "title": "Fraud Details",
    "when": "{{state.showDetails}}"
  },
  "children": [
    {
      "type": "Text",
      "props": {
        "content": "Score: {{state.fraudResult.score}}"
      }
    }
  ]
}

When the value is falsy, the component and all its children are skipped during rendering.

Full Example: Order Detail Block

This extension adds a "Delivery Notes" card to the order detail page. It fetches metafields from the app's backend, displays them, and lets the merchant update them.

{
  "extension_id": "delivery-notes",
  "target": "order.detail.block",
  "title": "Delivery Notes",
  "mode": "json",
  "initial_state": {
    "notes": "",
    "editing": false,
    "loaded": false
  },
  "load_action": {
    "type": "call_backend",
    "url": "https://myapp.com/api/delivery-notes",
    "method": "POST",
    "body": {
      "order_id": "{{context.order_id}}",
      "store_id": "{{context.store_id}}"
    },
    "onSuccess": {
      "type": "set_state",
      "key": "notes",
      "value": "{{response.notes}}"
    }
  },
  "ui": {
    "type": "Card",
    "props": { "title": "Delivery Notes" },
    "children": [
      {
        "type": "BlockStack",
        "props": { "gap": "md" },
        "children": [
          {
            "type": "Text",
            "props": {
              "content": "{{state.notes}}"
            }
          },
          {
            "type": "TextArea",
            "props": {
              "name": "notes",
              "label": "Notes",
              "bind": "notes",
              "rows": 3,
              "when": "{{state.editing}}"
            }
          },
          {
            "type": "InlineStack",
            "props": { "gap": "sm" },
            "children": [
              {
                "type": "Button",
                "props": {
                  "label": "Edit",
                  "variant": "secondary",
                  "action": {
                    "type": "set_state",
                    "key": "editing",
                    "value": true
                  }
                }
              },
              {
                "type": "Button",
                "props": {
                  "label": "Save",
                  "variant": "primary",
                  "when": "{{state.editing}}",
                  "action": {
                    "type": "call_backend",
                    "url": "https://myapp.com/api/delivery-notes/save",
                    "method": "POST",
                    "body": {
                      "order_id": "{{context.order_id}}",
                      "notes": "{{state.notes}}"
                    },
                    "onSuccess": {
                      "type": "set_state",
                      "key": "editing",
                      "value": false
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    ]
  }
}

How This Works

  1. When the order detail page loads, the dashboard fetches extensions for the order.detail.block target.
  2. The load_action fires immediately — it calls your backend with the order ID to fetch existing notes.
  3. The onSuccess handler stores the response in state.notes.
  4. The UI renders the notes text and an "Edit" button (both always visible).
  5. Clicking "Edit" sets state.editing to true, which shows the TextArea and "Save" button (they have "when": "{{state.editing}}").
  6. Clicking "Save" calls your backend with the updated notes. On success, it switches back to view mode by setting editing to false.

No JavaScript is executed in the browser. The dashboard interprets the JSON tree and handles all state transitions, conditional rendering, and backend calls.

Validation Rules

The platform enforces these rules when you deploy JSON extensions:

  • Every type must be one of the 74 valid components.
  • Component trees cannot exceed 10 levels of nesting.
  • Every action.type must be one of: navigate, open_link, set_state, call_backend, open_modal, open_drawer, close_modal, close_drawer, selorax_api.
  • The ui field is required when mode is json.
  • call_backend URLs must use HTTPS and match the app's registered domain.

If any extension in a deploy batch fails validation, the entire batch is rejected with detailed error messages.

What's Next