App SDK reference

Download all docs

Use the UI extension, installed-app bridge, hosted backend helper, and general API SDK correctly.

ImportWhere it runsPurpose
@octonodes/ui-extensions/reactInstalled browser extensionDeclare workspace.block or app.page, render host controls
@octonodes/ui-extensionsInstalled browser extensionRead current app session and shared configuration
@octonodes/ui-extensions/appBrowser extension or hosted pageUse the extension bridge or initialize hosted routing and sessions
@octonodes/ui-extensions/app/reactHosted React clientVerify workspace access before rendering; use the hosted bridge
@octonodes/ui-extensions/app/serverYour hosted backendVerify an installation bearer, then use consented project actions
@octonodes/sdkSeparately authorized backend or toolCall the general cloud API with a personal/service token

Install @octonodes/ui-extensions for app code. octonodes app create includes it and the CLI. The engine owns the UI SDK contract; the published package is synced from the engine. @octonodes/sdk/plugins defines workflow plugins and is not the app extension entry point.

UI contribution

Put this in src/extensions/notice.tsx and declare the same target in octonode.app.json:

import { getAppSession } from "@octonodes/ui-extensions";
import { defineExtension, Section } from "@octonodes/ui-extensions/react";

export default defineExtension("workspace.block", function Notice() {
  const message = getAppSession().configuration.message ?? "Welcome";
  return <Section title="Workspace notice">{message}</Section>;
});

The CLI calls startExtension when it bundles a declared entry. When bundling manually, call startExtension(defineExtension("app.page", Component)) in the browser entry. Available app controls include Section, NodeForm, Button, and TextField. InputField belongs to node inspector extensions. Extensions render inside an isolated iframe. No parent DOM, Octonode login token, or direct network access is provided to an extension-only bundle.

Consented project actions inside an installed extension

When the published app requested an action and the installer granted projects, use the host bridge:

import { connectApp } from "@octonodes/ui-extensions/app";

const app = await connectApp();
const project = app.project ?? app.forProject(selectedProjectId);
const rows = await project.tables.rows.list("products", { limit: 20 });
const run = await project.wf.run("sync-products", { source: "app" }, { idempotencyKey: "sync-123" });

app.projects lists granted project IDs. app.project exists when the host supplies a current granted project or exactly one project is granted. With multiple grants, present a project picker and call forProject(id). project.get, tables.list, row list/insert/update/delete, and wf.run are the supported operations. Row updates require expectedVersion; use an idempotencyKey when retrying a workflow run. The host checks the installation, actor, action, and project grant on every call. A local development preview has no grants until an administrator approves temporary development access in Studio; production releases need separate installation consent.

Hosted backend session

A self-hosted app uses connectHostedApp() or OctonodeAppProvider for the trusted Studio handoff. The SDK clears launch coordinates from the fragment, receives the current bearer from the expected Studio origin and channel, and keeps it in memory. Use bridge.fetch to send it only to your own backend. The generated full template includes this handoff and /api/context route. On every backend request using Octonode data, verify the bearer:

import { connectAppServer } from "@octonodes/ui-extensions/app/server";

const token = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
const app = await connectAppServer(token, {
  appId: process.env.OCTONODE_APP_ID!,
  baseUrl: process.env.OCTONODE_API_URL!,
});
const project = app.project ?? app.forProject(selectedProjectId);
const rows = await project.tables.rows.list("products");

Set the registered app ID and Octonode API URL in the server environment, never from browser input. The helper verifies the bearer with Octonode and rejects a bearer for another app. Use its returned user/workspace/grants; do not trust a workspace or project ID from a request merely because the browser supplied it. Sessions are short lived and can be revoked; ask the user to reopen from Apps when verification fails. Do not store the bearer or put it in logs. This bearer is for app runtime endpoints, not general API access or background jobs.

Integrate the client and server

For the generated Vite app, keep the client in src/web/App.tsx and API routes in src/server.ts. OctonodeAppProvider first calls your server's /api/context; a client handshake alone does not authorize workspace content. Use useOctonodeApp().bridge.fetch for protected app requests. The server calls connectAppServer on each request and derives workspace/project access from its verified result. The generated /api/projects route demonstrates this flow.

Use the complete hosting instructions for the build command, public output directory, server variables, and adapters. The Cloudflare Worker recipe and Vercel recipe keep client and API routes on one origin. A static frontend deployment alone does not implement /api/context or the app's backend.

General cloud API SDK

@octonodes/sdk uses a separately supplied personal or service token. It is useful for an independently authorized integration, but the installed app bearer must not be passed to createClient. See general TypeScript SDK and app hosting.