Macros
One macro definition, every Yapture client. This is the full reference — template grammar, primitives namespace, formatters, import/export, sharing, and security.
Overview
A macro is a named replacement: a trigger and an expansion body. Type the trigger in any input that Yapture is watching — the web editor, a system text field on desktop, an input on any web page (Chrome), or a CLI command — and the body lands in its place.
Bodies are templates. They can reference user-supplied variables, live Yapture primitives (your task counts, your active workspace, the time), and use a handful of formatters to massage values into shape.
The grammar is closed by design: no eval, no scripts,
no fetches. The evaluator is a pure function over a closed AST.
- Author a macro in any client (or install one from the Market).
- Yapture stores it server-side and pushes to every authenticated client.
- Type the trigger; the client matches and resolves variables + primitives.
- The expansion replaces the trigger inline. Cursor lands at
{{|}}if present.
Macro Definition Format (MDF)
JSON is canonical. YAML is sugar accepted by CLI and Library import — it expands to canonical JSON on the way in.
Canonical JSON
{
"id": "mcr_01HXYZ...",
"ownerId": "usr_...",
"schemaVersion": 1,
"name": "Email signature",
"slug": "email-signature",
"description": "Standard sign-off with current task count",
"icon": "✉️",
"trigger": {
"value": ";sig",
"mode": "prefix",
"caseSensitive": false,
"explicitOnly": false
},
"scopes": {
"include": ["*"],
"exclude": ["bundle:com.apple.keychainaccess", "domain:bank.example.com"]
},
"variables": [
{ "name": "recipient", "label": "Recipient name", "type": "string", "default": "", "required": true },
{ "name": "tone", "label": "Tone", "type": "enum", "values": ["formal", "casual"], "default": "casual" }
],
"body": "Hi {{recipient}},\n\nThanks — I'll get back to you with the {{yap.tasks.openCount}} open items.\n\n— {{yap.user.name}}",
"tags": ["email", "work"],
"enabled": true,
"source": { "kind": "user" }
} YAML sugar
name: Email signature
trigger: ;sig
variables:
- recipient (required)
- tone: [formal, casual] = casual
body: |
Hi {{recipient}},
Thanks — I'll get back to you with the {{yap.tasks.openCount}} open items.
— {{yap.user.name}}
tags: [email, work]
The source.kind field is one of
user,
pack, or
shared-link. Pack-sourced macros also carry
packId + packVersion.
Template grammar
Every token the evaluator recognises. Anything outside this table is rendered as literal text.
| Token | Meaning | Notes |
|---|---|---|
{{name}} | Variable | Resolved from the macro's variables[] definition. Prompt UI fills it at expansion time. |
{{yap.<path>}} | Primitive | Read-only reference to live Yapture data. See § primitives. |
{{now}} | Date helper | Current ISO timestamp at expansion time. |
{{now+3d}} | Date helper | Relative date arithmetic (+/- d, w, m). |
{{today}} | Date helper | YYYY-MM-DD in user TZ. |
{{today | iso}} | Date helper | Pipe-formatted date — see formatters. |
{{|}} | Cursor sentinel | Optional. Marks where the cursor lands after expansion. |
{{#if expr}} ... {{/if}} | Conditional | Comparison only (>, <, ==). No arithmetic, no chained logic. |
{{#choice}}A|B|C{{/choice}} | Choice | Picks one at random; clients with a prompt UI may offer interactive selection. |
{{! comment }} | Comment | Ignored at evaluation. |
{{!slug}} | Deferred ref | Stored verbatim. Renders as a chip everywhere Yapture displays the text. |
What is not allowed
- Loops (planned for v1.1 — {{#each yap.tasks.recent}}…{{/each}} over a capped iterable).
- Arithmetic beyond comparison (>, <, ==).
- Filesystem, fetch, eval, or Function.
- Nested macro calls. Compose with packs instead.
Formatters
Pipe-applied pure functions. The set is fixed; v1 ships these ~20.
| Name | Signature | Example | Result |
|---|---|---|---|
upper | {{ value | upper }} | {{ "yapture" | upper }} | YAPTURE |
lower | {{ value | lower }} | {{ "Yapture" | lower }} | yapture |
titlecase | {{ value | titlecase }} | {{ "yap once daily" | titlecase }} | Yap Once Daily |
trim | {{ value | trim }} | {{ " hi " | trim }} | hi |
default | {{ value | default: "Unknown" }} | {{ blockers | default: "none" }} | none (when blockers is empty) |
date | {{ value | date: "YYYY-MM-DD" }} | {{ yap.now | date: "YYYY-MM-DD" }} | 2026-05-14 |
iso | {{ value | iso }} | {{ today | iso }} | 2026-05-14T00:00:00Z |
pluralize | {{ value | pluralize: "task" }} | {{ yap.tasks.openCount | pluralize: "task" }} | 4 tasks |
maskEmail | {{ value | maskEmail }} | {{ yap.user.email | maskEmail }} | a***@yapture.com |
Yapture primitives namespace
Read-only references under yap.*.
Resolved at expansion time against the current authenticated session — fetched from
/api/macros/primitives or a local cache.
yap.user.{ id, name, email, alias, avatarUrl, timezone }
yap.now // ISO timestamp
yap.today // YYYY-MM-DD in user TZ
yap.tasks.length
yap.tasks.openCount
yap.tasks.completedCount
yap.tasks.dueTodayCount
yap.tasks.overdueCount
yap.tasks.recent[0..n].{ id, text, priority, dueDate, completed }
yap.tasks.next.{ id, text, dueDate } // next due task or null
yap.workspaces.length
yap.workspaces.active.{ id, name }
yap.goals.length
yap.goals.active[0..n].{ id, name, progress }
yap.tags.list[0..n].{ id, name, color }
yap.notes.length
yap.notes.recent[0..n].{ id, title }
yap.clipboard // (desktop + chrome)
yap.selection // current selection text (where available)
yap.url // current page URL (chrome only)
yap.appBundleId // foreground app ID (desktop only)
A primitive that is unavailable in the current client (e.g. yap.url on desktop)
evaluates to an empty string. The evaluator never errors on a missing primitive.
yap.user.*
| Path | Description | Availability |
|---|---|---|
yap.user.id | Stable user ID. | all |
yap.user.name | Display name. | all |
yap.user.email | Authenticated email address. | all |
yap.user.alias | Public handle (no @). | all |
yap.user.avatarUrl | Profile avatar URL. | all |
yap.user.timezone | IANA TZ ID (e.g. America/Los_Angeles). | all |
yap.now / yap.today
| Path | Description | Availability |
|---|---|---|
yap.now | Server ISO timestamp at expansion time. | all |
yap.today | YYYY-MM-DD in user TZ. | all |
yap.tasks.*
| Path | Description | Availability |
|---|---|---|
yap.tasks.length | Total tasks across visible lists. | all |
yap.tasks.openCount | Tasks not marked complete. | all |
yap.tasks.completedCount | Tasks marked complete. | all |
yap.tasks.dueTodayCount | Tasks with a due date of today. | all |
yap.tasks.overdueCount | Tasks past due date and still open. | all |
yap.tasks.recent[0..n] | Recent tasks. Each has id, text, priority, dueDate, completed. | all |
yap.tasks.next | Next due open task, or null. Has id, text, dueDate. | all |
yap.workspaces.*
| Path | Description | Availability |
|---|---|---|
yap.workspaces.length | Total workspaces. | all |
yap.workspaces.active | Currently selected workspace. Has id, name. | all |
yap.goals.*
| Path | Description | Availability |
|---|---|---|
yap.goals.length | Total goals. | all |
yap.goals.active[0..n] | Active goals. Each has id, name, progress (0–1). | all |
yap.tags.* / yap.notes.*
| Path | Description | Availability |
|---|---|---|
yap.tags.list[0..n] | Available tags. Each has id, name, color. | all |
yap.notes.length | Total notes. | all |
yap.notes.recent[0..n] | Recent notes. Each has id, title. | all |
Context-dependent
| Path | Description | Availability |
|---|---|---|
yap.clipboard | Current clipboard text. | desktop, chrome |
yap.selection | Currently selected text (where available). | web, desktop, chrome |
yap.url | Current page URL. | chrome only |
yap.appBundleId | Foreground app ID (e.g. com.apple.mail). | desktop only |
Variable prompt UX
When a macro fires and has unresolved variables, every client renders the same modal — web popover, Chrome shadow-DOM popover, desktop floating window, CLI Ink form. The live preview re-runs the evaluator on every keystroke.
┌───────────────────────────────┐
│ Email signature │
│ ─────────────────────────────── │
│ Recipient * [_____________] │
│ Tone (•) casual │
│ ( ) formal │
│ ─────────────────────────────── │
│ Preview: │
│ "Hi Bob, Thanks — I'll get back │
│ to you with the 4 open items. │
│ — Alec" │
│ ─────────────────────────────── │
│ [Cancel] [Insert] │
└───────────────────────────────┘ Import / export
Two file shapes — a single macro and a pack. Both round-trip losslessly.
Single macro
- Export:
MacroJSON minusid,ownerId, and timestamps. MIME:application/json. - Import: accepts JSON or YAML. The validator runs first; on slug collision the importer prompts you to rename or overwrite.
Macro pack (.yapack)
A single JSON file (or tarball) with a manifest plus the contained macros.
{
"schemaVersion": 1,
"kind": "macro-pack",
"manifest": {
"name": "Recruiter starter pack",
"slug": "recruiter-starter",
"description": "Outreach, follow-ups, scheduling.",
"icon": "mail",
"version": "1.0.0",
"author": "Yapture",
"compatibility": {
"minYapture": "0.12.0",
"clients": ["app_v2", "browser_v2", "cli_v2", "desktop", "launcher"]
},
"changelog": [
{
"version": "1.0.0",
"date": "2026-05-24",
"notes": ["Initial outreach and follow-up macros."]
}
]
},
"macros": [
{
"slug": "candidate-follow-up",
"name": "Candidate follow-up",
"description": "Polite follow-up after a recruiter screen.",
"tags": ["recruiting", "email"],
"trigger": {
"value": ";cfu",
"mode": "prefix",
"caseSensitive": false,
"explicitOnly": false
},
"scopes": {
"include": ["domain:mail.google.com", "bundle:com.apple.mail"],
"exclude": ["input:password"]
},
"variables": [
{ "name": "candidate", "label": "Candidate name", "type": "string", "required": true },
{ "name": "role", "label": "Role", "type": "string", "required": true }
],
"body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}. I will follow up with next steps shortly.\n\n- {{yap.user.name}}"
}
]
} yap macro pack import <file> and the web Library
both accept this shape.
Macro pack manifest
A macro pack is a versioned bundle of MDF objects. The pack manifest
owns the pack identity - slug,
name,
author,
and version - plus included macros,
changelog entries, and client compatibility.
Pack macros do not include user ownership fields in the pack file. Those are assigned when the pack is installed and materialized into user macro records.
{
"schemaVersion": 1,
"kind": "macro-pack",
"manifest": {
"name": "Recruiter starter pack",
"slug": "recruiter-starter",
"description": "Outreach, follow-ups, scheduling.",
"icon": "mail",
"version": "1.0.0",
"author": "Yapture",
"compatibility": {
"minYapture": "0.12.0",
"clients": ["app_v2", "browser_v2", "cli_v2", "desktop", "launcher"]
},
"changelog": [
{
"version": "1.0.0",
"date": "2026-05-24",
"notes": ["Initial outreach and follow-up macros."]
}
]
},
"macros": [
{
"slug": "candidate-follow-up",
"name": "Candidate follow-up",
"description": "Polite follow-up after a recruiter screen.",
"tags": ["recruiting", "email"],
"trigger": {
"value": ";cfu",
"mode": "prefix",
"caseSensitive": false,
"explicitOnly": false
},
"scopes": {
"include": ["domain:mail.google.com", "bundle:com.apple.mail"],
"exclude": ["input:password"]
},
"variables": [
{ "name": "candidate", "label": "Candidate name", "type": "string", "required": true },
{ "name": "role", "label": "Role", "type": "string", "required": true }
],
"body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}. I will follow up with next steps shortly.\n\n- {{yap.user.name}}"
}
]
} Materialized user macro
After install, each included macro receives user-specific IDs and source pack metadata. Clients use this metadata to show pack origin, keep pack-sourced macros read-only, and remove them when the pack is uninstalled.
{
"id": "mcr_01J0PACKSOURCED",
"ownerId": "usr_01J0USER",
"slug": "candidate-follow-up",
"name": "Candidate follow-up",
"trigger": {
"value": ";cfu",
"mode": "prefix",
"caseSensitive": false,
"explicitOnly": false
},
"body": "Hi {{candidate}},\n\nThanks for speaking with us about {{role}}.",
"enabled": true,
"source": {
"kind": "pack",
"packSlug": "recruiter-starter",
"packVersion": "1.0.0",
"macroSlug": "candidate-follow-up",
"readonly": true
}
} Install, sync, and conflicts
Macro pack install state is canonical in
GET /api/user/installed.
The macro definitions themselves sync through
GET /api/macros.
A pack install appears in inventory as
macro_pack:<slug>.
Install
Market creates a UserMacroPack record, validates the pack, and materializes each included macro into the user macro list with source.kind = "pack".
Sync
Clients fetch GET /api/macros after auth and cache pack-sourced macros locally for offline expansion.
Inventory
GET /api/user/installed includes the pack as key macro_pack:<slug>, so app_v2, browser_v2, cli_v2, desktop, and launcher show the same installed state.
Uninstall
DELETE /api/market/macros/:slug/install removes the pack install and deletes unchanged pack-sourced macros on the next client sync.
Forks
If a user edits a pack macro, clients should fork it into source.kind = "user" or block editing; uninstall does not remove user-owned forks.
Conflict behavior
| Case | Behavior |
|---|---|
| Macro slug conflicts with a user macro | Skip the pack macro and record an install warning. The user-owned macro wins. |
| Trigger conflicts with an enabled macro | Keep the pack macro disabled until the user changes one trigger or disables the conflicting macro. |
| Pack uninstalled while a client is offline | Remove pack-sourced macros the next time that client syncs. |
| Pack version changes | Update unchanged pack-sourced macros; preserve user-owned forks and locally disabled state. |
| Primitive unavailable in a client | Resolve to an empty string and keep expansion deterministic. |
Cross-client availability
| Client | Macro behavior |
|---|---|
app_v2 | Authoring, Market install/uninstall, macro list, and server-side sync source. |
browser_v2 | After auth, syncs GET /api/macros into extension storage and expands from local cache. |
cli_v2 | Lists, syncs, imports, exports, and expands macros from ~/.yapture/macros.json. |
desktop | System text expansion, macro manager, and pack source metadata. |
launcher | Reads Desktop exported macro cache for searchable macro entries. |
site_v2 | Public documentation and Market listings only. |
Quotas & security
The macro system is designed to be useful and boring at the same time.
- No code execution. The evaluator is a pure function over a closed AST — no
Function, noeval, no dynamic require. - Primitives are read-only. Macros can never mutate user data; they only render it.
- Per-user quota: 500 macros, 50 KB per macro body, 20 variables per macro. Per pack: 100 macros, 1 MB manifest. Enforced at the API.
- Scoped expansion.
scopes.excludeis honoured on desktop + Chrome. Defaults exclude banking domains, password fields (<input type="password">), and inputs marked with sensitive heuristics. - Chrome content scripts never read field values upstream. Macro definitions and primitives flow to the field; field values never leave the page.
- Pack validator rejects packs containing undeclared variables, banned formatters, or oversize manifests. Abuse reports surface in the admin portal.
Example library
A few starting points. Copy, paste, edit.
# ~/Documents/yapture-macros.yaml
- name: Address
trigger: ;addr
body: |
123 Main St
San Francisco, CA 94110
- name: Standup
trigger: ;standup
body: |
Yesterday: {{yesterday}}
Today: {{#if yap.tasks.dueTodayCount > 0}}{{yap.tasks.dueTodayCount}} due ({{yap.tasks.next.text}}){{/if}}
Blockers: {{blockers | default: "none"}}
variables:
- yesterday (required)
- blockers
- name: Meeting note header
trigger: ;mtg
body: |
# {{title}} — {{today | date: "YYYY-MM-DD"}}
Attendees: {{attendees}}
Workspace: {{yap.workspaces.active.name}}
## Notes
{{|}}
variables:
- title (required)
- attendees (required) Looking for the feature page?
See the marketing landing for screenshots, the cross-client matrix, and a quick tour of what macros feel like in practice.
Features → Macros