# Manifest

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

```json title="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" }]
  }
}
```

:::note
Classic Crowdin app manifest fields such as `baseUrl`, `identifier`, and per-module `url` are not allowed - serverless apps have no backend. The schema rejects them.
:::

## Top-level fields

| Field | Required | Description |
|-------|----------|-------------|
| `name` | yes | Human-readable app name (2-255 characters) shown in Crowdin |
| `bundle` | yes | Where Crowdin loads the bundle from - see [bundle](#bundle) |
| `scopes` | yes | What the [in-app API client](/serverless-apps/building-app/crowdin-api/) may do on the user's behalf - see [scopes](#scopes) |
| `modules` | yes | The UI modules the app adds to Crowdin, keyed by module type - see [modules](#modules) |
| `description` | no | App description |
| `logo` | no | Logo path served from the bundle root; a root-relative path beginning with `/`, e.g. `/logo.svg` for `public/logo.svg` |
| `stringBasedAvailable` | no | Whether the app is offered in string-based projects in addition to file-based ones (default `false`) |
| `default_permissions` | no | Default audience after installation - see [default_permissions](#default_permissions) |

### bundle

`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](/serverless-apps/development/dev-and-publish/).

### scopes

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](https://support.crowdin.com/developer/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.

### default_permissions

Who the app is available to right after installation (the admin can change it later - see [app installation](https://support.crowdin.com/developer/crowdin-apps-installation/)):

| Key | Values | Default |
|-----|--------|---------|
| `user` | `owner`, `managers`, `all`, `guests` | `owner` |
| `project` | `own`, `restricted` | `own` |

## Modules

Every entry under `modules` is a list, keyed by [module type](/serverless-apps/building-app/modules/). The schema enumerates the module types the installed SDK version supports - new types arrive with SDK releases. All modules share three core fields:

| Field | Required | Description |
|-------|----------|-------------|
| `key` | yes | Unique within the app (3-255 characters; letters, digits, `_`, `-`); disambiguates multiple modules of the same type |
| `name` | yes | Human-readable module name (3-255 characters) |
| `logo` | no | Logo path served from the bundle root, e.g. `/logo.svg` |

Some module types require extra fields:

| Module type | Extra required fields |
|-------------|----------------------|
| `editor-right-panel`, `editor-translations-panel`, `editor-background-worker` | `modes` |
| `editor-asset-panel` | `fileNamePattern` |
| `context-menu` | `options` |
| all other types | none |

### modes

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`

### fileNamePattern

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

### options (context-menu)

Context-menu entries declare where they appear and what they do (see the [context menu module docs](https://support.crowdin.com/developer/crowdin-apps-module-context-menu/) for how the entries look in the Crowdin UI):

| Field | Required | Description |
|-------|----------|-------------|
| `location` | yes | Which Crowdin context menu the entry is added to: `tm`, `glossary`, `language`, `screenshot`, `style_guide`, `source_file`, `translated_file` |
| `type` | yes | What activating the entry does: `modal` opens one of the app's modal modules, `redirect` jumps to one of the app's menu modules |
| `module` | yes | The 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 |

### Example with several module types

```json title="manifest.json"
{
  "$schema": "./node_modules/@crowdin/serverless-apps-sdk/manifest.schema.json",
  "name": "My App",
  "description": "Editor panel and 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"] }
    ],
    "modal": [{ "key": "details", "name": "Details" }],
    "context-menu": [
      {
        "key": "tm-action",
        "name": "Open in My App",
        "options": { "location": "tm", "type": "modal", "module": { "modal": "details" } }
      }
    ]
  }
}
```

## Checking the manifest

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

```bash
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.

## Syncing with Crowdin

Crowdin stores its own copy of the manifest for the registered app. Use the CLI's [`manifest status` / `pull` / `push`](/serverless-apps/reference/cli-commands/#manifest) commands to diff and sync it with your local file.