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.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.