Skip to content

Reading Context and Performing Document Actions

Once your extension has called AppExtension.registerAppExtension() (see the overview), the host sends an app context into the extension and exposes a set of document actions you can call back into the host. Everything is imported from @contrail/extensions-sdk.

The SDK gives you a context helper plus app-specific action classes:

  • getAppContext() — the current context, regardless of which app launched the extension (board, plan, or showcase).
  • BoardsApp — read and mutate a board document.
  • PlanApp — read placeholders and add rows in a plan.

Synchronous reads vs. async calls

Reads that come straight from the context the host already sent are synchronous and return immediately: getCurrentBoard, getCurrentPlan, getAllPlanPlaceholders, getFilteredPlanPlaceholders.

Calls that go back to the host are async and return a Promise: getElements, recolorImages, addFramesFromTemplate, createNewContentAndAssignToEntity.

Mutations are fire-and-forget and return void: addElements, modifyElements, deleteElements, addRows, clearSelectedRows, showMessage.

Each app class checks that the extension was launched in that app — for example BoardsApp throws "App extension has not been initialized with Board context." if you call it outside a board.

Reading context (any app)

getAppContext() returns an AppContext. Use appContext.vibeIQApp to branch on which app you're in, and appContext.selectedElements for the elements the user had selected when launching a contextual extension. user is the current user.

import { getAppContext, VibeIQAppType } from '@contrail/extensions-sdk';

const context = getAppContext();
const app = context.appContext?.vibeIQApp; // 'boards' | 'plan' | 'showcase' | 'admin_console'
const user = context.user;

// Elements the user selected before launching (contextual extensions)
const selected = context.appContext?.selectedElements ?? [];
const firstItemRef = selected[0]?.modelBindings?.item; // reference to the bound item, if any

switch (app) {
  case VibeIQAppType.BOARDS:    /* use BoardsApp */ break;
  case VibeIQAppType.PLAN:      /* use PlanApp */ break;
}

See Contextual Extensions for more on selectedElements and modelBindings.

Board (BoardsApp)

A board is a canvas document made of elements (images, text, frames, etc.). Mutations are applied through the board's document service, so they participate in the board's normal undo/redo history.

Read board context and elements

The board context already includes the full document, so its elements are available directly on getCurrentBoard() — no call back to the host is needed to read them:

import { getAppContext, BoardsApp } from '@contrail/extensions-sdk';

// Board metadata + the full document (id, name, backingAssortmentId, document, ...)
const board = BoardsApp.getCurrentBoard();
console.log(board.id, board.name, board.backingAssortmentId);

// Elements come with the context — read and filter them directly.
const elements = board.document?.elements ?? [];
const imageElements = elements.filter((element) => element.type === 'image');

// The elements the user had selected when launching are also on the context.
const selectedElements = getAppContext().appContext?.selectedElements ?? [];

When to use getElements() instead

board.document.elements is a snapshot taken when the extension launched — it does not update as the board changes. Use BoardsApp.getElements(criteria) when you need the live document (for example after your extension has added or modified elements), or to scope to a single frame. Pass {} for the whole document or { frameId } for one frame:

const liveElements = await BoardsApp.getElements({});                          // current whole document
const frameElements = await BoardsApp.getElements({ frameId: 'frame-123' });   // one frame

Add, modify, and delete elements

Build elements with DocumentElementFactory from @contrail/documents so they get a generated id and a valid shape. The factory has type-specific helpers (createTextElement, createImageElement, createFrameElement) and a generic createElement(type, options).

import { BoardsApp } from '@contrail/extensions-sdk';
import { DocumentElementFactory } from '@contrail/documents';

// Add a text label to the canvas.
const label = DocumentElementFactory.createTextElement('Generated by my extension', {
  position: { x: 100, y: 100 },
  size: { width: 240, height: 60 },
});
BoardsApp.addElements([label]);

// Modify elements: pass the element id and the properties to change
BoardsApp.modifyElements([
  { id: label.id, changes: { position: { x: 320, y: 100 } } },
]);

// Delete elements (each must have an `id`)
BoardsApp.deleteElements([label]);

Recolor images, add frames, assign content

// Recolor image elements to a single hex code (or pass a hexMap for per-color mapping)
const recolored = await BoardsApp.recolorImages(imageElements, '#1d4ed8');

// Add frames from a saved frame template (by id or an inline template)
await BoardsApp.addFramesFromTemplate([
  { frameTemplateId: 'template-123', frameOptions: { position: { x: 0, y: 0 }, name: 'Look 1' } },
]);

// Create new content from elements and attach it to an entity (e.g. an item) as a viewable
await BoardsApp.createNewContentAndAssignToEntity(assignmentOptions, entity, elements);

Plan (PlanApp)

A plan is a spreadsheet-style surface of plan placeholders (rows).

Read the plan and its placeholders

import { PlanApp } from '@contrail/extensions-sdk';

// Plan metadata (id, name, targetAssortmentId, workspaceId, hasFilter, ...)
const plan = PlanApp.getCurrentPlan();

// All rows, or only the rows currently visible under the user's filter
const allRows = PlanApp.getAllPlanPlaceholders();
const visibleRows = PlanApp.getFilteredPlanPlaceholders(); // respects the active filter

Add rows and message the user

import { PlanApp } from '@contrail/extensions-sdk';

// Add rows to the plan (collection elements)
PlanApp.addRows(newCollectionElements);

// Clear the user's current row selection
PlanApp.clearSelectedRows();

// Show a transient message in the plan UI
PlanApp.showMessage('Imported 12 rows from the forecast');

Showcase

A showcase presents items across frames. Showcase extensions read their context through getAppContext():

import { getAppContext, ShowcaseContext } from '@contrail/extensions-sdk';

// Showcase metadata (id, name, currentFrame, frames, backingAssortmentId, ...)
const showcase = getAppContext().appContext?.showcase as ShowcaseContext | undefined;
const currentFrame = showcase?.currentFrame;
const allFrames = showcase?.frames ?? [];

// Elements the user selected when launching (contextual extensions)
const selected = getAppContext().appContext?.selectedElements ?? [];

Working with entities and types

Beyond document actions, extensions can read and write platform data through the host's session using EntitiesClient and TypesClient (also from @contrail/extensions-sdk). These proxy @contrail/sdk calls through the host, so they respect the current user's org and permissions.

import { EntitiesClient } from '@contrail/extensions-sdk';

const entities = new EntitiesClient();
const item = await entities.get({ entityName: 'item', id: 'item-1' });
await entities.update({ entityName: 'item', id: 'item-1', object: { name: 'Updated name' } });

For the connection/handshake and display configuration, see the extension overview; for selection-driven extensions, see Contextual Extensions.