JSON Plugin API

Send DynamicLake Live Activity updates from a local plugin process.

Overview

Use the JSON Plugin API when predefined DynamicLake components are enough and you do not need a SwiftUI ExtensionKit extension. Plugins can be written in any language that can connect to a Unix domain socket and write framed JSON.

For a full starter walkthrough, see CreateYourFirstPlugin. For package limits and DynamicLake Market review, see PluginPackagingAndMarket.

!A DynamicLake JSON plugin sending framed commands through the local socket to validation and rendering.

Connection

DynamicLake launches installed plugins and passes the socket path in the process environment:

DYNAMICLAKE_JSON_SOCKET=/path/to/dynamiclake-json-plugin.sock
DYNAMICLAKE_PLUGIN_SETTINGS_PATH=/path/to/plugin-settings.json

Each message is one frame:

  1. 4-byte unsigned big-endian payload length.
  2. UTF-8 JSON payload.

The JSON payload must be 64 KB or smaller. DynamicLake validates the full message before rendering.

Command Object

Every command must include schemaVersion and type.

{
  "schemaVersion": 1,
  "requestID": "req-1",
  "type": "create",
  "activityID": "build-42",
  "title": "Build",
  "priority": "normal",
  "size": "large",
  "surfaces": {
    "compactLiveActivity": {
      "leftSlot": { "type": "status", "status": "inProgress", "tint": "blue" },
      "rightSlot": { "type": "progress", "value": 0.42, "tint": "blue" }
    },
    "sneakPeek": {
      "leftSlot": { "type": "status", "status": "inProgress", "tint": "blue" },
      "center": { "type": "text", "text": "Building DynamicLakeKit", "style": "marquee" }
    }
  }
}

| Field | Value | | --- | --- | | schemaVersion | Required. Must be 1. | | requestID | Optional opaque identifier returned in responses. | | type | Required. create, update, or dismiss. | | activityID | Required for create and update. Optional for dismiss. | | title | Optional display title, up to 80 characters. | | priority | Optional. low, normal, or high. Defaults to normal. | | size | Optional. small, normal, or large. Defaults to normal. | | surfaces | Required for create. Optional for update. | | presentSneakPeek | Optional on create and update. Opens the sneak peek for 1-10 seconds without hovering. See JSONPluginAPI — Show The Sneak Peek Automatically. |

Identifier-like fields may contain letters, numbers, ., _, :, and -.

Feature Detection

DynamicLake lists newer protocol features in the plugin's environment:

DYNAMICLAKE_PLUGIN_FEATURES=numericText,presentSneakPeek

Older DynamicLake versions don't set this variable and reject any message that uses an unknown field or value. Check for a feature before using it and fall back when it's missing.

features = set(os.environ.get("DYNAMICLAKE_PLUGIN_FEATURES", "").split(","))
supports_numeric_text = "numericText" in features
supports_present_sneak_peek = "presentSneakPeek" in features

| Feature | Enables | | --- | --- | | numericText | text components with style: "numeric". | | presentSneakPeek | The presentSneakPeek command field. |

Commands

create opens or replaces a plugin activity. It requires surfaces.compactLiveActivity.

update changes an existing activity by activityID. Send only the surfaces that changed.

dismiss closes a plugin activity. If activityID is omitted, DynamicLake may dismiss plugin-owned activity state for that client.

Size And Priority

Use size to choose the compact live activity width:

| Value | Meaning | | --- | --- | | small | Minimal compact activity for icon-only, status, progress, or timer layouts that must stay tight. | | normal | Compact visual, status, progress, or duration layouts that need slightly more room than small. | | large | Widest compact activity for visual, status, progress, or duration layouts that need the most room. |

Do not put descriptive text in compactLiveActivity slots. Use text in sneakPeek; in the live activity, text should be duration-style content such as a timer or countdown.

Use priority to choose how the plugin competes with other active DynamicLake content:

| Value | Meaning | | --- | --- | | low | Long-running, ambient state that can wait behind more immediate content. Use it for calendar events, ongoing status, and other activities that may remain active for a long time. | | normal | The default for active work the user may want to glance at, such as music playback, file uploads, builds, or downloads. | | high | Important, time-sensitive content that should win over normal activity because the user should not miss it, such as calls, messages, and urgent notifications. |

When size or priority is omitted from create, DynamicLake uses normal. When omitted from update, DynamicLake keeps the previous value.

Surfaces

Plugins can send two surfaces:

| Surface | Slots | | --- | --- | | compactLiveActivity | leftSlot, rightSlot | | sneakPeek | leftSlot, center, rightSlot |

The compact live activity should stay visual and minimal. Put labels, descriptive text, and controls in sneakPeek.

Plugin Settings

Plugins can declare settings in plugin.json. DynamicLake renders those settings with native settings cells and exposes the current values to the plugin at launch.

DynamicLake shows plugin settings in a fixed 520 x 620 window. The header uses the plugin icon and bold plugin name. Settings content is inside a vertical scroll view.

!Plugin settings components showing switch, slider, select, and button rows.

Supported setting types:

| Type | Required fields | Optional fields | | --- | --- | --- | | switch | id, type, title | description, systemImage, tint, default | | slider | id, type, title | description, systemImage, tint, default, min, max, step, suffix | | select | id, type, title, options | description, systemImage, tint, default; options may include systemImage | | button | id, type, title, url | description, systemImage, tint, buttonTitle, destructive |

{
  "settings": [
    {
      "id": "showCodexIcon",
      "type": "switch",
      "title": "Show Codex Icon",
      "systemImage": "terminal.fill",
      "tint": "cyan",
      "default": true
    },
    {
      "id": "staleSeconds",
      "type": "slider",
      "title": "Stale Timeout",
      "min": 15,
      "max": 600,
      "step": 15,
      "suffix": "sec",
      "default": 90
    },
    {
      "id": "agentStyle",
      "type": "select",
      "title": "Agent Style",
      "systemImage": "sparkles",
      "options": [
        {
          "title": "Compact",
          "systemImage": "rectangle.compress.vertical"
        },
        {
          "title": "Detailed",
          "systemImage": "list.bullet.rectangle"
        }
      ],
      "default": "Compact"
    },
    {
      "id": "help",
      "type": "button",
      "title": "Plugin Help",
      "buttonTitle": "Open",
      "url": "https://example.com/help"
    }
  ]
}

Setting validation rules:

At runtime, DynamicLake provides:

| Variable | Value | | --- | --- | | DYNAMICLAKE_PLUGIN_SETTINGS_PATH | JSON file with schemaVersion, identifier, and values. DynamicLake rewrites it when the user changes settings. | | DYNAMICLAKE_PLUGIN_SETTINGS_JSON | The same setting values as compact JSON. | | DYNAMICLAKE_SETTING_<SETTING_ID> | One variable per setting, such as DYNAMICLAKE_SETTING_SHOW_CODEX_ICON=1. |

Long-running plugins should reread or watch DYNAMICLAKE_PLUGIN_SETTINGS_PATH when they need live setting changes.

Components

Supported component types:

| Type | Required fields | Notes | | --- | --- | --- | | text | text | Use for sneak peek labels and marquee text. In compactLiveActivity, use only for duration-style values. Optional style: plain, marquee, compact, timer, or numeric. See JSONPluginAPI — Animated Numbers for numeric. | | image | depends on source | source: sfSymbol, appIcon, or inlineData. | | button | actionID | Optional title, systemImage, role, and shape. | | progress | none | Optional value from 0 to 1. Omit value for indeterminate progress. | | timer | startDate, endDate | ISO-8601 dates. startDate must be before endDate. | | status | none | Optional status: success, failed, inProgress, paused, or warning. |

All components may include:

| Field | Value | | --- | --- | | id | Optional component identifier. | | tint | Optional color: blue, cyan, teal, indigo, purple, pink, green, orange, yellow, red, gray, or white. |

Images

Prefer SF Symbols for small live activity UI.

{
  "type": "image",
  "source": "sfSymbol",
  "systemImage": "bolt.fill",
  "tint": "yellow"
}

Use appIcon to show the plugin icon:

{
  "type": "image",
  "source": "appIcon"
}

Use inlineData only for small PNG or JPEG data. Decoded inline image data must be 48 KB or smaller.

{
  "type": "image",
  "source": "inlineData",
  "mimeType": "image/png",
  "base64Data": "..."
}

File paths, URLs, shell commands, scripts, and arbitrary render code are not part of the component schema.

Buttons And Actions

Buttons never execute commands directly. A button sends an action callback to the plugin over the same framed socket connection.

Buttons use shape: "circle" by default. In sneakPeek, use shape: "roundedRect" when the button needs visible text. Keep compact live activity buttons icon-only.

{
  "type": "button",
  "id": "cancel",
  "title": "Cancel",
  "systemImage": "xmark",
  "actionID": "cancel-build",
  "shape": "roundedRect",
  "role": "destructive"
}
{
  "schemaVersion": 1,
  "type": "action",
  "activityID": "build-42",
  "actionID": "cancel-build",
  "componentID": "cancel",
  "surface": "sneakPeek"
}

The plugin decides what to do with the action and may send an update or dismiss command afterward.

Animated Numbers

Use style: "numeric" for short values that change often, such as a percentage or a count. DynamicLake rolls each digit that changes, similar to SwiftUI's .contentTransition(.numericText()). In the sneak peek, numeric text is bold and slightly smaller than other sneak peek text. Numeric text works in sneakPeek.leftSlot, sneakPeek.center, sneakPeek.rightSlot, and duration-style compactLiveActivity text.

"rightSlot": { "type": "text", "id": "percent", "text": "45%", "style": "numeric" }

Sneak peek side slots widen to fit short numeric text such as 100%. Other text styles in side slots show only their first two characters, so use center for longer text.

Numeric text requires the numericText feature. Without it, show the value in center with style: "compact", or use a progress component in the side slot.

Show The Sneak Peek Automatically

By default a sneak peek opens only while the user hovers over the live activity. Add presentSneakPeek to a create or update command to open it for a number of seconds without hovering:

{
  "schemaVersion": 1,
  "type": "update",
  "activityID": "download",
  "presentSneakPeek": 2,
  "surfaces": {
    "compactLiveActivity": {
      "rightSlot": { "type": "status", "status": "success", "tint": "green" }
    },
    "sneakPeek": {
      "leftSlot": { "type": "button", "systemImage": "folder.fill", "actionID": "show-in-finder" },
      "center": { "type": "text", "text": "Report.pdf", "style": "compact" }
    }
  }
}

presentSneakPeek requires the presentSneakPeek feature. Without it, send the same command without the field; the sneak peek content still updates and appears on hover.

Responses And Errors

DynamicLake can send a response frame after handling a request.

{
  "schemaVersion": 1,
  "type": "response",
  "requestID": "req-1",
  "activityID": "build-42",
  "ok": true
}

If validation fails, DynamicLake sends an error response and keeps the socket open when possible.

{
  "schemaVersion": 1,
  "type": "error",
  "ok": false,
  "error": "Missing field 'compactLiveActivity' at $.surfaces."
}

Topics

Protocol Types