Jump List Parser
Back to blog

The CustomDestinations-ms File Format Explained

2026-05-253 min read

Unlike automaticDestinations-ms, which Windows assembles automatically from per-application MRU data, a customDestinations-ms file is written by the application itself through the ICustomDestinationList COM API. Its purpose is to publish what the app explicitly wants on its Jump List — Tasks, pinned items, and arbitrary named categories. The on-disk format is a flat, sequential stream of categorized shell links rather than an OLE Compound File: a small header followed by a series of category blocks, each containing a count and then that many serialized LNK structures.

Why CustomDestinations exist

AutomaticDestinations answers "what did the user touch recently?". Custom- Destinations answers "what does the application want to offer?". The developer calls BeginList, appends IObjectCollection instances via AppendCategory, AppendKnownCategory, or AddUserTasks, and finalizes with CommitList. The shell serializes the result to %AppData%\Microsoft\Windows\Recent\ CustomDestinations\<AppID>.customDestinations-ms.

Typical contents include a Tasks category (app-defined verbs such as "New Incognito Window" or "New Private Window"), a known category like Frequent or Recent that the app delegates to the shell, and one or more custom categories with arbitrary UTF-16 names (browsers, IDEs, and media players use this heavily).

On-disk structure

The file opens with a fixed header containing a magic value and a format version, followed by a count of category entries. Each category entry encodes:

  • a type field — 0 for a custom (app-named) category, 1 for a known category such as Frequent or Recent, and 2 for the Tasks category;
  • for custom categories, a UTF-16LE name prefixed by its length in characters (not bytes);
  • an entry count;
  • that many serialized LNK structures written back-to-back.
[ header: magic + version + category_count ]
repeat category_count times:
    [ type (0=custom | 1=known | 2=tasks) ]
    [ if type==0: name_len_chars + UTF-16LE name ]
    [ entry_count ]
    [ LNK #1 ][ LNK #2 ] ... [ LNK #entry_count ]
[ trailing footer / padding ]

There is no per-entry length prefix in front of each LNK — you parse the shell link in place and let the LNK structure tell you where it ends.

The LNK payload

Each destination is a standard MS-SHLLINK shell link, identical in shape to the numbered streams found inside AutomaticDestinations. That means the same LNK parser is reused: ShellLinkHeader, optional LinkTargetIDList, LinkInfo, the StringData blocks, and any ExtraData blocks (notably the TrackerDataBlock with its volume/machine IDs and droid GUIDs). Target path, working directory, command-line arguments, icon location, and the three FILETIME timestamps all come from the LNK itself — the CustomDestinations container does not duplicate them.

Pinned items and the Tasks category

The Tasks category (type 2) holds developer-defined launch verbs rather than user documents — "Open new window", "Compose mail", "Start a private session". These LNKs typically point at the application executable with specific command-line arguments.

Pinned entries are stored alongside other categories but survive Jump List resets and explicit clearing of recent items, which makes them a useful persistence signal during triage: a path that a user actively pinned implies intent, not just incidental MRU activity.

Practical parsing notes

  • Read strictly sequentially. There is no central directory; offsets are implicit in the cursor position.
  • There is no OLE layer — do not try to open the file with a Compound File reader. Treat it as a raw byte stream.
  • UTF-16LE strings are length-prefixed in characters, so multiply by two before advancing the cursor.
  • Expect trailing footer bytes or padding after the last category; tolerate a short tail rather than treating it as corruption.
  • Reuse your existing LNK parser verbatim; the format is documented by Microsoft as part of the shell developer documentation and has been reverse-engineered by Volatility, libforensics, fox-it, and kacos2000.

If you have a sample file, try it in the in-browser parser — it decodes both file types side-by-side.