Skip to main content
Addons are dynamically-linked shared objects written in C++. The require() function can load addons as ordinary Node.js modules. Addons provide an interface between JavaScript and C/C++ libraries.

Implementation Options

There are three options for implementing addons:
  1. Node-API (recommended) - Provides ABI stability across Node.js versions
  2. nan (Native Abstractions for Node.js) - Compatibility layer for V8 API changes
  3. Direct V8, libuv, and Node.js APIs - For advanced use cases requiring low-level access
Unless you need direct access to functionality not exposed by Node-API, use Node-API. Refer to C/C++ addons with Node-API for more information.

Components

When not using Node-API, implementing addons requires knowledge of several components:

V8

The C++ library Node.js uses to provide the JavaScript implementation. V8 provides mechanisms for:
  • Creating objects
  • Calling functions
  • Managing memory and garbage collection
The V8 API is documented in v8.h and available online.

libuv

The C library implementing the Node.js event loop, worker threads, and asynchronous behaviors. It provides:
  • Cross-platform abstraction for common system tasks
  • File system interactions
  • Sockets and networking
  • Timers and system events
  • Threading abstraction for sophisticated async operations

Internal Node.js Libraries

Node.js exports C++ APIs that addons can use, including the node::ObjectWrap class.

Other Libraries

Node.js statically links libraries including OpenSSL, V8, and zlib, whose symbols are purposefully re-exported for addon use.

Hello World Example

This simple addon is equivalent to the following JavaScript:
module.exports.hello = () => 'world';
C++ implementation (hello.cc):
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(
      isolate, "world").ToLocalChecked());
}

void Initialize(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

}  // namespace demo

Building Addons

Once the source code is written, it must be compiled into the binary addon.node file. Create a binding.gyp file:
{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "hello.cc" ]
    }
  ]
}
Build the addon:
npm install -g node-gyp
node-gyp configure
node-gyp build

Using Addons

Once built, the addon can be loaded from Node.js:
const addon = require('./build/Release/addon');

console.log(addon.hello());
// Prints: 'world'

Context-Aware Addons

Addons may need to be loaded multiple times in multiple contexts. For example, Electron runs multiple instances of Node.js in a single process. Construct a context-aware addon using NODE_MODULE_INITIALIZER:
using namespace v8;

extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
                        Local<Value> module,
                        Local<Context> context) {
  /* Perform addon initialization */
}
Or use NODE_MODULE_INIT():
NODE_MODULE_INIT(/* exports, module, context */) {
  Isolate* isolate = Isolate::GetCurrent();
  // Initialize addon
}

Managing Global State

Context-aware addons require careful management of global static data:
  1. Define a class to hold per-addon-instance data with a static DeleteInstance() method
  2. Heap-allocate an instance in the addon initializer
  3. Call node::AddEnvironmentCleanupHook() to ensure cleanup
  4. Store the instance in a v8::External
  5. Pass the v8::External to all exposed methods
Example:
class AddonData {
 public:
  explicit AddonData(Isolate* isolate) : call_count(0) {
    node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
  }

  int call_count;

  static void DeleteInstance(void* data) {
    delete static_cast<AddonData*>(data);
  }
};

NODE_MODULE_INIT() {
  Isolate* isolate = Isolate::GetCurrent();
  AddonData* data = new AddonData(isolate);
  Local<External> external = External::New(isolate, data);
  
  exports->Set(context,
               String::NewFromUtf8(isolate, "method").ToLocalChecked(),
               FunctionTemplate::New(isolate, Method, external)
                  ->GetFunction(context).ToLocalChecked()).FromJust();
}

Worker Thread Support

To support Worker threads, addons must:
  1. Be a Node-API addon, or
  2. Be declared as context-aware using NODE_MODULE_INIT()
Addons must clean up resources when threads exit using AddEnvironmentCleanupHook():
void AddEnvironmentCleanupHook(v8::Isolate* isolate,
                               void (*fun)(void* arg),
                               void* arg);

Linking to Node.js Libraries

Addons are required to link to V8 and may link to other Node.js dependencies:
  • Include headers with #include <...> statements
  • node-gyp automatically locates appropriate headers
  • When downloading full source, addons have access to all Node.js dependencies
  • When downloading only headers, only exported symbols are available

Common Patterns

Function Arguments

Reading arguments passed from JavaScript:
void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  if (args.Length() < 2) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong number of arguments")
            .ToLocalChecked()));
    return;
  }

  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong arguments")
            .ToLocalChecked()));
    return;
  }

  double value = args[0].As<Number>()->Value() + 
                 args[1].As<Number>()->Value();
  args.GetReturnValue().Set(Number::New(isolate, value));
}

Callbacks

Passing JavaScript functions to C++ and executing them:
void RunCallback(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();
  Local<Function> cb = Local<Function>::Cast(args[0]);
  
  const unsigned argc = 1;
  Local<Value> argv[argc] = {
    String::NewFromUtf8(isolate, "hello world").ToLocalChecked()
  };
  
  cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
}

Object Factory

Creating and returning objects from C++:
void CreateObject(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> obj = Object::New(isolate);
  
  obj->Set(context,
           String::NewFromUtf8(isolate, "msg").ToLocalChecked(),
           args[0]->ToString(context).ToLocalChecked())
     .FromJust();
  
  args.GetReturnValue().Set(obj);
}

Wrapping C++ Objects

Wrap C++ classes for use with JavaScript new operator by inheriting from node::ObjectWrap:
class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Local<v8::Object> exports);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  
  double value_;
};

Node-API Alternative

Stability: 2 - Stable
Node-API is an API for building native addons that is:
  • Independent from the underlying JavaScript runtime (V8)
  • Maintained as part of Node.js
  • ABI stable across Node.js versions
  • Allows modules compiled for one version to run on later versions without recompilation
Example using Node-API:
#include <node_api.h>

namespace demo {

napi_value Method(napi_env env, napi_callback_info args) {
  napi_value greeting;
  napi_status status;

  status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting);
  if (status != napi_ok) return nullptr;
  return greeting;
}

napi_value init(napi_env env, napi_value exports) {
  napi_status status;
  napi_value fn;

  status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
  if (status != napi_ok) return nullptr;

  status = napi_set_named_property(env, exports, "hello", fn);
  if (status != napi_ok) return nullptr;
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, init)

}  // namespace demo
See C/C++ addons with Node-API for complete documentation.