cd /news/developer-tools/a-few-notes-on-decompiling-apple-sho… · home topics developer-tools article
[ARTICLE · art-8808] src=gist.github.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

A few notes on decompiling Apple Shortcuts workflows into their raw XML 'source code'.

This article explains how to decompile Apple Shortcuts workflows by modifying iCloud Share URLs to access the raw JSON data, which contains the download URL for the shortcut's binary property list file and its name. It then describes converting that binary plist to XML using the `plutil` command-line tool. Additionally, the article provides technical details on the low-level storage of Shortcuts on macOS, noting that they are managed in a SQLite database at `~/Library/Shortcuts/Shortcuts.sqlite` with tables for shortcut metadata, actions, and other related data.

read11 min views24 publishedMay 22, 2023

Table of Contents #

<!-- TOC start (generated with https://bitdowntoc.derlin.ch/) -->

Original Notes #

A few notes on decompiling Apple Shortcuts workflows into their raw XML 'source code'.

You may also find some benefit in something like this:

  • https://routinehub.co/shortcut/5256/
    • Shortcut Source Tool: View, convert, save source in plist or json, edit and import back to Shortcuts, review in browser

Originally posted by @0xdevalias in https://github.com/joshfarrant/shortcuts-js/issues/683#issuecomment-1548831442

Crossposted: https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4be0p/

Looking a little closer at that shortcut, the main bit of functionality seems to basically be modifying the iCloud Share URL slightly:

Given a URL like this:

https://www.icloud.com/shortcuts/ABC12

It becomes:

https://www.icloud.com/shortcuts/api/records/ABC123

Then extracts the following from the JSON on that page:

  • the URL for the unsigned shortcut file (which is an 'Apple binary property list' file): fields -> shortcut -> value -> downloadURL
  • the name of the shortcut workflow: fields -> name -> value

We can then convert that binary plist file to XML or JSON using plutil (though when I tried the JSON format I got an error (invalid object in plist for destination format), so might have to stick to XML):

plutil -convert xml1 -e plist.xml -- the-downloaded-shortcut.plist

or

plutil -convert json -e plist.json -- the-downloaded-shortcut.plist

Originally posted by @0xdevalias in https://github.com/joshfarrant/shortcuts-js/issues/683#issuecomment-1556546326

Crossposted: https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4dw28/

Further Notes from ChatGPT #

The following are some (as yet barely verified) notes from ChatGPT:

  • Private ChatGPT Convo Link: https://chatgpt.com/c/6775b854-cac4-8008-84ce-321110ba5303

On macOS, Apple Shortcuts (formerly Workflow) are stored in a combination of locations, but primarily they are managed using a SQLite database and associated plist files. Here's a breakdown of where and how they are stored at a low level:

1. SQLite Database

The main storage for Shortcuts is in a SQLite database located at:

~/Library/Shortcuts/ShortcutsDatabase.db

This database contains metadata, configuration, and the structure of each shortcut. It includes tables for:

  • Shortcut names
  • Actions and sequences
  • Variables and other contextual data

The above path doesn't seem to exist on macOS 14.5, but the following does:

~/Library/Shortcuts/Shortcuts.sqlite

Tables:

⇒ sqlite3 -readonly ~/Library/Shortcuts/Shortcuts.sqlite ".tables"

ZACCESSRESOURCEPERMISSION
ZAUTOSHORTCUTSPREFERENCES
ZCLOUDKITSYNCTOKEN
ZCOLLECTION
ZLIBRARY
ZPERSISTEDSERIALIZEDPARAMETERS
ZSHORTCUT
ZSHORTCUTACTIONS
ZSHORTCUTBOOKMARK
ZSHORTCUTICON
ZSHORTCUTQUARANTINE
ZSHORTCUTRUNEVENT
ZSMARTPROMPTPERMISSION
ZTRIGGER
ZTRIGGEREVENT
ZTRUSTEDDOMAIN
ZVCVOICESHORTCUTMANAGEDOBJECT
ZVCVOICESHORTCUTSUGGESTIONLISTMANAGEDOBJECT
ZVCVOICESHORTCUTSYNCSTATEMANAGEDOBJECT
Z_4PARENTS
Z_4SHORTCUTS
Z_METADATA
Z_MODELCACHE
Z_PRIMARYKEY

CoreData Model Hierachy:

⇒ ./extract-coredata-model-hierarchy.py ~/Library/Shortcuts/Shortcuts.sqlite

- 1: AccessResourcePermission (Table: ZACCESSRESOURCEPERMISSION)
- 2: AutoShortcutsPreferences (Table: ZAUTOSHORTCUTSPREFERENCES)
- 3: CloudKitSyncToken (Table: ZCLOUDKITSYNCTOKEN)
- 4: Collection (Table: ZCOLLECTION)
- 5: Library (Table: ZLIBRARY)
- 6: PersistedSerializedParameters (Table: ZPERSISTEDSERIALIZEDPARAMETERS)
- 7: Shortcut (Table: ZSHORTCUT)
- 8: ShortcutActions (Table: ZSHORTCUTACTIONS)
- 9: ShortcutBookmark (Table: ZSHORTCUTBOOKMARK)
- 10: ShortcutIcon (Table: ZSHORTCUTICON)
- 11: ShortcutQuarantine (Table: ZSHORTCUTQUARANTINE)
- 12: ShortcutRunEvent (Table: ZSHORTCUTRUNEVENT)
- 13: SmartPromptPermission (Table: ZSMARTPROMPTPERMISSION)
- 14: Trigger (Table: ZTRIGGER)
- 15: TriggerEvent (Table: ZTRIGGEREVENT)
- 16: TrustedDomain (Table: ZTRUSTEDDOMAIN)
- 17: VCVoiceShortcutManagedObject (Table: ZVCVOICESHORTCUTMANAGEDOBJECT)
- 18: VCVoiceShortcutSuggestionListManagedObject (Table: ZVCVOICESHORTCUTSUGGESTIONLISTMANAGEDOBJECT)
- 19: VCVoiceShortcutSyncStateManagedObject (Table: ZVCVOICESHORTCUTSYNCSTATEMANAGEDOBJECT)
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZCOLLECTION');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZLASTSYNCEDHASH","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZTOMBSTONED","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":5,"name":"ZICON","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":6,"name":"ZIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":7,"name":"ZNAME","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":8,"name":"ZCLOUDKITFOLDERRECORDMETADATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":9,"name":"ZCLOUDKITORDERINGRECORDMETADATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":10,"name":"ZLASTREMOTECOLLECTIONORDERINGDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":11,"name":"ZLASTREMOTECOLLECTIONORDERINGSUBSETDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":12,"name":"ZLASTREMOTESHORTCUTORDERINGDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":13,"name":"ZLASTREMOTESHORTCUTORDERINGSUBSETDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":14,"name":"ZLASTSYNCEDENCRYPTEDSCHEMAVERSION","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":15,"name":"ZWANTEDENCRYPTEDSCHEMAVERSION","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0}]
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZLIBRARY');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZLASTSYNCEDHASH","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":5,"name":"ZVERSION","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":6,"name":"ZCLOUDKITRECORDMETADATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":7,"name":"ZDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0}]
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZSHORTCUT');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZACTIONCOUNT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZHASOUTPUTFALLBACK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":5,"name":"ZHASSHORTCUTINPUTVARIABLES","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":6,"name":"ZHIDDENFROMWIDGET","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":7,"name":"ZLASTSYNCEDHASH","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":8,"name":"ZRECEIVESONSCREENCONTENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":9,"name":"ZREMOTEQUARANTINESTATUSVALUE","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":10,"name":"ZRUNEVENTSCOUNT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":11,"name":"ZSYNCHASH","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":12,"name":"ZTOMBSTONED","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":13,"name":"ZTRIGGERCOUNT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":14,"name":"ZACTIONS","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":15,"name":"ZCONFLICTOF","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":16,"name":"ZICON","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":17,"name":"ZQUARANTINE","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":18,"name":"ZCREATIONDATE","type":"TIMESTAMP","notnull":0,"dflt_value":null,"pk":0},
{"cid":19,"name":"ZLASTRUNEVENTDATE","type":"TIMESTAMP","notnull":0,"dflt_value":null,"pk":0},
{"cid":20,"name":"ZMODIFICATIONDATE","type":"TIMESTAMP","notnull":0,"dflt_value":null,"pk":0},
{"cid":21,"name":"ZACTIONSDESCRIPTION","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":22,"name":"ZASSOCIATEDAPPBUNDLEIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":23,"name":"ZGALLERYIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":24,"name":"ZLASTMIGRATEDCLIENTVERSION","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":25,"name":"ZLASTSAVEDONDEVICENAME","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":26,"name":"ZMINIMUMCLIENTVERSION","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":27,"name":"ZNAME","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":28,"name":"ZPHRASE","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":29,"name":"ZSOURCE","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":30,"name":"ZWORKFLOWID","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":31,"name":"ZWORKFLOWSUBTITLE","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":32,"name":"ZCLOUDKITRECORDMETADATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":33,"name":"ZIMPORTQUESTIONSDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":34,"name":"ZINPUTCLASSESDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":35,"name":"ZNOINPUTBEHAVIORDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":36,"name":"ZOUTPUTCLASSESDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":37,"name":"ZLASTSYNCEDENCRYPTEDSCHEMAVERSION","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":38,"name":"ZWANTEDENCRYPTEDSCHEMAVERSION","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":39,"name":"ZDISABLEDONLOCKSCREEN","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":40,"name":"ZREMOTEQUARANTINEHASH","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":41,"name":"ZSHOULDAUTOUPDATEASSOCIATEDAPPBUNDLEIDENTIFIER","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":42,"name":"ZHIDDENFROMLIBRARYANDSYNC","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0}]
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZSHORTCUTACTIONS');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZSHORTCUT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0}]
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZSHORTCUTBOOKMARK');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZPATH","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":5,"name":"ZBOOKMARKDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0}]
⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "PRAGMA table_info('ZTRIGGER');"

[{"cid":0,"name":"Z_PK","type":"INTEGER","notnull":0,"dflt_value":null,"pk":1},
{"cid":1,"name":"Z_ENT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":2,"name":"Z_OPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":3,"name":"ZENABLED","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":4,"name":"ZNOTIFICATIONLEVEL","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":5,"name":"ZSHOULDNOTIFY","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":6,"name":"ZSHOULDPROMPT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":7,"name":"ZSOURCE","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":8,"name":"ZRUNEVENTS","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":9,"name":"ZSHORTCUT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":10,"name":"ZIDENTIFIER","type":"VARCHAR","notnull":0,"dflt_value":null,"pk":0},
{"cid":11,"name":"ZDATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0},
{"cid":12,"name":"ZEDITABLESHORTCUT","type":"INTEGER","notnull":0,"dflt_value":null,"pk":0},
{"cid":13,"name":"ZSELECTEDENTRYMETADATA","type":"BLOB","notnull":0,"dflt_value":null,"pk":0}]

This shows a few of the relevant'ish looking human readable columns:

⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "SELECT Z_PK, ZNAME, ZWORKFLOWSUBTITLE, ZACTIONCOUNT, ZTRIGGERCOUNT, ZACTIONS, ZSOURCE, ZWORKFLOWID FROM ZSHORTCUT;"

[
    // ..snip..
    {
        "Z_PK": 15,
        "ZNAME": "Sleep (20min)",
        "ZWORKFLOWSUBTITLE": "5 actions",
        "ZACTIONCOUNT": 5,
        "ZTRIGGERCOUNT": 0,
        "ZACTIONS": 15,
        "ZSOURCE": null,
        "ZWORKFLOWID": "66DF3D2D-0FCF-471E-84D2-597A501648D4"
    },
    // ..snip..
]       

This is similar to above, but will also JOIN with ZSHORTCUTACTIONS to link the ZDATA field as well (which appears to be a bplist):

⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "
  SELECT 
    ZSHORTCUT.Z_PK,
    ZSHORTCUT.ZNAME,
    ZSHORTCUT.ZWORKFLOWSUBTITLE,
    ZSHORTCUT.ZACTIONCOUNT,
    ZSHORTCUT.ZTRIGGERCOUNT,
    ZSHORTCUT.ZACTIONS,
    ZSHORTCUT.ZSOURCE,
    ZSHORTCUT.ZWORKFLOWID,
    ZSHORTCUTACTIONS.ZDATA
  FROM ZSHORTCUT
  LEFT JOIN ZSHORTCUTACTIONS ON ZSHORTCUT.ZACTIONS = ZSHORTCUTACTIONS.Z_PK;"

This is similar to above, but will also attempt to parse the ZDATA bplist with python's plistlib:

⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite "
  SELECT 
    ZSHORTCUT.Z_PK, 
    ZSHORTCUT.ZNAME, 
    ZSHORTCUT.ZWORKFLOWSUBTITLE, 
    ZSHORTCUT.ZACTIONCOUNT, 
    ZSHORTCUT.ZTRIGGERCOUNT, 
    ZSHORTCUT.ZACTIONS, 
    ZSHORTCUT.ZSOURCE, 
    ZSHORTCUT.ZWORKFLOWID, 
    hex(ZSHORTCUTACTIONS.ZDATA) AS ZDATA_HEX
  FROM ZSHORTCUT
  LEFT JOIN ZSHORTCUTACTIONS ON ZSHORTCUT.ZACTIONS = ZSHORTCUTACTIONS.Z_PK;" | python3 -c "
import sys, json, plistlib, binascii

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, bytes):
            return obj.hex()  # Convert bytes to a hexadecimal string
        return super().default(obj)

data = json.load(sys.stdin)

for row in data:
    if row.get('ZDATA_HEX'):
        try:
            row['ZDATA'] = plistlib.loads(binascii.unhexlify(row['ZDATA_HEX']))
            del row['ZDATA_HEX']  # Remove the hex version
        except Exception as e:
            row['ZDATA'] = f'Decoding Error: {e}'

print(json.dumps(data, indent=2, cls=CustomJSONEncoder))
"

Which results in output like this:

[
  // ..snip
  {
    "Z_PK": 15,
    "ZNAME": "Sleep (20min)",
    "ZWORKFLOWSUBTITLE": "5 actions",
    "ZACTIONCOUNT": 5,
    "ZTRIGGERCOUNT": 0,
    "ZACTIONS": 15,
    "ZSOURCE": null,
    "ZWORKFLOWID": "66DF3D2D-0FCF-471E-84D2-597A501648D4",
    "ZDATA": [
      {
        "WFWorkflowActionIdentifier": "is.workflow.actions.number",
        "WFWorkflowActionParameters": {
          "UUID": "4A620A3D-E6A4-48AA-8191-A5A29F77B327",
          "WFNumberActionNumber": 1200.0
        }
      },
      {
        "WFWorkflowActionIdentifier": "is.workflow.actions.delay",
        "WFWorkflowActionParameters": {
          "WFDelayTime": {
            "Value": {
              "OutputUUID": "4A620A3D-E6A4-48AA-8191-A5A29F77B327",
              "Type": "ActionOutput",
              "OutputName": "Number"
            },
            "WFSerializationType": "WFTextTokenAttachment"
          }
        }
      },
      {
        "WFWorkflowActionIdentifier": "is.workflow.actions.music",
        "WFWorkflowActionParameters": {}
      },
      {
        "WFWorkflowActionIdentifier": "is.workflow.actions.airplanemode.set",
        "WFWorkflowActionParameters": {}
      },
      {
        "WFWorkflowActionIdentifier": "is.workflow.actions.timer.start",
        "WFWorkflowActionParameters": {}
      }
    ]
  },
  // ..snip
]

2. Shortcut Files

Individual shortcuts are also stored as serialized plist (property list) files in the directory:

~/Library/Shortcuts/Shortcuts

Each shortcut has a unique identifier, and its details are saved in .shortcut files. These files are a binary or XML representation of the shortcut's workflow.

On macOS 14.5, that directory doesn't seem to exist, but if we go up one directory, these are the files I can see in that folder:

⇒ ls -l ~/Library/Shortcuts

SecuredPreferences.plist
ShareSheetState.plist
Shortcuts.sqlite
Shortcuts.sqlite-shm
Shortcuts.sqlite-wal
Spotlight.dat
Temporary/
ssh/
⇒ ls -l ~/Library/Shortcuts/Temporary

com.apple.WorkflowKit.BackgroundShortcutRunner/
com.apple.WorkflowUI.CatalystContentExtension/
com.apple.shortcuts/
com.apple.shortcuts.Run-Workflow/
com.apple.shortcuts.ThumbnailExtension/
com.apple.shortcuts.events/
com.apple.siriactionsd/

That said, based on the exploration of the sqlite DB earlier.. it looks like we can get the shortcut actions from the ZSHORTCUT / ZSHORTCUTACTIONS tables.

3. iCloud Sync

If iCloud syncing is enabled, shortcuts may also be stored in the iCloud directory:

~/Library/Mobile Documents/iCloud~is~workflow/Shortcuts

This allows synchronization across Apple devices, and the format mirrors the local storage with plist files and associated metadata.

On macOS 14.5, that folder doesn't seem to exist, but these ones did (though they seem to be empty?):

⇒ ls -la ~/Library/Mobile\ Documents | grep -E 'workflow|shortcut'

iCloud~com~apple~shortcuts~runtime/
iCloud~is~workflow~my~workflows/
⇒ fd . ~/Library/Mobile\ Documents/iCloud\~com\~apple\~shortcuts\~runtime

/Users/devalias/Library/Mobile Documents/iCloud~com~apple~shortcuts~runtime/Documents/
⇒ fd . ~/Library/Mobile\ Documents/iCloud\~is\~workflow\~my\~workflows

/Users/devalias/Library/Mobile Documents/iCloud~is~workflow~my~workflows/Documents/

4. Auxiliary Files

Additional metadata, such as user preferences and categories, is often stored in other plist files in:

~/Library/Preferences/com.apple.shortcuts.plist

On macOS 14.5, this file seems to exist, but it doesn't look super interesting:

⇒ plutil -convert xml1 -o - ~/Library/Preferences/com.apple.shortcuts.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CKPerBootTasks</key>
	<array>
		<string>CKAcccountInfoCacheReset</string>
	</array>
	<key>CKStartupTime</key>
	<integer>1731387059</integer>
	<key>MainViewLayoutMode</key>
	<string>grid</string>
	<key>NSNavLastRootDirectory</key>
	<string>~/Desktop</string>
	<key>NSNavPanelExpandedSizeForSaveMode</key>
	<string>{800, 448}</string>
	<key>NSNavPanelExpandedStateForSaveMode</key>
	<true/>
	<key>NSWindow Frame Main Window</key>
	<string>765 279 1000 678 0 0 1792 1095 </string>
	<key>NSWindow Frame NSNavPanelAutosaveName</key>
	<string>1320 590 800 364 0 0 3440 1415 </string>
	<key>NSWindow Frame NSSpellCheckerSubstitutionsPanel2</key>
	<string>-116 1527 428 137 -808 1120 3440 1415 </string>
	<key>wdsCacheCleanupState</key>
	<dict>
		<key>com.apple.shortcuts</key>
		<true/>
	</dict>
</dict>
</plist>

Key Points

  • SQLite Database: Used for managing and querying shortcuts metadata efficiently.
  • Plist Files: Contain the actual logic and structure of the shortcuts.
  • iCloud Sync: Optional storage for cloud syncing.
  • Sandboxing: The Shortcuts app adheres to macOS sandboxing rules, restricting access to its files unless explicitly granted.

If you want to explore or modify these files, you should do so cautiously and make backups, as manual changes could corrupt the database or shortcuts.

Further Reading / Others Research #

  • https://github.com/joshfarrant/shortcuts-js
    • A JavaScript iOS 12 Shortcuts creator

    • https://shortcuts.fun/
      • Shortcuts JS 🧞‍♂️ Create Apple Shortcuts using JavaScript

  • https://github.com/sebj/iOS-Shortcuts-Reference
    • Shortcuts File Format Reference

    • Reference documentation for the iOS Shortcuts app file structure

  • https://zachary7829.github.io/blog/shortcuts/
    • Deep Into Shortcuts

    • https://zachary7829.github.io/blog/shortcuts/fileformat
      • Shortcuts File Format Documentation

      • How shortcuts are stored On iOS 13, shortcuts are stored in /var/mobile/Library/Shortcuts/Shortcuts.realm. However, iOS 14 changes this to be in Shortcuts.sqlite

      • On iOS 12/13/14 devices, a shortcut that's stored on a device can be exported as an unsigned .shortcut file by using the Get My Shortcuts action (You can use a Save File action to save the output).

      • iOS 15 is slightly different, however: you can't export shortcuts on device as unsigned shortcuts using Get My Shortcuts, only signed. (Shortcuts in iOS 15 are signed with Apple Encrypted Archives - learn more about them here: https://man.cameronkatri.com/macOS/aea). You can, however, get a unsigned .shortcut from the iCloud API. Let's upload a shortcut to iCloud, and imagine our link is https://www.icloud.com/shortcuts/77dfe31578ac4f6fb084ebb418b34a49. Change /shortcuts/ to /shortcuts/api/records/ (https://www.icloud.com/shortcuts/api/records/77dfe31578ac4f6fb084ebb418b34a49). The value for fields.shortcut.value.downloadURL should be the URL for the unsigned .shortcut (Note: If you opened in Safari, change \/ to / in the URL). After getting the unsigned .shortcut, rename this to a .plist and you should be able to easily open this in Xcode (or, use set name to rename to something.plist with Do Not Include File Extension on, and Get Text from that).

      • Importing On iOS 12, you could just import .shortcuts from files no issue. However, this was disabled on iOS 13. It's still fairly easy to work around this though - you could have a shortcut that passes a .shortcut file into the Get Link to File action, and it should generate an iCloud link to this .shortcut in which you can import. Similar behavior on iOS 14. (Fun fact: While this is disabled, the code for it is still there: you can actually make a backup, edit a boolean in a plist, restore from said backup and you can import .shortcuts again. Apple actually accidentally enabled it again in iOS 14b1, though quickly realized their mistake and disabled it again.)

        On iOS 15, however, Apple introduced shortcuts signing, as well as removed the special treatment for Get Link to File regarding .shortcut/.wflow files. This means that you need to sign a shortcut file if you want to import it on an iOS 15 device - and no, you can't do it on device. (Note: There was a way to bypass signing and import unsigned shortcuts files on iOS 15b1, but it was quickly patched.) You need either a Mac or an iOS 12/13/14 device to sign said shortcut file. (To sign on macOS - use the shortcuts CLI tool. To sign on an iOS 12/13/14 device, have a shortcut get the iOS 15 shortcut you want to sign, and use the Get Link to File action to upload to iCloud and sign it. The link to the signed shortcut file should be in fields.signedShortcut.value.downloadURL. However, you could set up a shortcuts signing server if you have a Mac if you really want to - but be aware that if someone uploads a malicious shortcut, Apple could ban it.

See Also #

  • https://shortcuts.fun/
    • Create Apple Shortcuts using JavaScript

    • https://github.com/joshfarrant/shortcuts-js
      • A JavaScript iOS 12 Shortcuts creator

      • https://github.com/joshfarrant/shortcuts-js/issues/683
        • Looking for new maintainers

  • https://www.reddit.com/r/shortcuts/comments/13h61hv/gpt4_can_write_shortcuts/
    • GPT4 can write shortcuts

Announcement Tweet

  • Gist Announcement Tweet: https://twitter.com/_devalias/status/1660516786826264585

My Other Related Deepdive Gist's and Projects

── more in #developer-tools 4 stories · sorted by recency
── more on @apple 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-few-notes-on-decom…] indexed:0 read:11min 2023-05-22 ·