App manifest
Use manifest.json to describe your extension for Kittl: listing fields, the embed HTML entrypoint, requested SDK scopes, and optional OAuth providers. The Kittl CLI validates this file with the same shape as below when you create or update a draft version.
Manifest schema
Field names are camelCase in JSON (for example authorizationUrl, not authorization_url).
type Scope =
| 'design:state:read'
| 'design:state:write'
| 'uploads:create'
| 'fonts:create'
| 'uploads:global:read'
| 'ai:credit:spend'
| 'workspace:state:read'
| 'workspace:state:write';
type OAuthProvider = {
authorizationUrl: string;
clientId: string;
tokenUrl: string;
scope?: string;
accessType?: string;
customFieldMappings?: Record<string, unknown>;
};
type ExtensionManifest = {
$schema?: string;
displayName: string;
icon: string;
tagline: string;
installPage: {
description: string;
coverImage: string;
termsAndConditionsLink: string;
privacyPolicyLink: string;
};
config: {
scopes: Scope[];
embed: {
mainAppPanel: {
path: string;
};
scriptRunner?: {
path: string;
};
};
appDevelopment?: {
local: {
requireHTTPS?: boolean;
port?: number;
};
};
oauthProviders?: Record<string, OAuthProvider>;
};
};
config.scopes is required. If you want no permissions, set it to an empty array ([]). With an empty scope set, many SDK calls will be denied by scope checks.
Complete example
{
"$schema": "https://api.kittl.com/extensions/manifest/schema.json",
"displayName": "My extension",
"icon": "icon.svg",
"tagline": "My extension for Kittl.",
"installPage": {
"description": "Install My extension to use it in Kittl.",
"coverImage": "icon.svg",
"termsAndConditionsLink": "https://example.com/terms",
"privacyPolicyLink": "https://example.com/privacy"
},
"config": {
"scopes": ["design:state:read", "design:state:write"],
"embed": {
"mainAppPanel": {
"path": "index.html"
}
},
"appDevelopment": {
"local": {
"requireHTTPS": true,
"port": 5173
}
},
"oauthProviders": {
"myProvider": {
"authorizationUrl": "https://provider.example.com/oauth2/authorize",
"clientId": "client-id-from-provider",
"scope": "assets.read assets.write",
"accessType": "offline",
"tokenUrl": "https://provider.example.com/oauth2/token"
}
}
}
}
Manifest location
Place manifest.json at the root of your app project (same level as the output/ directory), not inside output/.
Field notes
displayName,tagline,icon: short listing fields for your extension.installPage: longer description, cover art, and links shown in the install flow (termsAndConditionsLinkandprivacyPolicyLinkare required).config.embed.mainAppPanel.path: HTML entrypoint for the main panel. The path is interpreted relative to the built artifact root (the contents you upload), for exampleindex.htmlnext to your bundled assets.config.embed.scriptRunner: optional second HTML entrypoint for a headless runner — a hidden iframe that runs background logic (AI pipelines, data sync, etc.) with full SDK access but no visible UI.config.scopes: SDK permissions your extension requests. See SDK scopes.config.oauthProviders: map of provider id to OAuth endpoints; the key is theproviderstring you pass tokittl.auth.scopeandaccessTypeare optional in the manifest if your provider does not need them.config.appDevelopment.local: optional local-dev hints (requireHTTPSis useful when a provider expects a secure origin;portcan match your dev server).
Development helpers
The canonical JSON Schema for extension manifests is published at https://api.kittl.com/extensions/manifest/schema.json.
Add a $schema field in manifest.json so your editor can validate and autocomplete against it:
{
"$schema": "https://api.kittl.com/extensions/manifest/schema.json"
}