Chataway Developers
Reference

Manifest reference

Every field of plugin.json for store apps (pluginApiVersion 3), the grants, and every validation code the CLI, the store and the desktop installer can report.

plugin.json sits at the root of your app. It's the part of your app the store reviews and the user consents to: what the app is, where its tools live, which hosts it may reach, and what it may do.

For editor completion and inline errors, point it at the JSON Schema:

json
{ "$schema": "https://chataway.co/schemas/app-manifest-v3.json" }

The schema checks shape. chataway validate --store runs the full store checks — the same code the store and the desktop installer run.

#A complete example

plugin.json
json
{
  "$schema": "https://chataway.co/schemas/app-manifest-v3.json",
  "pluginApiVersion": 3,
  "id": "com.example.acme-assets",
  "name": "Acme Assets",
  "version": "1.2.0",
  "description": "Upload images and audio from your chats to your Acme library.",
  "publisher": "acme",
  "categories": ["media", "games"],
  "homepage": "https://example.com/chataway",
  "support": "mailto:help@example.com",
  "branding": {
    "icon": "assets/icon.png",
    "mark": "assets/mark.svg",
    "accent": "#2F7BF6",
    "screenshots": ["assets/s1.png", "assets/s2.png"]
  },
  "remote": {
    "mcpUrl": "https://apps.example.com/chataway/mcp",
    "domains": ["apps.example.com", "cdn.example.com"]
  },
  "accounts": [{
    "id": "acme", "label": "Acme",
    "authorizeUrl": "https://auth.example.com/oauth/authorize",
    "tokenUrl": "https://auth.example.com/oauth/token",
    "clientId": "chataway-3f9c1a",
    "scopes": ["openid", "asset:write"],
    "required": true
  }],
  "grants": ["files:receive", "host:media"],
  "permissions": [
    "Receive images and audio you choose to upload to Acme",
    "Resize images to Acme's decal size before uploading"
  ],
  "contributes": {
    "tools": [{
      "id": "upload_decals",
      "name": "Upload decals",
      "description": "Upload images to the user's Acme inventory as decals. Returns the new asset ids.",
      "files": {
        "images": {
          "accept": ["image/png", "image/jpeg"],
          "multiple": true,
          "maxBytes": 20000000,
          "prepare": { "op": "resize", "width": 512, "height": 512, "fit": "contain", "format": "png" }
        }
      },
      "confirm": { "title": "Upload {{count}} image(s) to Acme?", "action": "Upload" },
      "account": "acme"
    }],
    "panels": [{
      "id": "main", "title": "Acme", "icon": "branding", "component": "webview",
      "config": { "entry": "ui/index.html" },
      "views": [{ "id": "assets", "label": "Assets" }]
    }],
    "settings": []
  }
}

#Top-level fields

FieldTypeRequired
pluginApiVersion3YesStore apps target 3. (Local dev folders may use 1 or 2.)
idstringYesReverse-DNS, lowercase: com.yourcompany.app-name. Letters, digits, . and -; at least two segments; ≤ 64. Permanent once published.
namestringYes≤ 40 characters. May not contain Chataway, Otto or claw-dev.
versionstringYesSemver: 1.2.0, 1.3.0-beta.1. Each upload needs a new version.
descriptionstringYes≤ 400 characters. One or two sentences, shown in the store and on the install screen.
permissionsstring[]YesPlain-language sentences the user reads before enabling (≤ 200 characters each). Must be non-empty when you ask for any grant.
contributesobjectYesTools, panels, settings. Use {} for none.
publisherstringYes (store)Your store publisher slug (≤ 40).
remoteobjectYes (store)Where your tools live, and which hosts you reach. →
brandingobjectYes (store)Icon (required), mark, accent, screenshots. →
grantsstring[]NoCapabilities. →
accountsobject[]NoOAuth accounts. →
categoriesstring[]RecommendedUp to 3. →
homepagestringNohttps URL.
supportstringNohttps URL or mailto: address.
authorstringNoShown on the detail page.
platformsstring[]NoOperating systems your app works on: darwin, win32, linux. Omit for all. Elsewhere it's listed as "Not available on Windows yet" and can't be installed or switched on.
archstring[]NoCPU architectures: x64, arm64. Omit for all.
requires.providersstring[]NoLimit which chat providers get your tools: claw-chat, claude-code, codex, grok, cursor, opencode. Omit for all.
requires.appsobject[]NoApps that must be on in the same project: { id, reason }. Store apps: built-ins and store apps only. →
requires.connectorsobject[]NoConnectors (hosted MCP servers) your app works with: { url, reason, required? }. Fallbacks unless required: true. →
requires.optionalobject[]NoCompanions the user may tick: { id, reason } or { url, reason }. →

#Not available to store apps

These exist for Chataway's built-in apps and for local apps (which also declare network.domains), and are rejected in store bundles (in dev mode, they're warnings):

FieldWhyUse instead
serverStore apps don't run code on the Macremote.mcpUrl
contributes.skills, contributes.hooks, contributes.mcp, contributes.adaptersThey install files into the user's projectTools on your MCP server
contributes.secretsApps don't hold API keysaccounts
activation: "always"Runs in every project regardless of the switch—
Grants network, exec, project:readUnrestricted accessremote.domains, host services, file hand-off

#requires

json
"requires": {
  "apps":       [{ "id": "browser-use", "reason": "Opens canva.com when the connector isn't connected" }],
  "connectors": [{ "url": "https://mcp.canva.com/mcp", "reason": "Fast, reliable Canva edits" }],
  "optional":   [{ "id": "media", "reason": "Saves exports into your Media library" }]
}
FieldRules
apps[].idAn app id. Store apps: a built-in (browser-use, media, chat-artifacts, computer-use) or a published store app (com.acme.app) — never gh-…, npm-…, local.…. Not your own id; no duplicates.
apps[].reason, connectors[].reason, optional[].reasonRequired, ≤ 200 characters — shown on the install sheet.
apps[].githubNot for store apps. Apps installed from GitHub: owner/repo[@ref] of a GitHub dependency (it gets its own install screen).
connectors[].urlThe connector's https MCP URL (http://localhost in dev mode, with a warning).
connectors[].requiredtrue = your app waits until the connector is added and on. Default: a fallback.
optional[]Exactly one of id or url.

At most 10 entries per list. Adding a required app or connector in an update needs review and fresh consent. See Dependencies.

#remote

FieldType
mcpUrlstringYour Streamable HTTP MCP endpoint. Must be https (in dev mode, http://localhost / 127.0.0.1 is allowed with a warning). Its host is always allowed.
domainsstring[]Every other host your app reaches: your API, your CDN. Hostnames only — no scheme, no path, no port. *.example.com allowed; * and *.com are too broad. At most 20.

domains is used for:

  • your panel's CSP (connect-src, img-src, media-src),
  • every request Chataway makes for your app: MCP calls, redirects, and resource_link downloads,
  • the store listing (users see the list).

Adding a domain in an update needs human review and the user's consent again.

#grants

GrantLets the appNeeded for
storageKeep files in its own per-project storage. Implicit — you don't need to list it.—
files:receiveReceive files the user or agent hands to its tools or panelAny tool with files; bridge.pickFiles
files:returnReturn files that are kept in its storage and offered to the Media libraryKeeping files from tool results
host:mediaHave Chataway run host media operations on files it was handedprepare; bridge.host.media
project:writeAsk to save a file into the project folder — always behind a cardDirect saves (panel API not available yet; galleries already offer Save to project)

Every grant must be explained in permissions. Adding a grant in an update needs human review and the user's consent again.

#branding

FieldType
iconpathPNG or SVG in the bundle, square, 512×512 (at least 256), ≤ 1 MB. Required for the store.
markpathSingle-colour SVG, no scripts or event handlers, ≤ 1 MB. Tinted per theme.
accentstring#RRGGBB. Header stripe, card edge.
screenshotspath[]Up to 8 PNG/JPG/WebP files, ≤ 3 MB each.

Paths are relative to the bundle root, with no .. and no leading /. See Branding.

#accounts

FieldTypeRequired
idstringYesLowercase letters, digits, _; starts with a letter. Unique in the app.
labelstringYes≤ 40.
auth"mcp"NoThe MCP authorization spec: endpoints are discovered from remote.mcpUrl and Chataway registers itself (Dynamic Client Registration) — authorizeUrl, tokenUrl and clientId are then not needed (clientId = fallback for servers without registration).
authorizeUrlstringYes¹https (http://localhost allowed in dev mode, with a warning).
tokenUrlstringYes¹https.
revokeUrlstringNohttps.
clientIdstringYes¹Public client id (≤ 200). Never a secret.
scopesstring[]YesMay be empty. Adding scopes needs review.
requiredbooleanNoOffer Connect when the app is enabled.
paramsobjectNoExtra authorize params (string → string). May not set client_secret, redirect_uri, code_challenge or state.

¹ Not with auth: "mcp".

See Connected accounts.

#contributes.tools[]

FieldTypeRequired
idstringYesThe tool's name on your MCP server. Lowercase letters, digits, _; starts with a letter; ≤ 48. Unique.
namestringYesHuman name for cards and the Inspector (≤ 60).
descriptionstringYesWhat the agent reads to decide when and how to call the tool (≤ 1024). Overrides your server's description.
filesobjectNoFile params, keyed by argument name. Needs files:receive. →
confirmobjectNoA blocking approval card. →
accountstringNoAn accounts[].id. The call carries that account's token; not connected → a Connect card.

Write descriptions for the agent: what the tool does, when to use it, what it returns, and anything it must not do. "Upload images to the user's Acme inventory as decals. Returns the new asset ids. Only call it when the user asked to upload."

#files

json
"files": { "<param>": { "accept": ["image/*"], "multiple": true, "maxBytes": 20000000, "prepare": { "op": "resize", "width": 512 } } }
FieldTypeDefault
acceptstring[]anyMIME types or type/*.
multiplebooleanfalseA list of files.
maxBytesnumber20 MiBPer file, after prepare. 1 to 20,971,520.
prepareobject—A host media request: { "op": …, …params }. Needs host:media.

Param names follow the tool id rules. See Files & hand-off.

#confirm

FieldType
titlestringRequired, ≤ 120. {{count}} = number of files handed over.
bodystringMarkdown under the title.
actionstringApprove button label.

See Cards & approvals.

#contributes.panels[]

FieldTypeRequired
idstringYes≤ 40.
titlestringYes≤ 40.
component"webview"YesThe only component for store apps.
iconstringNo"branding" for your mark.
config.entrypathNoHTML entry in the bundle. Default ui/index.html. Must exist.
views{ id, label }[]NoHost-drawn header tabs.

See Panels & the bridge.

#contributes.settings[] and settingSections[]

See Settings for every field.

#Categories

Up to three of:

media · design · productivity · developer · data · marketing · sales · games · communication · finance · education · other

#Bundle

A .chataway-app file is a zip of:

Path
plugin.jsonRequired, at the root.
ui/**Your panel (HTML, JS, CSS, fonts, images).
assets/**Icon, mark, screenshots, other listing art.
README.mdBecomes the store description. Recommended.
CHANGELOG.mdShown with updates.
LICENSE, LICENSE.mdOptional.

Anything else at the top level is rejected. No executables (.node, .dylib, .so, .dll, .exe, .sh, .command, .app, .pkg, .dmg, .py, .rb, .jar). Total ≤ 25 MiB. Minified JavaScript is fine; obfuscated JavaScript is flagged. chataway pack builds the bundle for you.

#Validation codes

Every issue has a stable code, a path into the manifest (or bundle) and a message. chataway validate prints them like this:

✖ contributes.tools[0].files.images.prepare: "prepare" needs the "host:media" grant [tools.files.prepare.grant]
⚠ categories: Add at least one category so people can find the app [categories.missing]

Error issues block publishing. Warnings don't, but reviewers see them. Rules marked Error (store) · warning (dev) are store-only: chataway dev accepts them so you can experiment locally, and the store rejects them.

This table is generated from the validator the store runs.

CodeLevelMessage
accounts.authErrorauth must be "mcp" (or left out for a plain OAuth account)
accounts.auth.remoteErrorauth "mcp" needs remote.mcpUrl (the endpoint is discovered from it)
accounts.authorizeUrlErrorauthorizeUrl must be https
accounts.authorizeUrl.localhostWarningauthorizeUrl points at localhost — fine for development, the store needs https
accounts.clientIdErrorclientId is required (public client, PKCE — never a secret)
accounts.duplicateErrorDuplicate account id "…"
accounts.formatErroraccounts must be a list
accounts.idErroraccount id: lowercase letters, digits, _
accounts.itemErrorEach account is an object
accounts.labelErroraccount label is required
accounts.paramsErrorparams must map strings to strings
accounts.params.reservedErrorparams may not set client_secret, redirect_uri, code_challenge or state
accounts.revokeUrlErrorrevokeUrl must be https
accounts.revokeUrl.localhostWarningrevokeUrl points at localhost — fine for development, the store needs https
accounts.scopesErrorscopes must be a list of strings
accounts.tokenUrlErrortokenUrl must be https
accounts.tokenUrl.localhostWarningtokenUrl points at localhost — fine for development, the store needs https
arch.formatErrorarch: a non-empty list without repeats (omit it to mean every CPU)
arch.unknownErrorUnknown architecture "…" (use: …)
branding.accentErroraccent must be #RRGGBB
branding.formatErrorbranding must be an object
branding.iconErrorbranding.icon must be a relative path in the bundle
branding.icon.missingErrorStore apps need branding.icon (512×512)
branding.icon.sizeErrorbranding.icon must be ≤ 1 MB
branding.icon.smallErrorbranding.icon is …px; use 512×512
branding.icon.squareErrorbranding.icon is …×…; it must be square
branding.icon.typeErrorbranding.icon must be PNG or SVG
branding.markErrorbranding.mark must be a relative path in the bundle
branding.mark.scriptErrorbranding.mark may not contain scripts or event handlers
branding.mark.sizeErrorbranding.mark must be ≤ 1 MB
branding.mark.svgErrorbranding.mark must be an SVG
branding.screenshot.sizeErrorscreenshots must be ≤ 3 MB each
branding.screenshotsErrorscreenshots: up to 8 PNG/JPG/WebP paths in the bundle
bundle.executableError"…" looks executable; store bundles carry no native code
bundle.manifestErrorplugin.json is missing at the bundle root
bundle.manifest.jsonErrorplugin.json is not valid JSON: …
bundle.missingError… "…" is not in the bundle
bundle.obfuscatedWarning"…" looks obfuscated — reviewers may reject it; ship readable (minified is fine) code
bundle.pathErrorUnsafe path "…"
bundle.readmeWarningAdd a README.md — it becomes the store description
bundle.sizeErrorBundle is … MB; the limit is 25 MiB (26.2 MB)
bundle.unexpectedError"…" — store bundles hold plugin.json, ui/, assets/, README.md, CHANGELOG.md, LICENSE
categories.formatErrorcategories: up to 3
categories.missingWarningAdd at least one category so people can find the app
categories.unknownErrorUnknown category "…" (use: …)
contributes.missingErrorcontributes is required (use {} for none)
description.missingErrordescription is required (≤ 400 characters)
grants.formatErrorgrants must be a list
grants.levelError (store) · warning (dev)"…" is only for built-in apps
grants.unknownErrorUnknown grant "…"
homepage.urlErrorhomepage must be an https URL
id.formatErrorStore app ids are reverse-DNS, lowercase: com.yourcompany.app-name
id.missingErrorid is required
id.reservedError"…" is a built-in app id
level.activationError (store) · warning (dev)activation 'always' is only for built-in apps
level.adaptersError (store) · warning (dev)contributes.adapters is only for built-in apps
level.hooksError (store) · warning (dev)contributes.hooks is only for built-in apps
level.mcpError (store) · warning (dev)contributes.mcp is only for built-in apps
level.secretsError (store) · warning (dev)Store apps connect accounts (accounts[]) instead of asking for API keys
level.serverError (store) · warning (dev)Store apps cannot ship server code — put your logic behind remote.mcpUrl
level.skillsError (store) · warning (dev)contributes.skills is only for built-in apps
manifest.api-versionErrorpluginApiVersion must be one of 1, 2, 3
manifest.api-version-storeErrorStore apps must target pluginApiVersion 3
manifest.not-objectErrorplugin.json must be a JSON object
media.aspectErroraspect like "1:1" or "9:16"
media.compressErrorcompress needs quality or targetKb
media.convertErrorconvert needs a format
media.cropErrorcrop needs width+height (with x/y) or an aspect
media.dimensionError… must be an integer 1..…
media.fitErrorfit: cover | contain | fill
media.formatErrorA media request is an object with an "op"
media.format.valueErrorunsupported format
media.gravityErrorgravity: center | north | south | east | west
media.offsetError… must be an integer 0..16384
media.opErrorop must be one of …
media.qualityErrorquality must be 1..100
media.resizeErrorresize needs width and/or height
media.targetKbErrortargetKb must be 10..50000
media.timeError… must be 0..600 seconds
media.trimErrortrim needs start and/or end
media.trim.rangeErrorend must be after start
name.missingErrorname is required (≤ 40 characters)
name.reservedErrorThe name may not contain "Chataway" or other reserved names
network.domainError"…" is not a hostname (no scheme, no path; "*.example.com" allowed)
network.domain.broadError"…" is too broad
network.domains.countErrorAt most 50 domains
network.formatErrornetwork must be { domains: string[] }
network.grantWarningnetwork.domains has no effect without the "network" grant
network.storeErrorStore apps declare remote.domains, not network
panels.componentErrorStore apps render panels as 'webview'
panels.entryErrorconfig.entry must be a relative path inside the bundle
panels.formatErrorcontributes.panels must be a list
panels.idErrorpanel id is required
panels.itemErrorEach panel is an object
panels.titleErrorpanel title is required
permissions.emptyErrorExplain every grant to the user in permissions[]
permissions.formatErrorpermissions must be a list of plain-language sentences
platforms.formatErrorplatforms: a non-empty list without repeats (omit it to mean every OS)
platforms.unknownErrorUnknown platform "…" (use: …)
publisher.missingErrorpublisher (your store publisher slug) is required
remote.and-serverErrorAn app is either remote or has server code, not both
remote.domainError"…" is not a hostname (no scheme, no path; "*.example.com" allowed)
remote.domain.broadError"…" is too broad
remote.domainsErrorremote.domains must list the hosts the app reaches
remote.domains.countErrorAt most 20 domains
remote.localhostWarningremote.mcpUrl points at localhost — fine for development, the store needs https
remote.missingErrorStore apps need remote.mcpUrl and remote.domains
remote.urlErrorremote.mcpUrl must be an https URL
requires.apps.urlErrorConnectors go in requires.connectors ({ url, reason })
requires.countErrorAt most … entries in requires.…
requires.duplicateError"…" is listed twice
requires.formatErrorrequires must be an object
requires.githubErrorgithub must be "owner/repo" (optionally "@ref")
requires.github.storeErrorStore apps can't depend on GitHub code ("…")
requires.idErrorid must be an app id (browser-use, com.acme.app…)
requires.itemErrorEach required app is { id, reason }
requires.optional.kindErrorGive either id (an app) or url (a connector)
requires.reasonErrorSay why in "reason" (≤ 200 characters) — it is shown on the install sheet
requires.requiredErrorrequired must be true or false
requires.selfErrorAn app cannot require itself
requires.urlErrorurl must be the connector's https MCP URL
requires.url.localhostWarningA localhost connector — fine for development, the store needs https
settings.formatErrorcontributes.settings must be a list
support.urlErrorsupport must be an https URL or mailto:
tools.accountErroraccount "…" is not declared in accounts[]
tools.confirmErrorconfirm needs a title
tools.descriptionErrortool description is required (≤ 1024) — it is what the agent reads
tools.duplicateErrorDuplicate tool id "…"
tools.filesErrorfiles maps param names to file rules
tools.files.acceptErroraccept lists MIME types ("image/png", "image/*")
tools.files.grantErrorTools with file params need the "files:receive" grant
tools.files.maxBytesErrormaxBytes must be 1..20971520
tools.files.paramErrorparam names: lowercase letters, digits, _
tools.files.prepare.grantError"prepare" needs the "host:media" grant
tools.files.ruleErrorEach file param is an object
tools.formatErrorcontributes.tools must be a list
tools.idErrortool id: lowercase letters, digits, _ (≤ 48)
tools.itemErrorEach tool is an object
tools.nameErrortool name is required
version.formatErrorversion must be semver (1.2.3)