From copyq-scripting
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).
npx claudepluginhub danielrosehill/claude-code-plugins --plugin copyq-scriptingThis skill uses the workspace's default tool permissions.
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).
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
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).
| 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.
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.
Input=text/plain, Match=^https?://, or application/x-copyq-owner-window-title regex if scope matters.copyq-cli-reference.Automatic=true if it should run unattended; otherwise InMenu=true.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)));' # won't work — data() is event-scoped
copyq eval -- 'print(str(clipboard()));' # works
Two options:
# Option A: GUI — Preferences → Commands → Load Commands → pick the .ini
# Option B: CLI — pipe the .ini contents into copyq
copyq loadCommands "$(cat my-command.ini)"
loadCommands appends to existing commands; it does not replace.
[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
[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.
\\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.