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:
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:
- The component's own model event workflows
- 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
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 exist403 Forbidden— User lacksextensions::readpermission
Permissions Required: extensions::read
Create Extension
Request body:
{
"name": "my_extension",
"is_active": true,
"fields": [...],
"functions": [...],
"workflows": [...],
"config": {...}
}
Response: 201 Created — Returns the updated component settings.
Update Extension
Request body (partial update — only include fields you want to change):
Response: 200 OK — Returns the updated component settings.
Delete Extension
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
- Use descriptive names — Extension names should clearly indicate their purpose (e.g.,
shipping_tracker,email_notifications). - Always use the full prefix — When reading, writing, or querying extension fields, always use
extensions.{extension_name}.{field_key}. - Version your extensions — Use semantic versioning to track changes to your extension's structure.
- Keep config separate — Store API keys, URLs, and other configuration in the extension's
configobject rather than hardcoding them in functions. - Handle deactivation gracefully — Your workflows should account for the possibility that the extension may be deactivated.