Enabling Development Mode

In order to create an extension the first thing we’ll need to do is open Preferences > General and check the option for Show extension development items in the Extensions menu.

The General tab of Nova's preferences

Structuring Your Project

A project created with Nova’s extension wizard (Extensions > Create New Extension…) uses the root of the extension bundle as the root of the project:

├── Images
├── Scripts
│   └── main.js
├── .gitignore
├── extension.json
└── README.md

This works great for any extension that doesn’t require external dependencies. We’ll need to take a slightly different approach, however, when using NPM packages.

Let’s say we’re building an extension called Novachrome. First, we’ll create a standard NPM project as our top-level directory:

mkdir nova-novachrome
cd nova-novachrome
npm init -y
echo node_modules >> .gitignore
echo "# Novachrome" >> README.md
├── .gitignore
├── package.json
├── package.lock.json
└── README.md

Next, let’s move (or create) our Nova extension bundle inside the NPM project:

├── .gitignore
├── Novachrome**.novaextension**
│   ├── Images
│   ├── Scripts
│   │   └── main.js
│   ├── .gitignore
│   ├── extension.json
│   └── README.md
├── package.json
├── package.lock.json
└── README.md

Installing Project Dependencies

Our extension will make use of the chroma-js package’s API, so let’s include that in dependencies now:

npm install --save chroma-js
  "name": "nova-novachrome",
  "version": "0.1.0",
  "description": "A Nova extension that does colorful things",
  "main": "index.js",
  "dependencies": {
		"chroma-js": "^2.1.0"

It’s natural to want to open Novachrome.novaextension/Scripts/main.js next and add the following:

const chroma = require("chroma-js");

Like Node.js, Nova does support CommonJS-style require statements for importing local modules (ex., sibling files in the Scripts directory), but it doesn’t have any notion of Node’s module logic, structure, or standard locations.

Let’s activate our extension now (Extensions > Activate Project as Extension) then open the Extension Console (Extensions > Show Extension Console) to see what happens:

Extension encountered an uncaught exception:
Novachrome.novaextension/Scripts/main.js (Line 1, Column 0)
Error: Could not find a module at path: “chroma-js”

Nova couldn’t find the module chroma-js even though we already installed it as a dependency. So how do we use an NPM package’s API in Nova?

The answer is by using “module bundling,” but first we need configure a few more project settings.

Getting Ready For Bundling

Source Files

Unlike a traditional Nova extension, we’re going to do our primary development outside the extension bundle, in a new directory at the root of our top-level project.

Let’s create a folder called src and, to mirror the overall structure of an extension bundle, inside that we’ll create Scripts/main.js:

├── .gitignore
├── Novachrome**.novaextension**
├── src
│   └── Scripts
│       └── main.js

You can call your source directory anything you’d like: source, lib, or whatever feels right.

For fun, let’s add the following to src/Scripts/main.js:

const chroma = require("chroma-js");
console.log( chroma("#36036a").brighten().hex() );

and then set that file aside for now…

To help keep our Git repository tidy, we’re going to use a different name for the entry point script that gets built by our module bundler. For the purposes of this guide, we’ll use main.dist.js, but you can choose any name you like.


We need to tell Nova where to look for the new entry point script. Open Novachrome.novaextension/extension.json and change the value of main from the default:

"main": "main.js"

to our new entry point filename:

"main": "main.dist.js"


It’s common to not include build artifacts in version control, and we’ll follow that practice in Novachrome.

Add main.dist.js to the top-level .gitignore:


Finally, delete Novachrome.novaextension/Scripts/main.js and we’re ready to continue!

Part 3: Bundling Dependencies
Part 1: Using NPM Packages in Nova Extensions

This article was last updated on September 24, 2020