×

Bundle Plug-Ins

Omni Automation Plug-Ins are a great way to deliver organized contextual automation tools for Omni applications in both macOS and iOS. Omni Automation plug-ins may created as either solitary files containing a single action, or as bundle files containing or more actions, libraries, and other supporting files and images. A “bundle” is essentially a folder that appears in the Finder or Files app as a single file.

This section details the design of bundle plug-ins, beginning a description of two typical bundle components:

The Plug-In Bundle

Omni Automation bundle plug-in files are “bundles,” which are essentially folders that appear as files. Within a plug-in bundle are the files and directories that define the manner in which the plug-in provides its functionality.

To assist you in understanding and creating your own bundle plug-ins, fully editable templates for each Omni application are available for download:

To open the unarchived Plug-In bundle on macOS, right-click the file and choose “Show Package Contents” from the Finder’s contextual menu.

The illustration below shows the hierarchy and contents of an Omni Automation Plug-In bundle:

plugin-anatomy

At the topmost level in the bundle folder are two items:

The Manifest

The Plug-In manifest JSON text file contains between 5 to 7 keys and their corresponding values.

Bundle Plug-In Manifest


{ "defaultLocale":"en", "identifier": "com.YourIdentifier.NameOfPlugIn", "author": "Your Name or Organization", "description": "A collection of actions (scripts) for NameOfApp.", "version": "1.0", "actions": [ { "identifier": "myFirstActionFileName", "image": "1st-action-toolbar-icon.png" }, { "identifier": "mySecondActionFileName", "image": "2nd-action-toolbar-icon.png" } ], "libraries": [ { "identifier": "MyFirstLibraryFileName" }, { "identifier": "MySecondLibraryFileName" } ] }

The Manifest Strings file

The mainfest.strings file, located in the localized project folder within the Resources folder, is a text file containing a single key:value pair indicating the Plug-In name that should be displayed to the user in any menus. The key of the manifest.strings file is the value of the Plug-In identifier key from the manifest.json file. The value of the key is the localized string for the Plug-In name. (Note the inclusion of an ending semicolon character)

Manifest Strings


"com.youOrCompanyID.nameOfPlugIn" = "Plug-In for Your App";

The Resources folder

The Resources folder contains all other Plug-In components, including:

plugin-anatomy

IMPORTANT: In the illustration of the plug-in folder heirarchy, the placeholders for the libary and action file names are encased within < > characters. Replace these placeholders with the file names of the individual action and library files. TIP: use filenames without spaces: my-action-filename.js, my-action-filename.strings

Plug-In File Extensions

When creating a bundle plug-in from scratch, start with a folder and add the various elements described in this documentation. When completed, rename the folder to the name for plug-in appended with one of the following file extensions.

omnifocusjs, omnioutlinerjs, omnigrafflejs, omniplanjs

After approving the forthcoming dialog in the macOS Finder, the folder icon will automatically change to the appropriate Omni plug-in icon, and the bundle will be treated as if it were a single file.

Plug-In Actions

The work of an Omni Automation plug-in is accomplished by its “actions.” Bundle plug-ins may have one or more actions, expressed in individual JavaScript files stored within the plug-in bundle.

An Omni Automation action is comprised of JavaScript functions placed within a self-invoking anonymous (unnamed) JavaScript arrow function. This anonymous (unnamed) function wrapper is used to minimize the chance of conflicts with other actions whose code statements contain variables using the same titles as those used in other actions.

Basic Bundle Action Template


(() => { const action = new PlugIn.Action(function(selection, sender){ // action processing code goes here }); action.validate = function(selection, sender){ // perform any status checks and return a boolean value return true }; return action; })();

The following are the essential components for creating an Omni Automation action:

Selection Input

The action declaration and validation handlers include two input parameters in their functions:

Selection Object Parameter


const action = new PlugIn.Action(function(selection, sender){ // OmniFocus: selected tasks selection.tasks // OmniPlan: selected resources selection.resources // OmniGraffle: selected non-line graphics selection.solids // OmniOutliner: selected rows selection.items selection.nodes });

The sender parameter is documented in detail in the Sender Parameter section of this website.

Validation Function

The value returned by the Validation Function of the PlugIn.Action instance determines whether or not the action is enabled or disabled in the application interface. A returned value of true means that the Automation Menu, Share Menu, and any corresponding Toolbar Item will be enabled for use. A returned value of false will make those disabled.

NOTE: Use of the Validation Function is optional. If omitted from the action code, a result value of true will be assumed by the plug-in.

The Validation Function is often used to ensure that the items necessary to be processed by the plug-in are indeed currently selected in the application interface. Here are examples of the Validation Function using the passed-in selection parameter to identify selected items:

Validation Function


action.validate = function(selection, sender){ // validation code return a value that is either true or false };
OmniFocus: Single Task whose Name begins with “Call”


action.validate = function(selection, sender){ return ( selection.tasks.length === 1 && selection.tasks[0].name.startsWith("Call") ) };
OmniPlan: One or More Resources Selected


action.validate = function(selection, sender){ return (selection.resources.length > 0) };
OmniGraffle: Single Selected Shape with Text


action.validate = function(selection, sender){ return ( selection.solids.length === 1 && selection.solids[0].text != "" ) };
OmniOutliner: One or More Columns Selected


action.validate = function(selection, sender){ return (selection.columns.length > 0) };

The Action File

Actions are saved as JavaScript script files with the file extension of: .js They are placed at the top-level within the PlugIn bundle’s Resources folder.

Since the name of an action file also serves as its identifier, its file name should not contain spaces or special characters.

The Action Strings File

Localized titles and/or descriptions of an action are displayed to the user in three ways:

Action Strings File


"label" = "About this PlugIn…"; "shortLabel" = "About…"; "mediumLabel" = "About PlugIn"; "longLabel" = "Information about this PlugIn"; "paletteLabel" = "About…";

The Action Icon File

Each action should have its own toolbar icon. Typically, these icons are image files (saved in PNG format) that are indicative of the type of task the action performs, like these:

text-align-center text-align-justified text-align-left text-align-right paint align-top text-size-up

Save your toolbar icons as 48x48 PNG files with 144 pixel resolution, and place them within the plugin’s Resources folder.

IMPORTANT: (req: macOS 11/iOS 14/iPadOS 14): as an alternative to using an image file, a plug-in’s icon can be a specified Apple SF Symbols character identified by its assigned name, such as: "newpaper.fill". See the section on this page about the bundle plug-in manifest. For a complete catalog of SF Symbols characters, download the SF Symbols application from Apple.

Screenshot

Importing Bundle Resource

In this example action, an image file located within the Plug-In’s Resources folder, is placed onto the current OmniGraffle canvas.

The image resource is identified using the resourceNamed() function of the PlugIn class (documentation).

Import Image Resource


(() => { var action = new PlugIn.Action(function(selection){ let plugin = action.plugIn let imageFileName = "ImageName.png" let = plugin.resourceNamed(imageFileName) url.fetch(function(data){ let aRect = new Rect(0, 0, 72, 72) let cnvs = document.windows[0].selection.canvas let aGraphic = cnvs.addShape('Rectangle', aRect) aGraphic.strokeThickness = 0 aGraphic.shadowColor = null aGraphic.fillColor = null aGraphic.image = addImage(data) aGraphic.name = imageFileName let imageSize = aGraphic.image.originalSize let imgX = imageSize.width let imgY = imageSize.height aGraphic.geometry = new Rect(aGraphic.geometry.x, aGraphic.geometry.y, imgX, imgY) document.windows[0].selection.view.select([aGraphic]) }) }); // routine determines if menu item is enabled action.validate = function(selection){ return true }; return action; })();

Bundle Plug-In Libraries

Omni Automation Libraries provide the means for storing, using, and sharing your favorite Omni Automation functions. They can make your automation tasks easier by dramatically shortening your scripts, and they enable the ability to share useful routines with other computers and tablets.

IMPORTANT: Bundle Plug-In libraries are similar to single-file library plug-ins, but their metadata is presented differently. Creating single-file library plug-ins is documented in detail in the Library section of the Plug-Ins sub-site. The following documentation is about creating bundle plug-in libraries.

In bundle plug-ins, libraries exist as JavaScript script files (.js) placed within the Resources folder in plugin bundles. The functions contained within a Omni Automation Library can be called from within the host plug-in’s actions, or by scripts executed outside of the host plugin.

An Omni Automation bundle plug-in library is essentially a JavaScript script, comprised of a single function that is executed when the library is called (line 16). Within the main function (line 1 ~ line 15) a library object is created (line 2), and the various library functions are added to the library object (line 4, line 9). The main script finishes by returning the created library object populated with the handlers you added (line 14).

Bundle Plug-In Library


(() => { var myLibrary = new PlugIn.Library(new Version("1.0")); myLibrary.myFirstFunction = function(passedParameter){ // function code return myFirstFunctionResult } myLibrary.mySecondFunction = function(passedParameter){ // function code return mySecondFunctionResult } return myLibrary; })();

Libraries can contain as many functions as you find useful to include. The example shown above contains two library functions that can be called from within your plugins or scripts.

Library files are saved with the file extension: .js (which stands for JavaScript) and are placed in the Resources folder in the plugin bundle. IMPORTANT: For bundle plug-in libraries is best to avoid using spaces or special characters like dashes in the filenames of your libraries.

Registering the Bundle Plug-In Library

In order to be available to the user, libraries must be registered by including them in the plugin’s manifest.json file. This file contains metadata about the plugin, as well an array of plugin actions (line 7), and an optional array of one or more libraries (line 17).

Each action element is comprised of a key:value pair for the action’s identifier (filename) (lines 9, 13) and the filename of its corresponding toolbar icon image file (lines 10, 14).

Each library element is comprised of a key:value pair (lines 19, 22) for the library’s identifier, which is the library’s filename without the file extension.

Bundle Plug-In Manifest


{ "defaultLocale":"en", "identifier": "com.YourIdentifier.name-of-plug-in", "author": "Your Name or Organization", "description": "A collection of actions (scripts) for NameOfApp.", "version": "1.0", "actions": [ { "identifier": "myFirstAction", "image": "toolbar-icon.png" }, { "identifier": "mySecondAction", "image": "toolbar-icon.png" } ], "libraries": [ { "identifier": "MyFirstLibraryFileName" }, { "identifier": "MySecondLibraryFileName" } ] }

Calling Bundle Plug-In Library from a Bundle Plug-In Action

The example code shown below is of a plug-in action. It is comprised of two sections: the validation code (line 14), and the execution code (line 4).

The validation section (line 14) is used to determine if the action should execute, based upon whether or not there is a selection in the frontmost document. If your action does not rely upon a selection, simply return true (15) as shown in the example.

In the following example, the library file names are: StencilLib and ShapeLib

Calling Bundle Library from Bundle Action


(() => { var action = new PlugIn.Action(function(selection, sender){ // call using “this” keyword & library file name (no spaces or special characters!) var stencilLib = this.StencilLib var shapeLib = this.ShapeLib // call library functions using references var names = stencilLib.getStencilNames() var aGraphic = shapeLib.shapeWithContentsOfArray(names) // select the created graphic document.windows[0].selection.view.select([aGraphic]) }); // validation function determines if menu item is enabled action.validate = function(selection, sender){ return true }; return action; })();

Loading a library is accomplished by simply appending the library identifier (filename) to the global object: this. In the example shown above two libraries are loaded into variables (line 4, 5).

Once the libraries have been loaded, any library functions can be called by appending the name of the function to the variable representing the host library (lines 7, 8).

Calling a Bundle Plug-In Library from an External Script or Plug-In

Calling into libraries within an external script is a slightly more involved process in that you must first identify the plugin that contains the library whose functions you wish to access.

In the example below, the plug-in is located using the find method on the Plugin class, and the plugin’s identifier. If the plugin is installed, then an object reference to the plug-in is returned and stored in a variable (line 2). If the plug-in is not installed, a value of null will be returned and the next line in the script (line 3) will throw an error indicating its status as missing.

Once the plug-in has been successfully identified, any libraries can be loaded using the library method on the plugin reference stored previously in a variable (lines 5, 7).

Once the library or libraries have been loaded, their functions can be accessed by appending the function name to the variable containing the loaded library (lines 9, 11).

Calling Bundle Plug-In Library Externally


// locate the plugin by identifier var plugin = PlugIn.find("com.YourIdentifier.name-of-plug-in") if (plugin == null){throw new Error("Plug-in is not installed.")} // load a library identified by name var firstLibrary = plugin.library("NameOfFirstLibrary") // load a library identified by name var secondLibrary = plugin.library("NameOfSecondLibrary") // call a library function by name var aVariable = firstLibrary.nameOfLibraryFunction() // call a library function by name secondLibrary.nameOfLibraryFunction(aVariable)

Here is more documentation regarding accessing plug-ins from external sources.

Accessing the Plug-In Remotely

Omni Automation plug-ins can be called from external scripts or plug-ins. (documentation)