Bring Your Own (BYO) 7.26 Implementation Examples

Prev Next

The Bring Your Own feature is intended for developers who have a strong understanding of the Unqork Designer Platform and JavaScript.

Overview                                            

The Bring Your Own  (BYO) 7.26 implementation examples page contains example implementations of a component, event, and operation. Refer to these exampless when building your components with the BYO Component SDK guide.

AG Grid Component

This example displays the customization options, manifest.json, and implementation of a custom AG Grid component.

Configuration

Settings

Object Type

Description

columnDefinitions                                                                

string componentKey

The definitions of the columns from the key and label data.

value                                                                

string componentKey

The value of the grid data. This version accepts a string, but it can be defined as needed.

Events

Event Name

Event Value

Description

Cell Clicked

agGridCellClicked                                                                

Add a custom event that emits on cell click.

Event Payload: Add the details of the cell that was clicked into the Event Payload to use in logic:

  • Row of the cell that was clicked.

  • Column of the cell that was clicked.

  • Value of the cell that was click.

Example AG Grid Package

Click on each of the tabs below to view the JSON and Javascript required to implement an AG Grid:

Manifest JSON Example

Get an in-depth understanding of manifest.json specifications in our Bring Your Own (BYO): Understanding the manifest.json File article.

{
  "name": "AgGrid",
  "version": "1.0.0",
  "main": "agGrid.js",
  "type": "custom",
  "productType": "BYO",
  "description": "AG Grid integration with Unqork",
  "components": [
    {
      "name": "AG Grid",
      "type": "agGrid",
      "description": "AG Grid component for Unqork",
      "model": {
        "type": "object",
        "properties": {
          "columnDefinitions": {
            "name": "Column Definitions",
            "type": "array",
            "description": "Column definitions for this grid (currently only supporting key and label)",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Unique key for the column"
                },
                "label": {
                  "type": "string",
                  "description": "Display label for the column"
                }
              }
            }
          },
          "value": {
            "name": "Grid Data (JSON)",
            "type": "string",
            "description": "JSON string of data for the grid"
          }
        },
        "required": ["columnDefinitions"]
      },
      "events": [
        {
          "name": "AG Grid Cell Clicked",
          "type": "agGridCellClicked",
          "description": "This event is triggered when the user clicks on a grid cell.",
          "stability": "STABLE"
        }
      ]
    }
  ]
}

agGrid.js

The implementation javascript file name must match the manifest.json's main value. In this example, the agGrid.js file matches line four in the manifest.

class UnqorkAgGridComponent extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    this.gridOptions = {
      columnDefs: [],
      rowData: [],
    }
  }
  /**
   * This is the only required method to integrate with the Unqork system
   * 
   * @param {*} api The interface to the Unqork Runtime, used for both
   *                monitoring state and emitting events
   */
  initialize(api) {
    this.config = api.state.currentState()
    this.api = api
    if (this.config.columnDefinitions?.length) {
      const defs = this._normalizeInput(this.config.columnDefinitions)
      this.gridOptions.columnDefs = this._formatColumnDefinitions(defs)
    }
    if (this.config.value?.length) {
      this.gridOptions.rowData = this._normalizeInput(this.config.value)
    }
    this.renderGrid(this.shadowRoot, this.gridOptions)
    this.subscribeColumnDefs()
    this.subscribeData()
  }
  /**
   * Subscribe to the source for column definitions
   * 
   * This can be made more robust by first using
   * api.state.state$('columnDefsReference') to subscribe to
   * any changes to that value (i.e. the target key), and then using
   * resolveByKey$ to resolve the current value of that key, but
   * keeping it simple for example here
   */
  subscribeColumnDefs() {
    this
      .api
      .state
      .state$('columnDefinitions').subscribe((columnDefs) => {
        console.log('AG Grid column definitions updated to', columnDefs)
        // This _should_ be an array, but sometimes components try to set it as a
        // JSON string instead, so we handle that here
        const value = typeof(columnDefs) === 'string' ? JSON.parse(columnDefs) : columnDefs
        this.gridOptions = {
          rowData: this.gridOptions.rowData,
          columnDefs: value ? this._formatColumnDefinitions(value) : [],
        }
        this.updateGrid(this.gridOptions)
      })
  }
  subscribeData() {
    this.api.state.state$('value').subscribe((rowData) => {
      console.log('AG Grid value updated to', rowData)
      // We support setting this as a JSON string or already parsed array of objects
      // so handle that here
      const value = typeof(rowData) === 'string' ? JSON.parse(rowData) : rowData
      this.gridOptions = {
        rowData: value || [],
        columnDefs: this.gridOptions.columnDefs,
      }
      this.updateGrid(this.gridOptions)
    })
  }
  updateGrid(gridOptions) {
    console.log('Updating AG Grid', gridOptions)
    this.gridApi.updateGridOptions(gridOptions)
  }
  async renderGrid(document, gridOptions) {
    console.log('Rendering AG Grid component', {gridOptions, agGrid, windowAgGrid: window.agGrid})
    document.innerHTML = this.view()
    this.gridEl = document.querySelector('#myGrid')
    this.gridApi = window.agGrid.createGrid(this.gridEl, gridOptions)
    // Set this here so that it doesn't overridden when we update gridOptions
    this.gridApi.addEventListener('cellClicked', (ev) => {
      console.log('Emitting AG Grid Cell Clicked Event', ev)
      this.emitCellClickedEvent(ev)
    })
    console.log('Created AG Grid', {
      document,
      gridOptions,
      gridEl: this.gridEl,
      gridApi: this.gridApi,
    })
  }
  // Emit an event when a cell is clicked
  // Full docs: https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-selection-cellClicked
  emitCellClickedEvent = (ev) => {
    this.api.events.emit({
      name: 'onAgGridCellClicked',
      payload: ev
    })
  }
  view() {
    return `
      <div id="myGrid" style="height:500px"></div>
    `
  }
  /**
   * Input data can be either JSON string or actual data, due to
   * Unqork data constraints, so ensure it's proper data before
   * using
   */
  _normalizeInput(val) {
    return typeof(val) === 'string' ? JSON.parse(val) : val
  }
  /**
   * We store column definitions with standard key/label properties
   * But AG Grid using different property names, so we map it here
   */
  _formatColumnDefinitions(defs) {
    return defs.map(({key, label}) => {
      return {
        field: key,
        headerName: label ?? key
      }
    })
  }
}
// This definition defines the state settings (model) for this component
// It mirrors the fuller description in manifest.json and will
// eventually be used to auto-generate the manifest.json JSON schema
class AgGridDefinition {
  columnDefsReference
  dataReference
}
// Actual export that exposes both the view layer and the model
// and is consumed by the Unqork Runtime for both configuration and
// execution
// All of these are `async` to allow for us to load them on demand
// without impacting initial page load performance
export const agGrid = {
  model: async () => AgGridDefinition,
  view: async () => UnqorkAgGridComponent,
}
// This definition describes the schema of the event that is emitted
// when the user clicks on an item
class CellClicked {
  name = 'agGridCellClicked'
  payload = {
    index,
    text,
  }
}
// This export is used to expose the event to the Unqork Runtime
export const agGridCellClicked = {
  model: async () => CellClicked,
}     

Understand what's required to build a custom component in  BYO Component SDK article.

Task List Component

This example displays the customization options, manifest.json, and implementation of a custom Task List component. This asset also contains an event and an operation.

Discover how to create a custom task list component in our How To Example: Bring Your Own Task List article.

Configuration

Settings

Type

Object Type

Description

label                                                                

Input

string componentKey

The title that displays above the task list.

Default value is "Tasks".

value                                                                

Input

array componentKey

An array of objects that defines the task items in the list. Each object in the array contains the following:

  • text (string): The text of the task item.

  • completed (boolean): Whether the task item's checkbox is checked.
    Default value is false (unchecked).

Event

Event Name

Event Value

Description

Task List Item Toggled

taskListItemToggled                                                                

An event that emits when a user checks or unchecks a task item. The event includes a payload with the text and completed status of the updated item.

Event Payload: Adds the item name of the item that was clicked.

Operation

Event Name

Event Value

Description

Task List Toggle Task List Item

toggleTaskListItem                                                                

Toggles or explicitly sets the completed state of a single item in a target task list list.

Includes the following settings:

  • targetKey (string, required): component key of the task list to modify.

  • item (number|string, required) : either the index of the item or the exact text to match.

  • completed (boolean, optional): A boolean to set the completed property of item. If not provided, toggles the existing value to the opposite. For example, true (checked) becomes false (unchecked), or, false (unchecked) becomes true (checked).

Example Task List Package

Click on each of the tabs below to view the JSON and Javascript required to implement a Task List component, including an event and operation:

Manifest JSON Example

Get an in-depth understanding of manifest.json specifications in our Bring Your Own (BYO): Understanding the manifest.json File article.

{
  "name": "TaskList",
  "version": "1.0.0",
  "main": "taskList.js",
  "type": "custom",
  "productType": "BYO",
  "description": "A simple task list component with an event and an operation.",
  "components": [
    {
      "name": "Example Task List",
      "type": "taskList",
      "description": "Simple task list with checkboxes.",
      "model": {
        "type": "object",
        "properties": {
          "label": {
            "name": "Label",
            "type": "string",
            "description": "Label of Task List",
            "default": "Tasks"
          },
          "value": {
            "name": "Value",
            "type": "array",
            "description": "Array of task items",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string",
                  "description": "Text to display for the task item"
                },
                "completed": {
                  "type": "boolean",
                  "description": "Whether the item is completed",
                  "default": false
                }
              },
              "required": ["text"]
            },
            "default": []
          }
        },
        "required": ["value"]
      },
      "events": [
        {
          "name": "Example Task List Item Toggled",
          "type": "taskListItemToggled",
          "description": "Emitted when an end-user checks/unchecks a task item.",
          "stability": "STABLE",
          "model": {
            "type": "object",
            "properties": {
              "name": {
                "const": "taskListItemToggled",
                "type": "string",
                "description": "Name of the event"
              },
              "payload": {
                "type": "object",
                "description": "The event payload.",
                "properties": {
                  "item": {
                    "type": "object",
                    "description": "A task list item.",
                    "properties": {
                      "text": { "type": "string", "description": "Task name."},
                      "completed": { "type": "boolean", "description": "Whether this task is completed."}
                    }
                  }
                },
                "required": [
                  "item"
                ]
              }
            }
          }
        }
      ],
      "operations": [
        {
          "name": "Example Task List Toggle Task List Item",
          "type": "toggleTaskListItem",
          "description": "Toggle (or set) the completion state of a specific item on a target task list.",
          "stability": "STABLE",
          "model": {
            "type": "object",
            "properties": {
              "options": {
                "type": "object",
                "properties": {
                  "targetKey": {
                    "name": "Target Key",
                    "type": "string",
                    "description": "Component key of the task list to modify"
                  },
                  "item": {
                    "name": "Item",
                    "description": "Index (number) or text (string) of the item to change",
                    "type": ["number", "string"]
                  },
                  "completed": {
                    "name": "Completed",
                    "type": "boolean",
                    "description": "Optional explicit completed value; if omitted, the value is toggled"
                  }
                }
              },
              "type": {
                "const": "toggleTaskListItem",
                "type": "string"
              }
            },
            "required": ["targetKey", "item"]
          }
        }
      ]
    }
  ]
}

taskList.js

The implementation JavaScript file name must match the manifest.json's main value. In this example, the operationsOnly.js file matches line four in the manifest.

class UnqorkTaskListComponent extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    this.items = []
    this.label = 'Tasks'
  }
  /**
   * This is the only required method to integrate with the Unqork system
   *
   * @param {*} api The interface to the Unqork Runtime, used for both
   *                monitoring state and emitting events
   */
  initialize(api) {
    this.api = api
    this.config = api.state.currentState()
    // Initial label and value from config (if provided)
    if (this.config.label != null) this.label = String(this.config.label)
    if (this.config.value != null) this.items = this._normalizeItems(this.config.value)
    this.render(this.shadowRoot, this.label, this.items)
    // Subscribe to state changes such as SET_PROPERTY
    this.subscribeLabel()
    this.subscribeValue()
  }
  /**
   * Subscribes to changes in the `label` property of the component's state.
   *
   * This method listens to the `state$('label')` observable for updates to the `label` property.
   * When a new value is emitted, it updates the `label` property of the component and re-renders the component.
   *
   * @returns {void}
   */
  subscribeLabel() {
    this.api.state.state$('label').subscribe((label) => {
      this.label = label ?? ''
      this.shadowRoot.innerHTML = this.view(this.label, this.items)
    })
  }
  /**
   * Subscribes to changes in the `value` property of the component's state.
   *
   * This method listens to the `state$('value')` observable for updates to the `value` property.
   * When a new value is emitted, it normalizes the value into an array of items and updates the `items` property.
   * It then re-renders the component with the updated items.
   *
   * @returns {void}
   */
  subscribeValue() {
    this.api.state.state$('value').subscribe((value) => {
      this.items = this._normalizeItems(value)
      this.shadowRoot.innerHTML = this.view(this.label, this.items)
    })
  }
  /**
   * Normalizes the input value into an array of objects with a specific structure.
   *
   * This method ensures that the input `value` is converted into an array of objects,
   * where each object contains the following properties:
   * - text: The text to actually display in the taskList item (defaults to an empty string if missing).
   * - completed: A boolean indicating whether the item is completed (defaults to `false` if missing).
   *
   * @param value - The input value to normalize. Can be a JSON string or an array.
   * @returns The normalized array of items.
   */
  _normalizeItems(value) {
    const arr = typeof value === 'string' ? JSON.parse(value) : value
    if (!Array.isArray(arr)) return []
    return arr.map((item) => ({
      text: String(item?.text ?? ''),
      completed: Boolean(item?.completed),
    }))
  }
  render(root, label, items) {
    root.innerHTML = this.view(label, items)
    root.addEventListener('change', (ev) => {
      const el = ev.target
      if (!(el instanceof HTMLInputElement) || el.type !== 'checkbox') return
      const idx = Number(el.getAttribute('data-index'))
      if (Number.isNaN(idx) || !this.items[idx]) return
      const updatedItem = { ...this.items[idx], completed: el.checked }
      const next = [...this.items]
      next[idx] = updatedItem
      // Update state with new item value
      this.api.state.set({ value: next })
      // Emit taskListItemToggled event
      this.emitItemToggledEvent(updatedItem)
    })
  }
  /**
   * Emits a `taskListItemToggled` event when a to-do list item is toggled.
   *
   * This method triggers a domain event with the name `taskListItemToggled` and includes
   * the toggled item's details in the event payload. The payload contains the updated item
   * with its `text` and `completed` properties.
   *
   */
  emitItemToggledEvent = (item) => {
    this.api.events.emit({
      name: 'taskListItemToggled',
      payload: { item },
    })
  }
  // The component view
  view(label, items) {
    const list = items
      .map(
        (item, i) => `
        <li>
          <label>
            <input type="checkbox" data-index="${i}" ${item.completed ? 'checked' : ''} />
            ${item.text}
          </label>
        </li>`,
      )
      .join('')
    return `
    <div>${label ?? ''}</div>
    <ul data-role="list">
      ${list}
    </ul>
  `
  }
}
/**
 * This definition defines the state settings (model) for this component
 * It mirrors the fuller description in manifest.json and will
 * eventually be used to auto-generate the manifest.json schema
 */
class TaskListDefinition {
  label
  value
}
/**
 * This export provides the view and model definitions for the component,
 * which are utilized by the Unqork Runtime for configuration and execution.
 * Both definitions are `async` to enable on-demand loading, improving initial page load performance.
 *
 * Component export name MUST match manifest `type`
 */
export const taskList = {
  model: async () => TaskListDefinition,
  view: async () => UnqorkTaskListComponent,
}
/** ========= Example Task List Item Toggled Event Model ========= **/
// Defines the structure of the payload for the taskListItemToggled event
class TaskListItemToggledPayload {
  item = { text: '', completed: false }
}
// This definition describes the schema of the event that is emitted
// when the user toggles an item
class TaskListItemToggled {
  // The name of the event
  name = 'taskListItemToggled'
  // The details of the toggled to-do list item
  payload = new TaskListItemToggledPayload()
}
// This export is used to expose the event to the Unqork Runtime
// Note: event export name MUST match manifest `type`
export const taskListItemToggled = {
  model: async () => TaskListItemToggled,
}
/** ======== Example Task List Toggle Task List Item Operation ======== **/
// Operation options/schema for toggling a task list item
class ToggleTaskListItemOptions {
  // key of the taskList component to modify
  targetKey
  // number (index) OR string (text) of the item to change
  item
  // Boolean to set completed property of item to. If not provided, just toggle the existing value (e.g. true -> false and false -> true)
  completed
}
class ToggleTaskListItemOperation {
  // MUST match the manifest "type" and the named export below
  type = 'toggleTaskListItem'
  options = new ToggleTaskListItemOptions()
}
class ToggleTaskListItemHandler {
  /**
   * Execute the operation.
   * @param {ToggleTaskListItemOperation} operation
   * @param {*} api Operation API: { state: { resolveByKey, set, get }, events: { emit } }
   */
  async execute(operation, api) {
    const { targetKey, item, completed } = operation.options.config || {}
    if (!targetKey) throw new Error('toggleTaskListItem Operation: "targetKey" is required.')
    if (item === undefined || item === null)
      throw new Error('toggleTaskListItem Operation: "item" (index or text) is required.')
    // Resolve the target component state
    const target = api.state.resolveByKey(targetKey)
    // Expecting a list of items like the following:
    // [
    //   { "text": "Buy groceries", "completed": false },
    //   { "text": "Complete design doc", "completed": true }
    // ]
    const list = target?.value ?? []
    if (!Array.isArray(list)) throw new Error('toggleTaskListItem Operation: target value is not an array.')
    // Find the target index
    let index = typeof item === 'number' ? item : list.findIndex((it) => String(it?.text ?? '') === String(item))
    // Ensure the index is within bounds, otherwise throw an error
    if (index < 0 || index >= list.length) {
      throw new Error(`toggleTaskListItem Operation: could not find item "${item}".`)
    }
    // Get the current item or default to an empty object with default properties
    const current = list[index] ?? { text: '', completed: false }
    // Determine the next completion state based on the provided `completed` boolean or toggle the current state
    const nextCompleted = typeof completed === 'boolean' ? completed : !current.completed
    // Create a new list with the updated item at the specified index
    const newList = [...list]
    newList[index] = { ...current, completed: nextCompleted }
    // Update the target component state with the new list
    api.state.set(targetKey, { value: newList })
  }
}
// The following class is omitted since the operation handler does not return a result
// class ToggleTaskListItemContext {
//   ...
// }
// Named export — MUST match manifest "type"
export const toggleTaskListItem = {
  model: async () => ToggleTaskListItemOperation,
  handler: async () => ToggleTaskListItemHandler,
  //This is omitted since the operation handler does not return a result
  //contextModel: async () => ToggleTaskListItemContext,
}

Understand what's required to build a custom component in  BYO Component SDK article.

Root Level Event

This example displays  the Manifest JSON and JavaScript implementation of a custom event. After uploading a package containing an event asset, Creators can use the event in the Operations Builder.

Click on each of the tabs below to view the JSON and Javascript required to implement a root level event:

Manifest JSON Example

Get an in-depth understanding of manifest.json specifications in our Bring Your Own (BYO): Understanding the manifest.json File article.

{
  "name": "eventsOnly",
  "version": "1.0.0",
  "main": "eventsOnly.js",
  "type": "custom",
  "productType": "BYO",
  "description": "Asset that contains only general events.",
  "events": [
    {
      "name": "Events Only",
      "type": "eventsOnly",
      "description": "A top-level event.",
      "stability": "ALPHA",
      "model": {
        "type": "object",
        "properties": {
          "name": {
            "const": "eventsOnly",
            "type": "string"
          },
          "payload": {
            "type": "object",
            "properties": {
              "data": {
                "description": "Sample property",
                "type": "string"
              }
            }
          }
        }
      }
    }
  ]
}

eventsOnly.js

The implementation JavaScript file name must match the manifest.json's main value. In this example, the eventsOnly.js file matches line four in the manifest.

class EventPayload {
  data = 'defaultValue'
}
class Event {
  name = 'eventsOnly'
  payload = new EventPayload()
}
// Export for the event
export const eventsOnly = {
  model: async () => Event,
}

Understand what's required to build a custom component in  BYO Component SDK article.

Average and Round Operation

This example displays an example of the Manifest JSON and JavaScript implementation of a custom averaging and rounding operation. After uploading a package containing an operation asset, Creators can use the operation in the Operations Builder.

Configuration Options

Settings

Type

Object Type

Description

numbers(num[], optional                                                                

Input

Array componentKey

The array of numbers to average.

By default this value is [ ] (none).

roundNumDec ( integer, optional)                                                                

Input

Integer

An integer representing the number of decimal places for rounding.

  • Constrained: 0 –10

  • Default: 2

  • Placeholder: “Specify an integer between 0 and 10”

Payload

Settings

Description

result (number, required)

The averaged value after rounding to the requested decimals.

Average and Round Operation Package

Click on each of the tabs below to view the JSON and Javascript required to implement a root level operation:

Manifest JSON Example

Get an in-depth understanding of manifest.json specifications in our Bring Your Own (BYO): Understanding the manifest.json File article.

{
  "name": "exAvgAndRound",
  "version": "1.0.0",
  "main": "exAvgAndRoundOperation.js",
  "type": "custom",
  "productType": "BYO",
  "description": "A simple operation that averages an array of numbers and rounds result to specified decimals.",
  "operations": [
    {
      "name": "Example Average and Round",
      "type": "exAvgAndRound",
      "description": "Averages an array of numbers and rounds result to specified decimals.",
      "stability": "STABLE",
      "model": {
        "type": "object",
        "properties": {
          "options": {
            "type": "object",
            "properties": {
              "numbers": {
                "name": "Numbers",
                "type": "array",
                "description": "Flat array of numbers to average.",
                "items": { "type": "number" },
                "default": []
              },
              "roundNumDec": {
                "name": "Round Number Decimals",
                "type": "integer",
                "description": "Number of decimals to round result.",
                "minimum": 0,
                "maximum": 10,
                "default": 2,
                "placeholder": "specify an integer between 0 and 10"
              }
            }
          },
          "type": {
            "const": "exAvgAndRound",
            "type": "string"
          }
        }
      },
      "contextModel": {
        "type": "object",
        "properties": {
          "result": {
            "type": "object",
            "properties": {
              "result": {
                "name": "Result",
                "type": "number",
                "description": "Result of the average operation."
              }
            }
          }
        }
      }
    }
  ]
}

exAvgAndRoundOptions.js

The implementation JavaScript file name must match the manifest.json's main value. In this example, the operationsOnly.js file matches line four in the manifest.

// Operation options
class ExAvgAndRoundOptions {
  // Flat array of numbers to average. Defaults to an empty array
  numbers = []
  // Integer decimals (0..10) to round the result. Defaults to 2
  roundNumDec = 2
}
// Operation model
class ExAvgAndRoundOperation {
  // MUST match the manifest "type" and the named export below
  type = 'exAvgAndRound'
  options = new ExAvgAndRoundOptions()
}
// Operation handler
class ExAvgAndRoundHandler {
  /**
   * Execute the operation.
   * @param {ExAvgAndRoundOperation} operation
   * @param {*} api Operation API: { state: { resolveByKey, set, get }, events: { emit } }
   * @returns {{ result: number }}
   */
  async execute(operation, api) {
    // Get the operation options or default to an empty object
    const opts = operation.options.config ?? {}
    // Extract the numbers array from the options
    const nums = opts.numbers
    // Calculate the mean (average).
    // The result is either 0 (if the array is empty) or the calculated mean of the numbers.
    const mean = nums.length === 0 ? 0 : nums.reduce((acc, n) => acc + n, 0) / nums.length
    // Round the mean to the specified number of decimals
    // Javascript method toFixed returns a string so it needs to be coerced back to number
    const decimals = opts.roundNumDec
    const result = Number(mean.toFixed(decimals))
    return { result }
  }
}
// Context result (what the op returns into context)
// If the operation handler does not return a result. This can be omitted.
class ExAvgAndRoundContextResult {
  result = 0
}
// If the operation handler does not return a result. This can be omitted.
class ExAvgAndRoundContext {
  result = new ExAvgAndRoundContextResult()
}
// Export for the operation
// Named export MUST match manifest "type"
export const exAvgAndRound = {
  model: async () => ExAvgAndRoundOperation,
  handler: async () => ExAvgAndRoundHandler,
  contextModel: async () => ExAvgAndRoundContext, // If the operation handler does not return a result. This can be omitted.
}

Understand what's required to build a custom component in  BYO Component SDK article.

Resources

Bring Your Own (BYO) Framework Landing Page

Find all the resources needed to build your own components here.

Introduction to the Bring Your Own (BYO) Framework

Get started building your own custom assets, including components, events, and operations.

Create a BYO Package

Discover the structure and content of a BYO Package

Understanding the manifest.json File

Learn about the file's specification, component and event definitions.

Understanding the BYO Runtime Engine API

Discover the interface between your custom BYO components and the Unqork platform.

Custom Component SDK

Extend component functionality in the Unqork Designer Platform.

Custom Component

Understand the state, interface, and events of a custom component.

Custom Events

Learn more about creating custom events for use in the Operations Builder.

Event Payloads

Learn more about viewing and using event payloads with the Operations Builder.

Custom Operations

Learn more about creating custom operations for use in the Operations Builder.

BYO Best Practices

Learn about best practices for implementing and using BYO assets.

BYO General FAQ

Learn about the Bring Your Own Framework and discover frequently asked questions.