# Backups and restore (/docs/backups)
Every tool in this category promises to back up first. Here the promise is not
a policy — it is a type.
## The guarantee [#the-guarantee]
The functions that change the registry all take a `BackupToken` as an argument.
The only way to obtain one is a successful export, and the token records which
keys it covers. Before a key is touched, the token is checked against that key.
Nothing enforces this at run time and nothing has to: a call without a token
does not compile. "Never change without a backup" is therefore not a rule
somebody has to remember while writing the next feature.
A group action takes exactly one backup for the whole group. If the export
fails for one key, that step is refused and reported instead of running
unbacked — nine of ten things done silently is the failure this design exists
to prevent.
## Backing up on demand [#backing-up-on-demand]
The *Back up* button in the top bar makes a backup and changes nothing else. It
covers the selected rows, or everything currently listed when nothing is
selected.
*Back Up Everything*, on the Backups tab, takes along every location the
program touches at all. On the machine this was measured on that is 1.2 MB and
takes under a second.
## Getting back [#getting-back]
The Backups tab lists what was taken, when, and for which action. Restoring
plays the export back into the registry.
`reg import` adds and overwrites; it never removes. Restoring after a delete
recreates exactly the keys that were removed. Restoring over a key that has
gained values since leaves those extra values in place. The one exception is a
key the backup found empty — that empty state is put back by removing the key
again.
## Where the files are [#where-the-files-are]
Backups live under `%LOCALAPPDATA%\ctxmenu\backups`, one directory per action,
named with a timestamp down to the millisecond. Each holds the exported `.reg`
files and a `manifest.json` describing what was taken and why.
They are plain files. Nothing stops you from reading them, copying them
elsewhere, or importing one by hand with `reg import`.
# Building it yourself (/docs/building)
## What you need [#what-you-need]
Rust 1.95 or newer, for the `x86_64-pc-windows-msvc` target.
The Visual Studio Build Tools with the C++ toolchain.
The Windows SDK. The build writes the icon and the version number into the
file resource, and that step calls `rc.exe`, which ships with the SDK and not
with the build tools.
## Two commands [#two-commands]
```powershell
cargo build --release
cargo test
```
The suite is 536 tests. `cargo clippy --all-targets -- -D warnings` passes
with nothing left over, and CI runs the same check again.
Run `cargo test` without `--test-threads=1`. CI calls it that way, and faults
between concurrent tests only show up then.
## Where the file lands [#where-the-file-lands]
The result is `target\x86_64-pc-windows-msvc\release\ctxmenu.exe`, not
`target\release\ctxmenu.exe`. The reason is `.cargo\config.toml`: it names
`target = "x86_64-pc-windows-msvc"` under `[build]`, and cargo puts the
artefacts of an explicitly named target in their own directory.
That name is not decoration. The same file sets
`-C target-feature=+crt-static` for the target, which links the C runtime
into the binary. `crt-static` is not the default here. A plain release build
links `VCRUNTIME140.dll` plus the `api-ms-win-crt-*` forwarders, and that
breaks the promise of one `.exe` with nothing to install beside it.
With an explicit target, cargo builds the build scripts and proc-macro crates
for the host without those flags. Without it, the flag would hit them too, and
a proc-macro dylib cannot link against a static CRT. The build then fails
outright.
The finished file therefore needs no Visual C++ Redistributable. That was
checked on a freshly installed Windows 10 carrying no other software.
## The handler DLL is built along the way [#the-handler-dll-is-built-along-the-way]
`ctxmenu/build.rs` calls cargo a second time and builds the `ctxmenu-handler`
crate as `ctxmenu_handler.dll`, always in release, into its own `--target-dir`
below `OUT_DIR`. The window then embeds that DLL with `include_bytes!` and
writes it to `%LOCALAPPDATA%` when you switch the Windows 11 entries on.
Two details in that inner build matter. It always uses the release profile, so
a debug build of the window still carries the optimised handler. And it clears
`CARGO_TARGET_DIR` first, because sharing the outer target directory deadlocks
on cargo's build lock.
A `rerun-if-changed` on the handler's sources keeps this off every build. As
long as those files are untouched, cargo skips the whole script.
## When setup fails [#when-setup-fails]
| Symptom | Cause |
| ----------------------------------- | -------------------------------------------------------------------------------------- |
| `link.exe not found` | The MSVC build tools are missing, or the installed architecture is the wrong one. |
| `rc.exe not found` | The Windows SDK is missing. `winresource` calls `rc.exe`, and that comes from the SDK. |
| Path errors in deep directories | `LongPathsEnabled` is not set. |
| A black window, or no window at all | OpenGL is missing. This happens in remote sessions and in virtual machines. |
| A full build takes a very long time | Defender is scanning `target\` along with everything else. |
| `cargo` not found | The console was opened before `rustup` ran. Open a new one. |
## Speed [#speed]
These numbers were measured on the author's machine, four screens at
3840x2160. Your machine will give you its own.
| What was measured | Result |
| ----------------------------------------------------------------- | ---------------------------- |
| Process creation to the first visible list, with 927 real entries | 714 to 724 ms |
| The same, the very first run of a freshly built `.exe` | 1113 to 1277 ms |
| Scrolling a table of 2000 rows | 16.7 ms per frame on average |
| The worst frame out of 300 | 18.5 ms |
`--synthetic 2000` fills the table with generated rows, so the list can be
judged without owning a machine that really carries that many.
`--bench 300` measures the next 300 frames.
# Command line (/docs/command-line)
The same `.exe` is the window and a diagnostic tool. Output goes to the console
it was started from, in the language the window is set to.
`ctxmenu` on its own opens the window. It does not print a usage message. The
window is the product, so the bare command hands you the product. The list of
commands and switches lives behind `ctxmenu --help`.
## Choosing a language for one run [#choosing-a-language-for-one-run]
`--lang de` and `--lang en` go in front of the command, never after it.
```powershell
ctxmenu --lang en scan
```
The flag covers the window and every subcommand alike, and it leaves the saved
setting as it is. It has to lead because `--name`, `--args` and `--sub` all take
free text. A global flag that might be hiding inside one of those values is a
flag nobody can reason about.
## Reading [#reading]
Nothing in this table writes to the registry.
| Command | What it prints |
| ------------------------ | --------------------------------------------------------- |
| `ctxmenu scan` | the context menu entries, as a table |
| `ctxmenu programs` | the same entries, grouped by the program they belong to |
| `ctxmenu filetype .jpg` | the resolution chain of one file type |
| `ctxmenu created` | the entries this tool created |
| `ctxmenu packaged` | the entries of the new Windows 11 menu, `--json` for JSON |
| `ctxmenu favourites` | the favourites |
| `ctxmenu backups` | the backups taken so far |
| `ctxmenu handler status` | whether the Windows 11 handler is installed |
### Switches for `scan` [#switches-for-scan]
These belong to `scan` alone. The other reading subcommands take no options.
| Switch | Effect |
| ------------------- | ------------------------------------------------------------------------------- |
| `--category ` | one category instead of all of them |
| `--scope ` | `user`, `machine`, `machine32` or `all`. The default is `all` |
| `--all-types` | walk the file type chain as well, for the curated list plus your own extensions |
| `--every-type` | instead of the curated list: every extension registered on this machine |
| `--json` | write JSON to stdout |
| `--quiet` | drop the progress output |
The category names are `allfiles`, `allfilesystemobjects`, `unknown`,
`directory`, `directorybackground`, `directoryaudio`, `directoryimage`,
`directoryvideo`, `folder`, `desktopbackground` and `drive`. The same names work
for `create` and for `favourite place`.
```powershell
ctxmenu scan --category directory
ctxmenu scan --all-types --json
ctxmenu scan --every-type
```
## Naming a key [#naming-a-key]
Every command that changes something takes a key. The key is the full path below
a Classes root, written the way `reg.exe` writes it.
```powershell
ctxmenu hide "HKCU\SOFTWARE\Classes\Directory\shell\MyEntry" --yes
```
Anything above that root is refused. So is any path that ends in a collecting
key such as `shell`, because such a path names a container and not an entry.
The plain `scan` table does not print the key. `ctxmenu scan --json` carries it
in the `registry_path` field, and the window shows it in the detail pane of the
selected entry.
## Changing [#changing]
| Command | What it does |
| --------------------------------- | ----------------------------------------------------------------------------- |
| `ctxmenu hide --yes` | sets the hide flag |
| `ctxmenu show --yes` | clears it again |
| `ctxmenu shift-only --yes` | shows the entry only on Shift and right-click |
| `ctxmenu always-show --yes` | clears that again |
| `ctxmenu delete --yes` | backs the key up, then deletes it |
| `ctxmenu backup-all` | backs up every place this tool touches |
| `ctxmenu restore ` | plays a backup directory back into the registry |
| `ctxmenu handler install` | offers your own entries in the upper Windows 11 menu. Needs admin rights once |
| `ctxmenu handler remove` | takes that offer back |
Each of the four flag verbs and `delete` takes a backup before it writes, and
asks for elevation when the key needs it.
Without `--yes` the command names the key it would touch. For the four flag
verbs it also states whether that step would need administrator rights. This is
the cheapest way to check a key you typed by hand.
## Creating an entry [#creating-an-entry]
`create` writes into `HKCU`. Pick the target with one of `--category `,
`--ext .png` or `--perceived image`.
```powershell
ctxmenu create --category directory --name "Open with Notepad" --command "\"C:\Windows\notepad.exe\" \"%1\""
```
| Switch | Effect |
| ------------------ | ----------------------------------------------------------- |
| `--name ` | the label the menu shows |
| `--command ` | the command line the entry runs |
| `--key ` | the registry key name, when it should differ from the label |
| `--icon [` | an icon reference |
| `--position` | `top` or `bottom`, to pin the entry to one end of the menu |
| `--extended` | the entry appears only on Shift and right-click |
`--sub` replaces `--command` and builds a submenu instead of a single entry. One
`--sub` per child, split at the first vertical bar. A `--sub-icon` applies to the
`--sub` in front of it.
```powershell
ctxmenu create --category directory --name "Tools" --sub "Open|\"C:\Windows\notepad.exe\" \"%1\"" --sub "List|cmd /c dir \"%1\" & pause"
```
In `directorybackground` and `desktopbackground`, Windows leaves `%1` empty.
`%V` carries the path there instead. The tool warns about this, because an entry
that does nothing looks exactly like an entry that works.
## Favourites [#favourites]
```powershell
ctxmenu favourite add --name "Shrink PNG" --url https://squoosh.app --mode clipboard
ctxmenu favourite place --ext .png
ctxmenu favourite remove
ctxmenu favourite run C:\pictures\shot.png
```
`favourite add` takes one of three sources.
| Source | Switches |
| -------------------------------- | ----------------------------------------------------------------- |
| A program | `--exe `, optionally `--args ` |
| A web tool without an endpoint | `--url `, optionally `--mode clipboard` or `--mode open` |
| A web tool that uploads the file | `--endpoint `, optionally `--raw` and `--field ` |
An endpoint favourite takes further switches: `--header "Name: Value"`,
`--result save`, `--result open` or `--result report`, `--suffix .min`,
`--json-path output.url`, and `--insecure` to allow an unencrypted `http://`
address.
`favourite place` puts a favourite into the menu. It takes the same three target
switches as `create`: `--category `, `--ext .png` or `--perceived image`.
`favourite run` runs one the way a click would, and reports on the console.
## Opening the window in a given state [#opening-the-window-in-a-given-state]
These switches open the window instead of printing anything.
| Switch | What opens |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| `--tab ` | the window on `categories`, `filetypes`, `programs`, `favourites`, `services` or `backups` |
| `--search ` | the window with the search box filled in |
| `--ext .png` | the file types tab, that extension selected |
| `--service ` | the services tab, that service selected and its tool list loaded. The id is the one in `services.json` |
| `--new ` | the editor for a new entry of that category, filled in with an example. Nothing is written |
]
```powershell
ctxmenu --tab services
ctxmenu --search 7-zip
ctxmenu --new directory
```
## Diagnostic switches [#diagnostic-switches]
| Switch | Effect |
| -------------------- | --------------------------------------------------------------------------------------- |
| `--window 1600x1000` | opens the window at that size in pixels on the leftmost screen. The minimum is 900x600 |
| `--synthetic ` | opens the window with n generated rows. `--bench ` turns it into a measured run |
| `--theme-probe` | flips the system theme once, reports whether the window followed, then restores it |
| `--smoke` | opens the smoke test window |
| `--version` | prints the version |
| `--help` | prints the command and switch list |
## Capturing the output [#capturing-the-output]
One trap here belongs to Windows and not to this program. The release build is
linked as a windowed program. The attribute is
`#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]` in
`ctxmenu/src/main.rs`, and it keeps a double-click from flashing a console. Two
things follow from it.
A windowed process starts with no standard handles at all. The program calls
`AttachConsole(ATTACH_PARENT_PROCESS)` before its first write, so a command
typed into a console does get its output. Started from Explorer the attach
fails, the program stays silent, and that is what the double-click case wants.
The shell also does not wait for a windowed process. `ctxmenu scan --json > scan.json`
therefore leaves the file empty, and it leaves no error behind to explain why.
Wait for the process explicitly, or work in a debug build. The
`windows_subsystem` attribute applies to `not(debug_assertions)` only, so a
debug build keeps the console subsystem and redirects the way any console
program does. Console work that runs longer than a single command is easier
there.
```powershell
Start-Process ctxmenu -ArgumentList 'scan','--json' -Wait -RedirectStandardOutput scan.json
```
```powershell
cargo run -- scan --json > scan.json
```
# How it compares (/docs/comparison)
Seven tools for the same menu, and none of them do the same thing. This page
says which is which, so the choice is an informed one rather than a search
result.
Documentation, release notes and licence files of each project, read on 21
August 2026. Every cell is read rather than tested, and the amber ones say
where a feature exists but is limited. Versions and prices age fast; if a
line here is wrong, [say so in an
issue](https://github.com/corgan2222/context-manager/issues/new/choose) and
it gets corrected.
## The field [#the-field]
| Tool | Licence | Built on | Needs admin | Approach |
| -------------------------------------------------------------------------- | -------------------- | --------------------------- | ----------- | --------------------------------------- |
| [context-manager](https://github.com/corgan2222/context-manager) | MIT | Rust, one file | no | GUI, writes to `HKCU` first |
| [ContextMenuManager](https://github.com/BluePointLilac/ContextMenuManager) | GPL-3.0 1 | C# WinForms, .NET Framework | partly | Toggles the entries that are there |
| [Nilesoft Shell](https://nilesoft.org/) | MIT | C++ shell extension | yes | Replaces the menu, configured in `.nss` |
| [FileMenu Tools](https://www.lopesoft.com/index.php/en/) | from about $11 | Closed source, native | yes | 40+ file utilities plus own commands |
| [ShellExView / ShellMenuView](https://www.nirsoft.net/utils/shexview.html) | Freeware, closed | Native, portable | for `HKLM` | View and disable, nothing else |
| [Easy Context Menu](https://www.sordum.org/7615/easy-context-menu-v1-6/) | Freeware, closed | Portable | yes | Checkbox presets per menu root |
| [Right Click Enhancer](https://rbsoft.org/downloads/right-click-enhancer/) | $9.99 Pro | .NET, closed | yes | Nine separate tools |
1 The repository's `LICENCE` file says GPL-3.0 while the About box
in the program says MIT. Treat it as GPL-3.0 until the project says otherwise.
## Feature by feature [#feature-by-feature]
present · limited, or needs a workaround · absent
| | [ctxmenu](https://github.com/corgan2222/context-manager) | [CMM](https://github.com/BluePointLilac/ContextMenuManager) | [Shell](https://nilesoft.org/) | [FMT](https://www.lopesoft.com/index.php/en/) | [NirSoft](https://www.nirsoft.net/utils/shexview.html) | [ECM](https://www.sordum.org/7615/easy-context-menu-v1-6/) | [RCE](https://rbsoft.org/downloads/right-click-enhancer/) |
| ------------------------------------------- | -------------------------------------------------------- | ----------------------------------------------------------- | ------------------------------ | --------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | --------------------------------------------------------- |
| Free of charge | | | | | | | |
| Open source | | | | | | | |
| Permissive licence | | | | – | – | – | – |
| Windows 11 menu | | | replaces it | | | | |
| **Works without administrator rights** | | | | | | | |
| GUI rather than a config file | | | | | | | |
| Backup before every change | | | | unverified | manual export | | reg export |
| Self-update, signature checked | | check only | | | | | |
| Favourites list | | | via config | | | presets | |
| Dark and light mode in the app | | | menu themes | | | | |
| Language switched at runtime | 2 | many | – | | restart | 34 | |
| Add entries by drag and drop | | | | | | | |
| Search across names, paths and **commands** | | filter | | | quick filter | | |
| **Finds entries whose program is gone** | | | | | manual | | |
| **Services from any OpenAPI address** | | | | | | | |
| One file, no runtime to install | | .NET | DLL | | | | .NET |
CMM = ContextMenuManager · Shell = Nilesoft Shell · FMT = FileMenu Tools ·
ECM = Easy Context Menu · RCE = Right Click Enhancer
# Contributing, security and licence (/docs/contributing)
The project has one maintainer and takes changes through pull requests. The
rules live in [CONTRIBUTING.md](https://github.com/corgan2222/context-manager/blob/main/CONTRIBUTING.md),
[SECURITY.md](https://github.com/corgan2222/context-manager/blob/main/SECURITY.md)
and [AI\_POLICY.md](https://github.com/corgan2222/context-manager/blob/main/AI_POLICY.md).
This page says what they contain and where each one applies.
## The one rule [#the-one-rule]
**Measured, not assumed.**
Windows behaves differently from its own documentation in more than one place.
The program therefore claims nothing that has not been verified on a real
system. Where a number is missing, no claim is made either. A change based on
"should work in theory" is not one. A number without a date is a suspicion.
Everything else in the contribution rules follows from that sentence.
## Reporting a bug [#reporting-a-bug]
The repository has issue forms under `.github/ISSUE_TEMPLATE`. Pick *Bug
report* and the form asks for what the maintainer needs to reproduce the
problem:
* **The ctxmenu version.** The About window names it, and so do the file
properties of the `.exe`.
* **The Windows version and build.** `winver` shows both. The build decides
which menus exist on your machine.
* **Which menu.** The new Windows 11 menu is the short one on the first
right-click. The classic menu sits behind *Show more options*. The form also
accepts "Both / not menu-related".
* **Where in the program.** A dropdown lists the tabs, the editor, own entries
in the Windows 11 menu, the self-update and the command line.
* **What happened.** The steps to get there, what you expected, and what you
saw instead.
* **The affected registry key**, if an entry is involved.
The last field is optional but it is the one that shortens most reports. The
detail pane shows the full path of the selected entry and copies it with one
click.
For a missing ability, pick *Feature request* instead. That form asks what
ctxmenu should be able to do, what situation makes you want it, where in the
program it would live, and how you solve it today. One ability per issue reads
best.
The log at `%LOCALAPPDATA%\ctxmenu\ctxmenu.log` names registry paths and file
names from your own machine. Review the excerpt before it goes into a public
issue.
## Reporting a vulnerability [#reporting-a-vulnerability]
A security vulnerability does not belong in an issue. An issue is public the
moment it is filed, and every reader is someone who can act on it before a fix
exists.
Two private channels exist, and both are fine:
* **A private report through GitHub.** Open the *Security* tab of the
repository and choose *Report a vulnerability*. This one is preferred. The
report, the discussion and the fix stay in one place, and you see the patch
before it goes public.
* **Email to `stefan@knaak.org`** with `ctxmenu security` in the subject.
Nothing is encrypted on the receiving end. If a detail is too sensitive for
plaintext mail, ask briefly for a different channel.
A useful report carries the same details as a bug report: the version from the
About window, the Windows build, the steps to trigger it, and the log excerpt
if you have one. Review the log first, for the reason above.
Only the latest release gets fixes. There is no branch on which an older
version continues to be maintained.
[SECURITY.md](https://github.com/corgan2222/context-manager/blob/main/SECURITY.md)
lists what counts as a vulnerability and what does not. Four properties put the
program in scope at all: it writes to the registry, some of it under `HKLM`; it
requests elevated privileges and restarts itself; it sends files to addresses
the user entered and stores the keys for that; and since 1.4.0 it replaces its
own executable with one it fetched from GitHub. Plaintext keys in
`%LOCALAPPDATA%\ctxmenu\` and the SmartScreen warning on an unsigned `.exe` are
documented behaviour, not findings.
You can expect acknowledgment of receipt within three days and an assessment
within two weeks. If nothing arrives after a week, the mail was lost, so follow
up through the other channel.
## Sending a change [#sending-a-change]
### Set up the toolchain [#set-up-the-toolchain]
Rust 1.95 or newer, target `x86_64-pc-windows-msvc`, plus the Visual Studio
Build Tools with the C++ toolchain.
### Branch from `origin/main` [#branch-from-originmain]
`main` is protected and takes no direct push, not from a contributor and not
from the maintainer. Start every change on its own branch.
```powershell
git fetch origin
git switch -c feature/short-name origin/main # or bugfix/, docs/, chore/
```
Those four prefixes are read by `.github/release-drafter.yml`, which sorts the
pull request into the right heading of the next release notes. Name the part
after the prefix in English. A branch name reaches the pull request list and
the release notes.
### Run the four checks [#run-the-four-checks]
```powershell
cargo fmt --all
cargo clippy --all-targets -- -D warnings
cargo test
cargo build --release
```
All four must be green. `-D warnings` is not negotiable. The same checks run as
git hooks once you install pre-commit, and CI runs them again and decides.
### Open the pull request [#open-the-pull-request]
Three checks must pass before a merge: `check` for formatting, clippy and
tests, `release-build` for the release profile, and `secrets` for gitleaks over
the whole history. The branch must also be up to date with `main`. Rebase onto
`origin/main` and push with `--force-with-lease` when another pull request
lands first.
What a pull request is expected to contain: one test per new pure function,
with a test name that is a complete sentence stating what holds; small
functions named after what they do; comments that explain why rather than what;
English in the code, including identifiers and comments; and a commit message
in complete sentences.
Changes to the registry carry the strictest rules. Nothing is deleted without a
backup, and that is enforced by the type system rather than by review:
`write::delete_tree` takes a `BackupToken`, and the only source of one is a
successful `backup::export`. Write attempts against `HKLM` belong in a
throwaway VM.
New dependencies, rewrites without a bug behind them, features Windows does not
offer, and machine-generated translations tend to get rejected.
## AI tools [#ai-tools]
The project states in
[AI\_POLICY.md](https://github.com/corgan2222/context-manager/blob/main/AI_POLICY.md)
that the maintainer uses AI tools when writing code for it: drafting,
refactoring, tests, documentation. Everything they produce is read before it
lands, and nothing is committed that the maintainer has not reviewed and
understood.
Contributors may use whatever tools help them. Two limits apply. Before you
open a pull request, make sure you can explain every change in the diff, not
just the summary. And do not point an agent at the repository and let it file
issues or pull requests on its own, because those get closed unread. The same
holds for bug reports: run the code, hit the bug, then describe what happened.
German and English both work in issues and discussions.
## Licence [#licence]
MIT, copyright 2026 Stefan Knaak. The full text is in
[LICENSE](https://github.com/corgan2222/context-manager/blob/main/LICENSE).
It permits use, copying, modification, merging, publishing, distribution,
sublicensing and sale, on the condition that the copyright notice and the
permission notice travel with every copy. The software comes without warranty
of any kind.
The licences of the crates the program links against are listed in
[docs/THIRD-PARTY-NOTICES.md](https://github.com/corgan2222/context-manager/blob/main/docs/THIRD-PARTY-NOTICES.md).
Behaviour in issues and pull requests is covered by the
[code of conduct](https://github.com/corgan2222/context-manager/blob/main/CODE_OF_CONDUCT.md).
# Editing the menu (/docs/editing)
The action bar offers five levels. They run from mild to severe, and each one
names the registry mechanism behind it, so nothing here is a black box.
## The five levels [#the-five-levels]
| Level | What it does | What it writes |
| ---------- | --------------------------------------- | ----------------------------- |
| Hide | Takes the entry out of the menu | `LegacyDisable` on the key |
| Shift only | Shows it only with the Shift key held | `Extended` on the key |
| Position | Moves it to the top or the bottom | `Position` on the key |
| Block | Stops a COM handler machine-wide | The CLSID on the blocked list |
| Delete | Removes the key and everything under it | Nothing; the key is gone |
The first three are reversible with the same button that set them. Blocking is
reversible by taking the CLSID off the list again. Delete is reversible only
through the backup, which is why it sits behind a separator, in red, and is
never abbreviated.
An entry under `HKLM` needs administrator rights; one under `HKCU` does not. A
mixed selection asks once, and declining keeps the changes to your own hive
rather than abandoning the whole action.
## Creating your own [#creating-your-own]
An entry needs a display name and a command. Everything else is optional: an
icon, a position, whether it only appears with Shift.
The registry path the entry will land in stands under the form and follows what
you type, so you can see where it goes before it goes there. The icon a
reference resolves to is drawn beside the field, which means a wrong index
shows itself before the entry exists.
Your own entries are always written to `HKCU`. That needs no administrator
rights and cannot affect another account on the machine.
### Submenus [#submenus]
Instead of a command, an entry can hold children that fold out inside the menu.
The order in the form is the order in the menu: Windows sorts registry keys
alphabetically, so the program numbers the children as it writes them.
## The order of everything else [#the-order-of-everything-else]
Windows sorts subkeys alphabetically, and the only lever beside that is
`Position`. It is a free-text value rather than a choice of two: `Top` and
`Bottom` behave predictably, while Microsoft itself ships `Last` and `After`
with a `PositionCompare` GUID. There is nothing finer, which is why free
ordering by dragging is not offered — it could only be faked with numbered key
names, and the first program update that rewrites its own key would undo it.
# Favourites (/docs/favourites)
A favourite is set up once and stays. From there it can be placed in the menu
as often as you like, at any spot, without being built again — the only thing
still asked is where.
## The list [#the-list]
Each row carries *Add to menu*, two arrows that move it up or down, *Edit* and
*Remove*. That order is saved and is the order the entries appear in. The
keyboard does the same: arrows, Home and End move the cursor, Enter places the
favourite, Delete takes it out.
*Add to menu* asks for the where, and takes one of three answers: a base
category, a single extension such as `.png`, or a whole kind of file, where
`image` covers every image format Windows knows.
## Why a web tool needs a sender [#why-a-web-tool-needs-a-sender]
A favourite does not have to be a program. If the tool lives in a browser,
there is a problem no registry entry solves: **a web page may not read a file
off your disk.** An address like `https://tool.example/?f=C:\shot.png` opens
the page, but the file never arrives, because no browser allows that — and that
is a good thing.
So the file has to be sent, and that takes a sender. The sender is this
program. The menu entry calls `ctxmenu --favourite "%1"`, and what happens
next depends on the mode.
## The three modes [#the-three-modes]
### Clipboard [#clipboard]
The file lands on the clipboard, the page opens, and Ctrl+V in the browser
finishes the job. This is the way for tools that offer no interface at all —
Squoosh, the TinyPNG page, remove.bg. No key, no endpoint, and it works with
tools that never planned for it. For a PNG the image itself is put on the
clipboard as well, so pages that expect an image rather than a file are
satisfied too.
### Upload [#upload]
For tools with a real endpoint. The file goes out as `multipart/form-data`
with a field name you choose, or as a raw body, and header lines carry a key
where one is needed.
A multipart request can take plain form fields beside the file, which is where
a tool's settings travel: one field holding the JSON block the service asked
for, or one field per option where the service names them separately.
What comes back is saved beside the original, opened in the browser, or just
reported. Saving never overwrites: `shot.png` becomes `shot.min.png`, with the
suffix you set. Where the answer only names an address, the program reads it
from the `Location` header of a successful answer or from a JSON field such as
`output.url`.
### Open address [#open-address]
Builds an address from placeholders and opens it, transmitting nothing.
Available are `{name}`, `{stem}`, `{ext}`, `{path}`, `{dir}` and `{fileurl}`,
each correctly encoded. For a search, a wiki, a ticket form.
## Twenty-one services are already in it [#twenty-one-services-are-already-in-it]
A new web favourite offers a list of the services this program knows, by group:
share images, share files, edit images, documents, share text, development,
storage of your own, check a file. One click fills in the endpoint, the method,
the body shape, the field name, the header line and where the answer names the
result — everything except the key, which no list can hold. Where a key is
needed, the note says where to get one.
Two of them do something worth naming. **TinyPNG** documents `--user api:KEY`;
type that pair into the header's value field and press **Basic**, and the line
curl would have built is built. **VirusTotal** answers an upload with a job id
and no address, so the address of the page is built from the file's own
SHA-256 instead — which is how it names its pages anyway.
The list is a [JSON file in the repository](https://github.com/corgan2222/context-manager/blob/main/ctxmenu/templates.json),
not code: adding a service is a pull request on a text file. The
[formats page](/docs/formats) says what may go in it.
Beside the list stands **From a .sxcu file**, which reads an uploader file
ShareX published for a service. What such a file holds that this program cannot
do — content in a form field rather than an attached file, an answer read by
regular expression — ends the import with a sentence naming that thing, rather
than with a favourite that fails later at a real service.
## What it refuses, and what it asks [#what-it-refuses-and-what-it-asks]
**You are asked before the first upload.** Once per tool, naming the
destination and the size of the file. The answer is remembered, and it can be
taken back: the form says in a line that sending is confirmed for this tool,
and the button beside that line clears it, so the next click asks again.
Tools made from a [service](/docs/services) are the exception. The service was
set up with its address and its key in one deliberate step, so its tools count
as agreed to from the start and send on the first click.
**Unencrypted `http://` is refused** unless it has been allowed for this one
favourite. Sending a file across the network in the clear is meant to be a
decision, not a default.
**A key does not follow a result to another host.** Where a service answers
with an address, that address is fetched with the same headers only if it is on
the host the file went to; TinyPNG names its output under its own host and
needs them. An address on a bucket or a CDN somewhere else is a host nobody
agreed to, and it is fetched without them.
**A redirect ends the request.** A `3xx` is not followed; the program reports
which address it pointed at instead. The question before the upload named one
host, and a service that answers by pointing somewhere else is asking for a
decision nobody took. If that other address is the right one, it belongs in the
endpoint.
The transfer itself runs through WinHTTP, which is to say Windows' own client,
with the certificate store and the proxy settings that apply anyway.
## A queued job is waited out [#a-queued-job-is-waited-out]
A busy service answers with a receipt instead of a file — a `202`, or a `200`
carrying `"async": true`. Which of the two arrives depends on how busy the
service is, not on the endpoint, so it cannot be settled when the favourite is
made.
The program reads the job number out of the receipt and asks the service's own
progress path about it every one and a half seconds, for at most two minutes,
then saves the finished file as if it had come back straight away. A frame that
reports the job failed ends the wait at once rather than running out the clock.
This needs the description to name a progress path, and the favourite to say
where the answer names the finished file.
## Six files, one question and one message [#six-files-one-question-and-one-message]
Windows reads a context menu command ending in `"%1"` as "once per file", so
six selected files start six copies of this program, none of which knows about
the others.
They agree among themselves through one file per favourite and run under
`%TEMP%\ctxmenu-batch\`. **One** of them asks the question before the first
upload; the other five wait for that answer and act on it, a no included, in
which case nothing is sent at all. At the end they share **one** notification
instead of six: headed with the name of the tool, listing the file names one
under the other, updated as each file finishes rather than popping up again.
A single file still reads exactly as it did, with the whole sentence and no
counter. A file that fails keeps a message of its own, because the reason is
worth more than the tidiness. And if the six cannot reach each other, every one
of them asks and reports alone: six messages are a nuisance, a file that was
never sent is a fault.
## Where they are kept [#where-they-are-kept]
`%LOCALAPPDATA%\ctxmenu\favourites.json`, next to the other stored files. It
survives an update, and an older version of the program reads what a newer one
wrote.
The same list is reachable from the console — `ctxmenu favourite add`, `place`,
`remove` and `run`, with every switch in [Command line](/docs/command-line).
# Features (/docs/features)
This page is the reference list. Each item names the registry mechanism behind
it, and the pages linked from here go into the ones that need more than a
line.
## What the scan covers [#what-the-scan-covers]
Eleven base categories, across three registry areas: `HKCU`, `HKLM`, and the
32-bit view under `WOW6432Node`.
| Category | Key | Right-click on |
| ---------------------- | ---------------------------------------- | -------------------------------------------------------- |
| All files | `*` | any file |
| All filesystem objects | `AllFilesystemObjects` | files and folders |
| Unknown | `Unknown` | a file no program has claimed |
| Directory | `Directory` | a folder |
| Directory background | `Directory\Background` | empty space inside a folder |
| Music folder | `SystemFileAssociations\Directory.Audio` | a folder templated as music |
| Picture folder | `SystemFileAssociations\Directory.Image` | a folder of pictures |
| Video folder | `SystemFileAssociations\Directory.Video` | a folder of videos |
| Folder | `Folder` | folders and shell namespace objects such as ZIP archives |
| Desktop background | `DesktopBackground` | the desktop |
| Drive | `Drive` | a drive |
On the machine this was measured on, 2026-08-21, those eleven hold 142
entries. Resolve every file type as well and the scan reaches 938.
* Static verbs and COM handlers are listed apart, because they are different
things. A verb is a registry key holding a command. A COM handler is a CLSID
that a DLL implements.
* For a COM handler the table shows the key name, the plain-text name of the
CLSID, and the path of the DLL behind it.
* Submenus appear with their children, indented under the entry they hang
from.
* A double-click on a row, or *Look at this entry* in its context menu, opens
the form with everything that stands in the registry.
* A padlock marks what cannot be changed without administrator rights.
* The search field covers display name, command and registry path. For a COM
handler it covers the CLSID and the DLL as well, and a child of a submenu
matches for the entry it hangs under. It works on Categories, File Types and
Programs, even before anything is selected on the left.
`CommandStore` held 229 verbs on the machine this was measured on. None of
them appears in a menu until another entry names it in its `SubCommands` list.
ctxmenu lists them read-only, marked with a lock, so that a name found in such
a list can be looked up.
## File types [#file-types]
For a single extension, the program walks the whole resolution chain rather
than reading one key. Seven levels:
| Level | Where it reads |
| ----- | ------------------------------------------------------------ |
| 1 | `*`, every file |
| 2 | `AllFilesystemObjects` |
| 3 | `SystemFileAssociations` for the perceived type |
| 4 | `SystemFileAssociations` for the extension itself |
| 5 | the ProgID the user chose, under `FileExts\\UserChoice` |
| 6 | `.\shell`, rare but it exists |
| 7 | the further ProgIDs listed in `OpenWithProgids` |
That is what the right-click actually shows, not what is registered at one
single spot. For `.jpg` it comes to just under sixty entries, two thirds of
which apply to every file. Those two thirds are identical for every extension,
so the tab leaves them out until *Include entries for all files* asks for
them.
* The tab offers a curated selection of 98 types.
* A field above the tree takes any further extension, and keeps it.
* *All installed* swaps the 98 for every type registered on the machine: 1739
of them on the machine this was measured on. Reading them in takes
correspondingly longer.
## Programs [#programs]
* A program registered in twenty file types appears as one group holding every
occurrence, with its icon in front.
* The name comes from the version resource of the `.exe`, not from the key
name.
* An entry pointing at a program that no longer exists shows in red. This
happens mostly after updates to Store apps, whose folder carries the version
number in its name.
* One click moves a program that keeps showing up into the favourites list.
## Changing an entry [#changing-an-entry]
Five levels, from mild to severe. [Editing the menu](/docs/editing) has the
table of what each one writes.
* **Hide** writes `LegacyDisable`.
* **Shift only** writes `Extended`.
* **Position** writes `Position`, `Top` or `Bottom`.
* **Block** puts the CLSID of a COM handler on the blocked list, machine-wide.
* **Delete** removes the key and everything under it.
The first three are undone with the same button that set them. Delete is
undone only through the backup.
## Creating an entry [#creating-an-entry]
* Display name, command, icon, position, and whether it appears only with the
Shift key.
* For a base category, for a single extension, or for a whole kind of file.
* The browse buttons beside the command and icon fields open the ordinary
Windows file dialog and quote what comes back.
* The icon a reference resolves to is drawn beside the field, so a wrong index
shows before the entry does.
* The registry path the entry will land in stands under the form and follows
what you type.
* A folded *Help* carries the placeholder table and three working command
lines.
* Everything you create goes to `HKCU`: no administrator rights, no effect on
other accounts.
**As a submenu.** Instead of a command, the entry holds a list of children
that folds out inside the menu. The order in the form is the order in the
menu, because Windows sorts registry keys alphabetically and the program
numbers the children as it writes them.
**By drag and drop.** Drop an `.exe` into the window and the editor opens
filled in from it: name, command with the right placeholder, the program's own
icon. The category you drop it on decides which one the form starts in.
Nothing is written until you press the button in the form. Inside the editor,
the command and icon fields take a dropped file too.
## Favourites and services [#favourites-and-services]
* A favourite is entered once and stays. From there, one click places it in
any category or on a single file type.
* A favourite does not have to be an `.exe`. An address is enough.
* A web page may not read a file off your disk, so a web tool gets the file
*sent* to it instead. [Favourites](/docs/favourites) has the three ways that
happens, and what the program refuses to do.
* [Services](/docs/services) reads a web application's OpenAPI description and
turns the endpoints that accept a file into favourites, grouped the way the
service groups them. Where a tool takes options, the program generates a
form for them.
## Backups [#backups]
Every action takes a backup first, and a group action takes exactly one for
the whole group. [Backups and restore](/docs/backups) explains why a change
cannot happen without one.
* The *Back up* button in the top bar makes one on demand and changes nothing
else. It covers the selected rows, or everything currently listed when
nothing is selected.
* The Backups tab shows the history and plays it back.
* *Back Up Everything* takes along every location the program touches at all.
On the machine this was measured on that is 1.2 MB, in under a second.
## The window itself [#the-window-itself]
* German and English, light and dark, or *Follow system*. All of it without a
restart, and the title bar follows along.
* One request to GitHub as the window opens, and a dot on the logo button when
something newer exists. Fetching it takes a second click, and only runs when
the release signature and the published checksum both check out. The request
can be switched off in the About window.
## What it deliberately cannot do [#what-it-deliberately-cannot-do]
Each of these is a limit of Windows rather than a feature left unwritten.
**Change the text of a COM handler.** Windows generates that text at run time
in `IContextMenu::QueryContextMenu`, and it appears nowhere in the registry.
What ctxmenu shows instead is the key name, the plain-text name of the CLSID,
and the DLL behind it.
**Order the new Windows 11 menu.** Its entries are listed, hidden and created
like the classic ones, but where each one sits up there is Explorer's
decision. Neither the manifest nor the registry moves it. Sorting stays a
capability of the classic menu. See [the Windows 11 menu](/docs/windows-11).
**Reach the commands Explorer builds into that menu itself.** "Send to my
phone" and its kin carry no registration at all: no package manifest, no COM
registration, no verb. Measured on Windows 11 build 26200. What has no
registration cannot be listed, and cannot be hidden through the blocked list
either. Those switches live in Windows Settings.
**Determine the order freely.** Windows sorts subkeys alphabetically. The one
lever beside that is `Position`, and it is a free-text value rather than a
choice of two. `Top` and `Bottom` behave predictably, while Microsoft itself
ships `Last` and `After` with a `PositionCompare` GUID. The system offers
nothing finer. Submenus the program writes itself are the exception, because
it numbers their children as it writes them.
**Edit a scanned entry.** The form shows everything that stands in the
registry, but it does not write anything back yet. Entries you created
yourself are not affected by this.
# Service formats (/docs/formats)
Every service documents itself with a curl line. This page says which curl
lines the program can reproduce, which it cannot, and where each part of one
goes in the form.
It describes what an upload favourite sends. The services tab reads the same
shapes out of an OpenAPI description instead of asking for them; the last
section says what such a description has to contain.
## Before you fill anything in [#before-you-fill-anything-in]
Twenty-one services are built in, and picking one in the services tab fills
every field but the key. The list is a
[JSON file in the repository](https://github.com/corgan2222/context-manager/blob/main/ctxmenu/templates.json):
adding a service is a pull request on a text file, and the fields in it are the
ones this page describes. The rest of the page is for a service that is not on
that list.
## One request, and what it carries [#one-request-and-what-it-carries]
A favourite sends the clicked file in a single request. There is no session
before it and no second call to finish it.
| Part | What is possible |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Method | `POST` or `PUT` |
| Endpoint | A whole address, query string included. `{name}`, `{stem}` and `{ext}` are filled in from the clicked file, which is what a service wants that carries the target name in its path |
| Body | `multipart/form-data` with one file field, or the raw file as the whole body |
| Field name | Anything the service asks for. `file` is the default because most services use it |
| Extra fields | Any number of plain form fields beside the file, multipart only |
| Headers | Any number of name and value pairs, sent exactly as typed |
| Content type | Derived from the extension: `image/png`, `application/pdf`, `video/mp4`, and `application/octet-stream` for everything unknown |
Only those three placeholders, and only those. `{path}`, `{dir}` and
`{fileurl}` name a place on your own machine, and an upload endpoint is the one
address here a stranger reads. They stay untouched in an endpoint and are
filled in only for the two modes that open an address.
## Reading a curl example [#reading-a-curl-example]
Documentation is written for curl, the form asks for fields. This is the whole
translation.
| In the documentation | In the form |
| -------------------------------- | -------------------------------------------------------------------------------- |
| `-X POST`, `--request POST` | Method `POST` |
| `-T file.png`, `--upload-file` | Method `PUT` |
| `-F "file=@photo.png"` | Body `multipart`, field name `file` |
| `-F "image_file=@photo.jpg"` | Body `multipart`, field name `image_file` |
| `--data-binary @photo.jpg` | Body `raw` |
| `-F "size=auto"` | A form field beside the file, name `size`, value `auto` |
| `-H "X-Api-Key: abc"` | A header line, name `X-Api-Key`, value `abc` |
| `-H "Authorization: Bearer abc"` | A header line, written out the same way |
| `?key=abc` in the address | Leave it in the endpoint. The endpoint is a whole address, query string included |
| `-o result.png` | Result: save beside the original |
`-u api:abc` is the one line curl does something for you: it builds
`Authorization: Basic YXBpOmFiYw==` out of the two halves. Type `api:abc` into
the header's value field and press **Basic**, which does the same. Everything
else in a header value is sent exactly as it stands.
## Where the finished file is named [#where-the-finished-file-is-named]
Four answers cover the services measured so far. Which one applies is set per
favourite.
### The answer is the file [#the-answer-is-the-file]
The response body is the finished file, and it is written beside the original.
`shot.png` becomes `shot.min.png`, with the suffix you set, and an existing
file is never overwritten: a counter is added instead.
This is the shape of remove.bg, Clipdrop, PhotoRoom, Stirling-PDF and
Gotenberg, and it is the least work of the four: nothing has to be parsed.
### The answer names an address [#the-answer-names-an-address]
Either in the `Location` header of a successful answer, or in a JSON field
addressed by a dotted path. `secure_url` reaches a field at the top,
`data.link` reaches one inside an object, and a step that is a number reaches
into an array, so curl's `Files[0].Url` is written `Files.0.Url` here.
The address is fetched and the file saved, or opened in the browser, whichever
the favourite says.
### The address is built around what came back [#the-address-is-built-around-what-came-back]
For a service that answers with an id rather than with an address. The template
carries the field in braces —
`https://www.virustotal.com/gui/file/{sha256}` — and everything put in is
percent-encoded, so whatever the service sends stays one segment of the path it
was written into.
`{sha256}` is the one name that does not come out of the answer: it is the
digest of the file that was just sent. Services that check files name their
page after that digest while answering an upload with nothing but a job id.
### Nothing but a report [#nothing-but-a-report]
Status and size, for a service whose answer is of no further use.
A saved result keeps the original's extension unless the favourite says
otherwise. A converter is the case where it has to: without an extension of its
own the answer would be `brief.pdf.docx`, a PDF wearing the extension of the
file it was made from, which Windows hands to Word.
## Jobs a service only takes in [#jobs-a-service-only-takes-in]
A busy service may answer with a receipt rather than a result. The program
recognises two: HTTP 202, and a 200 whose body carries `"async": true`. Both
mean the same thing, and which one arrives is a property of how busy the
service is, not of the endpoint, so it cannot be decided when the favourite is
made.
The way back is a path under the service's own address, with the job id in
braces: `/api/v1/jobs/{jobId}/progress`. Two more settings say where the
receipt names the job, and where a progress frame names the finished file.
The finished file has to be named there as an **address**. A service that
answers with an id, leaving the address to be assembled from it, cannot be
followed.
## What it will not do [#what-it-will-not-do]
Each of these rules out a service completely, and each has a reason worth
knowing before you go looking.
| Not possible | Why, and who it affects |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| OAuth, and tokens that expire | The program stores what you type and sends it unchanged. It cannot run a consent flow in a browser or trade a refresh token for a new one. Dropbox, OneDrive, Google Drive, Box, Flickr, YouTube |
| Signed requests | AWS Signature V4 computes a signature per request out of the key, the time and the body. A presigned address works, but it expires. S3, R2, Backblaze, MinIO with keys |
| Uploads in several steps | Create a task, read an address out of the answer, send the file there, start the work, collect the result. CloudConvert, iLovePDF, Adobe, WeTransfer |
| The content as a form field | The file is attached as a file. A paste service that wants the text as the value of `api_paste_code`, or inside a JSON body, gets nothing it can use. Pastebin, GitHub Gist, GitLab snippets |
| Encryption before sending | Send and PrivateBin encrypt in the browser and hand the server ciphertext. There is no server-side path around it, by design |
| A result behind a header on another host | The key follows the result only where the address stays on the host the file was sent to. TinyPNG names its output under its own host and works; a service that names a bucket or a CDN somewhere else is naming a host you never agreed to, and it does not get the key |
## What a description has to contain [#what-a-description-has-to-contain]
The services tab does the same thing from an OpenAPI document, one address for
a hundred entries. An operation becomes a menu entry when three things hold:
its method is `post`, `put` or `patch`, its request body is
`multipart/form-data`, and one of the fields in it is `string` or `binary`.
The address to paste is the documentation page. The machine-readable document
behind it is looked for at the address itself and then at `openapi.json`,
`swagger.json` and `openapi.yaml` beside it, and at `/openapi.json`,
`/swagger.json`, `/api/openapi.json`, `/api-docs`, `/v3/api-docs`,
`/v1/api-docs` and `/swagger.v1.json` from the root. The last two are there for
Stirling-PDF, which moves the Springdoc default, and for Gitea and Forgejo,
which serve their whole description at the root of the instance. Whichever answered is remembered, so the next refresh is one request.
Two settings are asked once per service rather than once per tool: the header
line that carries the key, and the dotted path where an answer names the
finished file. Leave that path empty and the answer is taken to be the file
itself.
## Two limits that apply throughout [#two-limits-that-apply-throughout]
`https://` always, `http://` only where the tick box for it is set on that
favourite, and nothing else: a `file:` address in an answer is refused rather
than opened. Redirects are refused as well and reported with the address they
pointed at, because a redirect can move a file and its key to a host you never
agreed to. And an answer larger than 256 MB is refused: a service that replies
with a gigabyte is not one the program is talking to on purpose.
# Getting started (/docs/getting-started)
## Download and run [#download-and-run]
There is no installer. Download `ctxmenu.exe` from the
[latest release](https://github.com/corgan2222/context-manager/releases/latest)
and start it.
```powershell
ctxmenu.exe
```
Without arguments the window opens. Entirely without administrator rights:
they are requested once a change actually needs them, and then only for that
one step.
Every release carries `checksums.txt` and a signature over it. The signature
is made with a key that does not live in the repository, so whoever can
publish a release still cannot sign one. The release notes give the two
OpenSSL commands that verify it by hand.
## The six tabs [#the-six-tabs]
| Tab | What it holds |
| ---------- | ------------------------------------------------------- |
| Categories | The base categories, from "all files" to "drives" |
| File types | One extension at a time, with its full resolution chain |
| Programs | Every entry grouped by the program that registered it |
| Favourites | Your own toolbox of programs and web tools |
| Services | Tools read out of an OpenAPI description |
| Backups | Every backup taken, and the way back |
## A first round [#a-first-round]
### Look before you change anything [#look-before-you-change-anything]
Open **Categories** and let the scan finish. The number in the header is what
your machine actually carries. Sort by the *Program* column to see who has
been busy.
### Find the ones you never use [#find-the-ones-you-never-use]
**Programs** groups every entry by the program behind it. An entry pointing at
a program that no longer exists shows in red — those are the safest to remove,
because nothing is behind them any more.
### Hide rather than delete [#hide-rather-than-delete]
Select a row and press *Hide* in the action bar. That writes `LegacyDisable`,
which Explorer honours immediately and which you can undo with the same
button. Nothing is gone.
### Delete only what you are sure about [#delete-only-what-you-are-sure-about]
Delete sits behind a separator and is spelled out rather than abbreviated. It
takes a backup first, and it cannot run without one.
# ctxmenu (/docs)
ctxmenu reads what is in your right-click menu, shows where each entry lives in
the registry and which program put it there, and lets you hide, sort, delete or
create entries. Every change is backed up before it happens.
## What it sees [#what-it-sees]
The scan covers eleven base categories across three registry areas: `HKCU`,
`HKLM`, and the 32-bit view under `WOW6432Node`. On a machine that has grown
over the years, those eleven hold 142 entries; resolve the file types as well
and the scan reaches 938.
Verbs and COM handlers are listed apart, because they are different things. A
verb is a registry key holding a command. A COM handler is a CLSID that a DLL
implements, and for one of those the list shows the plain-text name of the
CLSID and the path of the DLL behind it.
## What it will not do [#what-it-will-not-do]
Some limits are the platform's, not the program's. Windows generates the text
of a COM handler at run time, so no registry reader can know it. The order of
entries in the new Windows 11 menu belongs to Explorer. And the commands
Explorer builds into that menu itself, such as "Send to my phone", carry no
registration at all, which means nothing can list them and nothing can hide
them.
# LLMs.txt (/docs/llms)
Every page of this site is also served as plain Markdown, and the whole set is
served as two text files. An assistant that reads them answers about ctxmenu
from the documentation rather than from what it happens to remember.
## The files [#the-files]
A single page is available on its own as well. Append `content.md` to its
address under `/llms.mdx`, so the Windows 11 page becomes:
```
https://corgan2222.github.io/context-manager/llms.mdx/docs/windows-11/content.md
```
The **Copy Markdown** button at the top of every page copies that same text to
the clipboard.
## Using it [#using-it]
Claude Code, Cursor, Windsurf and the like take a URL in the prompt:
```
Use the ctxmenu documentation from https://corgan2222.github.io/context-manager/llms-full.txt
```
For a project that touches ctxmenu often, put the line in the file your
assistant reads at startup — `CLAUDE.md`, `.cursorrules`, or whatever the tool
calls it — and it is loaded without being asked for.
They are generated from the same MDX the site renders, at build time. A page
that changes here changes there in the same deploy, so an assistant reading
them is never a release behind.
## What is not here [#what-is-not-here]
No MCP server, and no agent skill. Both would have to be hosted and kept
running; two text files are served by the same static host as the rest of the
site and cannot fall over on their own.
# Services (/docs/services)
If a web application describes itself through OpenAPI, its documentation page
is enough to start. Paste the address, and the program finds the
machine-readable document behind it, reads which endpoints accept a file, and
turns every one you tick into a menu entry.
## The services already built in [#the-services-already-built-in]
Twenty-one services are built in, by group. Picking one from this list fills
in every field but the key.
### Share images [#share-images]
### Share files [#share-files]
### Edit images [#edit-images]
### Documents [#documents]
### Share text [#share-text]
### Development [#development]
### Storage of your own [#storage-of-your-own]
### Check a file [#check-a-file]
The list is a [JSON file in the repository](https://github.com/corgan2222/context-manager/blob/main/ctxmenu/templates.json):
adding a service is a pull request on a text file.
A service described by OpenAPI is the other kind: it brings in many tools at
once rather than one. The technical detail for both is in
[Service formats](/docs/formats).
## The service the numbers come from [#the-service-the-numbers-come-from]
Every figure on this page was measured against
[SnapOtter](https://snapotter.com/), and the program ships a template for it:
pick it in the services tab and only the host and the key are left to fill in.
SnapOtter is a self-hosted file processing platform, AGPL-3.0, run on your own
machine so the files never leave the network. Its own site counted 243 tools on
21 August 2026 — 107 for images, 57 for video, 27 for audio, 29 for PDF and
documents, 23 for files.
Of those, **232 arrive as menu entries**, in seven groups:
| Group | Tools |
| --------- | ----- |
| Image | 96 |
| Video | 52 |
| PDF | 30 |
| Audio | 26 |
| Files | 26 |
| Features | 1 |
| Pipelines | 1 |
Two things account for the difference between 243 and 232. An endpoint that
takes no file cannot become a menu entry, so it is not offered. And the groups
above are not the five the website advertises: they are measured from the
service's own paths, which is why PDF stands on its own here and documents sit
under Files.
Nothing about the program is tied to this service. Any description published as
OpenAPI works the same way, and the empty template exists for exactly that.
## From an address to a menu entry [#from-an-address-to-a-menu-entry]
### Add the service [#add-the-service]
Pick the template, paste the address of the documentation page, and add the key
if the service wants one. The key is stored locally and sent with every request.
`Result is named in` says where the service puts the address of the finished
file in its answer — for SnapOtter that is `downloadUrl`.
### Read what came back [#read-what-came-back]
The tools arrive grouped and counted, and the search field above them reads
names, paths and groups at once. A tool marked *works in the background* answers
with a job number rather than a file; the program follows it either way.
### Set the options you want fixed [#set-the-options-you-want-fixed]
Where an endpoint documents options, `Settings` opens them as a form. Whatever
is filled in here is fixed for that menu entry, so a compression entry can carry
its quality and its target size and ask nothing at run time.
### Place it [#place-it]
`+` puts the ticked tools in the menu — either as their own entries or gathered
in one submenu. From there a right-click sends the file and the answer comes
back as a notification.
The menu above was taken on a German system: *An SnapOtter senden* is the
submenu, holding *Verkleinern*, *Metadaten entfernen* and *Fürs Web optimieren*
— resize, strip metadata, optimise for the web.
## What happens to the address [#what-happens-to-the-address]
### The document is found, not guessed [#the-document-is-found-not-guessed]
The address of a documentation page is not the address of the description. The
program tries the usual places behind it and decides by whether the answer
parses as a description, not by the HTTP status code — a server that answers
`200` with an error page is common enough that the status alone proves nothing.
### Only endpoints that take a file are offered [#only-endpoints-that-take-a-file-are-offered]
Of 351 paths on the test service, 232 accept a file as `multipart/form-data`.
The rest cannot become a menu entry, so they are not listed.
### The grouping is measured, not taken on trust [#the-grouping-is-measured-not-taken-on-trust]
An OpenAPI tag is often a single flat drawer holding everything. The program
scores the tag against every path segment and picks whichever divides the tools
best. On the test service that beat the tag by a factor of 26.
### Options become a form [#options-become-a-form]
Where an endpoint documents its options, the program builds a form for them —
including where the service describes them only in running prose. Of 227 such
descriptions it produced 431 fields. Where the prose is not unambiguous, the
field stays a text box with the description above it: a wrong field would send
nonsense to a real service, while a missing one costs a checkbox.
## Tools that answer with a job number [#tools-that-answer-with-a-job-number]
Some endpoints queue the work and answer with a job number instead of a file.
Those are listed like any other and marked *works in the background*. The
favourite made from one follows the job over the service's progress path and
saves the finished file when it is done.
The declaration in the description does not decide this. The same endpoint on
the test service answered directly and with a job number in turns, so the
program looks at the real answer rather than the promise.
## Why the program sends the file [#why-the-program-sends-the-file]
A web page may not read a file off your disk — that is a browser rule, not an
oversight. So the menu entry does not open a page and hope. It sends the file
itself: as `multipart/form-data`, as a raw body, or by putting it on the
clipboard and opening the page ready to paste.
The result lands beside the original. The original is never overwritten.
# Working the list (/docs/the-list)
The table in the middle holds one row per entry. A submenu shows its children
indented underneath it, so a row is either an entry or one of its children.
Selection, right-click and the arrow keys all reach a child the same way they
reach its parent.
## The columns [#the-columns]
| Column | What it says |
| ---------- | --------------------------------------------------------------------------- |
| Icon | The icon Windows draws for the entry. No header text |
| Name | The display name, with `▸` after it when the entry opens a submenu |
| Type | `Verb`, `COM Handler` or `Win11 verb` |
| Scope | `HKCU`, `HKLM` or `HKLM32`, the hive the key sits in |
| Appears on | Where the entry shows up, in words |
| Flags | One symbol per state: the lock, the Shift sign, `Hidden`, `Blocked` |
| Command | The command line. For a COM handler its CLSID, for a Win11 verb the package |
The **Appears on** column says "All Files" rather than `*`, and ".zip" rather
than a path with `SystemFileAssociations` in the middle. `*` and `Folder` are
registry shorthand, and they say nothing to the person deciding whether to
delete a row. The real registry path sits in the tooltip. Hover the cell to
read it.
## Sorting, and the third click [#sorting-and-the-third-click]
Clicking a column header sorts by it. A second click on the same header
reverses the direction. A third puts the table back into the order the rows
were collected in. The header of the active column carries an arrow, so you can
tell which of the five columns the order comes from.
*Flags* and the icon column do not sort. A row of symbols has no order worth
the click.
The collected order carries meaning in **File types**. It puts the entries
belonging to the chosen extension in front of the ones that apply to every
file. Sorting by name shuffles those two groups back together, and the third
click is the way back.
## The search field [#the-search-field]
The field above the table reads more than the name:
* the display name and the key name
* the registry path
* the command line
* for a COM handler, the CLSID and the server DLL
* for a Win11 verb, the package name, the full package name and the CLSID
It applies to the three tabs that show scanned entries: Categories, File types
and Programs. It works even when nothing is selected in the tree on the left.
A submenu stays in the list when one of its children matches. Searching for the
child and being told there is no such entry would be wrong, because the menu
does offer it.
The **Services** tab brings a search of its own, above the tool list.
Favourites and Backups are short enough to be shown whole.
Typing `git` leaves one entry standing, and the right-hand side says where it
lives: `Directory\Background\shell\git_shell`, under `HKCU`, with `%V` rather
than `%1` because it hangs on a folder background.
## The detail pane [#the-detail-pane]
Select a row and the pane on the right fills. It carries, top to bottom:
* the display name, with a folder button beside it that opens Explorer with the
program itself picked out
* Registry Path and Raw Value
* Icon, Appears on and Position
* Command, or CLSID and Server DLL for a COM handler, or Package for a Win11
verb
* the children, when the entry is a submenu
* every flag that the table had room for only as a symbol, spelled out in words
together with the reason it holds
Every field can be selected and copied, so a registry path goes straight into
regedit. When the program behind an entry is no longer installed, the pane says
so in a red sentence. The table has room for the colour alone.
One COM handler, opened: registry path, CLSID, the DLL behind it, and three
short reasons why nothing here can be edited. The key belongs to `HKLM`, the
text is produced at runtime, and the entry is read-only for this account.
## Right-click [#right-click]
A right-click offers exactly the actions that would change something about the
item clicked. With a multi-selection, the ones that only make sense for a
single entry drop out. In the empty area below the last row it offers *New*.
Right-clicking a row that is not part of the selection makes that row the
selection first. Otherwise the menu would list what one row can do and then do
it to twenty others.
The trees on the left answer a right-click too, each with the target it stands
for. A category row creates in that category. A row in the file type tree
creates for that extension alone, which is the shortest way to "this entry, but
only for `.png`".
## The keyboard [#the-keyboard]
| Key | What it does |
| -------------------- | ------------------------------------------------------------------- |
| Arrow up, arrow down | Moves the cursor one row, submenu children included |
| Home, End | Jumps to the first or the last row |
| Shift and an arrow | Grows the selection |
| Ctrl+A | Selects every visible row |
| Double click | Opens the entry in the form, filled in from the registry and locked |
The keys reach the table only while no text field holds the keyboard. Inside
the search box, Home, End and the arrows stay editing keys, and Ctrl+A goes on
meaning "select this text".
# Keeping it up to date (/docs/updating)
There is no installer, so nothing on this machine notices that a copy has gone
stale. The program asks GitHub instead.
## The dot on the logo button [#the-dot-on-the-logo-button]
While the window opens, the program makes one GET to
`api.github.com/repos/corgan2222/context-manager/releases/latest`. No account,
no token. Nothing else leaves the machine until you press the button described
below.
What comes back stays quiet. When there is nothing newer, nothing appears. When
the request fails, nothing appears either and the reason goes to the log. A
program that opens a window to announce that it could not reach GitHub is a
program people learn to switch off.
Only a newer version shows itself, and only as a dot in the corner of the logo
button in the top bar. The tooltip on that button names the version. The About
window behind it carries the version number, the release notes, and a **Fetch
and restart** button.
## What the second click checks [#what-the-second-click-checks]
That click is the first moment the program downloads anything. It then works in
this order.
### Fetch the list and the signature [#fetch-the-list-and-the-signature]
The program fetches `checksums.txt` and `checksums.txt.sig`.
### Verify the signature [#verify-the-signature]
The signature has to verify against the public key compiled into the running
`.exe`. That key is `ctxmenu/release-signing.pub.pem`. The scheme is RSA
PKCS#1 v1.5 over SHA-256, and Windows' own CNG does the arithmetic through
`BCryptVerifySignature`. Nothing below this line runs when the check fails.
### Tie the list to the version being offered [#tie-the-list-to-the-version-being-offered]
The signature covers the digests and nothing else. It does not cover the tag,
and it does not cover the release the assets hang on. So the program requires a
line for `ctxmenu__windows_amd64.zip` in the list. It never
downloads that archive. It needs the name only.
### Read the digest for the executable [#read-the-digest-for-the-executable]
The program reads the digest for `ctxmenu.exe` out of a file it has now proved
to be both the author's and this version's.
### Fetch the executable [#fetch-the-executable]
The program fetches `ctxmenu.exe` and accepts it only when its SHA-256 is that
digest.
Somebody who takes over the GitHub account cannot sign anything. That person
can still tag a release `v99.0.0` and attach the `checksums.txt`,
`checksums.txt.sig` and `ctxmenu.exe` of an older release that was genuinely
signed. Every check after that passes, and the user moves back to a version
with a hole that was already fixed. A line naming the offered version can only
sit in a list that was signed for exactly that version, so requiring one closes
the gap.
## Two things are trusted, and an attacker needs both [#two-things-are-trusted-and-an-attacker-needs-both]
TLS says the bytes came from GitHub. WinHTTP validates the certificate against
the Windows certificate store, the same store every other program on this
machine relies on.
The signature says the bytes came from whoever holds the private key. That is
the half that still holds when the first one does not. The private key lives in
the `RELEASE_SIGNING_KEY` repository secret, and one step of `release.yml` uses
it. It does not live in the account it protects. Somebody who can publish a
release cannot sign one.
The author generated the key pair once and does not rotate it. A new pair would
take self-update away from every copy already shipped.
When `checksums.txt.sig` is missing, or when it does not verify, the program
does not offer that release for installing. This covers every release before
1.4.0, and it is meant that way. A set of assets that may arrive short is a set
somebody gets to shorten.
Such a release does not count as "you are up to date" either, and neither does
one whose assets are still uploading. The program names both as announced but
not finished publishing yet, because from the outside they are the same thing:
a version that exists and that this program will not install. For the few
minutes after every publish that sentence is literally true, and the button
that would fetch the release is not there.
## Replacing the running file [#replacing-the-running-file]
Windows refuses to overwrite an executable that is running. It does allow
renaming one, and the swap uses that.
1. The program writes the new bytes beside the old file as `ctxmenu.exe.new`.
2. It renames the running file to `ctxmenu.exe.old`.
3. It renames `ctxmenu.exe.new` over the original name.
The download sits complete on disk before anything moves. The original name
therefore has no file behind it only for the gap between two renames. When step
3 fails anyway, the program undoes step 2. The next start deletes
`ctxmenu.exe.old`.
The new copy starts with the same command line this one had, and this window
closes.
When the `.exe` sits in a folder this account may not write to, usually
`C:\Program Files`, the message says exactly that. It does not report a broken
download.
## Switching the check off [#switching-the-check-off]
*Look for new versions on start*, in the About window, is on by default.
Unticking it stops the request entirely. **Look now**, at the foot of the same
block, works either way, because pressing it is the decision the setting
otherwise makes.
There is no background service and no scheduled task. The program measures
nothing and reports nothing back. It downloads and installs nothing without
that second click.
The `.exe` is still not Authenticode-signed, and SmartScreen still warns about
it after a download in a browser. Windows checks Authenticode before it runs a
downloaded file. This program checks the release signature before it replaces
itself.
# The Windows 11 menu (/docs/windows-11)
Windows 11 shows a short menu on first right-click and puts everything else
behind *Show more options*. Those are two different menus with two different
mechanisms, and ctxmenu manages both.
## The upper menu [#the-upper-menu]
Its entries do not come from `…\shell` registry keys. Each one is an
`IExplorerCommand` handler that a packaged app declares in its
`AppxManifest.xml`, and the menu is assembled from those declarations.
ctxmenu reads them the way the shell does: the packages that registered COM
classes, then each package's manifest, then the verbs and item types in it.
They appear in the same views as classic entries, marked so you can tell them
apart.
Hiding one is a different lever than for a classic entry. There is no
`LegacyDisable`; instead the handler's CLSID goes on the per-user blocked list.
That needs no administrator rights and no Explorer restart — the next menu you
open is already without it.
## Your own entries up there [#your-own-entries-up-there]
The executable carries an `IExplorerCommand` handler and a sparse package
inside itself. Turning the feature on writes them beside your settings and
registers the package, and from then on your own entries — favourites and
service tools included — appear in the upper menu too.
Registration needs an elevated context once. After that, creating and removing
entries needs no further package work: the handler reads the same file the
window writes.
## What belongs to Explorer [#what-belongs-to-explorer]
Two things in the upper menu are not yours to arrange.
**The order.** Where a packaged entry sits is Windows' decision. Neither the
manifest nor the registry moves it, so sorting stays a capability of the
classic menu.
**The commands Explorer builds in itself.** "Send to my phone" and its kin
carry no registration at all: no package manifest, no COM registration, no
verb. Measured on Windows 11 build 26200 — there is nothing to list and
nothing to block. Those switches live in Windows Settings.
## Switching the upper menu off [#switching-the-upper-menu-off]
If you would rather have the classic menu on first click, one key does it:
an empty `InprocServer32` under the CLSID Explorer loads the new menu from. A
COM server pointing at nothing does not load, so Explorer falls back. ctxmenu
offers this as a switch in the top bar, and offers it only on builds that have
the new menu at all.