Extension Authoring

Package Format & Directory Schema

Structure of .openclipext packages, asset naming, options schema, and SecretStore integration.

On this page (12 sections)

Authoritative reference: the current manifest schema, all action kinds, options, requirements, groups, and the runtime result surface are documented in Extensions/AGENTS.md. This page is a summary and may lag it.

OpenClip extension packages are directory bundles (suffixed with .openclipext or plain directories) containing a manifest JSON file (manifest.json, openclip.json, or Config.json) along with supporting script files and local asset icons.


Package Directory Structure#

my-extension.openclipext/
├── manifest.json # Required: Extension manifest definition
├── main.js # Action script file (JavaScript, AppleScript, or Executable)
├── icon.png # Optional: Local action icon image
└── README.md # Optional: Documentation

Installation#

  • Finder Double-Click: .openclipext bundles are registered macOS document packages owned by OpenClip (com.openclip.extension). Double-clicking a .openclipext package (or .openclipext.zip archive) opens a confirmation dialog in OpenClip with package metadata, installs the package directly into ~/.openclip/extensions, and marks it trusted.
  • Extension Store: Deep links (openclip://install?id=...&url=...) download and install verified releases.
  • Manual / CLI: Drop folders into ~/.openclip/extensions or run ./Extensions/scripts/install.sh <path>.

Manifest JSON Schema (ExtensionMetadata)#

OpenClip decodes extension metadata via ExtensionMetadata. To ensure backward compatibility, the decoder supports both modern camelCase keys and legacy capitalized/singular keys.

Complete Example manifest.json#

json
{
 "identifier": "com.example.jsonformatter",
 "name": "JSON & Text Utilities",
 "actions": [
 {
 "id": "com.example.jsonformatter.prettify",
 "title": "Prettify JSON",
 "icon": "symbol:doc.plaintext",
 "type": "javascript",
 "script": "main.js"
 },
 {
 "id": "com.example.jsonformatter.docs",
 "title": "Search JSON Docs",
 "icon": "symbol:magnifyingglass",
 "url": "https://developer.mozilla.org/en-US/search?q={query}"
 }
 ],
 "options": [
 {
 "identifier": "indent_spaces",
 "label": "Indentation Spaces",
 "type": "string",
 "default": "2"
 }
 ]
}

Manifest Fields Reference#

Top-Level Metadata (ExtensionMetadata)#

FieldTypeLegacy AliasDescription
identifierStringid, IdentifierUnique package identifier (e.g. com.user.ext).
nameString / ObjectNameDisplay name of the extension package. Plain string or localized dictionary (e.g. {"en": "Word Tools", "zh-Hans": "字词工具"}).
descriptionString / ObjectDescriptionOptional package summary. Plain string or localized dictionary.
actionsArray / Objectaction, ActionsList of action definitions (or single action object).
optionsArrayOptionsOptional array of user-configurable settings.
keywordsArray / String—Optional search keywords for the package actions in the action-search palette.
versionString—Declared package version; recorded in the validation log line, not used for loading.
capabilitiesArray—Declared runtime capabilities. The known set is empty on day one, so any non-empty value rejects the manifest. Reserved.
outputString—Optional default output contract kind ("text", "file", "none", "dynamic") inherited by actions in the package.
resultString—Optional default delivery recommendation ("preview", "paste", "copy", "paste-or-copy", "open", "save") inherited by actions.

Manifests are validated on load (ManifestValidator): unknown action kinds, missing required fields (keyPress/shortcutName/subActions/executable payload), and any declared capability reject the package, which is then logged (category extensions) rather than silently skipped. See docs/architecture/extensions.md.

Action Object (ExtensionActionMetadata)#

FieldTypeDescription
idStringUnique action identifier. If omitted, generated as <manifest.id>.action.<index>.
titleString / ObjectDisplay title presented in UI surfaces. Plain string or localized dictionary (e.g. {"en": "Count", "zh-Hans": "统计"}).
iconStringIcon definition. Accepts symbol:sf_symbol_name, local filename (icon.png), or URL.
typeStringRuntime kind (case-insensitive): "url" (default when absent), "javascript" ("js"), "applescript", "shell" ("shellinline"), "script" ("scriptfile"), "textSnippet" ("snippet"/"text"), "webSearch" ("web"/"search"), "keyPress" ("keys"), "service" ("servicemenu"), "shortcut" ("keyboardshortcut"), "group" ("subactions"). Unknown values reject the manifest at load.
scriptStringPath to script file relative to extension directory (defaults to main.js).
scriptCodeStringInline script code string (used when code is embedded directly in manifest/snippet).
asyncBooleanOptional. For type: "javascript" only: runs the script asynchronously, enabling a fetch() polyfill and awaiting the entry function's returned promise. Default false (legacy synchronous mode).
urlStringURL template pattern string with {query} or {text} placeholders.
regexStringOptional regular expression pattern to gate action visibility.
keywordsArray / StringOptional search keywords for the action palette (e.g. ["stats", "length", "字数"]).
loadingBooleanOptional. Closes popup immediately and shows a spinner toast until the action returns.
loadingMessageString / ObjectOptional text for loading spinner toast. Plain string or localized dictionary.
keyPressStringKey-press spec like "command+shift+o" (for type: "keyPress").
shortcutNameStringName of a macOS Shortcut to run (for type: "shortcut").
serviceNameStringReserved for the macOS Services menu (for type: "service").
subActionsArraySub-action objects for type: "group"; rendered as a sub-menu with IDs <groupID>.<subID>.
outputStringOptional declared output kind: "text", "file", "none", "dynamic". Inherits package-level output if omitted, or falls back to inference sniffing for legacy extensions.
resultStringOptional recommended delivery: "preview" (result card), "paste", "copy", "paste-or-copy", "open", "save". Defaults to package-level default or kind defaults (paste-or-copy for text, preview for file).
secondaryObjectOptional. Secondary-click (right-click/⇧-click) outcome: `{ "type": "copy"
toastObjectOptional. Primary-click companion toast `{ "message": string
secondaryToastObjectOptional. Secondary-click companion toast (same shape as toast). Valid on all kinds. Dash alias: secondary-toast.
requirements.contentArrayNon-empty list of url, email, date, path, phone, address; any detected type passes, makes the action contextual, and supplies openclip.input.detected. Requires 1.9.0.
requirements.inputStringInput contract: optional, text (default), liveSelection, or editableSelection. Legacy requiresSelection/requires-selection maps true to text and false to optional; do not combine legacy and new keys.
requirements.requiresPasteTargetBooleanOptional destination gate. true requires a confirmed Paste-capable target; false or omission means no paste target is required, not that paste is forbidden. Dash alias: requires-paste-target.

text accepts nonblank selection, clipboard, or OCR text. liveSelection requires text from the current live selection. editableSelection additionally requires confirmed editability of that selection; unknown editability fails closed. Paste destination support is independent from input source, so clipboard-derived transformations can still be pasted into a confirmed destination. Older OpenClip releases ignore unknown manifest keys; do not publish catalog packages that rely on these gates until an enforcing release exists. The current source version 1.7.3 is not a confirmed compatibility floor, so minOpenClipVersion must wait for that release's version to be established.

The secondary/toast/secondaryToast keys map onto the per-action Action.delivery (see Extensions/AGENTS.md §5b); the delivery decision (Select → Probe → Toast) then applies the probe and resolves the companion toast.

Extension Output Contract & Delivery Resolution#

OpenClip uses an Author Output Contract system (output and result) for extensions:

  • output (text | file | none | dynamic): Declares what type of output the action produces. Non-output actions (url, keyPress, shortcut, service) default to none.
  • result (preview | paste | copy | paste-or-copy | open | save): Declares the author's recommended default outcome. Text actions default to paste-or-copy; file actions default to preview.
  • User Customization: Users can override the delivery mode per-action in Settings (Show in card, Paste, Copy).
  • Clipboard Invariant: Secondary click (right-click or ⇧-click) always copies the result to the clipboard (or displays a preview card if the primary click was copy), unless the action explicitly declares a custom secondary outcome.

Native File Output Results#

Actions implemented in JavaScript (openclip.file, openclip.copyFile, openclip.saveFile) or executable scripts (JSON {"type": "file" | "copyFile" | "saveFile", ...} or auto-detected regular file paths on stdout) can produce native file results. When returning a file, OpenClip renders an interactive file preview card in the popup panel (supporting inline image/SVG previews, system file icons, metadata inspection, drag-and-drop into other apps, and Open/Copy/Save shortcuts) or triggers direct clipboard copy / disk saving.

type: "canvas" (removed)#

The former interactive-canvas kind "canvas" was removed; a type: "canvas" manifest is rejected at load (unknownActionKind("canvas")).


Options Schema (ExtensionOptionMetadata)#

Extensions can expose user preferences rendered in the Preferences window under Dynamic Action Configuration.

FieldTypeLegacy AliasDescription
identifierStringid, IdentifierOption key used when reading configuration.
labelString / ObjectLabelUser-facing title in the Preferences panel. Plain string or localized dictionary.
typeStringTypeInput control type: "string", "boolean", "multiple" (picker; see options), "secret" (SecretStore-backed).
defaultStringDefaultDefault value if unspecified by the user.
optionsArrayOptionsCandidate choices for type: "multiple".

Option Storage & Retrieval#

  • Non-secret option values are saved through SettingsStore using typed setting key strings: SettingKey<String>("action.<id>.option.<identifier>", defaultValue:).
  • type: "secret" option values live in SecretStore (~/.openclip/secrets.json with POSIX 0600 permissions), never SettingsStore/UserDefaults — resolved via SecretActionOptionStore.
  • Direct UserDefaults.standard access is discouraged and should not be added in new code. The JavaScript runtime reads options through the injected ActionOptionReading store (OpenClipJSHost), not UserDefaults.

Native result shapes and migration from retired requirements.expression are documented in native content requirements.