| name | drafts-actions |
| description | Create and script actions for the Drafts app. Use this skill when the user needs help creating custom Drafts actions, understanding action steps, using template tags, scripting with JavaScript, or configuring action workflows for automation and productivity. |
Drafts Actions
Overview
This skill provides comprehensive guidance for creating custom actions in Drafts, the text capture and automation app for iOS and macOS. Actions are powerful automation workflows that combine multiple steps to process text, integrate with external services, and automate repetitive tasks.
Core Concepts
What Are Actions
Actions in Drafts are automation workflows consisting of one or more sequential steps. Each action can:
- Process and transform draft content
- Send data to external services
- Create files, emails, messages, and tasks
- Run custom JavaScript code
- Integrate with other apps via URL schemes
Action Components
Every action has three core components:
- Metadata: Name, icon (including SF Symbols), color, keyboard shortcut
- Steps: Sequential operations to perform (System, Services, Utility, Advanced)
- Configuration: Behavior after success (archive, tag, etc.), notifications, logging
When Actions Execute
Actions can be triggered via:
- Action bars (keyboard row or action list)
- External keyboard shortcuts
- URL schemes (
drafts://x-callback-url/runAction)
- Scheduled automation (via Shortcuts)
Creating Actions
UI-Based Creation
To create an action through the Drafts interface:
iOS:
- Open action list
- Tap "+" button
- Configure name, icon, and tint color
- Tap "Steps" to add action steps
- Configure "After Success" behavior if needed
macOS:
- Open action list
- Click "+" button or File → New Action
- Follow same configuration process
Programmatic Creation
Actions can be created dynamically via JavaScript scripts within Drafts:
let group = ActionGroup.find("My Actions");
let action = Action.create();
action.name = "My Custom Action";
let step = action.addStep();
step.type = "script";
step.script = `
let content = draft.content.toUpperCase();
draft.content = content;
draft.update();
`;
action.actionGroup = group;
action.update();
Action Sharing and Export
Actions can be shared via URL schemes:
- Export: Right-click action → "Share" → Copy URL
- Import: Open action URL or use
drafts://action?data=[URL-encoded-JSON]
- Directory: Post to the Drafts Directory for public sharing
Action Steps
Actions consist of sequential steps from four categories:
System Steps (11 types)
Built-in iOS/macOS integration steps:
- Share: Send to system share sheet
- Clipboard: Replace, append, or prepend to clipboard
- Mail: Create email via Mail.app or background service (supports HTML)
- Message: Send iMessage to recipients
- Reminder: Create individual reminder
- List in Reminders: Convert draft lines to multiple reminders
- Event: Create calendar event
- Export: Save files to Files.app with folder selection
- Open in...: Share files via document interaction (iOS only)
- Print: Send to AirPrint printer (text or HTML)
- Speak Text: Use text-to-speech with auto-start/close
Service Steps (22 integrations)
External service integrations:
File Storage:
- File (iCloud Drive/local), Dropbox, Google Drive, Box, OneDrive, WebDAV
Task Management:
- Google Tasks, Microsoft To Do, Todoist
Notes & Documentation:
- Evernote (iOS only), OneNote, Notion
Email Services:
Publishing:
- WordPress, Ghost, Medium, Mastodon
Data:
- Airtable (insert records with up to 5 fields)
Utility Steps (4 types)
Internal Drafts operations:
- Create Draft: Generate new draft with content, tags, syntax, and location (inbox/archive)
- Insert Text: Insert templated content at cursor position (best for keyboard actions)
- Define Template Tag: Create custom template tag from template content
- Configure Window: Control UI visibility (draft list, action list, action bar, tag entry)
Advanced Steps
Scripting and advanced automation:
-
Script: Execute JavaScript with Drafts API access
- Enable "Allow asynchronous execution" for async operations
- Call
script.complete() when async work finishes
-
Include Action: Reuse steps from another action (prevents duplication)
-
Run Shortcut: Execute Apple Shortcuts with optional response waiting
-
Run AppleScript: macOS-only AppleScript execution
-
HTML Preview: Display interactive HTML with forms that send data back to actions
-
Prompt: Collect user input with customizable buttons and text fields
-
Callback URL: Send requests to apps supporting x-callback-url protocol
-
Open URL: Launch web URLs or app-specific URL schemes
-
Configured Value: Create user-configurable action parameters
HTMLPreview Forms
HTMLPreview allows you to create custom HTML-based user interfaces that go beyond standard prompts. This is useful for complex forms, multi-step wizards, draft selection interfaces, and any interaction requiring custom styling.
The Working Pattern
The reliable pattern for HTMLPreview in a Script step:
var preview = HTMLPreview.create();
preview.hideInterface = true;
if (preview.show(html)) {
var vals = context.previewValues["formValues"];
if (vals && vals.choice) {
}
} else {
context.cancel();
}
Key Points
- Use
context.previewValues to access data sent from HTML
- Check
preview.show() return value - true if continued, false if cancelled
- Call
Drafts.send(key, value) in HTML JavaScript to send data
- Call
Drafts.continue() to close the preview and continue execution
Basic HTML Template
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: -apple-system, sans-serif;
padding: 20px;
background: #fff;
color: #000;
}
@media (prefers-color-scheme: dark) {
body { background: #1c1c1e; color: #f5f5f7; }
}
button {
display: block;
width: 100%;
padding: 14px;
margin: 10px 0;
border-radius: 12px;
font-size: 16px;
}
.primary {
background: #007aff;
color: white;
border: none;
}
</style>
</head>
<body>
<h1>Choose an Option</h1>
<button class="primary" onclick="choose('yes')">Yes</button>
<button onclick="choose('no')">No</button>
<script>
function choose(choice) {
Drafts.send("formValues", { choice: choice });
Drafts.continue();
}
</script>
</body>
</html>
Form Data Collection
For forms with multiple fields:
<form id="data-form">
<input type="text" id="title" value="">
<textarea id="notes"></textarea>
<input type="checkbox" id="flagged">
</form>
<button onclick="submitForm()">Submit</button>
<script>
function submitForm() {
var form = document.getElementById('data-form');
var data = {};
for (var e of form.elements) {
if (e.type === 'checkbox') {
data[e.id] = e.checked;
} else if (e.id) {
data[e.id] = e.value;
}
}
Drafts.send("formValues", data);
Drafts.continue();
}
</script>
Then in your script:
if (preview.show(html)) {
var vals = context.previewValues["formValues"];
var title = vals.title;
var notes = vals.notes;
var flagged = vals.flagged;
}
Sequential Prompts
You can show multiple HTMLPreview prompts in a loop:
var items = Draft.query("", "inbox", [], [], "created", false);
for (var i = 0; i < items.length; i++) {
var html = buildPromptHTML(items[i], i + 1, items.length);
var preview = HTMLPreview.create();
preview.hideInterface = true;
if (preview.show(html)) {
var vals = context.previewValues["formValues"];
if (vals.choice === "stop") break;
} else {
break;
}
}
Escaping User Content
Always escape user content before inserting into HTML:
function escapeHtml(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
var html = `<div>${escapeHtml(draft.content)}</div>`;
Common Mistakes
- Don't use
preview.actionLog - use context.previewValues instead
- Don't forget
Drafts.continue() - preview won't close without it
- Don't skip checking
preview.show() return - user may have cancelled
- Don't use
let/const across multiple Script steps - causes duplicate variable errors
See references/htmlpreview-forms-reference.md for complete documentation including dark mode CSS, button styles, progress bars, and full working examples.
Template System
Drafts uses a lightweight template engine with [[tag]] syntax for dynamic content.
Core Template Tags
Identifier Tags:
[[uuid]]: Unique draft identifier
[[permalink]]: Shareable draft URL
Content Tags:
[[draft]]: Full draft text
[[title]]: First line of draft
[[body]]: All lines after first line
[[safe_title]]: First line with path-unsafe characters removed (\/:*?<>|#)
[[selection]]: Currently selected text
[[selection_only]]: Selected text or empty string if no selection
Location Tags:
[[latitude]], [[longitude]]: Current location
[[created_latitude]], [[created_longitude]]: Location when draft was created
[[modified_latitude]], [[modified_longitude]]: Location when draft was last modified
Metadata Tags:
[[tags]]: Comma-separated tag list
[[line|n]]: Specific line number (e.g., [[line|2]] for second line)
[[line|n..m]]: Line range (e.g., [[line|2..5]])
Date/Time Formatting
Two formatting systems available:
strftime formats (traditional):
[[date|%Y-%m-%d]] → 2025-10-21
[[date|%B %d, %Y]] → October 21, 2025
[[created_date|%Y-%m-%d]] → Creation date
[[modified_date|%Y-%m-%d]] → Modification date
DateFormatter patterns (localized):
[[date|=shortDate]] → 10/21/25
[[date|=longDate]] → October 21, 2025
[[date|=iso8601]] → 2025-10-21T14:30:00Z
[[date|~yyyy-MM-dd]] → 2025-10-21
Date Adjustment
Modify dates relative to current time:
[[date|+1 day|%Y-%m-%d]] → Tomorrow
[[date|-1 week|%Y-%m-%d]] → Last week
[[date|+1 year|%Y-%m-%d]] → Next year
[[date|+3 hours|%H:%M]] → 3 hours from now
Template Modifiers
Case transformation:
[[title:upper]]: UPPERCASE
[[title:lower]]: lowercase
Length limiting:
[[title:max=50]]: Truncate to 50 characters
URL encoding:
- Use
{{ }} syntax for URL encoding: {{title}}
Markdown conversion:
- Use
%% %% syntax to convert to Markdown: %%draft%%
Custom Template Tags
Create custom tags in scripts:
draft.setTemplateTag("word_count", draft.content.split(/\s+/).length);
Template Files
Load external templates from iCloud Drive:
[[template|path/to/template.txt]]
Escaping
Prevent template evaluation with backslash:
\[[title]] → Renders as literal "[[title]]"
JavaScript Scripting
Drafts provides a complete JavaScript runtime (ECMAScript 6) with extensive APIs.
Core Objects
Draft object:
draft.content
draft.title
draft.body
draft.tags
draft.isFlagged
draft.isArchived
draft.createdAt
draft.modifiedAt
draft.content = "New content";
draft.addTag("important");
draft.removeTag("old");
draft.update();
let processed = draft.processTemplate("[[title]] - [[date]]");
draft.setTemplateTag("custom", "value");
Editor object:
editor.getText()
editor.getSelectedText()
editor.getSelectedRange()
editor.getTextInRange(0, 10)
editor.setText("New text");
editor.setSelectedText("Replacement");
editor.setSelectedRange(0, 5);
editor.activate();
App object:
app.currentWorkspace
app.isIdleDisabled
app.displayInfoMessage("Info");
app.displayWarningMessage("Warning");
app.displayErrorMessage("Error");
app.openURL("https://example.com");
Context object:
context.callbackURL
context.configuredValues
let folderName = context.configuredValues["folderName"];
Working with Drafts
Query drafts:
let all = Draft.query("", "all", [], [], "accessed");
let tagged = Draft.query("", "inbox", ["important"], [], "created");
Create new drafts:
let d = Draft.create();
d.content = "New draft content";
d.addTag("auto-created");
d.update();
Process multiple drafts:
let drafts = Draft.query("todo", "inbox", [], [], "created");
for (let d of drafts) {
d.addTag("processed");
d.update();
}
Asynchronous Operations
For HTTP requests, delays, or async operations:
let http = HTTP.create();
let response = http.request({
"url": "https://api.example.com/data",
"method": "POST",
"encoding": "json",
"data": {
"title": draft.title
},
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY"
}
});
if (response.success) {
app.displayInfoMessage("Posted successfully");
} else {
app.displayErrorMessage("Request failed");
context.fail();
}
script.complete();
Important HTTP Request Parameters:
"encoding": "json" - CRITICAL: Tells Drafts to automatically JSON-serialize the data object. Without this, the request body will be empty!
"data" - Pass as a JavaScript object, NOT a JSON string. Drafts will serialize it automatically when encoding: "json" is set.
"headers" - Object containing HTTP headers
"method" - HTTP verb ("GET", "POST", "PUT", "DELETE", etc.)
Common mistake: Omitting "encoding": "json" or trying to JSON.stringify() the data manually. Always use "encoding": "json" with an object in the "data" field.
Integration Examples
Mail integration:
let mail = Mail.create();
mail.toRecipients = ["user@example.com"];
mail.subject = draft.title;
mail.body = draft.body;
mail.isHTML = false;
let success = mail.send();
if (!success) {
context.fail();
}
Dropbox integration:
let db = Dropbox.create();
let success = db.write(
`/notes/${draft.title}.txt`,
draft.content,
"add",
false
);
if (!success) {
app.displayErrorMessage("Dropbox write failed");
context.fail();
}
Action Configuration
Actions support user-configurable values for flexibility without editing.
Adding Configured Values
- Add "Configured Value" action step
- Set properties:
- Name: Display label ("Folder Name")
- Description: Usage explanation
- Key: Identifier for accessing value ("folderName")
- Value Type: String, number, or boolean
- Default Values: Optional suggestions
- Requires Configuration: Make mandatory or optional
Accessing Configured Values
In templates:
[[folderName]]
In scripts:
let folder = context.configuredValues["folderName"];
Configuration Workflow
Users configure actions via context menu:
- iOS: Long press → "Configure"
- Mac: Right-click → "Configure"
Only actions with configured values show "Configure" option.
Best Practices
Action Organization
- Use descriptive names: "Send to Notion" vs. "Action 1"
- Choose meaningful icons: Use SF Symbols for clarity
- Group related actions: Organize in action groups by workflow
- Create aliases: Maintain single source of truth, use aliases in multiple groups
Template Efficiency
- Use safe_title for filenames: Prevents path issues
- Test date formats: Verify output matches expectations
- Leverage template files: Reuse complex templates
- Create custom tags: Simplify repeated complex templates
Scripting Best Practices
- Handle errors gracefully: Use
context.fail() for failures
- Provide user feedback: Use
app.displayInfoMessage() for status
- Test with sample data: Verify behavior with test drafts
- Use async for network: Enable async execution for HTTP requests
- Update drafts explicitly: Call
draft.update() after modifications
Action Step Ordering
- Validate first: Check conditions before expensive operations
- Process then send: Transform content before external steps
- Prompt early: Get user input before processing
- Log appropriately: Use error-only logging for production, all for debugging
Common Action Patterns
Quick Capture to Service
Convert draft to task/note in external service:
Steps:
- Service step (e.g., Todoist, Notion)
- Title:
[[title]]
- Content:
[[body]]
- After Success: Archive draft, add tag "processed"
File Export with Date
Save draft as file with timestamp:
Steps:
- Export step
- Filename:
[[date|%Y-%m-%d]]-[[safe_title]].md
- Content:
[[draft]]
- Folder: Select destination
- After Success: Add tag "exported"
Email with Template
Send formatted email from template:
Steps:
- Mail step
- To:
user@example.com
- Subject:
[[title]]
- Body: Use template file with placeholders
- HTML: Enabled
- After Success: Archive draft
Multi-Service Publishing
Post to multiple platforms simultaneously:
Steps:
- Mastodon step (post status)
- Medium step (create story)
- WordPress step (create post)
- After Success: Archive, add tag "published"
Conditional Processing
Use script to check conditions:
if (draft.tags.includes("urgent")) {
let reminder = Reminder.create();
reminder.title = draft.title;
reminder.priority = 1;
reminder.update();
} else {
draft.addTag("normal");
draft.update();
}
Text Transformation
Transform draft content in-place:
draft.content = draft.content.toUpperCase();
draft.update();
let lines = draft.content.split("\n");
let numbered = lines.map((line, i) => `${i+1}. ${line}`).join("\n");
draft.content = numbered;
draft.update();
let words = draft.content.split(/\s+/).length;
draft.setTemplateTag("count", words);
app.displayInfoMessage(`Word count: ${words}`);
Batch Processing
Process multiple drafts matching criteria:
let drafts = Draft.query("", "flagged", [], [], "created");
for (let d of drafts) {
d.addTag("reviewed");
d.isFlagged = false;
d.update();
}
app.displayInfoMessage(`Processed ${drafts.length} drafts`);
API Integration: Claude Translation
Complete working example calling the Claude API to translate draft content:
const originalText = draft.content;
const credential = Credential.create("Anthropic API", "Enter your Anthropic API key");
credential.addPasswordField("apiKey", "API Key");
credential.authorize();
const apiKey = credential.getValue("apiKey");
if (!apiKey) {
alert("API key not found. Please set up your Anthropic API key.");
context.fail();
}
const endpoint = "https://api.anthropic.com/v1/messages";
const http = HTTP.create();
const requestData = {
"model": "claude-3-5-haiku-20241022",
"max_tokens": 4096,
"messages": [
{
"role": "user",
"content": `Translate the following text to English. Only provide the translation, no explanations or additional text:\n\n${originalText}`
}
]
};
const response = http.request({
"url": endpoint,
"method": "POST",
"encoding": "json",
"data": requestData,
"headers": {
"x-api-key": apiKey,
"anthropic-version": "2023-06-01"
}
});
if (response.success) {
const responseData = JSON.parse(response.responseText);
const translation = responseData.content[0].text;
draft.content = originalText + "\n\n---\n\n**English Translation:**\n\n" + translation;
draft.update();
app.displaySuccessMessage("Translation completed!");
} else {
let errorMsg = "Translation failed. Status code: " + response.statusCode;
try {
const errorData = JSON.parse(response.responseText);
if (errorData.error && errorData.error.message) {
errorMsg += "\n\nError: " + errorData.error.message;
}
} catch (e) {
errorMsg += "\n\nResponse: " + response.responseText;
}
alert(errorMsg);
context.fail();
}
Key Points:
- Uses Drafts' Credential system for secure API key storage
- Demonstrates proper error handling with detailed error messages
- Shows how to parse JSON responses and extract specific fields
- Appends result to original draft content
Resources
This skill includes reference documentation with detailed information:
references/action-steps-reference.md
Comprehensive documentation of all action step types with examples and configuration options. Load this reference when working with specific step types or needing detailed parameter information.
references/scripting-api-reference.md
Detailed JavaScript API reference covering all major objects (Draft, Editor, App, HTTP, etc.) with method signatures, parameters, and return values. Load when scripting complex actions or working with specific API features.
references/template-tags-reference.md
Complete reference for Drafts template tag syntax covering all built-in tags (identifier, content, location, date/time, utility), formatting options (strftime, DateFormatter), date adjustment, special markup, and escaping. Load when working with templates or needing detailed syntax beyond the core template concepts in this skill.
references/htmlpreview-forms-reference.md
Comprehensive guide to creating custom HTML-based user interfaces using HTMLPreview. Covers the working pattern for context.previewValues, form data collection, sequential prompts, dark mode CSS, button styles, progress bars, escaping user content, and common mistakes to avoid. Load when building custom HTML interfaces that go beyond standard prompts.
Use these references as needed for detailed implementation guidance beyond the core concepts covered in this skill.