Skip to content

Component Extensions

Extensions allow you to modularly expand a component's capabilities without modifying its core settings. They provide an isolated way to add new fields, functions, workflows, templates, and configuration to any component.


Overview

An extension is a self-contained package that attaches to a component and contributes:

  • Fields — Additional data fields that are stored alongside the component's native fields
  • Functions — Custom serverless functions scoped to the extension
  • Workflows — Event-driven workflow nodes that react to component model events (create, update, delete)
  • Templates — UI templates for rendering extension-specific views
  • Config — Extension-specific configuration data

Extensions are versioned and can be activated or deactivated independently. When an extension is active, its fields, functions, and workflows participate in all component operations automatically.


Extension Structure

Property Type Required Description
name string Yes Unique identifier for the extension within the component
is_active boolean Yes Whether the extension is currently active
fields Field[] No Additional fields contributed by the extension
functions Function[] No Custom functions available via the extension
workflows Node[] No Workflow nodes that react to model events
templates object No Source templates for UI rendering
templates_dist object No Compiled/distributed template assets
config object No Extension-specific configuration key-value pairs
version string No Semantic version of the extension
installed_by string No The entity (user or integration) that installed this extension
installed_at datetime No Timestamp of when the extension was installed

Example

{
  "name": "shipping_tracker",
  "is_active": true,
  "version": "1.0.0",
  "fields": [
    {
      "key": "tracking_number",
      "name": "Tracking Number",
      "type": "string",
      "module": "input",
      "visible": true,
      "constraints": {
        "required": true
      }
    },
    {
      "key": "carrier",
      "name": "Carrier",
      "type": "string",
      "module": "select",
      "visible": true,
      "constraints": {
        "selectables": {
          "items": ["DHL", "FedEx", "UPS", "USPS"]
        }
      }
    }
  ],
  "functions": [
    {
      "name": "get_tracking_status",
      "expr": "return await Http.get(`https://api.carrier.com/track/${input.tracking_number}`)"
    }
  ],
  "workflows": [],
  "config": {
    "api_key": "your-carrier-api-key",
    "default_carrier": "DHL"
  }
}

How Extensions Work

Fields

Extension fields are stored under a namespaced path: extensions.{extension_name}.{field_key}. This ensures there are no collisions between extension fields, or between extension fields and the component's native fields. When reading, writing, or querying extension field data, always use the full prefixed path.

Functions

Extension functions are namespaced using dot notation. To call a function from the shipping_tracker extension, use:

shipping_tracker.get_tracking_status

This prevents name collisions between extensions and the component's own functions.

Workflows

When a model event occurs (e.g., a record is created or updated), the platform executes:

  1. The component's own model event workflows
  2. Each active extension's workflows, in order

Extension workflows receive the same event context as the component's native workflows, plus additional metadata:

{
  "config": { ... },
  "context": {
    "event": "Before::Create",
    "initiator": { ... },
    "component": "orders",
    "extension": {
      "name": "shipping_tracker",
      "config": { ... }
    },
    "schema": "default"
  }
}

This allows extension workflows to access their own configuration and know which extension they belong to.

Activation and Deactivation

When an extension is deactivated (is_active: false):

  • Its fields remain in the data but are not validated or enforced
  • Its functions become unavailable
  • Its workflows stop executing on model events
  • Data stored by the extension is preserved

API Endpoints

All extension endpoints require the appropriate permission on the component.

Get Extension

GET /v4/system/component/{ref}/{version}/extensions/{name}

Retrieves a single extension by name from the specified component version.

URL Parameters:

  • {ref} — Component reference (e.g., namespace::component-name)
  • {version} — Component version (e.g., 0.1.0)
  • {name} — Extension name

Response: 200 OK

{
  "name": "shipping_tracker",
  "is_active": true,
  "version": "1.0.0",
  "installed_by": "admin@example.com",
  "installed_at": "2025-01-15T10:30:00Z",
  "fields": [...],
  "functions": [...],
  "workflows": [],
  "config": {
    "api_key": "your-carrier-api-key",
    "default_carrier": "DHL"
  }
}

Error Responses:

  • 404 Not Found — Extension with the given name does not exist
  • 403 Forbidden — User lacks extensions::read permission

Permissions Required: extensions::read

Create Extension

POST /v4/system/component/{ref}/{version}/extensions

Request body:

{
  "name": "my_extension",
  "is_active": true,
  "fields": [...],
  "functions": [...],
  "workflows": [...],
  "config": {...}
}

Response: 201 Created — Returns the updated component settings.

Update Extension

PATCH /v4/system/component/{ref}/{version}/extensions/{name}

Request body (partial update — only include fields you want to change):

{
  "is_active": false,
  "config": {
    "api_key": "new-key"
  }
}

Response: 200 OK — Returns the updated component settings.

Delete Extension

DELETE /v4/system/component/{ref}/{version}/extensions/{name}

Response: 200 OK — Returns the updated component settings.

Warning

Deleting an extension removes its definition from the component. Data stored in extension fields is preserved but will no longer be managed by the extension.


Using the SDK

Getting an Extension

const component = new Component("orders")

const extension = await component.getExtension("shipping_tracker", "0.1.0")
console.log(extension.data)

Installing an Extension

const component = new Component("orders")

await component.installExtension({
  name: "shipping_tracker",
  is_active: true,
  fields: [
    {
      key: "tracking_number",
      name: "Tracking Number",
      type: "string",
      module: "input",
      visible: true,
      constraints: { required: true }
    }
  ],
  functions: [
    {
      name: "get_status",
      expr: "return { status: 'in_transit' }"
    }
  ],
  config: {
    api_key: "your-key"
  }
}, "0.1.0")

Updating an Extension

You can partially update an extension — only include the properties you want to change:

const component = new Component("orders")

// Update config and deactivate
await component.updateExtension("shipping_tracker", "0.1.0", {
  is_active: false,
  config: {
    api_key: "new-key"
  }
})

Deleting an Extension

const component = new Component("orders")

await component.deleteExtension("shipping_tracker", "0.1.0")

Calling Extension Functions

Extension functions use dot notation — extensionName.functionName:

const component = new Component("orders")
const result = await component.workflow("shipping_tracker.get_status", {
  tracking_number: "1234567890"
})

Storing Data in Extension Fields

Extension fields are stored under a namespaced path using the prefix extensions.{extension_name}.{field_key}. This must be used when updating and querying extension field data.

When creating a record, you can pass extension fields as a nested object:

const component = new Component("orders")

// Create a record with extension fields using nested object
await component.create({
  order_id: "ORD-001",
  extensions: {
    shipping_tracker: {
      tracking_number: "1234567890",
      carrier: "DHL"
    }
  }
})

When updating or querying, use dot notation with the full prefix:

// Update extension fields on an existing record
await component.update(uuid, {
  "extensions.shipping_tracker.carrier": "FedEx"
})

// Query by extension fields
const results = await component.find({
  $adv: { "extensions.shipping_tracker.carrier": "DHL" }
})

Best Practices

  1. Use descriptive names — Extension names should clearly indicate their purpose (e.g., shipping_tracker, email_notifications).
  2. Always use the full prefix — When reading, writing, or querying extension fields, always use extensions.{extension_name}.{field_key}.
  3. Version your extensions — Use semantic versioning to track changes to your extension's structure.
  4. Keep config separate — Store API keys, URLs, and other configuration in the extension's config object rather than hardcoding them in functions.
  5. Handle deactivation gracefully — Your workflows should account for the possibility that the extension may be deactivated.