Alfred Workflow Authoring
Build Alfred workflows the way Alfred's GUI editor would: a native object graph in
info.plist, not a single mega-script. A native graph is what the user later sees and
edits on the canvas, gets per-object debugging, and survives GUI round-trips.
Requires macOS with Alfred 5 + Powerpack. If /Applications/Alfred 5.app is missing,
stop and tell the user instead of building blind.
Core model
- A workflow IS its
info.plist(Apple XML plist). A.alfredworkflowfile is a ZIP of the workflow folder's contents withinfo.plistat the archive root. - Four coupled top-level structures must stay consistent:
objects(nodes),connections(edges, keyed by source uid),uidata(canvas positions, keyed by uid),userconfigurationconfig(user-facing settings). - Prefer native objects (Conditional, Arg and Vars, Automation Task, List Filter,
Text View, Notification, …) over folding logic into one big Run Script. Scripts are
for actual logic only. For rich output (rendered Markdown, image grids, PDFs) reach
for a User Interface view rather than a Script Filter row — see the User Interface
section in
references/objects-catalog.md. - Never assemble plist XML by string concatenation —
&,<,>inside inline scripts silently corrupt the file. Start from{{SKILL_DIR}}/assets/skeleton.plistor generate/edit withpython3+plistlib.
Authoring loop
- Design the graph first: triggers/inputs → utilities → actions → outputs. Note
modifier-key alternatives, conditional branches, and every value a user should be
able to change (keyword, API key, paths → User Configuration). Design for live
feedback: one Script Filter whose items preview the outcome (with real item
icons) beats Enter-chained input objects — read
references/ux-and-performance.mdbefore settling the flow. - Scaffold: copy
{{SKILL_DIR}}/assets/skeleton.plistinto a working folder asinfo.plist, or build withplistlib. Real-world reference plists live in{{SKILL_DIR}}/references/examples/. - Write objects per
references/objects.md. Generate a fresh UPPERCASE UUID per object (uuidgenalready outputs uppercase). - Wire connections (same file, "Connections" section) — including conditional
sourceoutputuidrouting and modifier-key edges. - Lay out the canvas per
references/layout.mdso the graph reads left→right and nothing overlaps. - Validate:
python3 {{SKILL_DIR}}/scripts/validate.py <workflow-dir>. Fix every error; fix warnings too unless you can say why one is intentional. - Pack:
{{SKILL_DIR}}/scripts/pack.sh <workflow-dir> [out.alfredworkflow]. If there is noicon.pngyet, create one first — see the Icon section ofreferences/packaging.md(generate via an available image-generation skill). - Install and verify per
references/packaging.md, then sanity-check in Alfred's debugger and on the canvas.
Critical rules
These are the silent-failure points — Alfred won't raise an error, the workflow just misbehaves:
- Object
uids are UPPERCASE UUIDs and must be unique.connectionsanduidatareference objects only by these uids. - The connection key for "close Alfred window" is spelled
vitoclose— Alfred's own historical typo. A correctly-spelledvetocloseis silently ignored. - Script
config.typeintegers: use only verified values —0bash,2ruby,7JXA (osascript -l JavaScript),8External Script,11zsh (modern default; Alfred's own automation tasks use 11). An externalscriptfileruns ONLY withtype8 — any other type silently runs the inlinescriptinstead. For python3 or anything else, usescriptfile+ type 8, or a one-line shell shim (/usr/bin/env python3 script.py "$1") under type 0/11. - Pass input as argv (
scriptargtype= 1), not{query}(= 0): argv needs no escaping. Keep bothscriptandscriptfilekeys present; the unused one is an empty string. - Non-ASCII query/argv text can arrive NFD-decomposed (macOS input methods, file
paths), so Korean and accented matches against NFC data silently miss. Normalize
to NFC before comparing or searching — e.g.
unicodedata.normalize('NFC', text)in python3. - A Conditional's branch routing lives in the connection's
sourceoutputuid, which must equal theuidinside the matchingconfig.conditions[]entry. The edge withoutsourceoutputuidis the else branch. - Anything user-tunable (keywords, API keys, paths, options) goes in
userconfigurationconfigand is referenced as{var:name}in object fields — never hardcoded, never in plainvariables(those export with the file; secrets would leak). - In scripts: stdout is the result stream, stderr goes to Alfred's debugger. PATH is
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbinand rc files are not loaded. Use paths relative to the workflow folder for bundled files.
References
| Read | When |
|---|---|
references/objects.md |
Writing any object or connection — the full type-string index, the mechanics (envelope, connections, script config), and the common-object config schemas |
references/objects-catalog.md |
Config schemas for the specialized objects the index routes here — User Interface views, Automations extras, and the less-common triggers/inputs/actions/utilities/outputs |
references/layout.md |
Assigning uidata positions, notes, colors |
references/script-filter-json.md |
Writing a Script Filter's script output |
references/ux-and-performance.md |
Designing the interaction — live previews, forgiving input formats, item icons, caching, parallel fetch, rerun stability |
references/config-and-variables.md |
User Configuration fields, variable scopes, runtime env vars |
references/packaging.md |
Packing, installing, reloading, debugging, distributing |
references/examples/ |
Reference info.plist files — official/popular workflow exports plus one complete validated example — copy shapes from here when unsure |
Every object's exact type string is in the type index at the top of
references/objects.md — look it up there, never guess (a wrong string is silently
dropped). When an object's config shape is not catalogued either, don't guess key
names: find a real instance — in references/examples/, an installed workflow under
Alfred's preferences folder, or an alfredapp org workflow on GitHub — and copy its
shape.