| name | write-custom-command |
| description | Author a CopyQ Custom Command — automatic clipboard transformations, menu actions, or global-shortcut handlers. Produces a valid `.ini` snippet that can be imported via Preferences → Commands → Load, or via `copyq loadCommands`. Use when the user wants CopyQ to react automatically to clipboard events (e.g. strip formatting, route URLs to a tab, redact secrets). |
Write a CopyQ Custom Command
A Custom Command is a named bundle of: a trigger (automatic, menu, global shortcut), optional matchers (MIME types, regex, window title), and a body (CopyQ scripting language or external shell command).
When to reach for which trigger type
| Trigger | When |
|---|
Automatic | Run on every clipboard/selection change. For transformation, filtering, ignoring, or routing. |
In menu | User-initiated from the right-click menu or copyq menu. For ad-hoc operations on a selected item. |
Global shortcut | Bound to a system-wide keybinding. For "do X to whatever is in the clipboard right now". |
Display | Decorate items at display time (icons, colours). Doesn't mutate data. |
Refer to copyq-cli-reference for the scripting API. Always read that skill first if you're authoring command bodies — the API surface is non-obvious.
File format
CopyQ commands are stored as INI sections. A single command looks like this:
[Commands]
1\Name=Strip formatting on copy
1\Match=
1\Command="
copyq:
var t = str(data(mimeText));
if (t.length === 0) return;
setData(mimeText, t);
removeData(mimeHtml);
removeData('text/rtf');
"
1\Icon=\\xf0c5
1\Automatic=true
1\Input=text/plain
size=1
Key fields:
| Key | Meaning |
|---|
Name | Display name. |
Command | Body. Prefix copyq: means use the embedded scripting interpreter; otherwise it runs as a shell command with item data on stdin. |
Automatic | true to run on every clipboard change. |
InMenu | true to show in right-click menu. |
GlobalShortcut | Key sequence (e.g. ctrl+alt+v). |
Shortcut | In-app shortcut (only when window is focused). |
Input | MIME type required for the command to be eligible (e.g. text/plain, text/uri-list). |
Output | MIME type the command produces (only relevant for shell-style commands). |
Match | Regex on item text — command only runs if it matches. |
Wait | true makes the command block clipboard storage until it completes. |
Icon | Font-awesome glyph escape, e.g. \xf0c5. |
The size=N line at the bottom of [Commands] must equal the highest index used.
Authoring workflow
- Decide trigger type. Ask the user if it's not obvious.
- Decide matcher. Restrict by
Input=text/plain, Match=^https?://, or application/x-copyq-owner-window-title regex if scope matters.
- Write body in the scripting language (preferred — portable, no shell quoting hell). Use the API from
copyq-cli-reference.
- Set
Automatic=true if it should run unattended; otherwise InMenu=true.
- Test before committing.
Testing a command body without importing
copyq eval runs a script body in the live server context — fastest way to validate logic:
copyq eval -- 'var t = str(clipboard()); print(t.toUpperCase());'
For Automatic commands, the data() function is only populated during a real clipboard event. Simulate by setting clipboard then re-reading:
copyq copy "test value"
copyq eval -- 'print(str(data(mimeText)));'
copyq eval -- 'print(str(clipboard()));'
Importing the finished command
Two options:
copyq loadCommands "$(cat my-command.ini)"
loadCommands appends to existing commands; it does not replace.
Example: route URLs to a "Links" tab automatically
[Commands]
1\Name=Auto-route URLs to Links tab
1\Automatic=true
1\Input=text/plain
1\Match=^https?://
1\Command="
copyq:
var url = str(data(mimeText));
var dest = 'Links';
if (tabs().indexOf(dest) === -1) addTab(dest);
tab(dest);
add(url);
"
size=1
Example: redact secrets before storing
[Commands]
1\Name=Redact bearer tokens
1\Automatic=true
1\Input=text/plain
1\Match=Bearer\\s+[A-Za-z0-9._-]+
1\Wait=true
1\Command="
copyq:
var t = str(data(mimeText));
setData(mimeText, t.replace(/Bearer\\s+[A-Za-z0-9._-]+/g, 'Bearer <redacted>'));
"
size=1
The Wait=true is important here — without it, the unredacted value can race into the history before the command finishes.
Common pitfalls
- Backslashes in regex must be doubled inside the INI string (
\\s, \\d).
copyq: prefix is required for scripting bodies. Without it, the body runs as a shell command and data arrives on stdin.
Automatic commands run in declaration order. If two commands both transform text, the second sees the first's output.
abort() and ignore() are not the same. abort() stops the current command; ignore() tells CopyQ not to store the item at all.