Skip to content

Instantly share code, notes, and snippets.

@0xdevalias
Last active July 14, 2025 08:18
Show Gist options
  • Save 0xdevalias/27d9aea9529be7b6ce59055332a94477 to your computer and use it in GitHub Desktop.
Save 0xdevalias/27d9aea9529be7b6ce59055332a94477 to your computer and use it in GitHub Desktop.
A few notes on decompiling Apple Shortcuts workflows into their raw XML 'source code'.

Decompile Apple Shortcuts into raw XML 'source code'

Table of Contents

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:

Originally posted by @0xdevalias in joshfarrant/shortcuts-js#683 (comment)

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 joshfarrant/shortcuts-js#683 (comment)

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:

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

# Custom JSON encoder for unsupported types
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)

# Load JSON from stdin
data = json.load(sys.stdin)

# Decode ZDATA_HEX
for row in data:
    if row.get('ZDATA_HEX'):
        try:
            # Convert hex to bytes
            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 the modified JSON
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.pausemusic",
        "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

@enokseth
Copy link

tanks !

@0xdevalias
Copy link
Author

@enokseth No worries, hope it was helpful :)

I also just updated it with a bunch more info, looking a bit closer at the SQLite DB structure and how to access the shortcut source that way too: https://gist.github.com/0xdevalias/27d9aea9529be7b6ce59055332a94477/revisions#diff-fe24bfaa7c519750dadca91760cb9711e5d43dc15b2a3d4541d0bff7fd06101a

Curious (if you're open to sharing), what's your interest/use case? Any cool projects/ideas in mind?

@enokseth
Copy link

@enokseth No worries, hope it was helpful :)

I also just updated it with a bunch more info, looking a bit closer at the SQLite DB structure and how to access the shortcut source that way too: https://gist.github.com/0xdevalias/27d9aea9529be7b6ce59055332a94477/revisions#diff-fe24bfaa7c519750dadca91760cb9711e5d43dc15b2a3d4541d0bff7fd06101a

Curious (if you're open to sharing), what's your interest/use case? Any cool projects/ideas in mind?

Just learn have just attempted to make same thin with chatGPT but i lost my time censored in my countrie... and i'm going to make my own store and server for EU, just for fun, with your work i can understand better what i'm in making ;) tanks !

@0xdevalias
Copy link
Author

i'm going to make my own store and server for EU, just for fun

Neat :)

with your work i can understand better what i'm in making ;) tanks !

No worries!

@enokseth
Copy link

i'm going to make my own store and server for EU, just for fun

Neat :)

with your work i can understand better what i'm in making ;) tanks !

No worries!

and I mainly work on software like chimera tools a ROM modifier mounter / Compiler, Downgrader, silent enrollment BRUTERATEL and many other things in python3 and many language needed

if you want contact me on Sessions Messenger because in my coutry is difficult ( france to do anithings in publis whenevers it's legal ) !

seed's:

05e0b48593a9f881dd67ecd4b4a606207e8dcfdeded10af14cdcba85011ff60d70

@huangzhuxing
Copy link

I've created a tool based on your methods. Feel free to use it.
https://github.com/huangzhuxing/ShortcutSourceTool

@dhcav
Copy link

dhcav commented Jun 4, 2025

thanks for taking the time to write this up

@0xdevalias
Copy link
Author

@dhcav No worries; glad they're helpful :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment