# 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: every backup taken, with its action, its time and the keys it covers. 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 | The entry table filled with 2000 generated rows, with the flags column showing hidden, Shift-only, blocked and pinned entries beside each other `--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 | The window with the search box filled in, the table below it showing only the entries that match. ```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. The Categories tab with an entry selected, its detail pane showing the full registry path of the entry 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] The editor for a new entry: display name, command, icon and position, with the registry path it will land in shown under the form. 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 Favourites tab with nine web tools, and the form of one of them open ## 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. The Categories tab: the tree of base categories on the left, the entries of the selected category in the table, and the registry path of the selected row on the right. * 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. The entry table after a full scan, filled with hundreds of rows collected from every registered file type. ## 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 Categories tab: the tree of categories on the left, the entries of the selected one in the table, and the detail pane below it. ## 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. From one documentation address to a grouped menu: 351 paths are read, 232 of them take a file, and the grouping is chosen by measurement. The Services tab: the tools read out of one OpenAPI description, grouped the way the service groups itself. ## 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`. The Edit service dialog: template, address, key, and where the result is named. ### 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. The services tab after reading the description: 232 tools in seven groups. ### 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. The image group opened, with the settings of one tool showing mode, quality and target size. ### 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 finished menu: a Send to SnapOtter submenu holding three entries, and one tool placed on its own. 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. The search field with git typed into it, leaving a single Git Bash entry standing in the table 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. The detail pane for a COM handler, showing its registry path, CLSID, server DLL and the reasons the entry cannot be edited 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 Windows 11 menu shows five icons and four entries; the classic menu behind it holds nine. ctxmenu lists both in one place. ## 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.