{"slug": "a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source", "title": "A few notes on decompiling Apple Shortcuts workflows into their raw XML 'source code'.", "summary": "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.", "body_md": "# Decompile Apple Shortcuts into raw XML 'source code'\n\n## Table of Contents\n\n<!-- TOC start (generated with https://bitdowntoc.derlin.ch/) -->\n- [Original Notes](#original-notes)\n- [Further Notes from ChatGPT](#further-notes-from-chatgpt)\n- [Further Reading / Others Research](#further-reading--others-research)\n- [See Also](#see-also)\n  - [Announcement Tweet](#announcement-tweet)\n  - [My Other Related Deepdive Gist's and Projects](#my-other-related-deepdive-gists-and-projects)\n<!-- TOC end -->\n\n## Original Notes\n\nA few notes on decompiling Apple Shortcuts workflows into their raw XML 'source code'.\n\n> You may also find some benefit in something like this:\n> \n> - https://routinehub.co/shortcut/5256/\n>   - > Shortcut Source Tool: View, convert, save source in plist or json, edit and import back to Shortcuts, review in browser\n> \n> _Originally posted by @0xdevalias in https://github.com/joshfarrant/shortcuts-js/issues/683#issuecomment-1548831442_\n>\n> _Crossposted: [https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4be0p/](https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4be0p/)_\n\n> Looking a little closer at that shortcut, the main bit of functionality seems to basically be modifying the iCloud Share URL slightly:\n> \n> Given a URL like this:\n> \n> ```\n> https://www.icloud.com/shortcuts/ABC12\n> ```\n> \n> It becomes:\n> \n> ```\n> https://www.icloud.com/shortcuts/api/records/ABC123\n> ```\n> \n> Then extracts the following from the JSON on that page:\n> \n> - the URL for the unsigned shortcut file (which is an 'Apple binary property list' file): `fields -> shortcut -> value -> downloadURL`\n> - the name of the shortcut workflow: `fields -> name -> value`\n> \n> 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):\n> \n> ```shell\n> plutil -convert xml1 -e plist.xml -- the-downloaded-shortcut.plist\n> ```\n> \n> or\n> \n> ```\n> plutil -convert json -e plist.json -- the-downloaded-shortcut.plist\n> ```\n> \n> _Originally posted by @0xdevalias in https://github.com/joshfarrant/shortcuts-js/issues/683#issuecomment-1556546326_\n>\n> _Crossposted: [https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4dw28/](https://www.reddit.com/r/shortcuts/comments/13h61hv/comment/jl4dw28/)_\n\n## Further Notes from ChatGPT\n\nThe following are some (as yet barely verified) notes from ChatGPT:\n\n- Private ChatGPT Convo Link: https://chatgpt.com/c/6775b854-cac4-8008-84ce-321110ba5303\n\n> 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:\n> \n> ### **1. SQLite Database**\n> \n> The main storage for Shortcuts is in a SQLite database located at:\n> \n> ```plaintext\n> ~/Library/Shortcuts/ShortcutsDatabase.db\n> ```\n>\n> This database contains metadata, configuration, and the structure of each shortcut. It includes tables for:\n> \n> - Shortcut names\n> - Actions and sequences\n> - Variables and other contextual data\n\nThe above path doesn't seem to exist on macOS 14.5, but the following does:\n\n```\n~/Library/Shortcuts/Shortcuts.sqlite\n```\n\nTables:\n\n```shell\n⇒ sqlite3 -readonly ~/Library/Shortcuts/Shortcuts.sqlite \".tables\"\n\nZACCESSRESOURCEPERMISSION\nZAUTOSHORTCUTSPREFERENCES\nZCLOUDKITSYNCTOKEN\nZCOLLECTION\nZLIBRARY\nZPERSISTEDSERIALIZEDPARAMETERS\nZSHORTCUT\nZSHORTCUTACTIONS\nZSHORTCUTBOOKMARK\nZSHORTCUTICON\nZSHORTCUTQUARANTINE\nZSHORTCUTRUNEVENT\nZSMARTPROMPTPERMISSION\nZTRIGGER\nZTRIGGEREVENT\nZTRUSTEDDOMAIN\nZVCVOICESHORTCUTMANAGEDOBJECT\nZVCVOICESHORTCUTSUGGESTIONLISTMANAGEDOBJECT\nZVCVOICESHORTCUTSYNCSTATEMANAGEDOBJECT\nZ_4PARENTS\nZ_4SHORTCUTS\nZ_METADATA\nZ_MODELCACHE\nZ_PRIMARYKEY\n```\n\nCoreData Model Hierachy:\n\n- [`extract-coredata-model-hierarchy.py`](https://gist.github.com/0xdevalias/ccc2b083ff58b52aa701462f2cfb3cc8#file-extract-coredata-model-hierarchy-py)\n\n```shell\n⇒ ./extract-coredata-model-hierarchy.py ~/Library/Shortcuts/Shortcuts.sqlite\n\n- 1: AccessResourcePermission (Table: ZACCESSRESOURCEPERMISSION)\n- 2: AutoShortcutsPreferences (Table: ZAUTOSHORTCUTSPREFERENCES)\n- 3: CloudKitSyncToken (Table: ZCLOUDKITSYNCTOKEN)\n- 4: Collection (Table: ZCOLLECTION)\n- 5: Library (Table: ZLIBRARY)\n- 6: PersistedSerializedParameters (Table: ZPERSISTEDSERIALIZEDPARAMETERS)\n- 7: Shortcut (Table: ZSHORTCUT)\n- 8: ShortcutActions (Table: ZSHORTCUTACTIONS)\n- 9: ShortcutBookmark (Table: ZSHORTCUTBOOKMARK)\n- 10: ShortcutIcon (Table: ZSHORTCUTICON)\n- 11: ShortcutQuarantine (Table: ZSHORTCUTQUARANTINE)\n- 12: ShortcutRunEvent (Table: ZSHORTCUTRUNEVENT)\n- 13: SmartPromptPermission (Table: ZSMARTPROMPTPERMISSION)\n- 14: Trigger (Table: ZTRIGGER)\n- 15: TriggerEvent (Table: ZTRIGGEREVENT)\n- 16: TrustedDomain (Table: ZTRUSTEDDOMAIN)\n- 17: VCVoiceShortcutManagedObject (Table: ZVCVOICESHORTCUTMANAGEDOBJECT)\n- 18: VCVoiceShortcutSuggestionListManagedObject (Table: ZVCVOICESHORTCUTSUGGESTIONLISTMANAGEDOBJECT)\n- 19: VCVoiceShortcutSyncStateManagedObject (Table: ZVCVOICESHORTCUTSYNCSTATEMANAGEDOBJECT)\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZCOLLECTION');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZLASTSYNCEDHASH\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZTOMBSTONED\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":5,\"name\":\"ZICON\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":6,\"name\":\"ZIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":7,\"name\":\"ZNAME\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":8,\"name\":\"ZCLOUDKITFOLDERRECORDMETADATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":9,\"name\":\"ZCLOUDKITORDERINGRECORDMETADATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":10,\"name\":\"ZLASTREMOTECOLLECTIONORDERINGDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":11,\"name\":\"ZLASTREMOTECOLLECTIONORDERINGSUBSETDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":12,\"name\":\"ZLASTREMOTESHORTCUTORDERINGDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":13,\"name\":\"ZLASTREMOTESHORTCUTORDERINGSUBSETDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":14,\"name\":\"ZLASTSYNCEDENCRYPTEDSCHEMAVERSION\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":15,\"name\":\"ZWANTEDENCRYPTEDSCHEMAVERSION\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZLIBRARY');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZLASTSYNCEDHASH\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":5,\"name\":\"ZVERSION\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":6,\"name\":\"ZCLOUDKITRECORDMETADATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":7,\"name\":\"ZDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZSHORTCUT');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZACTIONCOUNT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZHASOUTPUTFALLBACK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":5,\"name\":\"ZHASSHORTCUTINPUTVARIABLES\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":6,\"name\":\"ZHIDDENFROMWIDGET\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":7,\"name\":\"ZLASTSYNCEDHASH\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":8,\"name\":\"ZRECEIVESONSCREENCONTENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":9,\"name\":\"ZREMOTEQUARANTINESTATUSVALUE\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":10,\"name\":\"ZRUNEVENTSCOUNT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":11,\"name\":\"ZSYNCHASH\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":12,\"name\":\"ZTOMBSTONED\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":13,\"name\":\"ZTRIGGERCOUNT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":14,\"name\":\"ZACTIONS\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":15,\"name\":\"ZCONFLICTOF\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":16,\"name\":\"ZICON\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":17,\"name\":\"ZQUARANTINE\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":18,\"name\":\"ZCREATIONDATE\",\"type\":\"TIMESTAMP\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":19,\"name\":\"ZLASTRUNEVENTDATE\",\"type\":\"TIMESTAMP\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":20,\"name\":\"ZMODIFICATIONDATE\",\"type\":\"TIMESTAMP\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":21,\"name\":\"ZACTIONSDESCRIPTION\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":22,\"name\":\"ZASSOCIATEDAPPBUNDLEIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":23,\"name\":\"ZGALLERYIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":24,\"name\":\"ZLASTMIGRATEDCLIENTVERSION\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":25,\"name\":\"ZLASTSAVEDONDEVICENAME\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":26,\"name\":\"ZMINIMUMCLIENTVERSION\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":27,\"name\":\"ZNAME\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":28,\"name\":\"ZPHRASE\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":29,\"name\":\"ZSOURCE\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":30,\"name\":\"ZWORKFLOWID\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":31,\"name\":\"ZWORKFLOWSUBTITLE\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":32,\"name\":\"ZCLOUDKITRECORDMETADATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":33,\"name\":\"ZIMPORTQUESTIONSDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":34,\"name\":\"ZINPUTCLASSESDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":35,\"name\":\"ZNOINPUTBEHAVIORDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":36,\"name\":\"ZOUTPUTCLASSESDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":37,\"name\":\"ZLASTSYNCEDENCRYPTEDSCHEMAVERSION\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":38,\"name\":\"ZWANTEDENCRYPTEDSCHEMAVERSION\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":39,\"name\":\"ZDISABLEDONLOCKSCREEN\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":40,\"name\":\"ZREMOTEQUARANTINEHASH\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":41,\"name\":\"ZSHOULDAUTOUPDATEASSOCIATEDAPPBUNDLEIDENTIFIER\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":42,\"name\":\"ZHIDDENFROMLIBRARYANDSYNC\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZSHORTCUTACTIONS');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZSHORTCUT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZSHORTCUTBOOKMARK');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZPATH\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":5,\"name\":\"ZBOOKMARKDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"PRAGMA table_info('ZTRIGGER');\"\n\n[{\"cid\":0,\"name\":\"Z_PK\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":1},\n{\"cid\":1,\"name\":\"Z_ENT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":2,\"name\":\"Z_OPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":3,\"name\":\"ZENABLED\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":4,\"name\":\"ZNOTIFICATIONLEVEL\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":5,\"name\":\"ZSHOULDNOTIFY\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":6,\"name\":\"ZSHOULDPROMPT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":7,\"name\":\"ZSOURCE\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":8,\"name\":\"ZRUNEVENTS\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":9,\"name\":\"ZSHORTCUT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":10,\"name\":\"ZIDENTIFIER\",\"type\":\"VARCHAR\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":11,\"name\":\"ZDATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":12,\"name\":\"ZEDITABLESHORTCUT\",\"type\":\"INTEGER\",\"notnull\":0,\"dflt_value\":null,\"pk\":0},\n{\"cid\":13,\"name\":\"ZSELECTEDENTRYMETADATA\",\"type\":\"BLOB\",\"notnull\":0,\"dflt_value\":null,\"pk\":0}]\n```\n\nThis shows a few of the relevant'ish looking human readable columns:\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"SELECT Z_PK, ZNAME, ZWORKFLOWSUBTITLE, ZACTIONCOUNT, ZTRIGGERCOUNT, ZACTIONS, ZSOURCE, ZWORKFLOWID FROM ZSHORTCUT;\"\n\n[\n    // ..snip..\n    {\n        \"Z_PK\": 15,\n        \"ZNAME\": \"Sleep (20min)\",\n        \"ZWORKFLOWSUBTITLE\": \"5 actions\",\n        \"ZACTIONCOUNT\": 5,\n        \"ZTRIGGERCOUNT\": 0,\n        \"ZACTIONS\": 15,\n        \"ZSOURCE\": null,\n        \"ZWORKFLOWID\": \"66DF3D2D-0FCF-471E-84D2-597A501648D4\"\n    },\n    // ..snip..\n]       \n```\n\nThis is similar to above, but will also `JOIN` with `ZSHORTCUTACTIONS` to link the `ZDATA` field as well (which appears to be a `bplist`):\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"\n  SELECT \n    ZSHORTCUT.Z_PK,\n    ZSHORTCUT.ZNAME,\n    ZSHORTCUT.ZWORKFLOWSUBTITLE,\n    ZSHORTCUT.ZACTIONCOUNT,\n    ZSHORTCUT.ZTRIGGERCOUNT,\n    ZSHORTCUT.ZACTIONS,\n    ZSHORTCUT.ZSOURCE,\n    ZSHORTCUT.ZWORKFLOWID,\n    ZSHORTCUTACTIONS.ZDATA\n  FROM ZSHORTCUT\n  LEFT JOIN ZSHORTCUTACTIONS ON ZSHORTCUT.ZACTIONS = ZSHORTCUTACTIONS.Z_PK;\"\n```\n\nThis is similar to above, but will also attempt to parse the `ZDATA` `bplist` with python's `plistlib`:\n\n```shell\n⇒ sqlite3 -readonly -json ~/Library/Shortcuts/Shortcuts.sqlite \"\n  SELECT \n    ZSHORTCUT.Z_PK, \n    ZSHORTCUT.ZNAME, \n    ZSHORTCUT.ZWORKFLOWSUBTITLE, \n    ZSHORTCUT.ZACTIONCOUNT, \n    ZSHORTCUT.ZTRIGGERCOUNT, \n    ZSHORTCUT.ZACTIONS, \n    ZSHORTCUT.ZSOURCE, \n    ZSHORTCUT.ZWORKFLOWID, \n    hex(ZSHORTCUTACTIONS.ZDATA) AS ZDATA_HEX\n  FROM ZSHORTCUT\n  LEFT JOIN ZSHORTCUTACTIONS ON ZSHORTCUT.ZACTIONS = ZSHORTCUTACTIONS.Z_PK;\" | python3 -c \"\nimport sys, json, plistlib, binascii\n\n# Custom JSON encoder for unsupported types\nclass CustomJSONEncoder(json.JSONEncoder):\n    def default(self, obj):\n        if isinstance(obj, bytes):\n            return obj.hex()  # Convert bytes to a hexadecimal string\n        return super().default(obj)\n\n# Load JSON from stdin\ndata = json.load(sys.stdin)\n\n# Decode ZDATA_HEX\nfor row in data:\n    if row.get('ZDATA_HEX'):\n        try:\n            # Convert hex to bytes\n            row['ZDATA'] = plistlib.loads(binascii.unhexlify(row['ZDATA_HEX']))\n            del row['ZDATA_HEX']  # Remove the hex version\n        except Exception as e:\n            row['ZDATA'] = f'Decoding Error: {e}'\n\n# Print the modified JSON\nprint(json.dumps(data, indent=2, cls=CustomJSONEncoder))\n\"\n```\n\nWhich results in output like this:\n\n```json5\n[\n  // ..snip\n  {\n    \"Z_PK\": 15,\n    \"ZNAME\": \"Sleep (20min)\",\n    \"ZWORKFLOWSUBTITLE\": \"5 actions\",\n    \"ZACTIONCOUNT\": 5,\n    \"ZTRIGGERCOUNT\": 0,\n    \"ZACTIONS\": 15,\n    \"ZSOURCE\": null,\n    \"ZWORKFLOWID\": \"66DF3D2D-0FCF-471E-84D2-597A501648D4\",\n    \"ZDATA\": [\n      {\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.number\",\n        \"WFWorkflowActionParameters\": {\n          \"UUID\": \"4A620A3D-E6A4-48AA-8191-A5A29F77B327\",\n          \"WFNumberActionNumber\": 1200.0\n        }\n      },\n      {\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.delay\",\n        \"WFWorkflowActionParameters\": {\n          \"WFDelayTime\": {\n            \"Value\": {\n              \"OutputUUID\": \"4A620A3D-E6A4-48AA-8191-A5A29F77B327\",\n              \"Type\": \"ActionOutput\",\n              \"OutputName\": \"Number\"\n            },\n            \"WFSerializationType\": \"WFTextTokenAttachment\"\n          }\n        }\n      },\n      {\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.pausemusic\",\n        \"WFWorkflowActionParameters\": {}\n      },\n      {\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.airplanemode.set\",\n        \"WFWorkflowActionParameters\": {}\n      },\n      {\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.timer.start\",\n        \"WFWorkflowActionParameters\": {}\n      }\n    ]\n  },\n  // ..snip\n]\n```\n\n> ### **2. Shortcut Files**\n> \n> Individual shortcuts are also stored as serialized plist (property list) files in the directory:\n> \n> ```plaintext\n> ~/Library/Shortcuts/Shortcuts\n> ```\n> \n> 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.\n\nOn 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:\n\n```shell\n⇒ ls -l ~/Library/Shortcuts\n\nSecuredPreferences.plist\nShareSheetState.plist\nShortcuts.sqlite\nShortcuts.sqlite-shm\nShortcuts.sqlite-wal\nSpotlight.dat\nTemporary/\nssh/\n```\n\n```shell\n⇒ ls -l ~/Library/Shortcuts/Temporary\n\ncom.apple.WorkflowKit.BackgroundShortcutRunner/\ncom.apple.WorkflowUI.CatalystContentExtension/\ncom.apple.shortcuts/\ncom.apple.shortcuts.Run-Workflow/\ncom.apple.shortcuts.ThumbnailExtension/\ncom.apple.shortcuts.events/\ncom.apple.siriactionsd/\n```\n\nThat said, based on the exploration of the sqlite DB earlier.. it looks like we can get the shortcut actions from the `ZSHORTCUT` / `ZSHORTCUTACTIONS` tables.\n\n> ### **3. iCloud Sync**\n> \n> If iCloud syncing is enabled, shortcuts may also be stored in the iCloud directory:\n> \n> ```plaintext\n> ~/Library/Mobile Documents/iCloud~is~workflow/Shortcuts\n> ```\n> \n> This allows synchronization across Apple devices, and the format mirrors the local storage with plist files and associated metadata.\n\nOn macOS 14.5, that folder doesn't seem to exist, but these ones did (though they seem to be empty?):\n\n```shell\n⇒ ls -la ~/Library/Mobile\\ Documents | grep -E 'workflow|shortcut'\n\niCloud~com~apple~shortcuts~runtime/\niCloud~is~workflow~my~workflows/\n```\n\n```shell\n⇒ fd . ~/Library/Mobile\\ Documents/iCloud\\~com\\~apple\\~shortcuts\\~runtime\n\n/Users/devalias/Library/Mobile Documents/iCloud~com~apple~shortcuts~runtime/Documents/\n```\n\n```shell\n⇒ fd . ~/Library/Mobile\\ Documents/iCloud\\~is\\~workflow\\~my\\~workflows\n\n/Users/devalias/Library/Mobile Documents/iCloud~is~workflow~my~workflows/Documents/\n```\n\n> ### **4. Auxiliary Files**\n> \n> Additional metadata, such as user preferences and categories, is often stored in other plist files in:\n> \n> ```plaintext\n> ~/Library/Preferences/com.apple.shortcuts.plist\n> ```\n\nOn macOS 14.5, this file seems to exist, but it doesn't look super interesting:\n\n```shell\n⇒ plutil -convert xml1 -o - ~/Library/Preferences/com.apple.shortcuts.plist\n```\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CKPerBootTasks</key>\n\t<array>\n\t\t<string>CKAcccountInfoCacheReset</string>\n\t</array>\n\t<key>CKStartupTime</key>\n\t<integer>1731387059</integer>\n\t<key>MainViewLayoutMode</key>\n\t<string>grid</string>\n\t<key>NSNavLastRootDirectory</key>\n\t<string>~/Desktop</string>\n\t<key>NSNavPanelExpandedSizeForSaveMode</key>\n\t<string>{800, 448}</string>\n\t<key>NSNavPanelExpandedStateForSaveMode</key>\n\t<true/>\n\t<key>NSWindow Frame Main Window</key>\n\t<string>765 279 1000 678 0 0 1792 1095 </string>\n\t<key>NSWindow Frame NSNavPanelAutosaveName</key>\n\t<string>1320 590 800 364 0 0 3440 1415 </string>\n\t<key>NSWindow Frame NSSpellCheckerSubstitutionsPanel2</key>\n\t<string>-116 1527 428 137 -808 1120 3440 1415 </string>\n\t<key>wdsCacheCleanupState</key>\n\t<dict>\n\t\t<key>com.apple.shortcuts</key>\n\t\t<true/>\n\t</dict>\n</dict>\n</plist>\n```\n\n> ### **Key Points**\n> \n> - **SQLite Database**: Used for managing and querying shortcuts metadata efficiently.\n> - **Plist Files**: Contain the actual logic and structure of the shortcuts.\n> - **iCloud Sync**: Optional storage for cloud syncing.\n> - **Sandboxing**: The Shortcuts app adheres to macOS sandboxing rules, restricting access to its files unless explicitly granted.\n> \n> 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.\n\n## Further Reading / Others Research\n\n- https://github.com/joshfarrant/shortcuts-js\n  - > A JavaScript iOS 12 Shortcuts creator\n  - https://shortcuts.fun/\n    - > Shortcuts JS 🧞‍♂️\n      > Create Apple Shortcuts using JavaScript\n- https://github.com/sebj/iOS-Shortcuts-Reference\n  - > Shortcuts File Format Reference\n  - > Reference documentation for the iOS Shortcuts app file structure\n- https://zachary7829.github.io/blog/shortcuts/\n  - > Deep Into Shortcuts\n  - https://zachary7829.github.io/blog/shortcuts/fileformat\n    - > Shortcuts File Format Documentation\n    - > How shortcuts are stored\n      > On iOS 13, shortcuts are stored in `/var/mobile/Library/Shortcuts/Shortcuts.realm`. However, iOS 14 changes this to be in `Shortcuts.sqlite`\n    - > 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).\n    - > 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).\n    - > Importing\n      > 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.)\n      > \n      > 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.\n\n## See Also\n\n- https://shortcuts.fun/\n  - > Create Apple Shortcuts using JavaScript\n  - https://github.com/joshfarrant/shortcuts-js\n    - > A JavaScript iOS 12 Shortcuts creator\n    - https://github.com/joshfarrant/shortcuts-js/issues/683\n      - > Looking for new maintainers\n- https://www.reddit.com/r/shortcuts/comments/13h61hv/gpt4_can_write_shortcuts/\n  - > GPT4 can write shortcuts\n\n### Announcement Tweet\n\n- Gist Announcement Tweet: https://twitter.com/_devalias/status/1660516786826264585\n\n### My Other Related Deepdive Gist's and Projects\n\n- https://github.com/0xdevalias\n- https://gist.github.com/0xdevalias\n- [Integrating Workflow Automation Tools (Apple Shortcuts, Alfred, BetterTouchTool, etc) via CLI, etc (0xdevalias' gist)](https://gist.github.com/0xdevalias/8c76b00670ab00f2a0023bafb0867efe#integrating-workflow-automation-tools-apple-shortcuts-alfred-bettertouchtool-etc-via-cli-etc)\n- [Accessing / Exporting Apple's Reminders Data on macOS (0xdevalias' gist)](https://gist.github.com/0xdevalias/ccc2b083ff58b52aa701462f2cfb3cc8#accessing--exporting-apples-reminders-data-on-macos)\n  - [Core Data](https://gist.github.com/0xdevalias/ccc2b083ff58b52aa701462f2cfb3cc8#core-data)\n    - [`extract-coredata-model-hierarchy.py`](https://gist.github.com/0xdevalias/ccc2b083ff58b52aa701462f2cfb3cc8#file-extract-coredata-model-hierarchy-py)\n  - [macOS Private Frameworks](https://gist.github.com/0xdevalias/ccc2b083ff58b52aa701462f2cfb3cc8#macos-private-frameworks)\n- [Accessing / Exporting Apple's Screen Time Data (0xdevalias' gist)](https://gist.github.com/0xdevalias/38cfc92278f85ae89a46f0c156208fd5#accessing--exporting-apples-screen-time-data)\n- [Accessing Apple's iCloud Synced Passwords Keychain (0xdevalias' gist)](https://gist.github.com/0xdevalias/2da38a9d57f3125a7214b4bf1cbc185e#accessing-apples-icloud-synced-passwords-keychain)\n- [AppleScript Automation Snippets (0xdevalias' gist)](https://gist.github.com/0xdevalias/db8b0c8b626ccb9a775b806be673eb55#applescript-automation-snippets)\n- [macOS Launchpad DB Tricks](https://gist.github.com/0xdevalias/fe8ebac5dd408c6c98ef0665820dc7ec#macos-launchpad-db-tricks)\n- [Toggl Track macOS SQLite Data (0xdevalias' gist)](https://gist.github.com/0xdevalias/46fe2f14196eb279ab749f18d2526f86#toggl-track-macos-sqlite-database-exploration-coredata)\n- [Reverse Engineering on macOS (0xdevalias' gist)](https://gist.github.com/0xdevalias/256a8018473839695e8684e37da92c25#reverse-engineering-on-macos)", "url": "https://wpnews.pro/news/a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source", "canonical_source": "https://gist.github.com/0xdevalias/27d9aea9529be7b6ce59055332a94477", "published_at": "2023-05-22 05:22:15+00:00", "updated_at": "2026-05-22 12:39:21.970351+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Apple", "RoutineHub", "0xdevalias", "joshfarrant", "Reddit"], "alternates": {"html": "https://wpnews.pro/news/a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source", "markdown": "https://wpnews.pro/news/a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source.md", "text": "https://wpnews.pro/news/a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source.txt", "jsonld": "https://wpnews.pro/news/a-few-notes-on-decompiling-apple-shortcuts-workflows-into-their-raw-xml-source.jsonld"}}