Pasteon Workflow Protocol
This document is the machine-readable reference for creating, explaining, or generating Pasteon Workflows. It describes the current Workflow model implemented by Pasteon for macOS.
Canonical human-readable reference: https://www.pasteon.app/workflows/protocol
Overview
A Workflow turns a selected clipboard item into a reusable Pasteon Action.
Two editor modes are supported:
steps: a linear visual pipeline of conditions, transforms, scripts, and one final output.code: an inline or external script that reads versioned JSON from stdin and writes protocol JSON to stdout.
Two execution modes are supported:
simple: execute immediately and return one result.scriptFilter: return up to 20 candidate Actions duringfilter, then compute only the selected result duringexecute.
WorkflowDefinition
| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| id | UUID | yes | Stable Workflow identifier. |
| name | string | yes | Display name in Settings and the Action list. |
| summary | string | no | User-facing explanation. |
| symbolName | SF Symbol name | yes | Action icon. |
| isEnabled | boolean | yes | Disabled Workflows remain saved but do not appear as Actions. |
| sortOrder | number | yes | Ordering after built-in Primary Actions. |
| mode | simple or scriptFilter | yes | Execution model. |
| supportedTypes | PasteType[] | yes | Clipboard types where the Workflow may appear. |
| editorMode | steps or code | no | Missing values from older files are treated as code. |
| script | ScriptConfiguration | code only | Script source, interpreter, arguments, working directory, and timeouts. |
| steps | WorkflowStepDefinition[] | steps only | Ordered visual pipeline. |
| variables | WorkflowVariableDefinition[] | no | Reusable Text and Secret values. |
| requiresTrustConfirmation | boolean or null | imported code | Imported code is disabled until the user confirms trust. |
Supported PasteType values include text, markdown, html, json, url, color, date, timestamp, fileURL, imageURL, videoURL, image, and appObject.
Workflow variables
Variables let a Workflow reuse configuration without hard-coding it in every Template or script.
WorkflowVariableDefinition
| Field | Type | Required | Meaning |
| --- | --- | --- | --- |
| id | UUID | yes | Stable identity used to associate Secret data with a Keychain entry. |
| name | string | yes | Case-sensitive lookup name. |
| kind | text or secret | yes | Storage behavior. |
| value | string | text only | Text value stored with the Workflow. Secret exports always contain an empty value. |
Variable names must:
- match
[A-Za-z_][A-Za-z0-9_]*; - be unique inside one Workflow;
- be referenced with exactly the same letter case.
Example:
{
"variables": [
{
"id": "C3EEAF0E-0AD8-4A26-924C-CB867088D955",
"name": "API_BASE_URL",
"kind": "text",
"value": "https://api.example.com"
},
{
"id": "7E05B2EE-E745-4558-A267-E63B7D1E2235",
"name": "API_TOKEN",
"kind": "secret",
"value": ""
}
]
}
Text variables
- Stored in the Workflow data.
- Included when the Workflow is copied or exported.
- Suitable for endpoints, formats, prefixes, project names, and other non-sensitive configuration.
Secret variables
- Stored in the macOS Keychain under the Workflow ID and variable ID.
- Never exported with their value.
- Must be entered again after importing a Workflow.
- Are deleted from Keychain when the variable or Workflow is removed.
- Are supplied to the Workflow only at runtime.
Copying a Workflow creates new variable IDs. Secret values therefore do not silently share Keychain entries between the original and its copy.
Variables in visual Steps
Use this exact placeholder syntax in Template content or an output file name:
{{variables.API_BASE_URL}}
{{variables.API_TOKEN}}
Built-in placeholders are:
{{input}}: current text from the preceding step.{{sourceApp}}: source application bundle identifier, or an empty string.{{targetApp}}: frontmost target application bundle identifier, or an empty string.{{clipboardType}}: current PasteType raw value.{{date}}: current date and time in ISO 8601 format.{{variables.NAME}}: resolved user variable.
An absent or empty variable resolves to an empty string.
Variables in Code Workflows
Resolved variables are included in the top-level variables object sent to stdin:
const payload = JSON.parse(input);
const baseURL = payload.variables.API_BASE_URL ?? "";
const token = payload.variables.API_TOKEN ?? "";
Both Text and Secret values are available during filter and execute.
Do not write Secret values to stdout or stderr. stdout must contain only protocol JSON, and stderr may be visible in diagnostics.
ScriptConfiguration
| Field | Type | Meaning |
| --- | --- | --- |
| source | inline or external | Inline stores code in the Workflow; external points to a file. |
| code | string | Inline script body. |
| scriptPath | file path | External script path; tilde is expanded. |
| interpreterPath | executable path | Interpreter such as /bin/zsh or a Node executable. |
| arguments | string[] | Passed as separate process arguments. No shell joining or wildcard expansion. |
| workingDirectory | directory path | Process current directory. |
| filterTimeout | seconds | Clamped to 1–120; default 5. |
| executeTimeout | seconds | Clamped to 1–120; default 15. |
JSON sent to stdin
{
"version": 1,
"workflowID": "5F604A89-7AC2-45D9-B48C-BB540B60CA67",
"phase": "execute",
"paste": {
"id": "D0A4E45A-61F6-4DAF-BC1E-4E17A23AA5C0",
"type": "json",
"text": "{\"name\":\"Pasteon\"}",
"sourceAppBundleIdentifier": "com.apple.Safari",
"representations": [
{
"type": "public.utf8-plain-text",
"value": "{\"name\":\"Pasteon\"}",
"path": null,
"fileName": null,
"fileSize": 18,
"temporary": false
}
]
},
"targetApp": {
"bundleIdentifier": "com.apple.dt.Xcode",
"name": "Xcode"
},
"directories": {
"input": "/tmp/Pasteon/WorkflowInputs/RUN_ID",
"output": "~/Library/Application Support/Pasteon/WorkflowOutputs/RUN_ID"
},
"variables": {
"API_BASE_URL": "https://api.example.com",
"API_TOKEN": "secret supplied at runtime"
},
"actionID": null,
"argument": null
}
Input fields
version: protocol version; currently1.workflowID: stable Workflow UUID.phase:filterorexecute.paste.id: selected history item UUID.paste.type: derived PasteType.paste.text: text representation when available.paste.sourceAppBundleIdentifier: app that originally copied the item.paste.representations: pasteboard representations.targetApp.bundleIdentifier: frontmost target app bundle identifier.targetApp.name: target app display name.directories.input: temporary input directory, removed after the run.directories.output: directory for files returned by the Workflow.variables: resolved Text and Secret values keyed by variable name.actionID: selected Filter candidate ID during execute, otherwise null.argument: selected Filter candidate JSON argument, otherwise null.
Reading input in common languages
Every Code Workflow must read the complete stdin stream before parsing JSON. Write exactly one protocol JSON object to stdout. Send logs and diagnostics to stderr.
Pasteon does not bundle language runtimes or jq. Set interpreterPath to an executable that exists on the current Mac. Homebrew paths commonly start with /opt/homebrew/bin on Apple silicon and /usr/local/bin on Intel Macs. Use which node, which python3, or the equivalent to find the actual path.
All examples below read:
payload.paste.text;- the user-defined
PREFIXvariable frompayload.variables; - and return a valid Simple Workflow text result.
JavaScript / Node.js
Example interpreter: /opt/homebrew/bin/node
const fs = require("node:fs");
const payload = JSON.parse(fs.readFileSync(0, "utf8"));
const text = payload.paste?.text ?? "";
const prefix = payload.variables?.PREFIX ?? "";
process.stdout.write(JSON.stringify({
version: 1,
result: { type: "text", value: prefix + text }
}));
Python 3
Example interpreter: /opt/homebrew/bin/python3
import json
import sys
payload = json.load(sys.stdin)
text = payload.get("paste", {}).get("text") or ""
prefix = payload.get("variables", {}).get("PREFIX", "")
json.dump({
"version": 1,
"result": {"type": "text", "value": prefix + text}
}, sys.stdout)
Ruby
Example interpreter: /opt/homebrew/bin/ruby
require "json"
payload = JSON.parse($stdin.read)
text = payload.dig("paste", "text") || ""
prefix = payload.dig("variables", "PREFIX") || ""
$stdout.write(JSON.generate({
version: 1,
result: { type: "text", value: prefix + text }
}))
PHP
Example interpreter: /opt/homebrew/bin/php
<?php
$payload = json_decode(stream_get_contents(STDIN), true);
$text = $payload["paste"]["text"] ?? "";
$prefix = $payload["variables"]["PREFIX"] ?? "";
echo json_encode([
"version" => 1,
"result" => ["type" => "text", "value" => $prefix . $text]
]);
Swift
Example interpreter: /usr/bin/swift
Swift is available when the relevant Apple developer tools are installed.
import Foundation
let data = FileHandle.standardInput.readDataToEndOfFile()
let payload = try JSONSerialization.jsonObject(with: data) as? [String: Any]
let paste = payload?["paste"] as? [String: Any]
let variables = payload?["variables"] as? [String: String]
let text = paste?["text"] as? String ?? ""
let prefix = variables?["PREFIX"] ?? ""
let response: [String: Any] = [
"version": 1,
"result": ["type": "text", "value": prefix + text]
]
let output = try JSONSerialization.data(withJSONObject: response)
FileHandle.standardOutput.write(output)
Zsh with jq
Interpreter: /bin/zsh
Zsh can read the raw stdin stream, but this example requires a separately installed jq executable for reliable JSON parsing and encoding.
#!/bin/zsh
set -euo pipefail
payload="$(cat)"
text="$(printf '%s' "$payload" | jq -r '.paste.text // ""')"
prefix="$(printf '%s' "$payload" | jq -r '.variables.PREFIX // ""')"
jq -n --arg value "$prefix$text" '{
version: 1,
result: {type: "text", value: $value}
}'
Representation fields
| Field | Type | Meaning |
| --- | --- | --- |
| type | UTI string | Pasteboard type, such as public.utf8-plain-text, public.file-url, or public.png. |
| value | string or null | Inline UTF-8 text or original file URL string. |
| path | file path or null | File or materialized binary representation. |
| fileName | string or null | Suggested file name. |
| fileSize | integer | Byte count. |
| temporary | boolean | Whether Pasteon created and owns this temporary file. |
Script Filter response
During filter, return:
{
"version": 1,
"actions": [
{
"id": "swift",
"title": "Generate Swift Model",
"subtitle": "Create Codable types",
"icon": "swift",
"argument": {
"language": "swift"
}
}
]
}
Rules:
- Maximum 20 candidates.
idmust be unique within the response.titleis required.subtitleandiconare optional.argumentmay be any JSON value and is returned unchanged during execute.- Filter should return metadata, not precompute every final result.
Execute response
Text
{
"version": 1,
"result": {
"type": "text",
"value": "Ready to paste"
}
}
Files
Files must be created inside directories.output. Return relative paths:
{
"version": 1,
"result": {
"type": "files",
"paths": ["GeneratedModel.swift"]
}
}
Open URL
{
"version": 1,
"result": {
"type": "openURL",
"url": "https://example.com"
}
}
Runtime rules
- stdout is reserved for one protocol JSON object.
- Write diagnostics to stderr.
- Non-zero exit status is a failure.
- Filter and Execute timeouts are clamped to 1–120 seconds.
- A single argument is limited to 64 KB.
- stdout is limited to 2 MB.
- stderr is limited to 256 KB.
- Switching Paste items, leaving Filter results, editing or deleting the Workflow, or closing the window cancels execution.
- Output directories older than 24 hours are removed during application cleanup.
- Scripts run locally with the current macOS user's permissions.
- Imported code requires trust confirmation before it can run.
Process environment variables:
PASTEON_WORKFLOW_IDPASTEON_WORKFLOW_PHASEPASTEON_INPUT_DIRPASTEON_OUTPUT_DIR
User-defined Workflow variables are intentionally passed through stdin, not added to the process environment.
Complete Node.js Script Filter example
async function readStdin() {
let input = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) input += chunk;
return JSON.parse(input);
}
async function main() {
const payload = await readStdin();
const text = payload.paste?.text ?? "";
const prefix = payload.variables.PREFIX ?? "";
const token = payload.variables.API_TOKEN ?? "";
if (payload.phase === "filter") {
process.stdout.write(JSON.stringify({
version: 1,
actions: [
{
id: "prefix",
title: "Add configured prefix",
subtitle: "Uses the PREFIX Workflow variable",
icon: "text.insert",
argument: null
}
]
}));
return;
}
// Use token for a local or remote operation if needed, but never log it.
void token;
process.stdout.write(JSON.stringify({
version: 1,
result: {
type: "text",
value: `${prefix}${text}`
}
}));
}
main().catch((error) => {
process.stderr.write(`${error.stack ?? error.message}\n`);
process.exit(1);
});
Import and export
A .pasteon-workflow file is versioned JSON and may contain one Workflow or a Workflow list.
- Text variable values are included in exports.
- Secret variable definitions are included, but their values are cleared.
- Imported scripts are marked untrusted and disabled until confirmed.
- Review imported code because it executes locally with the current user's permissions.