Skip to main content

Tolgee Apps API reference

This reference documents the Tolgee Apps contract: the manifest schema, the @tolgee/apps-sdk browser and server APIs, the iframe postMessage protocol, the context token, the webhook signature scheme, the decorators request and response, permission scopes, the app REST endpoints, and the tolgee-app CLI. Use it alongside the setup guide and the tutorials when building a Tolgee App.

Experimental

Tolgee Apps is in alpha — the contracts on this page may change.

Manifest

Your app hosts a manifest JSON describing what it contributes. Tolgee fetches it at install time and on refresh.

{
"id": "my-app",
"name": "My App",
"version": "0.1.0",
"baseUrl": "https://my-app.example.com",
"decoratorsUrl": "https://my-app.example.com/decorators",
"scopes": ["translations.view", "keys.view"],
"webhooks": {
"events": ["SET_TRANSLATIONS", "CREATE_KEY"],
"url": "/webhook"
},
"modules": {
"project-dashboard-page": [
{ "key": "home", "title": "My App", "icon": "LayoutAlt04", "entry": "/dashboard" }
]
}
}

Top-level fields

FieldTypeNotes
idstringStable, unique identifier. Cannot change after install.
namestringDisplay name.
versionstringsemver recommended.
baseUrlstringWhere module entry paths and a relative webhooks.url resolve. HTTPS in production.
decoratorsUrlstring?Absolute URL of your decorators endpoint (see Decorators).
scopesstring[]?Permission scopes requested at install (see Scopes).
webhooksobject?{ events: string[], url: string } — event names + delivery URL (relative to baseUrl or absolute).
modulesobject?The UI contributions, keyed by module type.

Modules

Iframe modules render a page from your baseUrl:

type AppIframeModule = {
key: string; // unique within the plugin
title: string;
icon?: string; // emoji or a platform icon name (e.g. "LayoutAlt04")
entry: string; // path under baseUrl
width?: number; // modal only
height?: number; // modal only
};

Used by: project-dashboard-page, translation-tools-panel, translation-tools-panel-empty, key-edit-tab, modal.

translation-tools-panel-empty renders in the translations tools area when no cell is selected. Instead of a key/language selection it receives the view's selected language tags via selection.selectedLanguages, which updates as the user changes the language selector.

Action modules are triggers; they don't render their own page but point at an iframe module or a link:

type AppAction = {
key: string;
type: 'link' | 'tab' | 'panel' | 'modal';
icon?: string;
tooltip?: string;
title?: string;
urlTemplate?: string; // link: target URL, may contain placeholders (below)
tabKey?: string; // tab: a key-edit-tab key
panelKey?: string; // panel: a translation-tools-panel key
modalKey?: string; // modal: a modal key
dynamic?: boolean; // visibility/badge driven by decoratorsUrl
};

type AppShortcut = Omit<AppAction, 'type'> & {
combination: string; // e.g. "Mod+Shift+E" (Mod = ⌘ / Ctrl)
type: 'link' | 'modal';
};

Allowed action type per module (enforced at install time):

ModuleAllowed typesRequired ref
key-actionlink, tab, modaltabKey / modalKey (or urlTemplate for link)
translation-actionlink, panel, modalpanelKey / modalKey (or urlTemplate)
bulk-actionlink, modalmodalKey (or urlTemplate)
translations-toolbar-actionlink, modalmodalKey (or urlTemplate)
project-menu-actionlink, modalmodalKey (or urlTemplate)
shortcutlink, modalcombination + modalKey (or urlTemplate)

A link action requires urlTemplate unless it is dynamic (then the URL comes from the decorators response). urlTemplate may contain {keyId}, {keyName}, {keyNamespace}, {projectId}, {languageTag}, {languageId}, {translationId}.

SDK — browser (@tolgee/apps-sdk/browser)

For iframe modules.

createTolgeeApp(): TolgeeApp

Sets up the postMessage handshake with Tolgee.

import { createTolgeeApp } from '@tolgee/apps-sdk/browser';

const app = createTolgeeApp();
const ctx = await app.context; // resolves once Tolgee sends init
const off = app.onSelectionChanged((s) => { /* s: TolgeeAppSelection */ });
app.resize(420); // ask the host to resize the iframe
app.close(); // close the containing modal/panel
app.dispose(); // detach the message listener
MemberSignatureDescription
contextPromise<TolgeeAppContext>Resolves when the parent posts tolgee-app:init.
onSelectionChanged(handler: (s: TolgeeAppSelection) => void) => () => voidSubscribe to focus changes; returns an unsubscribe fn; fires once with the initial selection.
onThemeChanged(handler: (t: TolgeeAppTheme) => void) => () => voidSubscribe to light/dark theme changes; returns an unsubscribe fn; fires once with the initial theme.
resize(height: number) => voidRequest an iframe height.
close() => voidClose the containing modal/panel.
dispose() => voidRemove the listener.
type TolgeeAppContext = {
token: string; // context JWT — use as a Bearer token to call Tolgee
apiUrl: string;
organizationId: number;
projectId: number;
selection: TolgeeAppSelection;
theme: TolgeeAppTheme; // host light/dark + palette; see onThemeChanged
extra: Record<string, unknown>; // trigger-specific extras (e.g. selectedKeyIds for bulk actions)
};

type TolgeeAppSelection = {
keyId?: number;
languageId?: number;
languageTag?: string;
translationId?: number;
selectedLanguages?: string[]; // tags shown in the view (translation-tools-panel-empty only)
};

type TolgeeAppTheme = {
mode: 'light' | 'dark';
colors: {
background: string; backgroundPaper: string;
text: string; textSecondary: string;
primary: string; primaryContrast: string;
divider: string; error: string;
};
};

applyTolgeeTheme(theme, root?)

Applies the host theme to the iframe document so the plugin matches Tolgee: exposes each color as a --tg-color-* CSS variable (--tg-color-background, --tg-color-text-secondary, …), sets [data-tg-theme="light|dark"], and sets color-scheme. Call it on init and from onThemeChanged to follow live toggles:

import { createTolgeeApp, applyTolgeeTheme } from '@tolgee/apps-sdk/browser';

const app = createTolgeeApp();
app.onThemeChanged(applyTolgeeTheme); // fires once with the initial theme, then on every toggle

Then key your CSS off the variables, e.g. background: var(--tg-color-background); color: var(--tg-color-text);. Don't use @media (prefers-color-scheme) — Tolgee's theme can differ from the OS.

createTolgeeAppClient(context): TolgeeRestClient

A typed Tolgee REST client wired with the context token, base URL, and project id. Errors are returned in the error field (no throwing).

import { createTolgeeAppClient } from '@tolgee/apps-sdk/browser';

const tolgee = createTolgeeAppClient(ctx);
const { data, error } = await tolgee.GET('/v2/projects/{projectId}/translations', {
params: { path: { projectId: ctx.projectId }, query: { size: 20 } },
});

SDK — server (@tolgee/apps-sdk/server)

Framework-agnostic helpers for your backend (no Express dependency).

ExportSignatureDescription
receiveWebhook(input: { rawBody: string; signatureHeader: string | null | undefined; secret: string | null | undefined }) => Promise<{ ok: true; payload } | { ok: false; status: 401 | 400; error: string }>Verifies the signature over the raw body and parses the typed payload.
onWebhook(payload, types: T | readonly T[], handler: (typed) => void) => booleanType-narrowing dispatcher by activity type; returns true if the handler ran.
verifyWebhookSignature({ header, rawBody, secret }) => Promise<boolean>HMAC-SHA256 verification (WebCrypto, timing-safe).
decodeContextToken(jwt: string) => AppContextClaimsDecodes (does not verify) the context token into { installId, projectId, userId, audience, expiresAt }.
tolgeeAppCorsHeaders() => Record<string, string>CORS headers your decorator/API endpoints must return (wildcard origin + Authorization).
renderManifest(template: string, baseUrl: string) => stringReplaces __BASE_URL__ in a manifest template with the live URL.
loadTolgeeAppConfig(env?) => { tolgeeUrl, webhookSecret, vitePort, serverPort }Reads the standard env vars (TOLGEE_URL, TOLGEE_WEBHOOK_SECRET, VITE_PORT, SERVER_PORT).
import { receiveWebhook, onWebhook } from '@tolgee/apps-sdk/server';

const r = await receiveWebhook({ rawBody, signatureHeader, secret });
if (!r.ok) return res.status(r.status).json({ error: r.error });
onWebhook(r.payload, 'SET_TRANSLATIONS', (typed) => {
// typed is narrowed to the SET_TRANSLATIONS payload
});

postMessage protocol

If you don't use the SDK, the iframe ↔ host messages are:

DirectionMessagePayload
iframe → hosttolgee-app:ready— (signals the iframe is listening)
host → iframetolgee-app:init{ token, apiUrl, organizationId, projectId, selection…, theme, extra… }
host → iframetolgee-app:selection-changed{ keyId?, languageId?, languageTag?, translationId?, selectedLanguages? }
host → iframetolgee-app:theme-changed{ theme: { mode, colors } }
iframe → hosttolgee-app:resize{ height }
iframe → hosttolgee-app:close

Context token

The init message carries a short-lived JWT:

  • Audience tg.app; claims sub (userId), tg.app.inst (installId), tg.app.proj (projectId), plus iat/exp.
  • Pass it as Authorization: Bearer <token> on REST calls; Tolgee verifies the signature server-side.
  • It is not a substitute for verifying user identity yourself — your backend should treat it as an opaque bearer credential and let Tolgee enforce permissions.

Webhooks

Tolgee POSTs subscribed events to webhooks.url. The request carries a signature header:

Tolgee-Signature: {"timestamp": 1717000000000, "signature": "<hex>"}

The signature is HMAC-SHA256(secret, "<timestamp>.<raw-request-body>"), where secret is the install's webhookSecret. Verify over the raw bytes — read the body as text before any JSON parsing. The SDK's receiveWebhook / verifyWebhookSignature do this for you.

Batch events (e.g. BATCH_KEY_DELETE) carry a revisionId; fetch the full set of changes from the activity API. See the platform webhooks docs for the event list and payload shapes.

Decorators

For dynamic action modules (dynamic: true), Tolgee asks your app which keys/cells to decorate. It POSTs to manifest.decoratorsUrl with a user context token as Authorization: Bearer:

Request

{
installId: number,
projectId: number,
keyIds?: number[], // key rows currently in view
languageTags?: string[] // language columns in view
}

Response

{
items?: Array<{
keyId: number,
languageTag?: string | null, // omit for a key-row decoration; set for a translation cell
actionKey: string, // must match a declared key-action / translation-action key
url?: string, // for link actions: overrides the manifest urlTemplate
count?: number, // badge count
visibility?: 'always' | 'on-hover'
}>
}

The decoration's icon and tooltip come from the matching manifest action; the response only references it by actionKey and may override url, count, and visibility. Your endpoint must return the CORS headers from tolgeeAppCorsHeaders() (see the server SDK above) and allow the Authorization header.

Scopes & permissions

Apps request scopes in the manifest; the wizard offers this common set:

translations.view, translations.edit, translations.state-edit, translations.suggest,
keys.view, keys.create, keys.edit, keys.delete,
screenshots.view, screenshots.upload,
activity.view

At runtime, an app's effective permissions on a project are the intersection of the scopes granted to the install and the calling user's own project permissions (scopes are expanded by hierarchy first — e.g. keys.edit implies keys.view). An app can never exceed what the user can do.

Because there is no incremental consent yet, request every scope you need at install time. The plugin-authenticated self-service endpoint can only narrow scopes, never widen them.

REST endpoints

Apps reuse the standard Tolgee REST API. The app-management endpoints:

EndpointPurpose
POST /v2/organizations/{orgId}/apps/previewValidate a manifest without installing
POST /v2/organizations/{orgId}/appsInstall (owner only) — returns clientId/secret + webhookSecret
GET /v2/organizations/{orgId}/appsList installs
POST /v2/organizations/{orgId}/apps/{installId}/refreshRe-fetch the manifest
PATCH /v2/organizations/{orgId}/apps/{installId}/manifest-urlRepoint at a new manifest URL
DELETE /v2/organizations/{orgId}/apps/{installId}Uninstall
PATCH /v2/apps/self/manifest-urlApp repoints its own manifest URL (auth: X-API-Key: tgapps_<clientSecret>)
GET /v2/projects/{projectId}/appsList installs + per-project enablement
PUT /v2/projects/{projectId}/apps/{installId}Enable for a project
DELETE /v2/projects/{projectId}/apps/{installId}Disable for a project
POST /v2/projects/{projectId}/apps/{installId}/tokenMint a user context token

CLI

The tolgee-app binary (from @tolgee/apps-dev) powers local development:

CommandWhat it does
tolgee-app devBoots a Cloudflare quick tunnel to your local app and repoints the install's manifest URL on every restart (skips the tunnel when Tolgee is local).
tolgee-app registerOne-time install via the browser consent flow. --pat=tgpat_… for a headless install.

Both read .env.local (TOLGEE_URL, ports) and the install record at .tolgee-dev/install.json.

Frequently asked questions

How are Tolgee App webhooks authenticated?

Each webhook carries a Tolgee-Signature header containing a timestamp and an HMAC-SHA256 signature over "timestamp.rawBody", keyed with the install's webhookSecret. Verify it over the raw request bytes; the SDK helpers verifyWebhookSignature and receiveWebhook do this for you.

What does the Tolgee App context token grant?

The context token is a short-lived JWT (audience tg.app) identifying the install, project, and user. Pass it as a Bearer token to call the Tolgee REST API. Effective permissions are the intersection of the app's granted scopes and the user's project permissions.

How do dynamic decorators work in a Tolgee App?

For action modules marked dynamic: true, Tolgee POSTs the keys and languages currently in view to your manifest decoratorsUrl. You return items referencing a manifest actionKey, optionally overriding url, count, and visibility. The icon and tooltip come from the manifest action.

Which Tolgee App manifest modules render an iframe?

project-dashboard-page, translation-tools-panel, translation-tools-panel-empty, key-edit-tab, and modal render an iframe from your baseUrl. The action modules (key-action, translation-action, bulk-action, translations-toolbar-action, project-menu-action, shortcut) are triggers configured entirely in the manifest.

How do I verify a Tolgee webhook signature?

Read the request body as raw text, then call verifyWebhookSignature({ header, rawBody, secret }) from @tolgee/apps-sdk/server, or use receiveWebhook to verify and parse in one step. Never parse JSON before verifying, because the signature is computed over the raw bytes.