Skip to content

Module-based extensions

Module-based extensions let you use the full power of JavaScript or TypeScript to define your PopClip extension. This allows you use code to construct properties like options at load time, and to define actions dynamically, for example to generate titles or icons in response to the input text.

When you provide a config file called Config.js or Config.ts, PopClip treats this as a JavaScript or TypeScript module and looks for the extension's properties in the exported object, after first loading static properties from YAML in a comment header.

All properties exported by the module will be merged into the extension's config, overriding any static properties with the same name (except for the static-only properties which cannot be overriden).

The module can also define a population function to dynamically populate the actions.

Snippets as modules

You can also define a module in a snippet by setting module: true.

Example

The following JavaScript snippet defines a complete module-based extension:

javascript
// #popclip
// name: Module Demo
// after: show-result
// language: javascript
// module: true

// this is only run once, at load time
const theNumber = String(Math.floor(Math.random() * 100));

module.exports = {
  icon: `square ${theNumber}`,
  actions: [
    {
      title: "The Title",
      code: (input) => {
        return `The number is ${theNumber}. Your text is: ${input.text}`;
      },
    },
  ],
};

Observe a few things:

  • The extension's name and the action's after step, show-result, are specified in the static config in the header.
  • At load time, the module generates a random number and saves it in a variable.
  • The module exports an icon property, displaying the random number in a square.
  • The module defines its actions by exporting an actions array. See Module actions.

More examples

See the following examples from the PopClip Extensions Directory:

File format

Comment header

In Config.js and Config.ts a YAML comment header must be provided defining the extension's name and any other static-only properties. The header is in the same format as for a snippet (see Snippets - Inverted syntax) except that you do not specify language or module in the header. The file is automatically loaded as a module.

Module format

The module file may be written in JavaScript (.js) or TypeScript (.ts).

The module format is CommonJS. You can either export a single object with module.exports = ... or export individual properties like exports.foo = ....

TypeScript files can use ES Modules syntax, which will be transpiled to CommonJS under the hood. JavaScript files may not use ES Modules syntax.

The exported property names and types are the same as defined in Config, with the execption of actions which has special handling - see Module actions.

Specifying the module file

The module does not have to be loaded from Config.js/Config.ts. Alternatively, you can provide static config in another format (e.g. Config.json) and specify a module file name as follows:

KeyTypeDescription
moduleStringThe path to a JavaScript (.js) or TypeScript (.ts) file to load.

Static-only properties

Certain properties of the extension can only be defined in the static config, and cannot be overriden by the module. These are identifier, popclipVersion, macosVersion and entitlements.

Module actions

Detailed API reference

A more detailed definition of the action object, action function and population function may be found in the JavaScript API Reference.

A module defines its actions by exporting an actions property, which can be either:

Note that a module always provides all the actions for the extension. You cannot mix regular actions and module actions in the same extension.

Action object

Each action object has the same properties as a regular action, with the addition of the following:

KeyTypeDescription
codeFunctionA function to run when the action is invoked. See: Action function.
regexRegExp ObjectYou may export a JavaScript RegExp, and PopClip will use this instead of a string regex.

Action function

The action function is called with the following arguments:

  • input: same object as popclip.input
  • options: same object as popclip.options
  • context: same object as popclip.context
javascript
{
  code: ((input, options, context) => {
    // ... do stuff ...
    doSomething();
    return someResult;
  });
}
javascript
{
  code: (async (input, options, context) => {
    // ... do stuff ...
    await doSomethingAsync();
    return someResult;
  });
}

The function may return a string, which will be passed to the after step. Otherwise it should return undefined or null.

The function may optionally be async, and use await.

The function may indicate an error by throwing an exception, as per JavaScript actions.

Population function

Entitlement needed

To use a population function, the dynamic entitlement must be present in the entitlements array in the static config. This cannot be set if the network entitlement is also being used.

The population function is set as the actions property of the module. It dynamically supplies actions every time the PopClip bar appears. The population function is called with the same arguments as the action function, and it returns an array of action objects.

javascript
// #popclip dynamic example
// { name: Dynamic Title, entitlements: [dynamic], lang: js, module: true }
exports.actions = (input, options, context) => {
  return [{
    title: `<${input.text.slice(0, 10)}>`,
    code: (input, options, context) => {
      popclip.showText("Hi from Action");
    },
  }];
};
typescript
// #popclip dynamic example
// { name: Dynamic Title, entitlements: [dynamic], lang: ts, module: true }
export const actions: Action[] = (input, options, context) => {
  return [{
    title: `<${input.text.slice(0, 10)}>`,
    code: (input, options, context) => {
      popclip.showText("Hi from Action");
    },
  }];
};

The population function has the following limitations:

  • Cannot access the network (XMLHttpRequest is unavailable).
  • Cannot call methods on the popclip global object.
  • Cannot access popclip.context.browserUrl or popclip.context.browserTitle.

Abbreviated forms

The action property

If the module defines only a single action, it may be exported as the action property instead of in an actions array. For example:

javascript
// #popclip
// { name: Single Action, lang: js, module: true}
exports.action = {
  code: () => {
    popclip.showText("hi mom!");
  },
};
typescript
// #popclip
// { name: Single Action, lang: ts, module: true}
export const action: Action = {
  code: () => {
    popclip.showText("hi mom!");
  },
};

Action function shorthand

If the action object has only a code property, it may be exported as a function instead of an object. For example:

javascript
// #popclip
// { name: Action Function, lang: js, module: true}
exports.action = () => {
  popclip.showText("hi mom!");
};
typescript
// #popclip
// { name: Action Function, lang: ts, module: true}
export const action: ActionFunction = () => {
  popclip.showText("hi mom!");
};