Manifest Specification¶
Every extension must ship with a manifest.yaml located under .kiket/extensions/<extension-id>/manifest.yaml. The manifest tells Kiket how to install, configure, and communicate with your extension. All manifests must include model_version: "1.0" at the top level.
Core Fields¶
| Field | Type | Required | Notes |
|---|---|---|---|
model_version |
string | ✓ | Currently only "1.0" is supported. |
extension.id |
string | ✓ | Stable identifier (kebab-case recommended). Duplicate IDs across configuration sources cause sync failures. |
extension.name |
string | ✓ | Human-readable name. |
extension.version |
string | ✱ | Defaults to 1.0.0 when omitted; use semver. |
extension.api_version |
string | ✱ | API version for webhook routing. Defaults to v1. Format: v1, v2, etc. |
extension.description |
string | ✱ | Displayed in UI and marketplace listings. |
extension.sdk |
string | ✱ | SDK language used by this extension. Values: ruby, python, node, java, dotnet, go. Used by Cloud Build to generate Dockerfiles from SDK templates. |
extension.delivery |
string | ✱ | Declares how the extension receives events. Use "http" for customer-hosted callbacks or "internal" for first-party bundles. Configure callback settings in the extension.callback block. |
extension.callback |
object | ✱ | Required when delivery is http. Defines url, secret, optional headers, timeout (ms), and required flag. |
extension.activationEvents |
array[string] | ✱ | List of domain events that activate the extension. Accepts either activationEvents (camelCase) or activation_events (snake_case). |
extension.lifecycleEvents |
array[string] | ✱ | List of installation lifecycle events to receive. See Lifecycle Events. |
extension.permissions |
array[string] | ✱ | API scopes required by the extension. |
extension.hooks |
array[Hook] | ✱ | Declarative hook definitions (see below). |
extension.configuration |
object | ✱ | Arbitrary key/value pairs used to render installation settings. Properties flagged with secret: true map to environment variables and secret store entries. |
extension.capabilities |
array[string] | ✱ | Tags surfaced in the UI (e.g., approvals, analytics). |
extension.assets |
object | ✱ | Paths to supplemental files (icons, docs) relative to the manifest. |
✱ marks optional fields.
Hook Objects¶
Each entry in extension.hooks describes how your extension handles a specific event:
| Key | Type | Required | Notes |
|---|---|---|---|
event |
string | ✓ | Event name (e.g., workflow.before_transition). |
timeout |
integer | ✱ | Milliseconds (100–10 000). Overrides the default 3 000 ms. |
retries |
integer | ✱ | Number of retries (0–5). Defaults to 0. |
description |
string | ✱ | UI hint describing the hook's role. |
template |
string | ✱ | Configuration key for the template to render (see Template Binding). |
conditions |
array | ✱ | Conditions that must match for this hook to apply. |
channels |
array | ✱ | Limit this hook to specific delivery channels (e.g., ["sms", "slack"]). |
Hooks may be declared as simple strings (treated as { event: "…" }).
Template Binding¶
Hooks can reference configuration templates that Kiket renders before sending the webhook. This allows admins to customize notification messages without modifying extension code.
hooks:
- event: issue.created
template: issue_created_message # References configuration key
timeout: 5000
configuration:
issue_created_message:
type: template
editor: template
language: liquid
label: Issue Created Message
default: |
**New Issue:** {{ issue.title }}
Priority: {{ issue.priority }}
Created by: {{ created_by.name }}
When the issue.created event fires, Kiket:
1. Looks up the issue_created_message template from configuration
2. Renders it with event context (issue, user, project data)
3. Includes the rendered content in the webhook payload under rendered
Extensions receive:
{
"event": "issue.created",
"issue": { "id": 123, "title": "Fix bug", ... },
"rendered": {
"content": "**New Issue:** Fix bug\nPriority: high\nCreated by: Alice",
"format": "markdown",
"template_key": "issue_created_message"
}
}
Conditional Hooks¶
Multiple hooks can target the same event with different conditions:
hooks:
# Use deployment template when transitioning to production
- event: issue.transitioned
template: deployment_approval_message
conditions:
- field: transition.to
value: production
# Default template for other transitions
- event: issue.transitioned
template: transition_message
Condition fields use dot notation to access nested context values. The value can be a single value or an array of acceptable values.
Example Manifest¶
model_version: "1.0"
extension:
id: dev.example.standup-notifier
name: Standup Notifier
version: 1.2.0
api_version: v1
description: Sends a daily digest of open issues.
sdk: python
delivery: http
callback:
url: https://hooks.example.test/workflow
secret: env.STANDUP_SECRET
headers:
X-Env: production
timeout: 6000
required: true
activationEvents:
- workflow.before_transition
- issue.created
permissions:
- issues:read
- issues:comment
hooks:
- event: workflow.before_transition
template: approval_request_message
timeout: 5000
retries: 1
description: Guard transitions before they execute.
- event: issue.created
template: issue_notification
timeout: 5000
configuration:
timezone:
type: string
default: "America/Chicago"
notify_roles:
type: array
default: ["developer", "qa"]
approval_request_message:
type: template
language: liquid
default: "Approval needed for {{ issue.title }}"
issue_notification:
type: template
language: liquid
default: "New issue: {{ issue.title }}"
capabilities:
- approvals
- notifications
assets:
icon: ./public/icon.svg
Configuration Fields¶
Extensions can declare configuration fields that are rendered in the Kiket UI. Simple fields are text inputs, but rich editor types enable template editing, code editing, and more.
Simple Configuration¶
configuration:
timezone: "America/Chicago"
notify_roles: ["developer", "qa"]
api_token:
type: string
secret: true # Stored securely in secret store
required: true # Extension is disabled until configured
label: "API Token"
Required vs Optional Fields¶
Configuration fields can be marked as required: true to indicate they must be configured before the extension becomes active:
| Property | Type | Description |
|---|---|---|
required |
boolean | When true, the extension enters pending_configuration status until this field has a value. The extension will not process events until all required fields are configured. |
label |
string | Human-readable label shown in the configuration UI. Defaults to the field key humanized. |
show_in_wizard |
boolean | When true, this field appears in the post-installation setup wizard even if not required. |
Extension Status Based on Configuration:
- Active: All required configuration fields have values. The extension processes events normally.
- Pending Configuration: One or more required fields are missing. The extension is installed but disabled - it will not receive webhook events until configured.
- Inactive: Manually disabled by the user.
- Suspended: Disabled by the platform (e.g., billing issues, policy violations).
This ensures extensions don't fail silently due to missing credentials. Users see a clear warning in the UI indicating which fields need to be configured.
Rich Configuration Fields¶
Rich configuration fields use specialized editors (powered by Monaco) for templates, code, and structured content:
| Editor Type | Use Case | Languages |
|---|---|---|
template |
Email/notification templates | liquid |
code |
Scripts, configs | javascript, yaml, json, sql, ruby, python |
markdown |
Documentation, descriptions | markdown |
html |
Rich content (auto-sanitized) | html |
json_schema |
Schema-driven forms | json |
Template Field Example¶
configuration:
email_template:
type: template
editor: template
language: liquid
label: "Email Template"
description: "Template for notification emails"
default: "Hello {{ user.name }}, issue {{ issue.title }} was updated."
variables:
- name: user
type: object
properties:
- { name: name, type: string }
- { name: email, type: string }
- name: issue
type: object
properties:
- { name: id, type: number }
- { name: title, type: string }
- { name: url, type: string }
validation:
max_length: 50000
required_variables: ["issue.title"]
Code Field Example (Configuration Data)¶
The code editor is for structured configuration data (JSON, YAML) that extensions read - not for executable code. Extensions perform actions via webhooks/API calls.
configuration:
webhook_payload_schema:
type: code
editor: code
language: json
label: "Webhook Payload Schema"
description: "JSON schema defining the expected webhook payload format"
default: |
{
"type": "object",
"properties": {
"issue_id": { "type": "number" },
"action": { "type": "string" }
}
}
notification_rules:
type: code
editor: code
language: yaml
label: "Notification Rules"
description: "YAML configuration for notification routing"
default: |
rules:
- priority: critical
channels: ["#ops-alerts"]
- priority: high
channels: ["#dev-alerts"]
Rich configuration values are stored in ExtensionInstallation.configuration and can be edited via the Configuration UI in project settings. Changes are versioned for audit and rollback.
Setup Wizard¶
Extensions can declare a step-by-step setup wizard that guides users through onboarding after installation. The wizard provides a guided experience for collecting credentials, configuring settings, and verifying connectivity.
Wizard Structure¶
The setup wizard can either define fields inline or reference configuration keys using collect::
extension:
id: dev.example.my-integration
configuration:
# Define all configuration fields here
SLACK_BOT_TOKEN:
type: string
secret: true
required: true
label: Bot Token
default_channel:
type: string
label: Default Channel
setup:
# Reference configuration keys with collect:
- secrets:
title: Connect to Slack
description: Enter your Slack credentials
required: true
collect:
- SLACK_BOT_TOKEN
# Or define fields inline
- configure:
title: Notification Settings
fields:
- key: channel
label: Default Channel
type: string
placeholder: "#general"
- test:
title: Verify Connection
action: slack.validateChannel
successMessage: Successfully connected!
- info:
title: Setup Complete
content: |
Your extension is ready! Check the [documentation](https://docs.example.com).
The collect: pattern is preferred as it avoids duplicating field definitions between configuration: and setup:.
Step Types¶
| Type | Purpose |
|---|---|
secrets |
Collect OAuth tokens, API keys, and other credentials |
configure |
Gather non-sensitive configuration values |
test |
Verify the extension's connectivity and credentials |
info |
Display helpful information or documentation |
Secrets Step¶
The secrets step type handles credential collection with built-in OAuth flows and secure storage. Use collect: to reference configuration keys:
# In configuration section
configuration:
ZOOM_ACCOUNT_ID:
type: string
secret: true
required: true
label: Account ID
ZOOM_CLIENT_ID:
type: string
secret: true
required: true
label: Client ID
ZOOM_CLIENT_SECRET:
type: string
secret: true
required: true
label: Client Secret
# In setup section
- secrets:
title: Authenticate with Zoom
description: Connect your Zoom account to enable video meeting features.
required: true
help_url: https://developers.zoom.us/docs/internal-apps/s2s-oauth/
collect:
- ZOOM_ACCOUNT_ID
- ZOOM_CLIENT_ID
- ZOOM_CLIENT_SECRET
For OAuth flows, use the obtain: block within the configuration field definition:
configuration:
ZOOM_ACCESS_TOKEN:
type: string
secret: true
required: true
obtain:
type: oauth2
provider: zoom
client_id: ext.zoom_client_id
client_secret: ext.zoom_client_secret
scopes: ["meeting:read", "meeting:write"]
store_as: org.zoom_access_token
Credential Scopes¶
Credentials can be scoped to different levels using prefixes:
| Prefix | Scope | Who Provides | Example |
|---|---|---|---|
ext.* |
Extension | Partner/developer | ext.slack_client_id |
org.* |
Organization | Org admins | org.zoom_token |
user.* |
User | Individual user | user.github_token |
Obtain Types¶
| Type | Description |
|---|---|
oauth2 |
Standard OAuth 2.0 authorization code flow |
oauth2_client_credentials |
Client credentials grant (server-to-server) |
api_key |
Manual API key entry |
token |
Manual token entry |
input |
Free-form text input |
basic |
Username/password pair |
auto_generate |
Platform-generated secrets (e.g., webhook signing keys) |
Configure Step¶
The configure step collects non-sensitive configuration values:
- configure:
title: Notification Settings
description: Configure how notifications are sent.
fields:
- key: channel
label: Default Channel
type: string
placeholder: "#general"
required: true
- key: notify_on_create
label: Notify on Issue Create
type: boolean
default: true
- key: priority_filter
label: Minimum Priority
type: select
options:
- { value: all, label: All Priorities }
- { value: high, label: High and Critical }
- { value: critical, label: Critical Only }
showWhen:
field: notify_on_create
value: true
Conditional Fields¶
Use showWhen to conditionally display fields based on other field values:
fields:
- key: auth_method
type: select
options:
- { value: oauth, label: OAuth }
- { value: api_key, label: API Key }
- key: api_key
type: string
showWhen:
field: auth_method
value: api_key
- key: oauth_scopes
type: string
showWhen:
field: auth_method
values: [oauth, oauth_enterprise] # Match multiple values
Dynamic Field Types¶
Dynamic field types resolve their options from the project context at runtime, ensuring users see options that match their project configuration. This is particularly useful for fields that depend on workflow definitions or project settings.
| Type | Description | Source |
|---|---|---|
issue_type |
Issue types from workflow definition | Organization's IssueTypeDefinition records |
workflow_status |
Workflow statuses | Project's workflow definition states |
board |
Project boards | Project's board list |
priority |
Priority levels | Standard priorities (low, medium, high, critical) |
project |
Organization projects | Searchable dropdown of all organization projects |
Example¶
- configure:
title: Issue Settings
fields:
- key: default_project_id
type: project
label: Default Project
description: Project where new issues will be created
required: true
- key: default_issue_type
type: issue_type
label: Default Issue Type
description: Issue type to create from incoming emails
required: true
- key: target_board
type: board
label: Target Board
description: Board to add new issues to
Dynamic types render as select dropdowns but pull their options from the project/organization context. This ensures extensions adapt to custom workflow templates without hardcoding options.
:::note If no issue types are defined for the organization, an empty list is returned. Extensions should handle this gracefully or mark the field as required to ensure configuration is complete. :::
Configuration Drift¶
When workflow definitions change (e.g., issue types are removed or renamed), extension configurations may reference values that no longer exist. Kiket automatically detects this "configuration drift" and:
- Sets affected installations to
pending_configurationstatus - Prompts users to reconfigure the extension with valid values
- Logs the drift event for debugging
Output Fields¶
Extensions can declare output fields that display extension-generated data back to users. Unlike configuration fields (which collect user input), output fields show read-only information that the extension populates during setup or operation.
Use Cases¶
- Inbound email addresses: Email-to-issue extensions can show the parse email address
- Webhook URLs: Integrations can display URLs for external services to call
- Connection status: OAuth integrations can show connected account info
- Generated identifiers: Extensions can display IDs or tokens needed for external configuration
Schema Definition¶
extension:
id: dev.example.mailjet-inbound
output_fields:
inbound_email:
label: Mailjet Inbound Email
description: |
Send emails to this address to create issues automatically.
Configure your email provider to forward to this address.
type: copyable
icon: envelope
connection_status:
label: Connection Status
type: badge
icon: link
webhook_endpoint:
label: Webhook Endpoint
description: Configure your external service to POST events here.
type: url
help_url: https://docs.example.com/webhooks
Field Properties¶
| Property | Type | Required | Description |
|---|---|---|---|
label |
string | Recommended | Human-readable label shown in the UI |
description |
string | - | Explanatory text shown below the label |
type |
string | - | Display type: copyable, url, code, badge, status |
icon |
string | - | Bootstrap icon name (e.g., envelope, link) |
help_url |
string | - | Link to documentation for this field |
Display Types¶
| Type | Description |
|---|---|
copyable |
Text field with copy button (default) |
url |
Clickable URL with copy button and external link |
code |
Monospace code block |
badge |
Status indicator with color based on value |
status |
Rich status with icon and message |
Populating Output Fields¶
Extensions populate output fields by including them in webhook responses. During the setup wizard test step, return output fields in the response metadata:
{
"status": "allow",
"metadata": {
"output_fields": {
"inbound_email": "abc123@parse.mailjet.com",
"connection_status": "active"
}
}
}
Output fields are stored in installation.metadata["output_fields"] and displayed in the extension configuration page.
Test Step¶
The test step verifies the extension's credentials and connectivity:
- test:
title: Verify Connection
description: Testing your Slack connection...
check_endpoint: /api/v1/extensions/health
timeout: 10000
on_failure:
message: Could not connect to Slack. Please check your credentials.
retry: true
Info Step¶
The info step displays helpful information using Markdown:
- info:
title: Setup Complete
icon: bi-check-circle
markdown: |
## You're all set!
Your Slack integration is now active. Here's what happens next:
- **Notifications**: Issues will be posted to your configured channel
- **Commands**: Use `/kiket` in Slack to interact with issues
[View Documentation](https://docs.kiket.dev/extensions/slack)
Partner Credentials¶
Extensions requiring OAuth apps or API credentials from the developer must declare them in the credentials.ext section:
extension:
id: dev.kiket.ext.zoom
credentials:
ext:
- key: zoom_client_id
label: Zoom OAuth Client ID
type: oauth_client_id
required: true
help_url: https://marketplace.zoom.us/docs/guides/build/oauth-app
- key: zoom_client_secret
label: Zoom OAuth Client Secret
type: oauth_client_secret
required: true
secret: true
Partner credentials must be configured in the Publisher Dashboard before the extension can be published. See Partner Credentials Guide for details.
Security Note: Extensions cannot execute arbitrary code within Kiket. All extension actions are performed through: - Webhook callbacks to extension-hosted endpoints - API calls using extension credentials - Kiket's sandboxed template rendering (Liquid for formatting only)
Hosting Configuration¶
Extensions that are hosted and managed by Kiket (managed extensions) can declare their hosting requirements using the hosting block. This determines the compute resources allocated to your extension and the associated billing tier.
Simple Hosting Declaration¶
For first-party extensions or extensions managed by Kiket, use the simple string format:
Managed Extension Tiers¶
Third-party extensions publishing to the marketplace can specify their hosting tier:
hosting:
tier: standard # minimal, standard, compute, or memory
egress: # Optional: allowed external domains
- api.example.com
- hooks.slack.com
managed: true # Indicates this extension is managed by Kiket
Hosting Tiers¶
| Tier | CPU | Memory | Price | Use Case |
|---|---|---|---|---|
minimal |
0.25 CPU | 256Mi | $5/mo | Lightweight webhooks, simple notifications |
standard |
1 CPU | 512Mi | $15/mo | Most extensions, API integrations |
compute |
2 CPU | 1Gi | $35/mo | Data processing, heavy computations |
memory |
1 CPU | 2Gi | $30/mo | Large datasets, caching workloads |
Egress Control¶
For security, managed extensions can declare which external domains they need to communicate with:
Only traffic to declared egress domains is allowed. This prevents data exfiltration and ensures extensions only communicate with expected services.
Self-Hosted Extensions¶
Extensions that handle their own hosting should use delivery: http instead:
extension:
id: my-custom-extension
delivery: http
callback:
url: https://my-server.example.com/webhook
secret: env.WEBHOOK_SECRET
Self-hosted extensions are not billed for hosting but are responsible for their own infrastructure.
Lifecycle Events¶
Extensions can opt-in to receive installation lifecycle events by declaring lifecycleEvents in their manifest. These are distinct from domain events (activationEvents) and notify your extension about installation status changes.
Available Lifecycle Events¶
| Event | Trigger |
|---|---|
installation.created |
A new installation is created for a project |
installation.active |
Installation transitions to active status |
installation.pending_configuration |
Installation is awaiting configuration |
installation.inactive |
Installation is deactivated |
installation.removed |
Installation is deleted |
Declaring Lifecycle Events¶
extension:
id: dev.example.metrics
lifecycleEvents:
- installation.created
- installation.removed
callback:
url: https://my-server.example.com/webhook
Use the wildcard installation.* to receive all installation lifecycle events:
Event Payload¶
Lifecycle events are delivered to your callback.url with the following structure:
{
"event": "installation.created",
"timestamp": "2025-01-21T10:30:00Z",
"extension_id": "dev.example.metrics",
"data": {
"installation_id": 123,
"project_id": 456,
"project_name": "My Project",
"organization_id": 789,
"status": "active",
"configuration": {},
"installed_by": "user@example.com",
"installed_at": "2025-01-21T10:30:00Z"
}
}
:::note
Lifecycle events are opt-in. Extensions that don't declare lifecycleEvents will not receive any lifecycle notifications. Most extensions only need domain events (activationEvents) and do not require lifecycle events.
:::
Secrets & Environment Variables¶
- Any configuration property marked with
secret: trueis treated as sensitive. At runtime the platform expects the value to come from either the secret store or an environment variable namedKIKET_SECRET_<KEY>, where the key is uppercased and non-alphanumeric characters become_(e.g.,api.token→KIKET_SECRET_API_TOKEN). - The Python SDK resolves secrets in the following order: explicit overrides → environment (
KIKET_SECRET_*) → platform secret store. Other runtimes should follow the same convention. - When specifying callback secrets in the manifest, you can reference environment variables directly via
env.MY_SECRET_NAME. During runtime the SDK resolves these references automatically.
Validation Rules¶
deliverymust be eitherhttporinternal. Customer-managed extensions should usehttp.- When
deliveryishttp,callback.urlmust be HTTPS andcallback.headers(if present) must be an object. activationEvents,permissions,hooks,capabilitiesmust be arrays when present.- Hook
timeoutvalues must be between 100 and 10 000 milliseconds. Hookretriesmust be between 0 and 5. - Duplicate extension IDs across configuration sources (local overrides, repositories, defaults) raise hard errors; resolve collisions before rerunning sync.
Command Palette Contributions¶
Extensions can register spotlight-style commands by declaring contributes.commands in their manifest. Each entry maps to an item in the in-app command palette (⌘/Ctrl + K).
extension:
id: support-toolkit
name: Support Toolkit
contributes:
commands:
- command: support.createTicket
title: Create Support Ticket
description: Open a new case in your external helpdesk.
category: Support
icon: bi-life-preserver
keywords: ["support", "ticket"]
shortcut: ["Shift", "S"]
handler:
type: extension_event
event: support.createTicket
permissions:
- manager
- executive
Guidelines:
- Command ids must be unique within the manifest; duplicates raise a validation error during sync.
permissionsshould list the Kiket roles allowed to run the command. Admins bypass the check automatically.- Omit
handlerto emit anextension_eventthat matches the command id. Additional handler strategies will be documented as they become available. - Additional metadata can be attached via
metadata, which is forwarded to the runtime API for custom clients.
At runtime the platform exposes your commands through:
GET /projects/:id/command_palette– returns all available commands for the current user/project.POST /projects/:id/command_palette_run– executes a command and forwards the payload to your extension.
Use the Extension SDK helper (window.kiketCommandPalette.register) if you need to add or remove commands dynamically from client-side code.
Storage & Inspection¶
During a configuration sync, manifests are normalised and stored in project.settings['extension_manifests'], keyed by extension.id. Inspect the stored data via the Rails console (project.settings['extension_manifests']) or the forthcoming admin API endpoints.
Troubleshooting¶
| Symptom | Resolution |
|---|---|
delivery 'foo' is not supported |
Use http (customer-hosted) or internal (first-party only). |
callback.url is required when delivery is "http" |
Provide an HTTPS endpoint for event delivery. |
hooks[0] is missing event |
Ensure each hook hash declares an event. |
| Duplicate ID error during sync | Rename one of the manifests or consolidate the definitions so only one file declares the ID. |
Refer to the Extension SDK documentation for guidance on building bundles compatible with the runtime you select.