Skip to content

Manifest

manifest.json describes the app. Point $schema at the bundled JSON Schema to get validation and autocomplete in your editor:

manifest.json
{
"$schema": "./node_modules/@crowdin/serverless-apps-sdk/manifest.schema.json",
"name": "My App",
"bundle": { "mode": "internal" },
"scopes": ["project:read"],
"modules": {
"project-menu": [{ "key": "menu", "name": "My App" }]
}
}
FieldRequiredDescription
nameyesHuman-readable app name (2-255 characters) shown in Crowdin
bundleyesWhere Crowdin loads the bundle from - see bundle
scopesyesWhat the in-app API client may do on the user’s behalf - see scopes
modulesyesThe UI modules the app adds to Crowdin, keyed by module type - see modules
descriptionnoApp description
logonoLogo path served from the bundle root; a root-relative path beginning with /, e.g. /logo.svg for public/logo.svg
stringBasedAvailablenoWhether the app is offered in string-based projects in addition to file-based ones (default false)
default_permissionsnoDefault audience after installation - see default_permissions

bundle.mode is internal (Crowdin serves the bundle you uploaded with publish) or external (Crowdin loads it from bundle.url - your own hosting or the local dev server). With external, url is required and points at a folder: the host appends app.js to it.

This field is managed for you by the CLI’s dev and publish commands - see how dev and publish work.

OAuth-style scopes the app may exercise against the Crowdin REST API on behalf of the current user; calls outside these scopes are rejected. An empty array means no API access.

See understanding scopes for the list - e.g. project, project.translation, tm, glossary. Narrow a scope with the :read or :write postfix (e.g. project:read); note that :write does not imply read access.

Crowdin Storage requires the application.storage scope.

Who the app is available to right after installation (the admin can change it later - see app installation):

KeyValuesDefault
userowner, managers, all, guestsowner
projectown, restrictedown

Every entry under modules is a list, keyed by module type. The schema enumerates the module types the installed SDK version supports - new types arrive with SDK releases. All modules share three core fields:

FieldRequiredDescription
keyyesUnique within the app (3-255 characters; letters, digits, _, -); disambiguates multiple modules of the same type
nameyesHuman-readable module name (3-255 characters)
logonoLogo path served from the bundle root, e.g. /logo.svg

Some module types require extra fields:

Module typeExtra required fields
editor-right-panel, editor-translations-panel, editor-background-workermodes
editor-asset-panelfileNamePattern
context-menuoptions
editor-buttonmodes, options
all other typesnone

Editor modules declare the editor modes and views they appear in - a unique, non-empty subset of:

translate, review, assets, comfortable, side-by-side, multilingual

Optional on editor-right-panel modules, false by default. A panel without it is treated as built around the one active string: the editor covers it while several strings are selected and disables it in multilingual mode opened without target languages. Set "supportsMultipleStrings": true when the panel works with a selection - read it with editor.getSelectedStrings() or the string.selected event.

Asset-panel modules declare which asset files they handle with a wildcard pattern, e.g. "*.psd".

Context-menu entries declare where they appear and what they do (see the context menu module docs for how the entries look in the Crowdin UI):

FieldRequiredDescription
locationyesWhich Crowdin context menu the entry is added to: tm, glossary, language, screenshot, style_guide, source_file, translated_file
typeyesWhat activating the entry does: modal opens one of the app’s modal modules, redirect jumps to one of the app’s menu modules
moduleyesThe target module as a single {"<module type>": "<module key>"} pair, e.g. {"modal": "details"}. modal entries always use the modal type; redirect entries use a menu module type allowed for the location: organization-menu (Crowdin Enterprise) or profile-resources-menu (crowdin.com) for tm, glossary and style_guide; project-tools, project-reports, project-menu or project-integrations for the rest

Editor buttons are rendered by Crowdin itself, so the bundle registers no prepare* function for them - only for the module a button targets. Each button declares where it appears and what it does (see the editor button module docs for how the buttons look in the editor):

FieldRequiredDescription
locationyesWhere the button appears: header-right or header-left (the editor header), menu (the editor’s Apps menu), translation-toolbar, or strings-menu (the strings context menu)
typeyesWhat activating the button does: panel opens an editor-right-panel module, modal opens a modal module, event sends an editor.button.click event to an editor-right-panel or editor-background-worker module. event is available at translation-toolbar and strings-menu only
moduleyesKey of the target module, e.g. "panel" - a plain key, not the {"<module type>": "<module key>"} pair context-menu uses
weightnoNon-negative integer that orders the buttons at one location; lower weights come first (default 100)

A button’s own key must differ from the keys of the app’s editor-right-panel, editor-background-worker and modal modules. The editor hides a button wherever its target is unavailable - for example, in an editor mode the target’s modes leave out. manifest validate checks the fields above, that the button’s key is free, and that module names a module of the type type targets - the same rules Crowdin applies on push. A panel that receives strings-menu clicks acts on several strings, so give it supportsMultipleStrings - without it the editor covers the panel while those strings are selected, even though the click still arrives.

manifest.json
{
"$schema": "./node_modules/@crowdin/serverless-apps-sdk/manifest.schema.json",
"name": "My App",
"description": "Editor panel with a header button, and a TM context menu",
"logo": "/logo.svg",
"bundle": { "mode": "internal" },
"scopes": ["project:read", "tm:read"],
"modules": {
"editor-right-panel": [
{ "key": "panel", "name": "My Panel", "modes": ["translate", "review"] }
],
"editor-button": [
{
"key": "open-panel",
"name": "Open My Panel",
"modes": ["translate", "review"],
"options": { "location": "header-right", "type": "panel", "module": "panel" }
}
],
"modal": [{ "key": "details", "name": "Details" }],
"context-menu": [
{
"key": "tm-action",
"name": "Open in My App",
"options": { "location": "tm", "type": "modal", "module": { "modal": "details" } }
}
]
}
}

manifest validate checks the local file against the same schema, without contacting Crowdin:

Terminal window
crowdin-serverless-apps manifest validate

It needs no login and no app link, exits with code 1 when the manifest is invalid, and reports each problem with the path to fix. Crowdin still validates server-side when the manifest is pushed; this is the fast local check. Keep $schema in the file so your editor flags mistakes as you type.

Crowdin stores its own copy of the manifest for the registered app. Use the CLI’s manifest status / pull / push commands to diff and sync it with your local file.