Skip to content

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:

  1. Sets affected installations to pending_configuration status
  2. Prompts users to reconfigure the extension with valid values
  3. 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:

hosting: internal  # Kiket-managed first-party extension

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:

hosting:
  tier: standard
  egress:
    - api.slack.com
    - hooks.zapier.com
    - *.googleapis.com

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:

lifecycleEvents:
  - installation.*

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: true is treated as sensitive. At runtime the platform expects the value to come from either the secret store or an environment variable named KIKET_SECRET_<KEY>, where the key is uppercased and non-alphanumeric characters become _ (e.g., api.tokenKIKET_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

  • delivery must be either http or internal. Customer-managed extensions should use http.
  • When delivery is http, callback.url must be HTTPS and callback.headers (if present) must be an object.
  • activationEvents, permissions, hooks, capabilities must be arrays when present.
  • Hook timeout values must be between 100 and 10 000 milliseconds. Hook retries must 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.
  • permissions should list the Kiket roles allowed to run the command. Admins bypass the check automatically.
  • Omit handler to emit an extension_event that 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.