Comprehensive toolkit for generating Apple Shortcuts programmatically
This repository documents the reverse-engineered process of creating Apple Shortcuts files (.shortcut) that can be imported into the Shortcuts app across iOS, iPadOS, and macOS.
Apple's Shortcuts app is incredibly powerful, but there's no official API or documentation for creating shortcut files programmatically. The official methods are:
- Manual creation in the Shortcuts app
- Sharing via iCloud links
- Using the Shortcuts URL scheme (limited)
This leaves developers unable to generate shortcuts dynamically for their users.
Through reverse engineering and testing, we've discovered the file format and structure requirements for creating importable .shortcut files.
iOS Shortcuts are stored as binary property list (bplist) files with a specific structure:
.shortcut file
βββ WFWorkflow (Dictionary)
β βββ WFWorkflowClientRelease (String): iOS version
β βββ WFWorkflowClientVersion (String): Shortcuts app version
β βββ WFWorkflowIcon (Dictionary): Icon configuration
β βββ WFWorkflowImportQuestions (Array): Import-time questions
β βββ WFWorkflowInputContentItemClasses (Array): Accepted inputs
β βββ WFWorkflowMinimumClientVersion (Integer): Min supported version
β βββ WFWorkflowMinimumClientVersionString (String): Min version string
β βββ WFWorkflowOutputContentItemClasses (Array): Output types
β βββ WFWorkflowHasOutputFallback (Boolean): Fallback behavior
β βββ WFWorkflowTypes (Array): Shortcut types
β βββ WFWorkflowHasShortcutInputVariables (Boolean): Input variables
β βββ WFWorkflowActions (Array): The actual actions
The key discovery: Shortcuts files must use a specific binary format to be recognized as valid by iOS:
// The magic format header that makes it work
const SHORTCUT_FILE_HEADER = Buffer.from([
0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30 // "bplist00"
]);This is the standard binary property list header (bplist00). The entire file must be properly structured as a binary plist with the correct WFWorkflow hierarchy for iOS to accept it. There's no cryptographic signing involved - just following Apple's undocumented format requirements.
const bplist = require('bplist-creator');
const fs = require('fs');
function createShortcut(name, url, apiKey) {
const shortcut = {
WFWorkflow: {
WFWorkflowClientRelease: "18.0",
WFWorkflowClientVersion: "1302.1.3",
WFWorkflowIcon: {
WFWorkflowIconStartColor: 4282601983,
WFWorkflowIconGlyphNumber: 61440
},
WFWorkflowImportQuestions: [],
WFWorkflowInputContentItemClasses: [
"WFURLContentItem",
"WFTextContentItem"
],
WFWorkflowMinimumClientVersion: 1300,
WFWorkflowMinimumClientVersionString: "1300",
WFWorkflowOutputContentItemClasses: [],
WFWorkflowHasOutputFallback: false,
WFWorkflowTypes: ["ActionExtension", "MenuBar"],
WFWorkflowHasShortcutInputVariables: true,
WFWorkflowActions: [
{
WFWorkflowActionIdentifier: "is.workflow.actions.url",
WFWorkflowActionParameters: {
WFURLActionURL: url
}
},
{
WFWorkflowActionIdentifier: "is.workflow.actions.downloadurl",
WFWorkflowActionParameters: {
WFHTTPMethod: "POST",
WFHTTPHeaders: {
"X-API-Key": apiKey,
"Content-Type": "application/json"
},
WFHTTPBodyType: "JSON",
WFJSONBody: {
url: "{ShortcutInput}",
source: "shortcut"
}
}
}
]
}
};
// Convert to binary plist
const buffer = bplist(shortcut);
// Save as .shortcut file
fs.writeFileSync(`${name}.shortcut`, buffer);
}import plistlib
import struct
def create_shortcut(name, url, api_key):
shortcut = {
'WFWorkflow': {
'WFWorkflowClientRelease': '18.0',
'WFWorkflowClientVersion': '1302.1.3',
'WFWorkflowIcon': {
'WFWorkflowIconStartColor': 4282601983,
'WFWorkflowIconGlyphNumber': 61440
},
'WFWorkflowImportQuestions': [],
'WFWorkflowInputContentItemClasses': [
'WFURLContentItem',
'WFTextContentItem'
],
'WFWorkflowMinimumClientVersion': 1300,
'WFWorkflowMinimumClientVersionString': '1300',
'WFWorkflowOutputContentItemClasses': [],
'WFWorkflowHasOutputFallback': False,
'WFWorkflowTypes': ['ActionExtension', 'MenuBar'],
'WFWorkflowHasShortcutInputVariables': True,
'WFWorkflowActions': [
{
'WFWorkflowActionIdentifier': 'is.workflow.actions.url',
'WFWorkflowActionParameters': {
'WFURLActionURL': url
}
},
{
'WFWorkflowActionIdentifier': 'is.workflow.actions.downloadurl',
'WFWorkflowActionParameters': {
'WFHTTPMethod': 'POST',
'WFHTTPHeaders': {
'X-API-Key': api_key,
'Content-Type': 'application/json'
},
'WFHTTPBodyType': 'JSON',
'WFJSONBody': {
'url': '{ShortcutInput}',
'source': 'shortcut'
}
}
}
]
}
}
# Write as binary plist
with open(f'{name}.shortcut', 'wb') as f:
plistlib.dump(shortcut, f, fmt=plistlib.FMT_BINARY)Common Shortcuts action identifiers:
is.workflow.actions.url- Get URLis.workflow.actions.downloadurl- Download URL contentis.workflow.actions.openurl- Open URLis.workflow.actions.text- Text actionis.workflow.actions.getclipboard- Get clipboardis.workflow.actions.setclipboard- Set clipboardis.workflow.actions.notification- Show notificationis.workflow.actions.runworkflow- Run another shortcut
Shortcuts support variables using special syntax:
{ShortcutInput}- Input passed to shortcut{Clipboard}- Current clipboard content{CurrentDate}- Current date/time
Content item classes for inputs/outputs:
WFURLContentItem- URLsWFTextContentItem- Plain textWFImageContentItem- ImagesWFPDFContentItem- PDFsWFWebPageContentItem- Web pages
You can prompt users for configuration during import:
WFWorkflowImportQuestions: [
{
WFWorkflowImportQuestionType: "Text",
WFWorkflowImportQuestionPrompt: "Enter your API key:",
WFWorkflowImportQuestionDefaultValue: "",
WFWorkflowImportQuestionVariable: "apiKey"
}
]Add if/else logic:
{
WFWorkflowActionIdentifier: "is.workflow.actions.conditional",
WFWorkflowActionParameters: {
WFCondition: "Contains",
WFConditionalIfTrueActions: [...],
WFConditionalIfFalseActions: [...]
}
}Create interactive menus:
{
WFWorkflowActionIdentifier: "is.workflow.actions.menu",
WFWorkflowActionParameters: {
WFMenuItems: ["Option 1", "Option 2"],
WFMenuItemActions: {
"Option 1": [...],
"Option 2": [...]
}
}
}Once generated, shortcuts can be distributed via:
- Direct download - Host .shortcut files on your server
- Base64 encoding - Embed in web pages
- Data URLs - Create downloadable links
- Email attachments - Send directly to users
<!-- Create download link -->
<a href="data:application/x-shortcut;base64,YnBsaXN0MDDC..."
download="MyShortcut.shortcut">
Download Shortcut
</a>
<!-- Or use JavaScript -->
<script>
function downloadShortcut(base64Data, filename) {
const link = document.createElement('a');
link.href = `data:application/x-shortcut;base64,${base64Data}`;
link.download = filename;
link.click();
}
</script>The format was discovered through:
- Exporting shortcuts from the Shortcuts app
- Analyzing the binary plist structure
- Testing various modifications
- Identifying required vs optional fields
Key findings:
- Files must be valid binary plists
- The
WFWorkflowroot key is required - Version strings affect compatibility
- Some fields are validated, others ignored
- The format has remained stable across iOS versions
- No official documentation (may break in future iOS versions)
- Cannot access all Shortcuts actions (some are private)
- Cannot programmatically install (user must manually import)
- No way to update existing shortcuts
- Limited debugging capabilities
bplist-creator- Create binary plistsbplist-parser- Parse binary plists
plistlib- Built-in plist supportbiplist- Advanced binary plist features
- Shortcut Inspector - Analyze shortcut files
- Plist Editor - Edit plist files
This is community-driven documentation. If you discover new features or techniques, please contribute!
This documentation is for educational purposes. Apple, iOS, and Shortcuts are trademarks of Apple Inc. This project is not affiliated with or endorsed by Apple.
Created through reverse engineering and community research to unlock the undocumented iOS Shortcuts file format.
Remember: This is unofficial and undocumented. Use at your own risk, and always test thoroughly before distributing shortcuts to users.