App SDK reference
Use the UI extension, installed-app bridge, hosted backend helper, and general API SDK correctly.
| Import | Where it runs | Purpose |
|---|---|---|
@octonodes/ui-extensions/react | Installed browser extension | Declare workspace.block or app.page, render host controls |
@octonodes/ui-extensions | Installed browser extension | Read current app session and shared configuration |
@octonodes/ui-extensions/app | Browser extension or hosted page | Use the extension bridge or initialize hosted routing and sessions |
@octonodes/ui-extensions/app/react | Hosted React client | Verify workspace access before rendering; use the hosted bridge |
@octonodes/ui-extensions/app/server | Your hosted backend | Verify an installation bearer, then use consented project actions |
@octonodes/sdk | Separately authorized backend or tool | Call 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.