App Surfaces
App Surfaces are specific locations within the epilot platform where your custom app can be embedded. Each surface type provides a different context and set of capabilities, allowing you to extend epilot's functionality in targeted ways.
Overviewโ
When you build an app for epilot, it runs inside an iframe embedded within the epilot XRM. The @epilot/app-bridge library enables communication between your app and the parent epilot application, providing:
- Authentication - OAuth tokens for epilot API calls
- Localization - Access to the user's language preference
- Context - Entity, page, or action configuration data
- Messaging - Two-way communication with the parent app
npm install @epilot/app-bridge
Available Surface Typesโ
Entity Capabilityโ
A collapsible section within an entity detail view. Use this surface to display entity-specific data, metrics, or custom UI elements directly on the entity page.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Entity: Contact - John Doe โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โผ Overview โ
โ Name: John Doe โ
โ Email: john@example.com โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โผ Your App Capability โ Your App โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Custom content here โ โ
โ โ (iframe) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Use cases:
- Display external data related to an entity
- Show metrics or analytics
- Provide quick actions or tools
- Integrate third-party services
Entity Tabโ
A dedicated tab within the entity detail view. Tabs provide more space than capabilities and are ideal for comprehensive features that require a full-page layout.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Entity: Contact - John Doe โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Overview โ History โ Your Tab โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โ Your full-page content โ โ
โ โ (iframe) โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Use cases:
- Complex data visualizations
- Full-featured integrations
- Document management
- Detailed analytics dashboards
Flow Action Configโ
Configuration UI for custom automation actions. When users add your custom action to an automation workflow, this surface displays your configuration interface.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Automation: New Order Processing โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Trigger: Order Created โ
โ โ โ
โ Action: Your Custom Action โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Configuration: โ โ
โ โ Webhook URL: [____________] โ โ
โ โ Enable: [โ] โ โ
โ โ (iframe) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ Action: Send Email โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Use cases:
- Configure webhook endpoints
- Set up third-party service credentials
- Define action parameters
- Map data fields
Custom Pageโ
A full standalone page within the epilot 360 portal. Custom Pages register their own /app/<slug> route, use the standard 360 layout (sidebar navigation + topbar), and appear as navigation items in the Custom workplace. They support sub-pages and deep-linking.
Use cases:
- Data dashboards and explorers
- Admin panels and settings pages
- Multi-step wizards or onboarding flows
- Full-page third-party integrations
See also: Custom Page component docs for component configuration details.
Getting Started with App Bridgeโ
Basic Initializationโ
tip
Every app must call initialize() before using any other app-bridge functions. This establishes the communication channel with the parent epilot application.
import { initialize } from '@epilot/app-bridge';
async function main() {
// Initialize and get authentication data
const { token, lang } = await initialize();
// token: OAuth access token for epilot APIs
// lang: User's language preference ('en', 'de', etc.)
console.log('App initialized with language:', lang);
}
main().catch(console.error);
Initialization Optionsโ
const session = await initialize({
contentHeight: 400, // Initial iframe height in pixels
timeout: 5000, // Timeout in milliseconds (default: 5000)
});
Entity Surface Implementationโ
For both Entity Capability and Entity Tab surfaces, use the entity-related APIs.
Getting Entity Contextโ
import { initialize, getEntityContext } from '@epilot/app-bridge';
async function main() {
const { token, lang } = await initialize();
// Get the entity being viewed
const context = await getEntityContext();
console.log('Entity ID:', context.entityId);
console.log('Schema:', context.schema); // e.g., 'contact', 'order'
console.log('Capability:', context.capability); // Your app's capability config
}
Context Parameters:
| Parameter | Type | Description |
|---|---|---|
entityId | string | UUID of the entity being viewed |
schema | string | Entity schema slug (e.g., 'contact', 'order', 'opportunity') |
capability | EntityCapability | Capability configuration from your app manifest |
isVisible | boolean | Whether the tab/capability is currently visible (tabs only) |
Dynamic Content Heightโ
Since your app runs in an iframe, you need to communicate your content height to epilot so it can adjust the iframe size appropriately.
import { initialize, updateContentHeight } from '@epilot/app-bridge';
async function main() {
await initialize();
// After rendering your content
renderContent();
// Report the content height
updateContentHeight(document.body.scrollHeight);
}
For dynamic content, use ResizeObserver:
import { initialize, updateContentHeight } from '@epilot/app-bridge';
async function main() {
await initialize();
const container = document.getElementById('app');
// Automatically update height when content changes
const observer = new ResizeObserver((entries) => {
const height = entries[0].contentRect.height;
updateContentHeight(height);
});
observer.observe(container);
}
Handling Tab Visibilityโ
For Entity Tab surfaces, the tab may be hidden when the user switches to another tab. Handle visibility changes to optimize performance:
import {
initialize,
getEntityContext,
onVisibilityChange
} from '@epilot/app-bridge';
async function main() {
await initialize();
const { entityId, schema, isVisible } = await getEntityContext();
// Initial load only if visible
if (isVisible) {
await loadData(entityId, schema);
}
// Subscribe to visibility changes
const unsubscribe = onVisibilityChange((visible) => {
if (visible) {
// Tab became visible - refresh data
refreshData();
}
});
// Clean up when your app unmounts
// unsubscribe();
}
Action Config Implementationโ
For Flow Action Config surfaces, use the action configuration APIs.
Reading Action Configurationโ
import { initialize, getActionConfig } from '@epilot/app-bridge';
// Define your configuration type
interface WebhookConfig {
url: string;
headers: Record<string, string>;
enabled: boolean;
}
async function main() {
await initialize();
// Get existing configuration
const config = await getActionConfig<WebhookConfig>();
// Access your custom config
const webhookUrl = config.custom_action_config?.url ?? '';
const isEnabled = config.custom_action_config?.enabled ?? false;
// Render your configuration form
renderForm({ webhookUrl, isEnabled });
}
Config Parameters:
| Parameter | Type | Description |
|---|---|---|
custom_action_config | T | Your custom configuration object |
description | string? | Action description shown in the UI |
app_id | string? | Associated app ID |
Updating Action Configurationโ
When the user changes configuration in your UI, update it immediately:
import { initialize, getActionConfig, updateActionConfig } from '@epilot/app-bridge';
interface WebhookConfig {
url: string;
enabled: boolean;
}
async function main() {
await initialize();
const { custom_action_config } = await getActionConfig<WebhookConfig>();
// Set up form with existing values
const urlInput = document.getElementById('webhook-url') as HTMLInputElement;
urlInput.value = custom_action_config?.url ?? '';
// Update config when user makes changes
urlInput.addEventListener('change', () => {
updateActionConfig<WebhookConfig>({
url: urlInput.value,
enabled: custom_action_config?.enabled ?? true,
});
});
}
Async Actions with Callbacksโ
If your action performs asynchronous work and the automation should wait for it to complete, use waitForCallback:
updateActionConfig(
{ url: 'https://api.example.com/webhook' },
{ waitForCallback: true }
);
When waitForCallback is true, the automation engine will wait for your action to signal completion before proceeding to the next step.
Page Surface Implementationโ
For Custom Page surfaces, use the page-related APIs. These APIs let you read the current page context, navigate between sub-pages, and respond to browser back/forward navigation.
Getting Page Contextโ
import { initialize, getPageContext } from '@epilot/app-bridge';
async function main() {
const { token, lang } = await initialize();
// Get the page context
const { slug, subPath, path } = await getPageContext();
console.log('Page slug:', slug); // e.g., "energy-prices"
console.log('Sub-path:', subPath); // e.g., "/dashboard"
console.log('Full path:', path); // e.g., "/app/energy-prices/dashboard"
// Render based on current sub-path
renderPage(subPath);
}
Context Parameters:
| Parameter | Type | Description |
|---|---|---|
slug | string | The page slug from the URL (e.g., 'energy-prices') |
subPath | string | Sub-path after the slug (e.g., '/dashboard', '/settings/advanced') |
path | string | Full URL path (e.g., '/app/energy-prices/dashboard') |
Sub-Page Navigationโ
Use navigate() to change the parent frame URL when users interact with your app's internal navigation. This enables deep-linking and updates the browser address bar.
import { navigate } from '@epilot/app-bridge';
// Navigate to sub-pages within your app
navigate('/dashboard'); // URL becomes /app/<slug>/dashboard
navigate('/settings/advanced'); // URL becomes /app/<slug>/settings/advanced
navigate('/'); // URL becomes /app/<slug>
tip
navigate() triggers a history.pushState in the parent frame. It does not reload the page or the iframe - your app stays mounted. Update your UI in response to the navigation.
Handling Browser Back/Forwardโ
When the user clicks the browser back or forward button, the parent frame detects the popstate event and notifies your app:
import { onLocationChange } from '@epilot/app-bridge';
const unsubscribe = onLocationChange((subPath) => {
console.log('Browser navigated to:', subPath);
renderPage(subPath);
});
// Clean up when your app unmounts
// unsubscribe();
Combining Navigate and Location Changeโ
A typical pattern is to use navigate() for user-initiated navigation and onLocationChange() for browser-initiated navigation:
import {
initialize,
getPageContext,
navigate,
onLocationChange,
updateContentHeight,
} from '@epilot/app-bridge';
async function main() {
await initialize();
const { subPath } = await getPageContext();
// Initial render
renderPage(subPath);
// Handle browser back/forward
onLocationChange((newSubPath) => {
renderPage(newSubPath);
});
// Handle user clicks on your internal nav
document.addEventListener('click', (e) => {
const link = (e.target as HTMLElement).closest('[data-route]');
if (link) {
e.preventDefault();
const route = link.getAttribute('data-route')!;
navigate(route);
renderPage(route);
}
});
}
function renderPage(subPath: string) {
// Your routing logic here
updateContentHeight(document.body.scrollHeight);
}
Authorizing API Clientsโ
The app-bridge provides a convenient way to authorize epilot SDK clients:
import { getClient } from '@epilot/entity-client';
import { getClient as getFileClient } from '@epilot/file-client';
import { initialize, authorizeClient } from '@epilot/app-bridge';
async function main() {
const session = await initialize();
// Create clients
const entityClient = getClient();
const fileClient = getFileClient();
// Authorize with the session
authorizeClient(entityClient, session);
authorizeClient(fileClient, session);
// Now ready to make API calls
const entities = await entityClient.searchEntities({
schema: 'contact',
query: 'john',
});
}
You can also pass just the token string:
authorizeClient(entityClient, session.token);
Custom Eventsโ
For advanced use cases, you can send and receive custom events:
Sending Eventsโ
import { send } from '@epilot/app-bridge';
// Send a custom event to the parent
send('my-custom-event', {
action: 'refresh',
data: { key: 'value' },
});
Receiving Eventsโ
import { on } from '@epilot/app-bridge';
// Listen for custom events
const unsubscribe = on<{ action: string }>('parent-event', (data) => {
console.log('Received action:', data.action);
});
// Cleanup when done
unsubscribe();
Wildcard Subscriptionsโ
// Listen to all events
on('*', (data) => {
console.log('Event received:', data);
});
// Listen to events with a prefix
on('custom-*', (data) => {
console.log('Custom event:', data);
});
Error Handlingโ
The app-bridge provides specific error types for better error handling:
import {
initialize,
getSession,
AppBridgeTimeoutError,
AppBridgeNotInitializedError,
} from '@epilot/app-bridge';
// Handle initialization timeout
try {
await initialize({ timeout: 3000 });
} catch (error) {
if (error instanceof AppBridgeTimeoutError) {
console.error(`Initialization timed out after ${error.timeout}ms`);
showError('Failed to connect to epilot. Please refresh the page.');
}
}
// Handle not initialized error
try {
const session = getSession();
} catch (error) {
if (error instanceof AppBridgeNotInitializedError) {
// App bridge not initialized yet
await initialize();
}
}
Complete Examplesโ
Entity Capability Appโ
A complete example of an app that displays external CRM data for a contact:
import {
initialize,
getEntityContext,
updateContentHeight,
authorizeClient,
} from '@epilot/app-bridge';
import { getClient } from '@epilot/entity-client';
interface ExternalCRMData {
score: number;
lastContact: string;
notes: string[];
}
async function main() {
// 1. Initialize app bridge
const { token, lang } = await initialize();
// 2. Set up API client
const entityClient = getClient();
authorizeClient(entityClient, token);
// 3. Set language
document.documentElement.lang = lang;
// 4. Get entity context
const { entityId, schema } = await getEntityContext();
// 5. Fetch entity data
const entity = await entityClient.getEntity({ slug: schema, id: entityId });
// 6. Fetch external CRM data (your API)
const externalData = await fetchExternalCRM(entity.data.email);
// 7. Render UI
renderCRMWidget(externalData);
// 8. Update content height
const observer = new ResizeObserver((entries) => {
updateContentHeight(entries[0].contentRect.height);
});
observer.observe(document.getElementById('app')!);
}
function renderCRMWidget(data: ExternalCRMData) {
const app = document.getElementById('app')!;
app.innerHTML = `
<div class="crm-widget">
<div class="score">Score: ${data.score}</div>
<div class="last-contact">Last Contact: ${data.lastContact}</div>
<div class="notes">
${data.notes.map(note => `<p>${note}</p>`).join('')}
</div>
</div>
`;
}
async function fetchExternalCRM(email: string): Promise<ExternalCRMData> {
const response = await fetch(`https://api.mycrm.com/lookup?email=${email}`);
return response.json();
}
main().catch(console.error);
Entity Tab with Visibility Handlingโ
An app that displays a dashboard, refreshing data when the tab becomes visible:
import {
initialize,
getEntityContext,
onVisibilityChange,
updateContentHeight,
} from '@epilot/app-bridge';
async function main() {
const { token, lang } = await initialize();
const { entityId, schema, isVisible } = await getEntityContext();
// Initial setup
await setupDashboard(entityId, schema);
// Load data if tab is already visible
if (isVisible) {
await loadDashboardData(entityId);
}
// Refresh data when tab becomes visible
onVisibilityChange(async (visible) => {
if (visible) {
await loadDashboardData(entityId);
}
});
// Update height
updateContentHeight(document.body.scrollHeight);
}
async function setupDashboard(entityId: string, schema: string) {
// Initialize dashboard UI
}
async function loadDashboardData(entityId: string) {
// Fetch and render dashboard data
}
main().catch(console.error);
Automation Action Configurationโ
A complete configuration UI for a webhook action:
import {
initialize,
getActionConfig,
updateActionConfig,
updateContentHeight,
} from '@epilot/app-bridge';
interface WebhookActionConfig {
url: string;
method: 'GET' | 'POST' | 'PUT';
headers: Record<string, string>;
retryCount: number;
}
async function main() {
await initialize();
// Get existing configuration
const { custom_action_config } = await getActionConfig<WebhookActionConfig>();
const config = custom_action_config ?? {
url: '',
method: 'POST',
headers: {},
retryCount: 3,
};
// Render configuration form
renderForm(config);
updateContentHeight(document.body.scrollHeight);
}
function renderForm(config: WebhookActionConfig) {
const app = document.getElementById('app')!;
app.innerHTML = `
<form id="config-form">
<div class="field">
<label>Webhook URL</label>
<input type="url" id="url" value="${config.url}" required />
</div>
<div class="field">
<label>HTTP Method</label>
<select id="method">
<option value="GET" ${config.method === 'GET' ? 'selected' : ''}>GET</option>
<option value="POST" ${config.method === 'POST' ? 'selected' : ''}>POST</option>
<option value="PUT" ${config.method === 'PUT' ? 'selected' : ''}>PUT</option>
</select>
</div>
<div class="field">
<label>Retry Count</label>
<input type="number" id="retryCount" value="${config.retryCount}" min="0" max="10" />
</div>
</form>
`;
// Listen for changes
const form = document.getElementById('config-form')!;
form.addEventListener('change', () => {
const newConfig: WebhookActionConfig = {
url: (document.getElementById('url') as HTMLInputElement).value,
method: (document.getElementById('method') as HTMLSelectElement).value as 'GET' | 'POST' | 'PUT',
headers: config.headers,
retryCount: parseInt((document.getElementById('retryCount') as HTMLInputElement).value),
};
updateActionConfig(newConfig);
});
}
main().catch(console.error);
Custom Page Appโ
A complete example of a custom page app with sub-page navigation:
import {
initialize,
getPageContext,
navigate,
onLocationChange,
updateContentHeight,
authorizeClient,
} from '@epilot/app-bridge';
import { getClient } from '@epilot/entity-client';
type Route = '/' | '/dashboard' | '/settings';
async function main() {
// 1. Initialize app bridge
const { token, lang } = await initialize();
// 2. Set up API client
const entityClient = getClient();
authorizeClient(entityClient, token);
// 3. Get page context
const { slug, subPath } = await getPageContext();
// 4. Render initial page
renderPage(subPath as Route);
// 5. Handle browser back/forward
onLocationChange((newSubPath) => {
renderPage(newSubPath as Route);
});
// 6. Set up internal navigation
setupNavigation();
}
function setupNavigation() {
document.addEventListener('click', (e) => {
const link = (e.target as HTMLElement).closest('[data-route]');
if (link) {
e.preventDefault();
const route = link.getAttribute('data-route') as Route;
navigate(route);
renderPage(route);
}
});
}
function renderPage(route: Route) {
const app = document.getElementById('app')!;
const nav = `
<nav>
<a data-route="/" ${route === '/' ? 'class="active"' : ''}>Home</a>
<a data-route="/dashboard" ${route === '/dashboard' ? 'class="active"' : ''}>Dashboard</a>
<a data-route="/settings" ${route === '/settings' ? 'class="active"' : ''}>Settings</a>
</nav>
`;
switch (route) {
case '/dashboard':
app.innerHTML = nav + '<h1>Dashboard</h1><p>Your data here</p>';
break;
case '/settings':
app.innerHTML = nav + '<h1>Settings</h1><p>Configuration options</p>';
break;
default:
app.innerHTML = nav + '<h1>Welcome</h1><p>Choose a section above</p>';
}
updateContentHeight(document.body.scrollHeight);
}
main().catch(console.error);
Sample App
For a complete, production-ready example of a Custom Page app, see the Energy Spot Price Explorer sample:
API Referenceโ
Session Managementโ
| Function | Returns | Description |
|---|---|---|
initialize(options?) | Promise<AppBridgeSession> | Initialize the app bridge and establish the postMessage channel with the parent epilot app. Returns session data with an OAuth token and the user's language. Safe to call multiple times - subsequent calls return the cached session. |
getSession() | AppBridgeSession | Get the cached session synchronously. Throws AppBridgeNotInitializedError if called before initialize(). |
isInitialized() | boolean | Check whether the app bridge has been initialized. |
InitOptions:
| Option | Type | Default | Description |
|---|---|---|---|
contentHeight | number | document.body.scrollHeight | Initial iframe height in pixels to report to the parent |
timeout | number | 5000 | Timeout in milliseconds for the initialization handshake |
Entity Surface APIโ
| Function | Returns | Description |
|---|---|---|
getEntityContext(options?) | Promise<EntityContext> | Get the entity being viewed. Available on Entity Capability and Entity Tab surfaces. |
updateContentHeight(height) | void | Report your content height to the parent so it can resize the iframe. Call after rendering or when content size changes. |
onVisibilityChange(handler) | Unsubscribe | Subscribe to visibility changes on Entity Tab surfaces. The handler receives true when the tab becomes visible, false when hidden. Returns an unsubscribe function. |
Action Config APIโ
| Function | Returns | Description |
|---|---|---|
getActionConfig<T>(options?) | Promise<ActionConfig<T>> | Get the current action configuration. Available on Flow Action Config surfaces. |
updateActionConfig<T>(config, options?) | void | Update the action configuration. The parent automation UI receives the new config immediately. |
UpdateConfigOptions:
| Option | Type | Default | Description |
|---|---|---|---|
waitForCallback | boolean | false | If true, the automation engine waits for an async callback before proceeding to the next action |
Page Surface APIโ
| Function | Returns | Description |
|---|---|---|
getPageContext(options?) | Promise<PageContext> | Get the page context including slug, sub-path, and full path. Available on Custom Page surfaces. |
navigate(subPath) | void | Navigate to a sub-path within the current page. Triggers history.pushState in the parent frame, updating the browser URL. Does not reload the iframe. |
onLocationChange(handler) | Unsubscribe | Subscribe to browser back/forward navigation. The handler receives the new sub-path. Returns an unsubscribe function. |
Generic Event APIโ
| Function | Returns | Description |
|---|---|---|
on<T>(event, handler) | Unsubscribe | Subscribe to events from the parent app. Supports wildcard patterns (e.g., 'custom-*' or '*'). Returns an unsubscribe function. |
send(event, data?) | void | Send a custom event to the parent app. Use for custom communication not covered by the high-level APIs. |
Client Authorizationโ
| Function | Returns | Description |
|---|---|---|
authorizeClient(client, sessionOrToken) | void | Authorize an @epilot/*-client SDK client with the session token. Accepts either an AppBridgeSession object or a token string. Sets the Authorization: Bearer <token> header on the client. |
Low-Level Messaging APIโ
For advanced use cases, the epilot object provides direct access to the postMessage transport:
import { epilot } from '@epilot/app-bridge';
// Send a raw message to the parent
epilot.sendMessageToParent('my-event', { key: 'value' });
// Subscribe to raw messages from the parent (supports wildcards)
const unsubscribe = epilot.subscribeToParentMessages('my-event', (data) => {
console.log('Received:', data);
});
| Function | Returns | Description |
|---|---|---|
epilot.sendMessageToParent(event, detail?) | void | Send a raw postMessage to the parent window |
epilot.subscribeToParentMessages(event, handler) | Unsubscribe | Subscribe to raw postMessage events from the parent. Supports wildcard matching with *. |
Typesโ
import type {
// Session
AppBridgeSession, // { token: string; lang: string }
InitOptions, // { contentHeight?: number; timeout?: number }
RequestOptions, // { timeout?: number }
// Entity Surface
EntityContext, // { entityId: string; schema: string; capability?: EntityCapability; isVisible?: boolean }
EntityCapability, // { name?: string; app_id?: string; [key: string]: unknown }
// Page Surface
PageContext, // { slug: string; subPath: string; path: string }
// Action Config Surface
ActionConfig<T>, // { custom_action_config?: T; description?: string; app_id?: string }
UpdateConfigOptions, // { waitForCallback?: boolean }
// Event Handlers
MessageHandler<T>, // (data: T) => void
VisibilityHandler, // (isVisible: boolean) => void
Unsubscribe, // () => void
} from '@epilot/app-bridge';
Error Typesโ
import {
AppBridgeError, // Base error class for all app-bridge errors
AppBridgeTimeoutError, // Thrown when a request times out (has .event and .timeout properties)
AppBridgeNotInitializedError // Thrown when calling functions before initialize()
} from '@epilot/app-bridge';
| Error | Properties | Description |
|---|---|---|
AppBridgeError | message | Base error class. All app-bridge errors extend this. |
AppBridgeTimeoutError | event: string, timeout: number | A request/response operation timed out. The event property identifies which operation failed and timeout is the duration in ms. |
AppBridgeNotInitializedError | - | getSession() or other functions were called before initialize(). |
Best Practicesโ
caution
Always wrap initialize() in a try-catch block. If the app bridge fails to connect (e.g., the app is loaded outside of epilot), your app should display a user-friendly error message rather than silently failing.
- Always initialize first - Call
initialize()before any other app-bridge functions - Handle errors gracefully - Wrap initialization in try-catch and show user-friendly errors
- Update content height - Always report your content height, especially after rendering
- Use ResizeObserver - For dynamic content, observe size changes automatically
- Handle visibility - For tabs, pause expensive operations when not visible
- Type your configs - Use TypeScript generics for type-safe configuration
- Clean up subscriptions - Call unsubscribe functions when your app unmounts