Skip to content

Getting Started

Apps enable you to extend the capability of Vibe's platform and integrate your product seamlessly. Apps are highly configurable and scalable, requiring no provisioning or infrastructure on your end.

Requirements

All contrail apps are built using NodeJS and NPM. The apps are managed using the publicly available Contrail cli tool.

Installing the CLI

The contrail CLI is published as @contrail/cli. Install it globally with npm:

npm install -g @contrail/cli
contrail --version   # verify the install (also available as `contrail-cli`)

There is no separate login command. The first time you run a command that contacts the platform, the CLI prompts for your email, password, and org, then caches your session under ~/.vibeiq/configs/. Your org lives in a named config profile — create and switch profiles with contrail config create and contrail config use. See Testing and Deploying an Extension for the full setup and authentication walkthrough, including CI/CD with CONTRAIL_CLI_API_KEY.

CLI Commands

The contrail CLI is your main tool to creating, managing, and installing apps. To list all the commands available to you, run contrail app --help. You can view how each of these commands are used in an app's lifecycle

contrail app --help
# Create, deploy, and manage apps
#
# USAGE
#   $ contrail app COMMAND
#
# COMMANDS
#   app create     Create an app from a template
#   app delete     Delete the app from the app marketplace
#   app getApiKey  Get an api key for an app
#   app install    Install an app from the marketplace
#   app publish    Publish the app
#   app uninstall  Uninstall an app that is currently installed
#   app upgrade    Upgrade an installed app

App Structure

The app template offers a simple place to start coding your integration. It comes with existing compilation configuration and the necessary node modules to run and request information from the Vibe platform.

.
├── README.md
├── app.yml
├── package-lock.json
├── package.json
├── src
│   ├── actions
│   │   └── logItem.ts
│   └── main.ts
└── tsconfig.json

Actions

You will spend most of your time in the actions directory. This is where all your app actions will live. The files in the actions directory will be scanned for a class definition matching the expected criteria:

  • All actions must be in a class definition that implements AppAction
  • The execute function of the class must be decorated to match the identifier in the app.yml file.
logItem.ts
import { Entities } from '@contrail/sdk';
import { Action, AppAction, AppActionCallBack, AppActionCallbackStatus, Logger } from '@contrail/app-framework';

export class LogItemAction implements AppAction { // (1)

  @Action('logItem') // (2)
  public async execute(event, config: any, logger: Logger): Promise<AppActionCallBack> {
    logger.log('Starting Example Action');
    logger.log('Getting item from SDK');

    const item = await new Entities().get({
      entityName: 'item',
      id: event.id,
    });
    logger.log('Got item: ', item);

    return {
      output: {
        item,
      },
      status: AppActionCallbackStatus.SUCCESS
    };
  }
}
  1. The action class must implement AppAction
  2. The execute function must be decorated with the same identifier as the one used in the app.yml file.

The event argument

The first argument to execute is the event object describing what happened. Its shape depends on how the workflow was triggered.

Internal events (system events)

When the workflow is triggered by a VibeIQ system event (item|create, project-item|update, etc.), the event has the standard CRUD shape:

interface Event {
  id: string;                 // Entity ID
  entityName: string;         // 'item', 'project-item', etc.
  newData: Record<string, any>; // Current state of the entity
  oldData: Record<string, any>; // Previous state (updates only; absent on create)
  propertyDiffs: Record<
    string,
    {
      propertyName: string;
      oldValue: any;
      newValue: any;
    }
  >;
}
  • newData and oldData are the full entity payloads before and after the change. On a create, oldData is absent.
  • propertyDiffs is keyed by the slug of each property that actually changed. Use it to react to a specific field change without diffing newData against oldData yourself.

Example: react only to a status change.

if (event.propertyDiffs?.status) {
  const { oldValue, newValue } = event.propertyDiffs.status;
  // ...
}

External events

When the workflow is triggered by an external system POSTing to /external-events, the event shape is not the CRUD interface above — it is whatever JSON payload the external system sent. Fields like newData, oldData, and propertyDiffs do not exist; only the keys the caller actually included will be present.

The same is true of events raised by another app via the SDK, or kicked off via an EventWorkflowRequest: the event is the raw object that was passed in.

Your action should branch on the relevant payload fields directly (e.g. event.details.eventType, event.actionType, or whatever the publishing system documents). The same eventWorkflowTriggerKeyMapping pattern that turned the payload into a trigger key (see eventWorkflowTriggerKeyMapping) is your map of which fields are reliable on the payload.

The config argument

When an event triggers your action, the framework injects runtime context for the specific event into the config argument, alongside any action-level configuration declared in app.yml (see also Action Configuration).

The runtime-injected properties are:

  • orgSlug (string) — the slug of the org the event was triggered in.
  • taskId (string) — the ID of the task running this action. Needed when posting an async-task callback.
  • processId (string) — the ID of the workflow process this task belongs to.
  • action (string) — the identifier of the action being run, in the format <appIdentifier>:<actionId>.
app.yml
actions:
  logItem: # (1)
    name: "Log Item"
    description: "Fetches an item from the API and returns it to the log."
  1. This identifier maps directly to one declared in the @Action('logItem') decorator.

App Manifest

The app.yml file declares your app's specs, visibility, and how other developers can interact with it. Every detail in this file is important and will significantly impact the way your app appears and works in the marketplace.

appIdentifier (string)

The app identifier is a unique string that others can use to install your app. Once claimed, an identifier is reserved throughout the Vibe platform, and can not be duplicated. Additionally, an app's identifier can not be altered after creation. It is good practice to include the publisher name in the identifier (e.g. @vibe/vntana is an identifier for the native Vntana plugin published by Vibe's internal team).

appName (string)

The app's name as it appears in the marketplace.

appDescription (string)

The app's detailed description as it appears in the marketplace. This is the best place to include detailed app information, such as any required post-installation setup.

version (string)

The app's version, as a semver string (e.g. 1.0.0). This field is required, and every contrail app publish expects a version that has not been published before. Rather than editing this value by hand, bump it with the CLI, which updates app.yml for you:

contrail app version patch   # 1.0.0 -> 1.0.1  (also: minor, major)

Each published version is tracked independently, which is what lets you install, upgrade, and downgrade between versions. See Publishing the app.

visibility (string)

public or private (default). Controls whether your app appears in the marketplace or not. See App visibility.

accessGrants (list)

A list of orgs that can install the app. Each org must have a orgSlug and a list of permissions that can be granted. The only permission available at the time of writing is install.

Example Access Grants
accessGrants:
  - orgSlug: recipient-org-1
    permissions:
      - install
  - orgSlug: recipient-org-2
    permissions:
      - install

publisher (string)

The publisher of the app, usually a company or team's name (e.g. Nike, Salesforce, VibeIQ, etc.)

thumbnail (string)

A public URL that points to an image. Used as the display icon in the marketplace.

Example Thumbnail Link
thumbnail: https://www.shutterstock.com/image-photo/birds-eye-view-pine-forest-600w-2239370259.jpg

appConfiguration (object)

Properties you can configure when you install the app. These property values will be available to all of your actions when they run. You can choose to mask sensitive configuration values (like API keys or passwords) in the admin UI by setting the masked property to true.

Example App Configuration
appConfiguration:
  apiHost:
    label: "API Host"
    propertyType: "string"
    dynamic: false
    masked: false # Set to true if you want to hide/mask this configuration value in the admin UI

actions (object)

An object that maps each key to an action name. Each action name should match exactly one Action decorator in one of the files in the actions directory. See Actions.

Example Actions List
actions:
  logItem:
    name: "Log Item"
    description: "Fetches an item from the API and returns it to the log."
    async: false
    actionConfiguration:
      message:
        label: "Additional message to log."
        propertyType: "string"

async

Set to true for long-running actions that finish via a callback rather than a synchronous return. The action returns immediately to free the worker; the platform expects a later POST to the callback endpoint to mark the task complete. See Async tasks for the callback contract.

messageGroupId

Set on an action to serialize its execution across every workflow that invokes it. Events that share the same messageGroupId run one-at-a-time. Use when the action writes to a shared external resource that must not be hit concurrently.

actions:
  loadSnowflakeForecastCSVsFromFolder:
    name: "Upload Snowflake/Forecast CSVs from Box"
    async: false
    messageGroupId: snowflake-forecast

Result of this configuration. Every message dispatched to "Upload Snowflake/Forecast CSVs from Box" lands on the FIFO queue under the snowflake-forecast group. The messages run one at a time, in the order they were received, regardless of how many fire at once. Increasing parallelism (via the workflow's parallelWorkerCount) lets multiple messages from this group run concurrently — faster, but at the cost of breaking strict arrival ordering. Set parallelWorkerCount to 1 when ordering matters (the default is 5).

actionConfiguration field options

Each entry under actionConfiguration describes one configurable input the user supplies when wiring the action into a workflow. Supported fields:

Field Type Description
label string Display label shown when configuring the action.
propertyType string string, number, boolean, or json (structured blob for nested config — e.g. column maps).
required boolean When true, the workflow won't save without a value.
defaultValue any Pre-populated value when the user has not entered one.
secret boolean Masks the value in the admin UI (use for API keys, passwords).
dynamic boolean The value is computed at runtime (e.g. from event payload) rather than entered statically.
masked boolean (App-level configuration only) Same as secret for appConfiguration entries.
actions:
  syncToFlexPLM:
    name: "Sync To FlexPLM"
    description: "Push the item to FlexPLM."
    async: true
    actionConfiguration:
      apiKey:
        label: "FlexPLM API Key"
        propertyType: "string"
        secret: true
        required: true

      maxRetries:
        label: "Max retries"
        propertyType: "number"
        defaultValue: 5

      federatedMappings:
        label: "Federated column mapping (FlexPLM  VibeIQ)"
        propertyType: "json"

      statusCounts:
        label: "Status counts (computed)"
        propertyType: "string"
        dynamic: true

A list of external links. Each external link will appear on the app's page once the app has been installed. The links can be used to direct the user to external pages that may be required for documentation or additional post-install integration. Note that the external link support a few computed properties that can be helpful to include:

  • orgSlug: the slug of the org that's launching the external link (e.g. nasa-suits-prod).
  • appOrgId: the id of the specific installed app in that org.
  • appApiKey: the api key of the specific app installed in that org. This key can be used to make API requests.
  • referer: The referer site from which the external link was launched (e.g. https://admin.vibeiq.com/).
Example External Link
externalLinks:
  - name: "openGoogle"
    label: "Open Google"
    link: "https://google.com?scope=foo&orgSlug={orgSlug}&referer={referer}"
    extraAttributes:
      - name: "foo"
        value: "bar"

extensions (list)

The extensions that are published as part of this app. Each extension requires an identifier unique throughout the system, and a name for the user to see. The extensionType controls what functionality the extension provides. userApps defines what apps the extension will appear in. iframeUrl is a link to where the extension is hosted (must be a publicly accessible URL). The display object controls how the extension is presented to the user — as a centered modal or as a docked side panel.

Example Extensions List
extensions:
  - identifier: "colorChipGenerator"
    name: "Color Chip Generator"
    extensionType: DOCUMENT_AUTOMATION
    userApps:
      - BOARDS
    display:
      type: modal
      dimensions:
        width: 800px
        height: 800px
        maxWidth: 95vw
    iframeUrl: "https://d3i3vkef9zj23s.cloudfront.net/"

Extension Types

  • DOCUMENT_AUTOMATION: manipulates documents in Board and Showcase apps. Has access to read and create elements on a document. For example, this can be used to add images or text to a board, or to run analysis of selected products in a board.
  • CONTEXTUAL_ACTION: An extension that uses a set of selected elements (image, item, plan placeholder, etc) in the boards, plan, or showcase apps. For example, an extension that generates an analysis on a number of selected items in a plan.
  • ADMIN_UTILITY: An extension that shows up as an additional tab in the admin console (as part of the left navbar). This can be often used to add a handy feature for a repetitive task for admins (for example, generating a report).

User Apps

  • BOARDS
  • SHOWCASE
  • PLAN
  • ADMIN_CONSOLE
  • ALL

display (object)

The display object controls how the extension is rendered when launched. It supersedes the older modalDimensions field (see the deprecation note below).

Field Type Description
type modal | side-menu How the extension is presented. modal is a centered dialog with a backdrop; side-menu is a floating panel docked to the right edge with no backdrop, letting users keep interacting with the document.
dimensions.width string CSS width of the frame (e.g. 800px, 340px).
dimensions.height string CSS height of the frame (e.g. 800px, calc(100vh - 82px)).
dimensions.maxWidth string CSS max width (e.g. 95vw).
position.top / position.right / position.bottom / position.left string Offsets from the viewport edges. Most relevant for side-menu (e.g. right: 0.625rem).

If neither display nor modalDimensions is provided, the extension renders as a modal at 1350px × 800px (maxWidth: 95vw).

Side panel extension
extensions:
  - identifier: "salesHistory"
    name: "Sales History"
    extensionType: CONTEXTUAL_ACTION
    userApps:
      - BOARDS
      - PLAN
    display:
      type: side-menu
      dimensions:
        width: 340px
        height: calc(100vh - 82px)
        maxWidth: 650px
      position:
        right: 0.625rem
        bottom: 0.625rem
    iframeUrl: "https://d3i3vkef9zj23s.cloudfront.net/"

When type: side-menu is set, the following defaults are applied for any field you omit: width: 340px, height: calc(100vh - 82px), maxWidth: 650px, right: 0.625rem, bottom: 0.625rem. The panel renders without a backdrop and automatically shifts left when an app side panel (such as the frames list) is open.

modalDimensions is deprecated

The older modalDimensions field is still supported for backwards compatibility and is automatically converted into an equivalent modal display configuration. New extensions should use display instead. The conversion maps width/height/maxWidth to display.dimensions and any top/right/bottom/left to display.position, with type: modal.

Deprecated — equivalent to a modal display
modalDimensions:
  width: 800px
  height: 800px
  maxWidth: 95vw

eventWorkflowTriggerKeyMapping (string)

This is an advanced feature that can 'derive' a trigger key for external events. External events are events sent to the VibeIQ API's /external-events end point from another system Here, you can define a string based on properties from the event payloads (which are custom to your payload). i.e. an event {details: {eventType: 'apparel'}, actionType: 'create'} can be parsed to apparel|create with "{details.eventType}|{actionType}". When this is defined, any event coming from your app's credentials will define a trigger key using this logic. You can then define workflows to run based on those external events, based on this trigger key definition.

Example Event Workflow Trigger Mapping
eventWorkflowTriggerKeyMapping: "{details.eventType}|{actionType}"

workflows (object)

Declares event workflows that are bundled with this app. Each workflow is keyed by a short identifier and specifies a trigger, optional parallelism settings, and an ordered list of action steps.

Workflows defined here are published as Workflow Template Definitions and can be installed by any org that has your app installed.

For the full YAML reference, including how to set per-workflow action configuration values, see Defining Workflows in app.yml.

Example Workflows
workflows:
  syncToProduction:
    identifier: 'syncToProduction'
    name: 'Sync to Production'
    triggerKey: item|update
    isActive: true
    public: true
    parallelWorkerCount: 5
    workflowDefinition:
      paths:
        - conditional: 'true'
          actionDefinitionConfigs:
            - action: syncItem
              name: Sync Item
              config:
                endpoint: 'https://prod.example.com/items'

nodeRuntime (string)

Specifies the Node.js runtime version to use when executing your app. This field allows you to control which version of Node.js your app runs on. This can be different for each version of the app.

Available runtime options:

  • nodejs18.x (deprecated)
  • nodejs22.x (recommended)

For more information on managing your Node.js runtime, see Manage Node.js Runtime.

Example Node Runtime Configuration
nodeRuntime: "nodejs22.x"