diff --git a/README.md b/README.md
index 206c7f108..bba353c81 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ The oldest Node.js version supported by the current version of node-addon-api is
 
 The following is the documentation for node-addon-api.
 
+ - [Addon Structure](doc/addon.md)
  - [Basic Types](doc/basic_types.md)
     - [Array](doc/basic_types.md#array)
     - [Symbol](doc/symbol.md)
diff --git a/benchmark/function_args.cc b/benchmark/function_args.cc
index 7dcc7c781..794229b75 100644
--- a/benchmark/function_args.cc
+++ b/benchmark/function_args.cc
@@ -78,76 +78,133 @@ static void FourArgFunction(const Napi::CallbackInfo& info) {
   Napi::Value argv3 = info[3]; (void) argv3;
 }
 
-static Napi::Object Init(Napi::Env env, Napi::Object exports) {
-  napi_value no_arg_function, one_arg_function, two_arg_function,
-      three_arg_function, four_arg_function;
-  napi_status status;
-
-  status = napi_create_function(env,
-                                "noArgFunction",
-                                NAPI_AUTO_LENGTH,
-                                NoArgFunction_Core,
-                                nullptr,
-                                &no_arg_function);
-  NAPI_THROW_IF_FAILED(env, status, Napi::Object());
-
-  status = napi_create_function(env,
-                                "oneArgFunction",
-                                NAPI_AUTO_LENGTH,
-                                OneArgFunction_Core,
-                                nullptr,
-                                &one_arg_function);
-  NAPI_THROW_IF_FAILED(env, status, Napi::Object());
-
-  status = napi_create_function(env,
-                                "twoArgFunction",
-                                NAPI_AUTO_LENGTH,
-                                TwoArgFunction_Core,
-                                nullptr,
-                                &two_arg_function);
-  NAPI_THROW_IF_FAILED(env, status, Napi::Object());
-
-  status = napi_create_function(env,
-                                "threeArgFunction",
-                                NAPI_AUTO_LENGTH,
-                                ThreeArgFunction_Core,
-                                nullptr,
-                                &three_arg_function);
-  NAPI_THROW_IF_FAILED(env, status, Napi::Object());
-
-  status = napi_create_function(env,
-                                "fourArgFunction",
-                                NAPI_AUTO_LENGTH,
-                                FourArgFunction_Core,
-                                nullptr,
-                                &four_arg_function);
-  NAPI_THROW_IF_FAILED(env, status, Napi::Object());
-
-  Napi::Object core = Napi::Object::New(env);
-  core["noArgFunction"] = Napi::Value(env, no_arg_function);
-  core["oneArgFunction"] = Napi::Value(env, one_arg_function);
-  core["twoArgFunction"] = Napi::Value(env, two_arg_function);
-  core["threeArgFunction"] = Napi::Value(env, three_arg_function);
-  core["fourArgFunction"] = Napi::Value(env, four_arg_function);
-  exports["core"] = core;
-
-  Napi::Object cplusplus = Napi::Object::New(env);
-  cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
-  cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
-  cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
-  cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
-  cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
-  exports["cplusplus"] = cplusplus;
-
-  Napi::Object templated = Napi::Object::New(env);
-  templated["noArgFunction"] = Napi::Function::New<NoArgFunction>(env);
-  templated["oneArgFunction"] = Napi::Function::New<OneArgFunction>(env);
-  templated["twoArgFunction"] = Napi::Function::New<TwoArgFunction>(env);
-  templated["threeArgFunction"] = Napi::Function::New<ThreeArgFunction>(env);
-  templated["fourArgFunction"] = Napi::Function::New<FourArgFunction>(env);
-  exports["templated"] = templated;
-
-  return exports;
-}
+class FunctionArgs : public Napi::Addon<FunctionArgs> {
+ public:
+  FunctionArgs(Napi::Env env, Napi::Object exports) {
+    napi_value no_arg_function, one_arg_function, two_arg_function,
+        three_arg_function, four_arg_function;
+    napi_status status;
+
+    status = napi_create_function(env,
+                                  "noArgFunction",
+                                  NAPI_AUTO_LENGTH,
+                                  NoArgFunction_Core,
+                                  nullptr,
+                                  &no_arg_function);
+    NAPI_THROW_IF_FAILED_VOID(env, status);
+
+    status = napi_create_function(env,
+                                  "oneArgFunction",
+                                  NAPI_AUTO_LENGTH,
+                                  OneArgFunction_Core,
+                                  nullptr,
+                                  &one_arg_function);
+    NAPI_THROW_IF_FAILED_VOID(env, status);
+
+    status = napi_create_function(env,
+                                  "twoArgFunction",
+                                  NAPI_AUTO_LENGTH,
+                                  TwoArgFunction_Core,
+                                  nullptr,
+                                  &two_arg_function);
+    NAPI_THROW_IF_FAILED_VOID(env, status);
+
+    status = napi_create_function(env,
+                                  "threeArgFunction",
+                                  NAPI_AUTO_LENGTH,
+                                  ThreeArgFunction_Core,
+                                  nullptr,
+                                  &three_arg_function);
+    NAPI_THROW_IF_FAILED_VOID(env, status);
+
+    status = napi_create_function(env,
+                                  "fourArgFunction",
+                                  NAPI_AUTO_LENGTH,
+                                  FourArgFunction_Core,
+                                  nullptr,
+                                  &four_arg_function);
+    NAPI_THROW_IF_FAILED_VOID(env, status);
+
+    Napi::Object core = Napi::Object::New(env);
+    core["noArgFunction"] = Napi::Value(env, no_arg_function);
+    core["oneArgFunction"] = Napi::Value(env, one_arg_function);
+    core["twoArgFunction"] = Napi::Value(env, two_arg_function);
+    core["threeArgFunction"] = Napi::Value(env, three_arg_function);
+    core["fourArgFunction"] = Napi::Value(env, four_arg_function);
+    exports["core"] = core;
+
+    Napi::Object cplusplus = Napi::Object::New(env);
+    cplusplus["noArgFunction"] = Napi::Function::New(env, NoArgFunction);
+    cplusplus["oneArgFunction"] = Napi::Function::New(env, OneArgFunction);
+    cplusplus["twoArgFunction"] = Napi::Function::New(env, TwoArgFunction);
+    cplusplus["threeArgFunction"] = Napi::Function::New(env, ThreeArgFunction);
+    cplusplus["fourArgFunction"] = Napi::Function::New(env, FourArgFunction);
+    exports["cplusplus"] = cplusplus;
+
+    Napi::Object templated = Napi::Object::New(env);
+    templated["noArgFunction"] = Napi::Function::New<NoArgFunction>(env);
+    templated["oneArgFunction"] = Napi::Function::New<OneArgFunction>(env);
+    templated["twoArgFunction"] = Napi::Function::New<TwoArgFunction>(env);
+    templated["threeArgFunction"] = Napi::Function::New<ThreeArgFunction>(env);
+    templated["fourArgFunction"] = Napi::Function::New<FourArgFunction>(env);
+    exports["templated"] = templated;
+
+    DefineAddon(exports, {
+      InstanceMethod("noArgFunction_inst", &FunctionArgs::InstNoArgFunction),
+      InstanceMethod("oneArgFunction_inst", &FunctionArgs::InstOneArgFunction),
+      InstanceMethod("twoArgFunction_inst", &FunctionArgs::InstTwoArgFunction),
+      InstanceMethod("threeArgFunction_inst", &FunctionArgs::InstThreeArgFunction),
+      InstanceMethod("fourArgFunction_inst", &FunctionArgs::InstFourArgFunction),
+      InstanceMethod<&FunctionArgs::InstNoArgFunction>("noArgFunction_intpl"),
+      InstanceMethod<&FunctionArgs::InstOneArgFunction>("oneArgFunction_intpl"),
+      InstanceMethod<&FunctionArgs::InstTwoArgFunction>("twoArgFunction_intpl"),
+      InstanceMethod<&FunctionArgs::InstThreeArgFunction>("threeArgFunction_intpl"),
+      InstanceMethod<&FunctionArgs::InstFourArgFunction>("fourArgFunction_intpl"),
+    });
+
+    Napi::Object inst = Napi::Object::New(env);
+    inst["noArgFunction"] = exports.Get("noArgFunction_inst");
+    inst["oneArgFunction"] = exports.Get("oneArgFunction_inst");
+    inst["twoArgFunction"] = exports.Get("twoArgFunction_inst");
+    inst["threeArgFunction"] = exports.Get("threeArgFunction_inst");
+    inst["fourArgFunction"] = exports.Get("fourArgFunction_inst");
+    exports["instance"] = inst;
+
+    Napi::Object intpl = Napi::Object::New(env);
+    intpl["noArgFunction"] = exports.Get("noArgFunction_intpl");
+    intpl["oneArgFunction"] = exports.Get("oneArgFunction_intpl");
+    intpl["twoArgFunction"] = exports.Get("twoArgFunction_intpl");
+    intpl["threeArgFunction"] = exports.Get("threeArgFunction_intpl");
+    intpl["fourArgFunction"] = exports.Get("fourArgFunction_intpl");
+    exports["instance_templated"] = intpl;
+  }
+
+ private:
+  void InstNoArgFunction(const Napi::CallbackInfo& info) {
+    (void) info;
+  }
+
+  void InstOneArgFunction(const Napi::CallbackInfo& info) {
+    Napi::Value argv0 = info[0]; (void) argv0;
+  }
+
+  void InstTwoArgFunction(const Napi::CallbackInfo& info) {
+    Napi::Value argv0 = info[0]; (void) argv0;
+    Napi::Value argv1 = info[1]; (void) argv1;
+  }
+
+  void InstThreeArgFunction(const Napi::CallbackInfo& info) {
+    Napi::Value argv0 = info[0]; (void) argv0;
+    Napi::Value argv1 = info[1]; (void) argv1;
+    Napi::Value argv2 = info[2]; (void) argv2;
+  }
+
+  void InstFourArgFunction(const Napi::CallbackInfo& info) {
+    Napi::Value argv0 = info[0]; (void) argv0;
+    Napi::Value argv1 = info[1]; (void) argv1;
+    Napi::Value argv2 = info[2]; (void) argv2;
+    Napi::Value argv3 = info[3]; (void) argv3;
+  }
+};
 
-NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
+NODE_API_ADDON(FunctionArgs)
diff --git a/benchmark/function_args.js b/benchmark/function_args.js
index 3dee09a68..65fd331f1 100644
--- a/benchmark/function_args.js
+++ b/benchmark/function_args.js
@@ -6,6 +6,8 @@ const addonName = path.basename(__filename, '.js');
   .forEach((addonName) => {
     const rootAddon = require(`./build/Release/${addonName}`);
     const implems = Object.keys(rootAddon);
+    const maxNameLength =
+      implems.reduce((soFar, value) => Math.max(soFar, value.length), 0);
     const anObject = {};
 
     console.log(`${addonName}: `);
@@ -13,7 +15,7 @@ const addonName = path.basename(__filename, '.js');
     console.log('no arguments:');
     implems.reduce((suite, implem) => {
       const fn = rootAddon[implem].noArgFunction;
-      return suite.add(implem, () => fn());
+      return suite.add(implem.padStart(maxNameLength, ' '), () => fn());
     }, new Benchmark.Suite)
       .on('cycle', (event) => console.log(String(event.target)))
       .run();
@@ -21,7 +23,7 @@ const addonName = path.basename(__filename, '.js');
     console.log('one argument:');
     implems.reduce((suite, implem) => {
       const fn = rootAddon[implem].oneArgFunction;
-      return suite.add(implem, () => fn('x'));
+      return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x'));
     }, new Benchmark.Suite)
       .on('cycle', (event) => console.log(String(event.target)))
       .run();
@@ -29,7 +31,7 @@ const addonName = path.basename(__filename, '.js');
     console.log('two arguments:');
     implems.reduce((suite, implem) => {
       const fn = rootAddon[implem].twoArgFunction;
-      return suite.add(implem, () => fn('x', 12));
+      return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12));
     }, new Benchmark.Suite)
       .on('cycle', (event) => console.log(String(event.target)))
       .run();
@@ -37,7 +39,8 @@ const addonName = path.basename(__filename, '.js');
     console.log('three arguments:');
     implems.reduce((suite, implem) => {
       const fn = rootAddon[implem].threeArgFunction;
-      return suite.add(implem, () => fn('x', 12, true));
+      return suite.add(implem.padStart(maxNameLength, ' '),
+        () => fn('x', 12, true));
     }, new Benchmark.Suite)
       .on('cycle', (event) => console.log(String(event.target)))
       .run();
@@ -45,7 +48,8 @@ const addonName = path.basename(__filename, '.js');
     console.log('four arguments:');
     implems.reduce((suite, implem) => {
       const fn = rootAddon[implem].fourArgFunction;
-      return suite.add(implem, () => fn('x', 12, true, anObject));
+      return suite.add(implem.padStart(maxNameLength, ' '),
+        () => fn('x', 12, true, anObject));
     }, new Benchmark.Suite)
       .on('cycle', (event) => console.log(String(event.target)))
       .run();
diff --git a/benchmark/property_descriptor.js b/benchmark/property_descriptor.js
index cab510601..f0c02ea08 100644
--- a/benchmark/property_descriptor.js
+++ b/benchmark/property_descriptor.js
@@ -7,14 +7,16 @@ const addonName = path.basename(__filename, '.js');
     const rootAddon = require(`./build/Release/${addonName}`);
     const getters = new Benchmark.Suite;
     const setters = new Benchmark.Suite;
+    const maxNameLength = Object.keys(rootAddon)
+      .reduce((soFar, value) => Math.max(soFar, value.length), 0);
 
     console.log(`${addonName}: `);
 
     Object.keys(rootAddon).forEach((key) => {
-      getters.add(`${key} getter`, () => {
+      getters.add(`${key} getter`.padStart(maxNameLength + 7), () => {
         const x = rootAddon[key];
       });
-      setters.add(`${key} setter`, () => {
+      setters.add(`${key} setter`.padStart(maxNameLength + 7), () => {
         rootAddon[key] = 5;
       })
     });
diff --git a/doc/addon.md b/doc/addon.md
new file mode 100644
index 000000000..3d34efd92
--- /dev/null
+++ b/doc/addon.md
@@ -0,0 +1,476 @@
+# Add-on Structure
+
+Creating add-ons that work correctly when loaded multiple times from the same
+source package into multiple Node.js threads and/or multiple times into the same
+Node.js thread requires that all global data they hold be associated with the
+environment in which they run. It is not safe to store global data in static
+variables because doing so does not take into account the fact that an add-on
+may be loaded into multiple threads nor that an addon may be loaded multiple
+times into a single thread.
+
+The `Napi::Addon` class can be used to define an entire add-on. Instances of
+`Napi::Addon` subclasses become instances of the add-on, safely loaded by
+Node.js on its various threads and into its various contexts.
+
+The `Napi::Addon` class can be used together with the `NODE_API_ADDON()` and
+`NODE_API_NAMED_ADDON()` macros to define add-ons.
+
+## Example
+
+```cpp
+#include <napi.h>
+
+class ExampleAddon : public Napi::Addon<ExampleAddon> {
+ public:
+  ExampleAddon(Napi::Env env, Napi::Object exports) {
+    // In the constructor we declare the functions the add-on makes avaialable
+    // to JavaScript.
+    DefineAddon(exports, {
+      InstanceMethod("increment", &ExampleAddon::Increment)
+    });
+  }
+ private:
+
+  // This method has access to the data stored in the environment because it is
+  // an instance method of `ExampleAddon` and because it was listed among the
+  // property descriptors passed to `DefineAddon()` in the constructor.
+  Napi::Value Increment(const Napi::CallbackInfo& info) {
+    return Napi::Number::New(info.Env(), value++);
+  }
+
+  // Data stored in these variables is unique to each instance of the add-on.
+  uint32_t value = 42;
+};
+
+// The macro announces that instances of the class `ExampleAddon` will be
+// created for each instance of the add-on that must be loaded into Node.js.
+NODE_API_ADDON(ExampleAddon)
+```
+
+The above code can be used from JavaScript as follows:
+
+```js
+'use strict'
+
+const exampleAddon = require('bindings')('example_addon');
+console.log(exampleAddon.increment()); // prints 42
+console.log(exampleAddon.increment()); // prints 43
+```
+
+When Node.js loads an instance of the add-on, a new instance of the class is
+created. Its constructor receives the environment `Napi::Env env` and the
+exports object `Napi::Object exports`. It can then use the method `DefineAddon`
+to either attach methods, accessors, and/or values to the `exports` object or to
+create its own `exports` object and attach methods, accessors, and/or values to
+it.
+
+Functions created with `Napi::Function::New()`, accessors created with
+`PropertyDescriptor::Accessor()`, and values can also be attached. If their
+implementation requires the `ExampleAddon` instance, it can be retrieved from
+the `Napi::Env env` with `GetInstanceData()`:
+
+```cpp
+void ExampleBinding(const Napi::CallbackInfo& info) {
+  ExampleAddon* addon = info.Env().GetInstanceData<ExampleAddon>();
+}
+```
+
+## Methods
+
+### Constructor
+
+Creates a new instance of the add-on.
+
+```cpp
+Napi::Addon(Napi::Env env, Napi::Object exports);
+```
+
+- `[in] env`: The environment into which the add-on is being loaded.
+- `[in] exports`: The exports object received from JavaScript.
+
+Typically, the constructor calls `DefineAddon()` to attach methods, accessors,
+and/or values to `exports`. The constructor may also create a new object and
+pass it to `DefineAddon()` as its first parameter if it wishes to replace the
+`exports` object as provided by Node.js.
+
+### DefineAddon
+
+Defines an add-on instance with functions, accessors, and/or values.
+
+```cpp
+void Napi::Addon::DefineAddon(Napi::Object exports,
+                   const std::initializer_list<PropertyDescriptor>& properties);
+```
+
+* `[in] exports`: The object to return to Node.js as an instance of the add-on.
+* `[in] properties`: Initializer list of add-on property descriptors describing
+the methods, property accessors, and values that define the add-on. They will be
+set on `exports`.
+See: [`Class property and descriptor`](class_property_descriptor.md).
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(const char* utf8name,
+                            InstanceVoidMethodCallback method,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+void MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(const char* utf8name,
+                            InstanceMethodCallback method,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+Napi::Value MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(Napi::Symbol name,
+                            InstanceVoidMethodCallback method,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] name`: JavaScript symbol that represents the name of the method provided
+by the add-on.
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+void MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(Napi::Symbol name,
+                            InstanceMethodCallback method,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] name`: JavaScript symbol that represents the name of the method provided
+by the add-on.
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+Napi::Value MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+template <InstanceVoidMethodCallback method>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(const char* utf8name,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+void MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+template <InstanceMethodCallback method>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(const char* utf8name,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+Napi::Value MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+template <InstanceVoidMethodCallback method>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(Napi::Symbol name,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] name`: The `Napi::Symbol` object whose value is used to identify the
+instance method for the class.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+void MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceMethod
+
+Creates a property descriptor that represents a method provided by the add-on.
+
+```cpp
+template <InstanceMethodCallback method>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceMethod(Napi::Symbol name,
+                            napi_property_attributes attributes = napi_default,
+                            void* data = nullptr);
+```
+
+- `[in] method`: The native function that represents a method provided by the
+add-on.
+- `[in] name`: The `Napi::Symbol` object whose value is used to identify the
+instance method for the class.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the method when it is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents a method provided
+by the add-on. The method must be of the form
+
+```cpp
+Napi::Value MethodName(const Napi::CallbackInfo& info);
+```
+
+### InstanceAccessor
+
+Creates a property descriptor that represents an instance accessor property
+provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceAccessor(const char* utf8name,
+                             InstanceGetterCallback getter,
+                             InstanceSetterCallback setter,
+                             napi_property_attributes attributes = napi_default,
+                             void* data = nullptr);
+```
+
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] getter`: The native function to call when a get access to the property
+is performed.
+- `[in] setter`: The native function to call when a set access to the property
+is performed.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the getter or the setter when it
+is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance accessor
+property provided by the add-on.
+
+### InstanceAccessor
+
+Creates a property descriptor that represents an instance accessor property
+provided by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceAccessor(Symbol name,
+                             InstanceGetterCallback getter,
+                             InstanceSetterCallback setter,
+                             napi_property_attributes attributes = napi_default,
+                             void* data = nullptr);
+```
+
+- `[in] name`: The `Napi::Symbol` object whose value is used to identify the
+instance accessor.
+- `[in] getter`: The native function to call when a get access to the property of
+a JavaScript class is performed.
+- `[in] setter`: The native function to call when a set access to the property of
+a JavaScript class is performed.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the getter or the setter when it
+is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance accessor
+property provided by the add-on.
+
+### InstanceAccessor
+
+Creates a property descriptor that represents an instance accessor property
+provided by the add-on.
+
+```cpp
+template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceAccessor(const char* utf8name,
+                             napi_property_attributes attributes = napi_default,
+                             void* data = nullptr);
+```
+
+- `[in] getter`: The native function to call when a get access to the property of
+a JavaScript class is performed.
+- `[in] setter`: The native function to call when a set access to the property of
+a JavaScript class is performed.
+- `[in] utf8name`: Null-terminated string that represents the name of the method
+provided by the add-on.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the getter or the setter when it
+is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance accessor
+property provided by the add-on.
+
+### InstanceAccessor
+
+Creates a property descriptor that represents an instance accessor property
+provided by the add-on.
+
+```cpp
+template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceAccessor(Symbol name,
+                             napi_property_attributes attributes = napi_default,
+                             void* data = nullptr);
+```
+
+- `[in] getter`: The native function to call when a get access to the property of
+a JavaScript class is performed.
+- `[in] setter`: The native function to call when a set access to the property of
+a JavaScript class is performed.
+- `[in] name`: The `Napi::Symbol` object whose value is used to identify the
+instance accessor.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+- `[in] data`: User-provided data passed into the getter or the setter when it
+is invoked.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance accessor
+property provided by the add-on.
+
+### InstanceValue
+
+Creates property descriptor that represents an instance value property provided
+by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceValue(const char* utf8name,
+                           Napi::Value value,
+                           napi_property_attributes attributes = napi_default);
+```
+
+- `[in] utf8name`: Null-terminated string that represents the name of the property.
+- `[in] value`: The value that's retrieved by a get access of the property.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance value
+property of an add-on.
+
+### InstanceValue
+
+Creates property descriptor that represents an instance value property provided
+by the add-on.
+
+```cpp
+static Napi::PropertyDescriptor
+Napi::Addon::InstanceValue(Symbol name,
+                           Napi::Value value,
+                           napi_property_attributes attributes = napi_default);
+```
+
+- `[in] name`: The `Napi::Symbol` object whose value is used to identify the
+name of the property.
+- `[in] value`: The value that's retrieved by a get access of the property.
+- `[in] attributes`: The attributes associated with the property. One or more of
+`napi_property_attributes`.
+
+Returns a `Napi::PropertyDescriptor` object that represents an instance value
+property of an add-on.
diff --git a/napi-inl.h b/napi-inl.h
index 649be98ac..b5533f225 100644
--- a/napi-inl.h
+++ b/napi-inl.h
@@ -238,13 +238,28 @@ struct AccessorCallbackData {
 // Module registration
 ////////////////////////////////////////////////////////////////////////////////
 
-#define NODE_API_MODULE(modname, regfunc)                 \
-  napi_value __napi_ ## regfunc(napi_env env,             \
-                                napi_value exports) {     \
-    return Napi::RegisterModule(env, exports, regfunc);   \
-  }                                                       \
+// Register an addon based on an initializer function.
+#define NODE_API_MODULE(modname, regfunc)                    \
+  static napi_value __napi_ ## regfunc(napi_env env,         \
+                                       napi_value exports) { \
+    return Napi::RegisterModule(env, exports, regfunc);      \
+  }                                                          \
   NAPI_MODULE(modname, __napi_ ## regfunc)
 
+// Register an addon based on a subclass of `Addon<T>` with a custom Node.js
+// module name.
+#define NODE_API_NAMED_ADDON(modname, classname)                 \
+  static napi_value __napi_ ## classname(napi_env env,           \
+                                         napi_value exports) {   \
+    return Napi::RegisterModule(env, exports, &classname::Init); \
+  }                                                              \
+  NAPI_MODULE(modname, __napi_ ## classname)
+
+// Register an addon based on a subclass of `Addon<T>` with the Node.js module
+// name given by node-gyp from the `target_name` in binding.gyp.
+#define NODE_API_ADDON(classname) \
+  NODE_API_NAMED_ADDON(NODE_GYP_MODULE_NAME, classname)
+
 // Adapt the NAPI_MODULE registration function:
 //  - Wrap the arguments in NAPI wrappers.
 //  - Catch any NAPI errors and rethrow as JS exceptions.
@@ -1199,20 +1214,27 @@ inline Array Object::GetPropertyNames() const {
 }
 
 inline void Object::DefineProperty(const PropertyDescriptor& property) {
-  napi_status status = napi_define_properties(_env, _value, 1,
-    reinterpret_cast<const napi_property_descriptor*>(&property));
-  NAPI_THROW_IF_FAILED_VOID(_env, status);
+  DefineProperties(1,
+                  reinterpret_cast<const napi_property_descriptor*>(&property));
 }
 
 inline void Object::DefineProperties(const std::initializer_list<PropertyDescriptor>& properties) {
-  napi_status status = napi_define_properties(_env, _value, properties.size(),
-    reinterpret_cast<const napi_property_descriptor*>(properties.begin()));
-  NAPI_THROW_IF_FAILED_VOID(_env, status);
+  DefineProperties(properties.size(),
+         reinterpret_cast<const napi_property_descriptor*>(properties.begin()));
 }
 
 inline void Object::DefineProperties(const std::vector<PropertyDescriptor>& properties) {
-  napi_status status = napi_define_properties(_env, _value, properties.size(),
-    reinterpret_cast<const napi_property_descriptor*>(properties.data()));
+  DefineProperties(properties.size(),
+          reinterpret_cast<const napi_property_descriptor*>(properties.data()));
+}
+
+inline void
+Object::DefineProperties(size_t prop_count,
+                         const napi_property_descriptor* properties) {
+  napi_status status = napi_define_properties(_env,
+                                              _value,
+                                              prop_count,
+                                              properties);
   NAPI_THROW_IF_FAILED_VOID(_env, status);
 }
 
@@ -3143,419 +3165,551 @@ inline PropertyDescriptor::operator const napi_property_descriptor&() const {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
-// ObjectWrap<T> class
+// InstanceWrap<T> class
 ////////////////////////////////////////////////////////////////////////////////
 
 template <typename T>
-inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
-  napi_env env = callbackInfo.Env();
-  napi_value wrapper = callbackInfo.This();
-  napi_status status;
-  napi_ref ref;
-  T* instance = static_cast<T*>(this);
-  status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
-  NAPI_THROW_IF_FAILED_VOID(env, status);
-
-  Reference<Object>* instanceRef = instance;
-  *instanceRef = Reference<Object>(env, ref);
-}
-
-template <typename T>
-inline ObjectWrap<T>::~ObjectWrap() {
-  // If the JS object still exists at this point, remove the finalizer added
-  // through `napi_wrap()`.
-  if (!IsEmpty()) {
-    Object object = Value();
-    // It is not valid to call `napi_remove_wrap()` with an empty `object`.
-    // This happens e.g. during garbage collection.
-    if (!object.IsEmpty() && _construction_failed) {
-      napi_remove_wrap(Env(), object, nullptr);
-    }
-  }
-}
-
-template<typename T>
-inline T* ObjectWrap<T>::Unwrap(Object wrapper) {
-  T* unwrapped;
-  napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast<void**>(&unwrapped));
-  NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr);
-  return unwrapped;
-}
-
-template <typename T>
-inline Function
-ObjectWrap<T>::DefineClass(Napi::Env env,
-                           const char* utf8name,
-                           const size_t props_count,
-                           const napi_property_descriptor* descriptors,
-                           void* data) {
+inline void InstanceWrap<T>::AttachPropData(napi_env env,
+                                       napi_value value,
+                                       const napi_property_descriptor* prop) {
   napi_status status;
-  std::vector<napi_property_descriptor> props(props_count);
-
-  // We copy the descriptors to a local array because before defining the class
-  // we must replace static method property descriptors with value property
-  // descriptors such that the value is a function-valued `napi_value` created
-  // with `CreateFunction()`.
-  //
-  // This replacement could be made for instance methods as well, but V8 aborts
-  // if we do that, because it expects methods defined on the prototype template
-  // to have `FunctionTemplate`s.
-  for (size_t index = 0; index < props_count; index++) {
-    props[index] = descriptors[index];
-    napi_property_descriptor* prop = &props[index];
-    if (prop->method == T::StaticMethodCallbackWrapper) {
-      status = CreateFunction(env,
-               utf8name,
-               prop->method,
-               static_cast<StaticMethodCallbackData*>(prop->data),
-               &(prop->value));
-      NAPI_THROW_IF_FAILED(env, status, Function());
-      prop->method = nullptr;
-      prop->data = nullptr;
-    } else if (prop->method == T::StaticVoidMethodCallbackWrapper) {
-      status = CreateFunction(env,
-               utf8name,
-               prop->method,
-               static_cast<StaticVoidMethodCallbackData*>(prop->data),
-               &(prop->value));
-      NAPI_THROW_IF_FAILED(env, status, Function());
-      prop->method = nullptr;
-      prop->data = nullptr;
-    }
-  }
-
-  napi_value value;
-  status = napi_define_class(env,
-                             utf8name,
-                             NAPI_AUTO_LENGTH,
-                             T::ConstructorCallbackWrapper,
-                             data,
-                             props_count,
-                             props.data(),
-                             &value);
-  NAPI_THROW_IF_FAILED(env, status, Function());
-
-  // After defining the class we iterate once more over the property descriptors
-  // and attach the data associated with accessors and instance methods to the
-  // newly created JavaScript class.
-  for (size_t idx = 0; idx < props_count; idx++) {
-    const napi_property_descriptor* prop = &props[idx];
-
-    if (prop->getter == T::StaticGetterCallbackWrapper ||
-        prop->setter == T::StaticSetterCallbackWrapper) {
+  if (prop->method != nullptr && !(prop->attributes & napi_static)) {
+    if (prop->method == T::InstanceVoidMethodCallbackWrapper) {
       status = Napi::details::AttachData(env,
-                          value,
-                          static_cast<StaticAccessorCallbackData*>(prop->data));
-      NAPI_THROW_IF_FAILED(env, status, Function());
+                    value,
+                    static_cast<InstanceVoidMethodCallbackData*>(prop->data));
+      NAPI_THROW_IF_FAILED_VOID(env, status);
+    } else if (prop->method == T::InstanceMethodCallbackWrapper) {
+      status = Napi::details::AttachData(env,
+                        value,
+                        static_cast<InstanceMethodCallbackData*>(prop->data));
+      NAPI_THROW_IF_FAILED_VOID(env, status);
     } else if (prop->getter == T::InstanceGetterCallbackWrapper ||
         prop->setter == T::InstanceSetterCallbackWrapper) {
       status = Napi::details::AttachData(env,
                           value,
                           static_cast<InstanceAccessorCallbackData*>(prop->data));
-      NAPI_THROW_IF_FAILED(env, status, Function());
-    } else if (prop->method != nullptr && !(prop->attributes & napi_static)) {
-      if (prop->method == T::InstanceVoidMethodCallbackWrapper) {
-        status = Napi::details::AttachData(env,
-                      value,
-                      static_cast<InstanceVoidMethodCallbackData*>(prop->data));
-        NAPI_THROW_IF_FAILED(env, status, Function());
-      } else if (prop->method == T::InstanceMethodCallbackWrapper) {
-        status = Napi::details::AttachData(env,
-                          value,
-                          static_cast<InstanceMethodCallbackData*>(prop->data));
-        NAPI_THROW_IF_FAILED(env, status, Function());
-      }
+      NAPI_THROW_IF_FAILED_VOID(env, status);
     }
   }
-
-  return Function(env, value);
-}
-
-template <typename T>
-inline Function ObjectWrap<T>::DefineClass(
-    Napi::Env env,
-    const char* utf8name,
-    const std::initializer_list<ClassPropertyDescriptor<T>>& properties,
-    void* data) {
-  return DefineClass(env,
-          utf8name,
-          properties.size(),
-          reinterpret_cast<const napi_property_descriptor*>(properties.begin()),
-          data);
-}
-
-template <typename T>
-inline Function ObjectWrap<T>::DefineClass(
-    Napi::Env env,
-    const char* utf8name,
-    const std::vector<ClassPropertyDescriptor<T>>& properties,
-    void* data) {
-  return DefineClass(env,
-           utf8name,
-           properties.size(),
-           reinterpret_cast<const napi_property_descriptor*>(properties.data()),
-           data);
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     const char* utf8name,
-    StaticVoidMethodCallback method,
+    InstanceVoidMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data });
+  InstanceVoidMethodCallbackData* callbackData =
+    new InstanceVoidMethodCallbackData({ method, data});
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.method = T::StaticVoidMethodCallbackWrapper;
+  desc.method = T::InstanceVoidMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     const char* utf8name,
-    StaticMethodCallback method,
+    InstanceMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data });
+  InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.method = T::StaticMethodCallbackWrapper;
+  desc.method = T::InstanceMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     Symbol name,
-    StaticVoidMethodCallback method,
+    InstanceVoidMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data });
+  InstanceVoidMethodCallbackData* callbackData =
+    new InstanceVoidMethodCallbackData({ method, data});
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.method = T::StaticVoidMethodCallbackWrapper;
+  desc.method = T::InstanceVoidMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     Symbol name,
-    StaticMethodCallback method,
+    InstanceMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data });
+  InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.method = T::StaticMethodCallbackWrapper;
+  desc.method = T::InstanceMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticVoidMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+template <typename InstanceWrap<T>::InstanceVoidMethodCallback method>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.method = &ObjectWrap<T>::WrappedMethod<method>;
+  desc.method = &InstanceWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticVoidMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
-    Symbol name,
+template <typename InstanceWrap<T>::InstanceMethodCallback method>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
+    const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
-  desc.name = name;
-  desc.method = &ObjectWrap<T>::WrappedMethod<method>;
+  desc.utf8name = utf8name;
+  desc.method = &InstanceWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
-    const char* utf8name,
+template <typename InstanceWrap<T>::InstanceVoidMethodCallback method>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
+    Symbol name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
-  desc.utf8name = utf8name;
-  desc.method = &ObjectWrap<T>::WrappedMethod<method>;
+  desc.name = name;
+  desc.method = &InstanceWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+template <typename InstanceWrap<T>::InstanceMethodCallback method>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceMethod(
     Symbol name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.method = &ObjectWrap<T>::WrappedMethod<method>;
+  desc.method = &InstanceWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceAccessor(
     const char* utf8name,
-    StaticGetterCallback getter,
-    StaticSetterCallback setter,
+    InstanceGetterCallback getter,
+    InstanceSetterCallback setter,
     napi_property_attributes attributes,
     void* data) {
-  StaticAccessorCallbackData* callbackData =
-    new StaticAccessorCallbackData({ getter, setter, data });
+  InstanceAccessorCallbackData* callbackData =
+    new InstanceAccessorCallbackData({ getter, setter, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr;
-  desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr;
+  desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr;
+  desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceAccessor(
     Symbol name,
-    StaticGetterCallback getter,
-    StaticSetterCallback setter,
+    InstanceGetterCallback getter,
+    InstanceSetterCallback setter,
     napi_property_attributes attributes,
     void* data) {
-  StaticAccessorCallbackData* callbackData =
-    new StaticAccessorCallbackData({ getter, setter, data });
+  InstanceAccessorCallbackData* callbackData =
+    new InstanceAccessorCallbackData({ getter, setter, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr;
-  desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr;
+  desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr;
+  desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr;
   desc.data = callbackData;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticGetterCallback getter,
-          typename ObjectWrap<T>::StaticSetterCallback setter>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
+template <typename InstanceWrap<T>::InstanceGetterCallback getter,
+          typename InstanceWrap<T>::InstanceSetterCallback setter>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceAccessor(
     const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.getter = This::WrapStaticGetter(This::StaticGetterTag<getter>());
-  desc.setter = This::WrapStaticSetter(This::StaticSetterTag<setter>());
+  desc.getter = This::WrapGetter(This::GetterTag<getter>());
+  desc.setter = This::WrapSetter(This::SetterTag<setter>());
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::StaticGetterCallback getter,
-          typename ObjectWrap<T>::StaticSetterCallback setter>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
+template <typename InstanceWrap<T>::InstanceGetterCallback getter,
+          typename InstanceWrap<T>::InstanceSetterCallback setter>
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceAccessor(
     Symbol name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.getter = This::WrapStaticGetter(This::StaticGetterTag<getter>());
-  desc.setter = This::WrapStaticSetter(This::StaticSetterTag<setter>());
+  desc.getter = This::WrapGetter(This::GetterTag<getter>());
+  desc.setter = This::WrapSetter(This::SetterTag<setter>());
   desc.data = data;
-  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceValue(
     const char* utf8name,
-    InstanceVoidMethodCallback method,
-    napi_property_attributes attributes,
-    void* data) {
-  InstanceVoidMethodCallbackData* callbackData =
-    new InstanceVoidMethodCallbackData({ method, data});
-
+    Napi::Value value,
+    napi_property_attributes attributes) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.method = T::InstanceVoidMethodCallbackWrapper;
-  desc.data = callbackData;
+  desc.value = value;
   desc.attributes = attributes;
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+inline ClassPropertyDescriptor<T> InstanceWrap<T>::InstanceValue(
+    Symbol name,
+    Napi::Value value,
+    napi_property_attributes attributes) {
+  napi_property_descriptor desc = napi_property_descriptor();
+  desc.name = name;
+  desc.value = value;
+  desc.attributes = attributes;
+  return desc;
+}
+
+template <typename T>
+inline napi_value InstanceWrap<T>::InstanceVoidMethodCallbackWrapper(
+    napi_env env,
+    napi_callback_info info) {
+  return details::WrapCallback([&] {
+    CallbackInfo callbackInfo(env, info);
+    InstanceVoidMethodCallbackData* callbackData =
+      reinterpret_cast<InstanceVoidMethodCallbackData*>(callbackInfo.Data());
+    callbackInfo.SetData(callbackData->data);
+    T* instance = T::Unwrap(callbackInfo.This().As<Object>());
+    auto cb = callbackData->callback;
+    (instance->*cb)(callbackInfo);
+    return nullptr;
+  });
+}
+
+template <typename T>
+inline napi_value InstanceWrap<T>::InstanceMethodCallbackWrapper(
+    napi_env env,
+    napi_callback_info info) {
+  return details::WrapCallback([&] {
+    CallbackInfo callbackInfo(env, info);
+    InstanceMethodCallbackData* callbackData =
+      reinterpret_cast<InstanceMethodCallbackData*>(callbackInfo.Data());
+    callbackInfo.SetData(callbackData->data);
+    T* instance = T::Unwrap(callbackInfo.This().As<Object>());
+    auto cb = callbackData->callback;
+    return (instance->*cb)(callbackInfo);
+  });
+}
+
+template <typename T>
+inline napi_value InstanceWrap<T>::InstanceGetterCallbackWrapper(
+    napi_env env,
+    napi_callback_info info) {
+  return details::WrapCallback([&] {
+    CallbackInfo callbackInfo(env, info);
+    InstanceAccessorCallbackData* callbackData =
+      reinterpret_cast<InstanceAccessorCallbackData*>(callbackInfo.Data());
+    callbackInfo.SetData(callbackData->data);
+    T* instance = T::Unwrap(callbackInfo.This().As<Object>());
+    auto cb = callbackData->getterCallback;
+    return (instance->*cb)(callbackInfo);
+  });
+}
+
+template <typename T>
+inline napi_value InstanceWrap<T>::InstanceSetterCallbackWrapper(
+    napi_env env,
+    napi_callback_info info) {
+  return details::WrapCallback([&] {
+    CallbackInfo callbackInfo(env, info);
+    InstanceAccessorCallbackData* callbackData =
+      reinterpret_cast<InstanceAccessorCallbackData*>(callbackInfo.Data());
+    callbackInfo.SetData(callbackData->data);
+    T* instance = T::Unwrap(callbackInfo.This().As<Object>());
+    auto cb = callbackData->setterCallback;
+    (instance->*cb)(callbackInfo, callbackInfo[0]);
+    return nullptr;
+  });
+}
+
+template <typename T>
+template <typename InstanceWrap<T>::InstanceVoidMethodCallback method>
+inline napi_value InstanceWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
+  return details::WrapCallback([&] {
+    const CallbackInfo cbInfo(env, info);
+    T* instance = T::Unwrap(cbInfo.This().As<Object>());
+    (instance->*method)(cbInfo);
+    return nullptr;
+  });
+}
+
+template <typename T>
+template <typename InstanceWrap<T>::InstanceMethodCallback method>
+inline napi_value InstanceWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
+  return details::WrapCallback([&] {
+    const CallbackInfo cbInfo(env, info);
+    T* instance = T::Unwrap(cbInfo.This().As<Object>());
+    return (instance->*method)(cbInfo);
+  });
+}
+
+template <typename T>
+template <typename InstanceWrap<T>::InstanceSetterCallback method>
+inline napi_value InstanceWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
+  return details::WrapCallback([&] {
+    const CallbackInfo cbInfo(env, info);
+    T* instance = T::Unwrap(cbInfo.This().As<Object>());
+    (instance->*method)(cbInfo, cbInfo[0]);
+    return nullptr;
+  });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ObjectWrap<T> class
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+inline ObjectWrap<T>::ObjectWrap(const Napi::CallbackInfo& callbackInfo) {
+  napi_env env = callbackInfo.Env();
+  napi_value wrapper = callbackInfo.This();
+  napi_status status;
+  napi_ref ref;
+  T* instance = static_cast<T*>(this);
+  status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref);
+  NAPI_THROW_IF_FAILED_VOID(env, status);
+
+  Reference<Object>* instanceRef = instance;
+  *instanceRef = Reference<Object>(env, ref);
+}
+
+template <typename T>
+inline ObjectWrap<T>::~ObjectWrap() {
+  // If the JS object still exists at this point, remove the finalizer added
+  // through `napi_wrap()`.
+  if (!IsEmpty()) {
+    Object object = Value();
+    // It is not valid to call `napi_remove_wrap()` with an empty `object`.
+    // This happens e.g. during garbage collection.
+    if (!object.IsEmpty() && _construction_failed) {
+      napi_remove_wrap(Env(), object, nullptr);
+    }
+  }
+}
+
+template<typename T>
+inline T* ObjectWrap<T>::Unwrap(Object wrapper) {
+  T* unwrapped;
+  napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast<void**>(&unwrapped));
+  NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr);
+  return unwrapped;
+}
+
+template <typename T>
+inline Function
+ObjectWrap<T>::DefineClass(Napi::Env env,
+                           const char* utf8name,
+                           const size_t props_count,
+                           const napi_property_descriptor* descriptors,
+                           void* data) {
+  napi_status status;
+  std::vector<napi_property_descriptor> props(props_count);
+
+  // We copy the descriptors to a local array because before defining the class
+  // we must replace static method property descriptors with value property
+  // descriptors such that the value is a function-valued `napi_value` created
+  // with `CreateFunction()`.
+  //
+  // This replacement could be made for instance methods as well, but V8 aborts
+  // if we do that, because it expects methods defined on the prototype template
+  // to have `FunctionTemplate`s.
+  for (size_t index = 0; index < props_count; index++) {
+    props[index] = descriptors[index];
+    napi_property_descriptor* prop = &props[index];
+    if (prop->method == T::StaticMethodCallbackWrapper) {
+      status = CreateFunction(env,
+                             utf8name,
+                             prop->method,
+                             static_cast<StaticMethodCallbackData*>(prop->data),
+               &(prop->value));
+      NAPI_THROW_IF_FAILED(env, status, Function());
+      prop->method = nullptr;
+      prop->data = nullptr;
+    } else if (prop->method == T::StaticVoidMethodCallbackWrapper) {
+      status = CreateFunction(env,
+                         utf8name,
+                         prop->method,
+                         static_cast<StaticVoidMethodCallbackData*>(prop->data),
+                         &(prop->value));
+      NAPI_THROW_IF_FAILED(env, status, Function());
+      prop->method = nullptr;
+      prop->data = nullptr;
+    }
+  }
+
+  napi_value value;
+  status = napi_define_class(env,
+                             utf8name,
+                             NAPI_AUTO_LENGTH,
+                             T::ConstructorCallbackWrapper,
+                             data,
+                             props_count,
+                             props.data(),
+                             &value);
+  NAPI_THROW_IF_FAILED(env, status, Function());
+
+  // After defining the class we iterate once more over the property descriptors
+  // and attach the data associated with accessors and instance methods to the
+  // newly created JavaScript class.
+  for (size_t idx = 0; idx < props_count; idx++) {
+    const napi_property_descriptor* prop = &props[idx];
+
+    if (prop->getter == T::StaticGetterCallbackWrapper ||
+        prop->setter == T::StaticSetterCallbackWrapper) {
+      status = Napi::details::AttachData(env,
+                          value,
+                          static_cast<StaticAccessorCallbackData*>(prop->data));
+      NAPI_THROW_IF_FAILED(env, status, Function());
+    } else {
+      T::AttachPropData(env, value, prop);
+    }
+  }
+
+  return Function(env, value);
+}
+
+template <typename T>
+inline Function ObjectWrap<T>::DefineClass(
+    Napi::Env env,
     const char* utf8name,
-    InstanceMethodCallback method,
+    const std::initializer_list<ClassPropertyDescriptor<T>>& properties,
+    void* data) {
+  return DefineClass(env,
+          utf8name,
+          properties.size(),
+          reinterpret_cast<const napi_property_descriptor*>(properties.begin()),
+          data);
+}
+
+template <typename T>
+inline Function ObjectWrap<T>::DefineClass(
+    Napi::Env env,
+    const char* utf8name,
+    const std::vector<ClassPropertyDescriptor<T>>& properties,
+    void* data) {
+  return DefineClass(env,
+           utf8name,
+           properties.size(),
+           reinterpret_cast<const napi_property_descriptor*>(properties.data()),
+           data);
+}
+
+template <typename T>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+    const char* utf8name,
+    StaticVoidMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data });
+  StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.method = T::InstanceMethodCallbackWrapper;
+  desc.method = T::StaticVoidMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+    const char* utf8name,
+    StaticMethodCallback method,
+    napi_property_attributes attributes,
+    void* data) {
+  StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data });
+
+  napi_property_descriptor desc = napi_property_descriptor();
+  desc.utf8name = utf8name;
+  desc.method = T::StaticMethodCallbackWrapper;
+  desc.data = callbackData;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
+  return desc;
+}
+
+template <typename T>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
     Symbol name,
-    InstanceVoidMethodCallback method,
+    StaticVoidMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  InstanceVoidMethodCallbackData* callbackData =
-    new InstanceVoidMethodCallbackData({ method, data});
+  StaticVoidMethodCallbackData* callbackData = new StaticVoidMethodCallbackData({ method, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.method = T::InstanceVoidMethodCallbackWrapper;
+  desc.method = T::StaticVoidMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
     Symbol name,
-    InstanceMethodCallback method,
+    StaticMethodCallback method,
     napi_property_attributes attributes,
     void* data) {
-  InstanceMethodCallbackData* callbackData = new InstanceMethodCallbackData({ method, data });
+  StaticMethodCallbackData* callbackData = new StaticMethodCallbackData({ method, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.method = T::InstanceMethodCallbackWrapper;
+  desc.method = T::StaticMethodCallbackWrapper;
   desc.data = callbackData;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceVoidMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+template <typename ObjectWrap<T>::StaticVoidMethodCallback method>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
     const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
@@ -3563,41 +3717,41 @@ inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
   desc.utf8name = utf8name;
   desc.method = &ObjectWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
-    const char* utf8name,
+template <typename ObjectWrap<T>::StaticVoidMethodCallback method>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+    Symbol name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
-  desc.utf8name = utf8name;
+  desc.name = name;
   desc.method = &ObjectWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceVoidMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
-    Symbol name,
+template <typename ObjectWrap<T>::StaticMethodCallback method>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
+    const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
-  desc.name = name;
+  desc.utf8name = utf8name;
   desc.method = &ObjectWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceMethodCallback method>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
+template <typename ObjectWrap<T>::StaticMethodCallback method>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticMethod(
     Symbol name,
     napi_property_attributes attributes,
     void* data) {
@@ -3605,77 +3759,77 @@ inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceMethod(
   desc.name = name;
   desc.method = &ObjectWrap<T>::WrappedMethod<method>;
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceAccessor(
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
     const char* utf8name,
-    InstanceGetterCallback getter,
-    InstanceSetterCallback setter,
+    StaticGetterCallback getter,
+    StaticSetterCallback setter,
     napi_property_attributes attributes,
     void* data) {
-  InstanceAccessorCallbackData* callbackData =
-    new InstanceAccessorCallbackData({ getter, setter, data });
+  StaticAccessorCallbackData* callbackData =
+    new StaticAccessorCallbackData({ getter, setter, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr;
-  desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr;
+  desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr;
+  desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr;
   desc.data = callbackData;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceAccessor(
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
     Symbol name,
-    InstanceGetterCallback getter,
-    InstanceSetterCallback setter,
+    StaticGetterCallback getter,
+    StaticSetterCallback setter,
     napi_property_attributes attributes,
     void* data) {
-  InstanceAccessorCallbackData* callbackData =
-    new InstanceAccessorCallbackData({ getter, setter, data });
+  StaticAccessorCallbackData* callbackData =
+    new StaticAccessorCallbackData({ getter, setter, data });
 
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.getter = getter != nullptr ? T::InstanceGetterCallbackWrapper : nullptr;
-  desc.setter = setter != nullptr ? T::InstanceSetterCallbackWrapper : nullptr;
+  desc.getter = getter != nullptr ? T::StaticGetterCallbackWrapper : nullptr;
+  desc.setter = setter != nullptr ? T::StaticSetterCallbackWrapper : nullptr;
   desc.data = callbackData;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceGetterCallback getter,
-          typename ObjectWrap<T>::InstanceSetterCallback setter>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceAccessor(
+template <typename ObjectWrap<T>::StaticGetterCallback getter,
+          typename ObjectWrap<T>::StaticSetterCallback setter>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
     const char* utf8name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.utf8name = utf8name;
-  desc.getter = This::WrapGetter(This::GetterTag<getter>());
-  desc.setter = This::WrapSetter(This::SetterTag<setter>());
+  desc.getter = This::WrapStaticGetter(This::StaticGetterTag<getter>());
+  desc.setter = This::WrapStaticSetter(This::StaticSetterTag<setter>());
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
 template <typename T>
-template <typename ObjectWrap<T>::InstanceGetterCallback getter,
-          typename ObjectWrap<T>::InstanceSetterCallback setter>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceAccessor(
+template <typename ObjectWrap<T>::StaticGetterCallback getter,
+          typename ObjectWrap<T>::StaticSetterCallback setter>
+inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticAccessor(
     Symbol name,
     napi_property_attributes attributes,
     void* data) {
   napi_property_descriptor desc = napi_property_descriptor();
   desc.name = name;
-  desc.getter = This::WrapGetter(This::GetterTag<getter>());
-  desc.setter = This::WrapSetter(This::SetterTag<setter>());
+  desc.getter = This::WrapStaticGetter(This::StaticGetterTag<getter>());
+  desc.setter = This::WrapStaticSetter(This::StaticSetterTag<setter>());
   desc.data = data;
-  desc.attributes = attributes;
+  desc.attributes = static_cast<napi_property_attributes>(attributes | napi_static);
   return desc;
 }
 
@@ -3699,30 +3853,6 @@ inline ClassPropertyDescriptor<T> ObjectWrap<T>::StaticValue(Symbol name,
   return desc;
 }
 
-template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceValue(
-    const char* utf8name,
-    Napi::Value value,
-    napi_property_attributes attributes) {
-  napi_property_descriptor desc = napi_property_descriptor();
-  desc.utf8name = utf8name;
-  desc.value = value;
-  desc.attributes = attributes;
-  return desc;
-}
-
-template <typename T>
-inline ClassPropertyDescriptor<T> ObjectWrap<T>::InstanceValue(
-    Symbol name,
-    Napi::Value value,
-    napi_property_attributes attributes) {
-  napi_property_descriptor desc = napi_property_descriptor();
-  desc.name = name;
-  desc.value = value;
-  desc.attributes = attributes;
-  return desc;
-}
-
 template <typename T>
 inline void ObjectWrap<T>::Finalize(Napi::Env /*env*/) {}
 
@@ -3815,68 +3945,6 @@ inline napi_value ObjectWrap<T>::StaticSetterCallbackWrapper(
   });
 }
 
-template <typename T>
-inline napi_value ObjectWrap<T>::InstanceVoidMethodCallbackWrapper(
-    napi_env env,
-    napi_callback_info info) {
-  return details::WrapCallback([&] {
-    CallbackInfo callbackInfo(env, info);
-    InstanceVoidMethodCallbackData* callbackData =
-      reinterpret_cast<InstanceVoidMethodCallbackData*>(callbackInfo.Data());
-    callbackInfo.SetData(callbackData->data);
-    T* instance = Unwrap(callbackInfo.This().As<Object>());
-    auto cb = callbackData->callback;
-    (instance->*cb)(callbackInfo);
-    return nullptr;
-  });
-}
-
-template <typename T>
-inline napi_value ObjectWrap<T>::InstanceMethodCallbackWrapper(
-    napi_env env,
-    napi_callback_info info) {
-  return details::WrapCallback([&] {
-    CallbackInfo callbackInfo(env, info);
-    InstanceMethodCallbackData* callbackData =
-      reinterpret_cast<InstanceMethodCallbackData*>(callbackInfo.Data());
-    callbackInfo.SetData(callbackData->data);
-    T* instance = Unwrap(callbackInfo.This().As<Object>());
-    auto cb = callbackData->callback;
-    return (instance->*cb)(callbackInfo);
-  });
-}
-
-template <typename T>
-inline napi_value ObjectWrap<T>::InstanceGetterCallbackWrapper(
-    napi_env env,
-    napi_callback_info info) {
-  return details::WrapCallback([&] {
-    CallbackInfo callbackInfo(env, info);
-    InstanceAccessorCallbackData* callbackData =
-      reinterpret_cast<InstanceAccessorCallbackData*>(callbackInfo.Data());
-    callbackInfo.SetData(callbackData->data);
-    T* instance = Unwrap(callbackInfo.This().As<Object>());
-    auto cb = callbackData->getterCallback;
-    return (instance->*cb)(callbackInfo);
-  });
-}
-
-template <typename T>
-inline napi_value ObjectWrap<T>::InstanceSetterCallbackWrapper(
-    napi_env env,
-    napi_callback_info info) {
-  return details::WrapCallback([&] {
-    CallbackInfo callbackInfo(env, info);
-    InstanceAccessorCallbackData* callbackData =
-      reinterpret_cast<InstanceAccessorCallbackData*>(callbackInfo.Data());
-    callbackInfo.SetData(callbackData->data);
-    T* instance = Unwrap(callbackInfo.This().As<Object>());
-    auto cb = callbackData->setterCallback;
-    (instance->*cb)(callbackInfo, callbackInfo[0]);
-    return nullptr;
-  });
-}
-
 template <typename T>
 inline void ObjectWrap<T>::FinalizeCallback(napi_env env, void* data, void* /*hint*/) {
   T* instance = static_cast<T*>(data);
@@ -3901,27 +3969,6 @@ inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info
   });
 }
 
-template <typename T>
-template <typename ObjectWrap<T>::InstanceVoidMethodCallback method>
-inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
-  return details::WrapCallback([&] {
-    const CallbackInfo cbInfo(env, info);
-    T* instance = Unwrap(cbInfo.This().As<Object>());
-    (instance->*method)(cbInfo);
-    return nullptr;
-  });
-}
-
-template <typename T>
-template <typename ObjectWrap<T>::InstanceMethodCallback method>
-inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
-  return details::WrapCallback([&] {
-    const CallbackInfo cbInfo(env, info);
-    T* instance = Unwrap(cbInfo.This().As<Object>());
-    return (instance->*method)(cbInfo);
-  });
-}
-
 template <typename T>
 template <typename ObjectWrap<T>::StaticSetterCallback method>
 inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
@@ -3932,17 +3979,6 @@ inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info
   });
 }
 
-template <typename T>
-template <typename ObjectWrap<T>::InstanceSetterCallback method>
-inline napi_value ObjectWrap<T>::WrappedMethod(napi_env env, napi_callback_info info) noexcept {
-  return details::WrapCallback([&] {
-    const CallbackInfo cbInfo(env, info);
-    T* instance = Unwrap(cbInfo.This().As<Object>());
-    (instance->*method)(cbInfo, cbInfo[0]);
-    return nullptr;
-  });
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // HandleScope class
 ////////////////////////////////////////////////////////////////////////////////
@@ -5000,6 +5036,37 @@ inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) {
   return result;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Addon<T> class
+////////////////////////////////////////////////////////////////////////////////
+
+template <typename T>
+inline Addon<T>::Addon() {}
+
+template <typename T>
+inline Object Addon<T>::Init(Env env, Object exports) {
+  T* addon = new T(env, exports);
+  env.SetInstanceData(addon);
+  return addon->entry_point_;
+}
+
+template <typename T>
+inline T* Addon<T>::Unwrap(Object wrapper) {
+  return wrapper.Env().GetInstanceData<T>();
+}
+
+template <typename T>
+inline void
+Addon<T>::DefineAddon(Object exports,
+                      const std::initializer_list<AddonProp>& props) {
+    entry_point_ = exports;
+    const napi_property_descriptor* properties =
+      reinterpret_cast<const napi_property_descriptor*>(props.begin());
+    exports.DefineProperties(props.size(), properties);
+    for (size_t idx = 0; idx < props.size(); idx++)
+      T::AttachPropData(exports.Env(), exports, &properties[idx]);
+}
+
 } // namespace Napi
 
 #endif // SRC_NAPI_INL_H_
diff --git a/napi.h b/napi.h
index de4d82f36..44b734bbb 100644
--- a/napi.h
+++ b/napi.h
@@ -728,6 +728,12 @@ namespace Napi {
         ///< Vector of descriptors for the properties to be defined
     );
 
+    /// Defines properties on the object.
+    void DefineProperties(
+      size_t prop_count,
+      const napi_property_descriptor* properties
+    );
+
     /// Checks if an object is an instance created by a constructor function.
     ///
     /// This is equivalent to the JavaScript `instanceof` operator.
@@ -1647,6 +1653,121 @@ namespace Napi {
     napi_property_descriptor _desc;
   };
 
+  template <typename T>
+  class InstanceWrap {
+   public:
+
+    typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info);
+    typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info);
+    typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info);
+    typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value);
+
+    typedef ClassPropertyDescriptor<T> PropertyDescriptor;
+
+    static PropertyDescriptor InstanceMethod(const char* utf8name,
+                                             InstanceVoidMethodCallback method,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    static PropertyDescriptor InstanceMethod(const char* utf8name,
+                                             InstanceMethodCallback method,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    static PropertyDescriptor InstanceMethod(Symbol name,
+                                             InstanceVoidMethodCallback method,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    static PropertyDescriptor InstanceMethod(Symbol name,
+                                             InstanceMethodCallback method,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    template <InstanceVoidMethodCallback method>
+    static PropertyDescriptor InstanceMethod(const char* utf8name,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    template <InstanceMethodCallback method>
+    static PropertyDescriptor InstanceMethod(const char* utf8name,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    template <InstanceVoidMethodCallback method>
+    static PropertyDescriptor InstanceMethod(Symbol name,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    template <InstanceMethodCallback method>
+    static PropertyDescriptor InstanceMethod(Symbol name,
+                                             napi_property_attributes attributes = napi_default,
+                                             void* data = nullptr);
+    static PropertyDescriptor InstanceAccessor(const char* utf8name,
+                                               InstanceGetterCallback getter,
+                                               InstanceSetterCallback setter,
+                                               napi_property_attributes attributes = napi_default,
+                                               void* data = nullptr);
+    static PropertyDescriptor InstanceAccessor(Symbol name,
+                                               InstanceGetterCallback getter,
+                                               InstanceSetterCallback setter,
+                                               napi_property_attributes attributes = napi_default,
+                                               void* data = nullptr);
+    template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
+    static PropertyDescriptor InstanceAccessor(const char* utf8name,
+                                               napi_property_attributes attributes = napi_default,
+                                               void* data = nullptr);
+    template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
+    static PropertyDescriptor InstanceAccessor(Symbol name,
+                                               napi_property_attributes attributes = napi_default,
+                                               void* data = nullptr);
+    static PropertyDescriptor InstanceValue(const char* utf8name,
+                                            Napi::Value value,
+                                            napi_property_attributes attributes = napi_default);
+    static PropertyDescriptor InstanceValue(Symbol name,
+                                            Napi::Value value,
+                                            napi_property_attributes attributes = napi_default);
+
+   protected:
+    static void AttachPropData(napi_env env, napi_value value, const napi_property_descriptor* prop);
+
+   private:
+    using This = InstanceWrap<T>;
+
+    template <typename TCallback>
+    struct MethodCallbackData {
+      TCallback callback;
+      void* data;
+    };
+
+    template <typename TGetterCallback, typename TSetterCallback>
+    struct AccessorCallbackData {
+      TGetterCallback getterCallback;
+      TSetterCallback setterCallback;
+      void* data;
+    };
+
+    typedef MethodCallbackData<InstanceVoidMethodCallback> InstanceVoidMethodCallbackData;
+    typedef MethodCallbackData<InstanceMethodCallback> InstanceMethodCallbackData;
+    typedef AccessorCallbackData<InstanceGetterCallback,
+                                 InstanceSetterCallback> InstanceAccessorCallbackData;
+
+    static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info);
+    static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info);
+    static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info);
+    static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info);
+
+    template <InstanceSetterCallback method>
+    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
+
+    template <InstanceGetterCallback getter> struct GetterTag {};
+    template <InstanceSetterCallback setter> struct SetterTag {};
+
+    template <InstanceVoidMethodCallback method>
+    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
+    template <InstanceMethodCallback method>
+    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
+    template <InstanceGetterCallback getter>
+    static napi_callback WrapGetter(GetterTag<getter>) noexcept { return &This::WrappedMethod<getter>; }
+    static napi_callback WrapGetter(GetterTag<nullptr>) noexcept { return nullptr; }
+    template <InstanceSetterCallback setter>
+    static napi_callback WrapSetter(SetterTag<setter>) noexcept { return &This::WrappedMethod<setter>; }
+    static napi_callback WrapSetter(SetterTag<nullptr>) noexcept { return nullptr; }
+  };
+
   /// Base class to be extended by C++ classes exposed to JavaScript; each C++ class instance gets
   /// "wrapped" by a JavaScript object that is managed by this class.
   ///
@@ -1673,7 +1794,7 @@ namespace Napi {
   ///         Napi::Value DoSomething(const Napi::CallbackInfo& info);
   ///     }
   template <typename T>
-  class ObjectWrap : public Reference<Object> {
+  class ObjectWrap : public InstanceWrap<T>, public Reference<Object> {
   public:
     ObjectWrap(const CallbackInfo& callbackInfo);
     virtual ~ObjectWrap();
@@ -1685,10 +1806,6 @@ namespace Napi {
     typedef Napi::Value (*StaticMethodCallback)(const CallbackInfo& info);
     typedef Napi::Value (*StaticGetterCallback)(const CallbackInfo& info);
     typedef void (*StaticSetterCallback)(const CallbackInfo& info, const Napi::Value& value);
-    typedef void (T::*InstanceVoidMethodCallback)(const CallbackInfo& info);
-    typedef Napi::Value (T::*InstanceMethodCallback)(const CallbackInfo& info);
-    typedef Napi::Value (T::*InstanceGetterCallback)(const CallbackInfo& info);
-    typedef void (T::*InstanceSetterCallback)(const CallbackInfo& info, const Napi::Value& value);
 
     typedef ClassPropertyDescriptor<T> PropertyDescriptor;
 
@@ -1750,68 +1867,12 @@ namespace Napi {
     static PropertyDescriptor StaticAccessor(Symbol name,
                                              napi_property_attributes attributes = napi_default,
                                              void* data = nullptr);
-    static PropertyDescriptor InstanceMethod(const char* utf8name,
-                                             InstanceVoidMethodCallback method,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    static PropertyDescriptor InstanceMethod(const char* utf8name,
-                                             InstanceMethodCallback method,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    static PropertyDescriptor InstanceMethod(Symbol name,
-                                             InstanceVoidMethodCallback method,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    static PropertyDescriptor InstanceMethod(Symbol name,
-                                             InstanceMethodCallback method,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    template <InstanceVoidMethodCallback method>
-    static PropertyDescriptor InstanceMethod(const char* utf8name,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    template <InstanceMethodCallback method>
-    static PropertyDescriptor InstanceMethod(const char* utf8name,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    template <InstanceVoidMethodCallback method>
-    static PropertyDescriptor InstanceMethod(Symbol name,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    template <InstanceMethodCallback method>
-    static PropertyDescriptor InstanceMethod(Symbol name,
-                                             napi_property_attributes attributes = napi_default,
-                                             void* data = nullptr);
-    static PropertyDescriptor InstanceAccessor(const char* utf8name,
-                                               InstanceGetterCallback getter,
-                                               InstanceSetterCallback setter,
-                                               napi_property_attributes attributes = napi_default,
-                                               void* data = nullptr);
-    static PropertyDescriptor InstanceAccessor(Symbol name,
-                                               InstanceGetterCallback getter,
-                                               InstanceSetterCallback setter,
-                                               napi_property_attributes attributes = napi_default,
-                                               void* data = nullptr);
-    template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
-    static PropertyDescriptor InstanceAccessor(const char* utf8name,
-                                               napi_property_attributes attributes = napi_default,
-                                               void* data = nullptr);
-    template <InstanceGetterCallback getter, InstanceSetterCallback setter=nullptr>
-    static PropertyDescriptor InstanceAccessor(Symbol name,
-                                               napi_property_attributes attributes = napi_default,
-                                               void* data = nullptr);
     static PropertyDescriptor StaticValue(const char* utf8name,
                                           Napi::Value value,
                                           napi_property_attributes attributes = napi_default);
     static PropertyDescriptor StaticValue(Symbol name,
                                           Napi::Value value,
                                           napi_property_attributes attributes = napi_default);
-    static PropertyDescriptor InstanceValue(const char* utf8name,
-                                            Napi::Value value,
-                                            napi_property_attributes attributes = napi_default);
-    static PropertyDescriptor InstanceValue(Symbol name,
-                                            Napi::Value value,
-                                            napi_property_attributes attributes = napi_default);
     virtual void Finalize(Napi::Env env);
 
   private:
@@ -1822,10 +1883,6 @@ namespace Napi {
     static napi_value StaticMethodCallbackWrapper(napi_env env, napi_callback_info info);
     static napi_value StaticGetterCallbackWrapper(napi_env env, napi_callback_info info);
     static napi_value StaticSetterCallbackWrapper(napi_env env, napi_callback_info info);
-    static napi_value InstanceVoidMethodCallbackWrapper(napi_env env, napi_callback_info info);
-    static napi_value InstanceMethodCallbackWrapper(napi_env env, napi_callback_info info);
-    static napi_value InstanceGetterCallbackWrapper(napi_env env, napi_callback_info info);
-    static napi_value InstanceSetterCallbackWrapper(napi_env env, napi_callback_info info);
     static void FinalizeCallback(napi_env env, void* data, void* hint);
     static Function DefineClass(Napi::Env env,
                                 const char* utf8name,
@@ -1840,8 +1897,6 @@ namespace Napi {
     };
     typedef MethodCallbackData<StaticVoidMethodCallback> StaticVoidMethodCallbackData;
     typedef MethodCallbackData<StaticMethodCallback> StaticMethodCallbackData;
-    typedef MethodCallbackData<InstanceVoidMethodCallback> InstanceVoidMethodCallbackData;
-    typedef MethodCallbackData<InstanceMethodCallback> InstanceMethodCallbackData;
 
     template <typename TGetterCallback, typename TSetterCallback>
     struct AccessorCallbackData {
@@ -1851,8 +1906,6 @@ namespace Napi {
     };
     typedef AccessorCallbackData<StaticGetterCallback, StaticSetterCallback>
       StaticAccessorCallbackData;
-    typedef AccessorCallbackData<InstanceGetterCallback, InstanceSetterCallback>
-      InstanceAccessorCallbackData;
 
     template <StaticVoidMethodCallback method>
     static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
@@ -1860,22 +1913,11 @@ namespace Napi {
     template <StaticMethodCallback method>
     static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
 
-    template <InstanceVoidMethodCallback method>
-    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
-
-    template <InstanceMethodCallback method>
-    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
-
     template <StaticSetterCallback method>
     static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
 
-    template <InstanceSetterCallback method>
-    static napi_value WrappedMethod(napi_env env, napi_callback_info info) noexcept;
-
     template <StaticGetterCallback   getter> struct StaticGetterTag {};
     template <StaticSetterCallback   setter> struct StaticSetterTag {};
-    template <InstanceGetterCallback getter> struct GetterTag {};
-    template <InstanceSetterCallback setter> struct SetterTag {};
 
     template <StaticGetterCallback getter>
     static napi_callback WrapStaticGetter(StaticGetterTag<getter>) noexcept { return &This::WrappedMethod<getter>; }
@@ -1885,14 +1927,6 @@ namespace Napi {
     static napi_callback WrapStaticSetter(StaticSetterTag<setter>) noexcept { return &This::WrappedMethod<setter>; }
     static napi_callback WrapStaticSetter(StaticSetterTag<nullptr>) noexcept { return nullptr; }
 
-    template <InstanceGetterCallback getter>
-    static napi_callback WrapGetter(GetterTag<getter>) noexcept { return &This::WrappedMethod<getter>; }
-    static napi_callback WrapGetter(GetterTag<nullptr>) noexcept { return nullptr; }
-
-    template <InstanceSetterCallback setter>
-    static napi_callback WrapSetter(SetterTag<setter>) noexcept { return &This::WrappedMethod<setter>; }
-    static napi_callback WrapSetter(SetterTag<nullptr>) noexcept { return nullptr; }
-
     bool _construction_failed = true;
   };
 
@@ -2420,6 +2454,22 @@ namespace Napi {
       static const napi_node_version* GetNodeVersion(Env env);
   };
 
+  template <typename T>
+  class Addon : public InstanceWrap<T> {
+   public:
+    Addon();
+    static inline Object Init(Env env, Object exports);
+    static T* Unwrap(Object wrapper);
+
+   protected:
+    typedef ClassPropertyDescriptor<T> AddonProp;
+    void DefineAddon(Object exports,
+                     const std::initializer_list<AddonProp>& props);
+
+   private:
+    Object entry_point_;
+  };
+
 } // namespace Napi
 
 // Inline implementations of all the above class methods are included here.
diff --git a/test/addon.cc b/test/addon.cc
new file mode 100644
index 000000000..581288213
--- /dev/null
+++ b/test/addon.cc
@@ -0,0 +1,45 @@
+#if (NAPI_VERSION > 5)
+#include <stdio.h>
+#include "napi.h"
+
+namespace {
+
+class TestAddon : public Napi::Addon<TestAddon> {
+ public:
+  inline TestAddon(Napi::Env, Napi::Object exports) {
+    DefineAddon(exports, {
+      InstanceMethod("increment", &TestAddon::Increment),
+      InstanceAccessor<&TestAddon::GetVerbose, &TestAddon::SetVerbose>("verbose")
+    });
+  }
+
+  ~TestAddon() {
+    if (verbose)
+      fprintf(stderr, "TestAddon::~TestAddon\n");
+  }
+
+ private:
+  Napi::Value Increment(const Napi::CallbackInfo& info) {
+    return Napi::Number::New(info.Env(), value++);
+  }
+
+  Napi::Value GetVerbose(const Napi::CallbackInfo& info) {
+    return Napi::Boolean::New(info.Env(), verbose);
+  }
+
+  void SetVerbose(const Napi::CallbackInfo&,
+                  const Napi::Value& newValue) {
+    verbose = newValue.As<Napi::Boolean>();
+  }
+
+  uint32_t value = 42;
+  bool verbose = false;
+};
+
+}  // end of anonymous namespace
+
+Napi::Object InitAddon(Napi::Env env) {
+  return TestAddon::Init(env, Napi::Object::New(env));
+}
+
+#endif  // (NAPI_VERSION > 5)
diff --git a/test/addon.js b/test/addon.js
new file mode 100644
index 000000000..099e0eb9d
--- /dev/null
+++ b/test/addon.js
@@ -0,0 +1,11 @@
+'use strict';
+const buildType = process.config.target_defaults.default_configuration;
+const assert = require('assert');
+
+test(require(`./build/${buildType}/binding.node`));
+test(require(`./build/${buildType}/binding_noexcept.node`));
+
+function test(binding) {
+  assert.strictEqual(binding.addon.increment(), 42);
+  assert.strictEqual(binding.addon.increment(), 43);
+}
diff --git a/test/binding.cc b/test/binding.cc
index 0eb22abbf..ebfe5e5db 100644
--- a/test/binding.cc
+++ b/test/binding.cc
@@ -3,6 +3,7 @@
 using namespace Napi;
 
 #if (NAPI_VERSION > 5)
+Object InitAddon(Env env);
 Object InitAddonData(Env env);
 #endif
 Object InitArrayBuffer(Env env);
@@ -61,6 +62,7 @@ Object InitThunkingManual(Env env);
 
 Object Init(Env env, Object exports) {
 #if (NAPI_VERSION > 5)
+  exports.Set("addon", InitAddon(env));
   exports.Set("addon_data", InitAddonData(env));
 #endif
   exports.Set("arraybuffer", InitArrayBuffer(env));
diff --git a/test/binding.gyp b/test/binding.gyp
index 797d81139..288051633 100644
--- a/test/binding.gyp
+++ b/test/binding.gyp
@@ -2,6 +2,7 @@
   'target_defaults': {
     'includes': ['../common.gypi'],
     'sources': [
+        'addon.cc',
         'addon_data.cc',
         'arraybuffer.cc',
         'asynccontext.cc',
diff --git a/test/index.js b/test/index.js
index e930eab5c..137cad238 100644
--- a/test/index.js
+++ b/test/index.js
@@ -8,6 +8,7 @@ process.config.target_defaults.default_configuration =
 // FIXME: We might need a way to load test modules automatically without
 // explicit declaration as follows.
 let testModules = [
+  'addon',
   'addon_data',
   'arraybuffer',
   'asynccontext',
@@ -83,9 +84,10 @@ if (napiVersion < 5) {
 }
 
 if (napiVersion < 6) {
+  testModules.splice(testModules.indexOf('addon'), 1);
+  testModules.splice(testModules.indexOf('addon_data'), 1);
   testModules.splice(testModules.indexOf('bigint'), 1);
   testModules.splice(testModules.indexOf('typedarray-bigint'), 1);
-  testModules.splice(testModules.indexOf('addon_data'), 1);
 }
 
 if (typeof global.gc === 'function') {