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.