Appearance
JavaScript actions
A JavaScript action runs code in PopClip's own JavaScript environment, with access to the selected text and to PopClip itself through the global popclip object. It is the simplest way to run code in PopClip. In a code snippet, everything after the header is the action's code, run when the action is clicked:
javascript
// #popclip
// name: Uppercase
// icon: square filled AB
// after: paste-result
return popclip.input.text.toUpperCase();Properties
A JavaScript action is defined by a code snippet whose config header uses the // comment prefix. Alternatively, a config snippet may define a javaScript or javaScriptFile field, as follows:
| Key | Type | Description |
|---|---|---|
javaScript | String | A JavaScript text string to load. |
javaScriptFile | String | Path to a .js or .ts file in the package directory. |
For example, here is a config snippet with the action's code inline in the javaScript field:
yaml
#popclip
name: Word Count
icon: square 123
javaScript: popclip.showText(popclip.input.text.split(/\s+/).length + " words")A code snippet is equivalent to a config snippet whose javaScriptFile is the snippet itself.
Script format
The script's entry point is at the top level of the file. Internally, PopClip loads the provided script and wraps it as a function. When the action is run, PopClip calls the function.
Function wrapper detail
As an example, imagine the following JavaScript is provided in the javaScript field:
javascript
return "foo";Internally, this will be wrapped in an async arrow function definition like this:
javascript
const main = async () => {
return "foo";
};When the action is run, PopClip calls this internal main function with no arguments.
In addition to pure JavaScript, PopClip can load TypeScript from files named with a .ts extension. See TypeScript support.
Input and output
Scripts take their input from the global popclip object.
If the script exits by returning a string, it will be passed to the after step.
Return type
To return a value to the after step, it must be of type string. If the script returns a value of any other type, such as number or object, PopClip will ignore it.
Indicating errors
Scripts should indicate success by completing normally (either by explicitly returning a value, or implicitly returning undefined) and should indicate failure by throwing an error. PopClip will catch any erros thrown by the script and display the shaking-'X'.
To indicate an error with the user's settings, and pop up the extension's settings UI, throw an error message starting with the specific words settings error or not signed in (not case sensitive). For example:
javascript
throw new Error("Settings error: missing API key");Examples
These examples are all complete code snippets — select the whole block to install one.
Paste the selected text, then press Return — two PopClip primitives chained with await:
javascript
// #popclip
// name: Paste & Enter
// icon: symbol:return
// requirements: [paste]
await popclip.pasteText(popclip.input.text);
await popclip.pressKey("return");Send the selection to a web app that has no query parameter for it. A URL action can't help here, so instead the script opens the page, waits with sleep() for it to load, puts the text on the clipboard and pastes it with a key press. A workaround, but it works:
javascript
// #popclip
// name: Gemini
// icon: symbol:sparkles
// description: Open Gemini and paste the selection.
// popclipVersion: 6221
await popclip.openUrl("https://gemini.google.com/app");
await sleep(1000);
pasteboard.text = popclip.input.text;
await popclip.pressKey("command v");Look up the selected word in the macOS dictionary, then speak the definition aloud through the say command — using the $ shell tag:
javascript
// #popclip
// name: Speak Definition
// icon: symbol:character.book.closed
// entitlements: [script]
const word = popclip.input.text.trim();
const definition = util.getDictionaryDefinition(word) ?? "no definition found";
await $`say ${definition}`;Fetch the page at the selected URL and show its title — network access, a bundled module, and the after step working together:
javascript
// #popclip
// name: Page Title
// icon: symbol:globe
// requirements: [url]
// entitlements: [network]
// after: show-result
const axios = require("axios");
const response = await axios.get(popclip.input.data.urls[0]);
return (
String(response.data).match(/<title[^>]*>([^<]*)</i)?.[1] ?? "No title found"
);Growing into a module
A JavaScript action is one script with static config around it. When you want code to define more of the extension — several actions, options, titles or icons computed at load time — export an extension object with defineExtension({...}) instead. The file is then loaded as a module extension: its top level runs once at load time to define the extension, and each action's code function runs at click time.