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."
}
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Component name (must be one of the 74 valid components) |
props | object | No | Properties passed to the component |
children | array | No | Nested 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)
| Component | Description | Key Props |
|---|---|---|
Card | Bordered container with optional title | title, subtitle, padding (none/sm/md/lg), border |
Stack | Flex container | direction (horizontal/vertical), gap, align, justify, wrap |
Grid | CSS grid layout | columns, gap |
Divider | Horizontal rule | spacing (sm/md/lg) |
Separator | Visual separator | spacing (sm/md/lg) |
Box | Generic container | padding, background, borderRadius, className |
InlineStack | Horizontal flex container | gap, align, className |
BlockStack | Vertical flex container | gap, align, className |
InlineGrid | Inline grid layout | columns, gap, className |
Bleed | Negative margin container | marginInline, marginBlock, className |
Layout | Page-level layout container | className |
LayoutSection | Section within a Layout | title, description, oneHalf, oneThird, fullWidth |
Page | Full page wrapper | title, subtitle, backAction, primaryAction, secondaryActions |
ButtonGroup | Group of buttons | segmented, className |
Collapsible | Expandable/collapsible container | open, className |
Display (19 components)
| Component | Description | Key Props |
|---|---|---|
Text | Body text | content, variant (body/caption/code), weight, color, align |
Heading | Section heading | content, level (1-6) |
Image | Image element | src, alt, width, height, fit, rounded |
Badge | Status badge | content, variant (default/success/warning/error/info) |
Icon | Icon element | name, size (sm/md/lg), color |
KeyValue | Label-value pair | label, value |
List | Ordered or unordered list | items (array of {label, description, icon, action}), ordered |
Table | Data table | columns (array of {key, label, width}), rows |
Thumbnail | Small image | src, alt, size (sm/md/lg) |
Banner | Announcement banner | title, content, status (success/warning/critical/info), dismissible |
CalloutCard | Highlighted card | title, content, illustration |
EmptyState | Empty placeholder | heading, content, image |
Tag | Removable tag | content, label, onRemove |
DescriptionList | Term-description list | items (array of {term, description}) |
MediaCard | Card with media | title, description, src, alt |
FooterHelp | Footer help text | content |
Avatar | User avatar | name, src, size (sm/md/lg) |
VideoThumbnail | Video preview | src, thumbnailUrl, onClick |
ExceptionList | Error/warning list | items (array of {icon, status, title, description}) |
Skeleton (4 components)
| Component | Description | Key Props |
|---|---|---|
SkeletonBodyText | Loading text lines | lines |
SkeletonPage | Loading page | title |
SkeletonDisplayText | Loading heading | size (sm/md/lg) |
SkeletonThumbnail | Loading image | size (sm/md/lg) |
Input (11 components)
| Component | Description | Key Props |
|---|---|---|
TextField | Text input | name, label, placeholder, value, type (text/number/email/password/url), required, helpText, error |
TextArea | Multi-line text | name, label, placeholder, rows, required, helpText, error |
Select | Dropdown select | name, label, options (array of {label, value}), value, placeholder |
Checkbox | Checkbox | name, label, checked, disabled, helpText |
Toggle | Toggle switch | name, label, checked, disabled, helpText |
DatePicker | Date input | name, label, value, min, max, required |
RadioGroup | Radio buttons | name, label, options (array of {label, value, description}), value |
Autocomplete | Autocomplete input | label, placeholder, options, value, bind |
ColorPicker | Color picker | label, value, bind |
DropZone | File upload area | label, accept, helpText |
RangeSlider | Slider input | label, min, max, step, value, bind |
Actions (4 components)
| Component | Description | Key Props |
|---|---|---|
Button | Clickable button | label, action, variant (primary/secondary/outline/ghost/destructive), size, disabled, loading, icon |
Link | Text link | content, url, external |
ActionMenu | Dropdown action menu | label, items (array of {label, action, icon, destructive}) |
PageActions | Page-level action bar | primaryAction ({label, action, disabled}), secondaryActions |
Data and Filters (4 components)
| Component | Description | Key Props |
|---|---|---|
IndexTable | Selectable data table | headers, rows, selectable |
DataTable | Simple data table | headers, rows, totals |
Filters | Filter bar | filters, query |
Pagination | Page navigation | hasPrevious, hasNext, onPrevious, onNext |
Overlay (5 components)
| Component | Description | Key Props |
|---|---|---|
Modal | Dialog modal | id, open, title, primaryAction, secondaryAction |
Drawer | Side drawer | id, open, title |
Popover | Popover container | trigger |
Tooltip | Hover tooltip | content |
Sheet | Bottom sheet | title, open |
Compound (2 components)
| Component | Description | Key Props |
|---|---|---|
Tabs | Tab container | tabs (array of {value, label, children, icon}), selected |
Accordion | Expandable sections | items (array of {id, title, content, defaultOpen}), multiple |
Feedback (3 components)
| Component | Description | Key Props |
|---|---|---|
Alert | Alert message | title, message, variant (info/success/warning/error), dismissible |
Progress | Progress bar | value, max, label, variant |
Spinner | Loading spinner | size (sm/md/lg), label |
Other (4 components)
| Component | Description | Key Props |
|---|---|---|
Truncate | Text truncation | content, lines |
TextStyle | Styled text | variation (positive/negative/strong/subdued/code), content |
Scrollable | Scrollable container | maxHeight |
OptionList | Selectable option list | title, options, selected, allowMultiple |
Navigation and Resource (3 components)
| Component | Description | Key Props |
|---|---|---|
Listbox | Listbox | options, selected |
ResourceList | Resource list container | className |
ResourceItem | Single resource row | name, 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
Navigate to a dashboard page (client-side routing).
{
"type": "Button",
"props": {
"label": "View Orders",
"action": {
"type": "navigate",
"url": "/orders"
}
}
}open_link
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:
| Expression | Description |
|---|---|
{{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
- When the order detail page loads, the dashboard fetches extensions for the
order.detail.blocktarget. - The
load_actionfires immediately — it calls your backend with the order ID to fetch existing notes. - The
onSuccesshandler stores the response instate.notes. - The UI renders the notes text and an "Edit" button (both always visible).
- Clicking "Edit" sets
state.editingtotrue, which shows the TextArea and "Save" button (they have"when": "{{state.editing}}"). - Clicking "Save" calls your backend with the updated notes. On success, it switches back to view mode by setting
editingtofalse.
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
typemust be one of the 74 valid components. - Component trees cannot exceed 10 levels of nesting.
- Every
action.typemust be one of:navigate,open_link,set_state,call_backend,open_modal,open_drawer,close_modal,close_drawer,selorax_api. - The
uifield is required whenmodeisjson. call_backendURLs 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
- Sandbox Extensions — Full JavaScript control with the
@selorax/uiSDK - CLI Reference — Deploy, validate, and dev-mode commands
- Merchant Settings — Define configurable settings for your extensions