Skip to content

Addon Manifests

Addon manifests are JSON files that define an addon’s metadata, permissions, runtime configuration, and UI extensions. Every addon requires a manifest.json in its project root.

For service catalog manifests (YAML), see Manifests.

{
"schemaVersion": 1,
"kind": "addon",
"metadata": { ... },
"addon": { ... }
}
FieldTypeRequiredDescription
schemaVersionintegerYesMust be 1
kindstringYesMust be "addon"
metadataobjectYesAddon identity and marketplace information
addonobjectYesRuntime and feature configuration
FieldTypeRequiredDescription
slugstringYesKebab-case identifier (3-50 chars). Pattern: ^[a-z][a-z0-9-]{1,48}[a-z0-9]$
namestringYesDisplay name
versionstringYesSemver version (e.g., 0.1.0)
descriptionstringYesShort description (shown in marketplace)
authorobjectYesAuthor info: name (required), email, url
categorystringYesCategory: integrations, analytics, cloud, custom
tierstringYesMarketplace tier: free, pro, enterprise
tagsarrayNoLowercase tags for discoverability
iconstringNoIcon name or SVG string
FieldTypeRequiredDescription
tierstringYesRuntime tier: declarative, scripted, full
runtimestringYesRuntime engine: quickjs
permissionsobjectYesCapabilities the addon requires
syncobjectNoScheduled sync configuration
configobjectNoAdmin-configurable settings
frontendobjectNoBrowser UI extensions
TierDescription
declarativeConfiguration-only, no code execution
scriptedBackend code in QuickJS sandbox, optional frontend
fullFull runtime access (databases, events, notifications)

Declares what resources the addon can access at runtime.

"permissions": {
"network": [
{ "url": "https://*.atlassian.net/*" }
],
"shoehorn": [
"secrets:read",
"entities:read",
"entities:write"
]
}

URL patterns the addon can reach via ctx.http.request(). Wildcards supported:

  • * matches any single path segment
  • Patterns are matched against the full URL
ScopeAPIDescription
secrets:readctx.secrets.get()Read encrypted secrets
entities:readctx.entities.get(), .list()Read catalog entities
entities:writectx.entities.upsert(), .delete()Create/update/delete entities
cache:readctx.cache.get(), .has()Read ephemeral cache
cache:writectx.cache.set(), .delete()Write ephemeral cache
events:publishctx.events.publish()Publish to event bus
events:subscribectx.events.subscribe()Subscribe to events
db:readctx.db.query()Read-only SQL queries
search:readctx.search.search()Full-text search
kv:readctx.kv.get(), .list(), .has()Read persistent KV store
kv:writectx.kv.set(), .delete()Write persistent KV store
notifications:sendctx.notifications.send()Send notifications

Configures periodic background synchronization.

"sync": {
"schedule": "*/5 * * * *",
"function": "sync"
}
FieldTypeRequiredDescription
schedulestringYesCron expression (5-field)
functionstringYesExported function name to call

Common schedules:

ExpressionFrequency
*/5 * * * *Every 5 minutes
*/15 * * * *Every 15 minutes
0 * * * *Every hour
0 0 * * *Daily at midnight

Declares admin-configurable settings. These appear as form fields in the addon admin UI.

"config": {
"api_url": {
"label": "API URL",
"type": "text",
"required": true,
"placeholder": "https://api.example.com"
},
"api_token": {
"label": "API Token",
"type": "secret",
"required": true
},
"sync_mode": {
"label": "Sync Mode",
"type": "select",
"default": "full",
"options": ["full", "incremental"]
}
}
TypeDescriptionAccess
textPlain text input, stored unencryptedctx.config.get(key)
secretPassword input, stored AES-256-GCM encryptedctx.secrets.get(key) (requires secrets:read)
selectDropdown with predefined options (requires options array)ctx.config.get(key)
FieldTypeRequiredDescription
labelstringYesUI label
typestringYestext, secret, or select
requiredbooleanNoWhether the field must be set
placeholderstringNoHint text
defaultstringNoDefault value
optionsarrayNoValid values (for select type)

Declares browser UI extensions: dashboard widgets and entity tabs.

"frontend": {
"widgets": [
{
"id": "jira-issues",
"label": "Jira Issues",
"componentName": "JiraIssuesWidget",
"size": {
"default": { "w": 4, "h": 3 },
"min": { "w": 2, "h": 2 },
"max": { "w": 6, "h": 6 }
}
}
]
}
FieldTypeRequiredDescription
idstringYesUnique widget identifier (scoped to addon)
labelstringYesDisplay name in widget picker
componentNamestringYesExported component from frontend bundle
size.defaultobjectYesDefault grid size: { w, h }
size.minobjectYesMinimum grid size
size.maxobjectNoMaximum grid size

Grid dimensions are in dashboard grid units (typically ~60px each). Width range: 1-12, height range: 1-8.

"frontend": {
"entityTabs": [
{
"id": "runbooks",
"label": "Runbooks",
"componentName": "RunbooksTab",
"entityTypes": ["service", "api"],
"priority": 10
}
]
}
FieldTypeRequiredDescription
idstringYesUnique tab identifier
labelstringYesTab label
componentNamestringYesExported component from frontend bundle
entityTypesarrayNoEntity types this tab applies to (omit for all)
prioritynumberNoSort order (lower = first, default: 0)
{
"schemaVersion": 1,
"kind": "addon",
"metadata": {
"slug": "jira-sync",
"name": "Jira Sync",
"version": "0.1.0",
"description": "Syncs Jira issues into the Shoehorn catalog.",
"author": { "name": "Shoehorn Team" },
"category": "integrations",
"tier": "free"
},
"addon": {
"tier": "scripted",
"runtime": "quickjs",
"permissions": {
"network": [{ "url": "https://*.atlassian.net/*" }],
"shoehorn": ["secrets:read", "entities:read", "entities:write"]
},
"sync": {
"schedule": "*/5 * * * *",
"function": "sync"
},
"config": {
"jira_base_url": {
"label": "Jira Base URL",
"type": "text",
"required": true,
"placeholder": "https://yourorg.atlassian.net"
},
"jira_email": {
"label": "Jira Email",
"type": "text",
"required": true,
"placeholder": "user@company.com"
},
"jira_api_token": {
"label": "Jira API Token",
"type": "secret",
"required": true
},
"jira_project": {
"label": "Jira Project Key",
"type": "text",
"required": false,
"placeholder": "PROJ"
}
},
"frontend": {
"widgets": [
{
"id": "jira-issues",
"label": "Jira Issues",
"componentName": "JiraIssuesWidget",
"size": {
"default": { "w": 4, "h": 3 },
"min": { "w": 2, "h": 2 },
"max": { "w": 6, "h": 6 }
}
}
]
}
}
}
  • 3-50 characters
  • Lowercase letters, numbers, and hyphens only
  • Must start with a letter, end with letter or digit
  • Must be unique per Shoehorn instance

Semantic versioning: MAJOR.MINOR.PATCH (e.g., 0.1.0, 1.2.3)

  • min width and height must be >= 1
  • default must be >= min and <= max (if max is set)