---
title: 'Module Initialization'
description: Run custom setup when your NAPI-RS native module is loaded.
---

# Module Initialization

NAPI-RS provides two APIs for module initialization: `#[napi_derive::module_init]` and `#[napi(module_exports)]`. While they may seem similar, they serve different purposes and execute at different times.

## Execution Timeline

Understanding when each API executes is crucial for using them correctly:

```
┌─────────────────────────────────────────────────────────────────┐
│                    Node.js loads .node file                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  1. #[napi_derive::module_init] runs                            │
│     (via ctor - runs at dynamic library load time)              │
│     Runs once for this native library load                     │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. napi_register_module_v1 called by Node.js                   │
│     - Registers all #[napi] exports (functions, classes, etc.)  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. #[napi(module_exports)] runs                                │
│     Receives the exports object, can customize it               │
│     Runs ONCE per Node.js thread/context                        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. Module is ready for use in JavaScript                       │
└─────────────────────────────────────────────────────────────────┘
```

## `#[napi_derive::module_init]`

This macro marks a function to run when the native module is loaded. It uses
the [`ctor`](https://crates.io/crates/ctor) crate internally to execute before
Node-API registers the JavaScript exports.

### Timing

- Runs at **dynamic library load time** (before `napi_register_module_v1`)
- Executes once for a native library load, not once per Node-API environment;
  workers in the same process normally share that loaded library
- No access to Node.js environment or exports object

### Signature

```rust
#[napi_derive::module_init]
fn init() {
  // initialization code
}
```

The function must have no parameters and no return value.

### When to Use

Use `#[napi_derive::module_init]` for:

- **Setting up async runtimes** (e.g., tokio)
- **Initializing global state** that should be shared across all threads
- **One-time setup** that must happen before any exports are registered
- **Configuring logging or tracing**

### Example: Custom Tokio Runtime

**lib.rs**

```rust
use napi::bindgen_prelude::create_custom_tokio_runtime;

#[napi_derive::module_init]
fn init() {
  let runtime = napi::tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .thread_name("my-native-module")
    .build();
  match runtime {
    Ok(rt) => create_custom_tokio_runtime(rt),
    Err(err) => eprintln!("failed to create custom Tokio runtime: {err}"),
  }
}
```

::: warning
The multi-thread Tokio runtime configured in the example above is
target-dependent. Gate that runtime setup or choose a runtime configuration
supported by your WebAssembly target. The `module_init` macro itself supports
WebAssembly builds.

:::

## `#[napi(module_exports)]`

This macro marks a function that receives the module's `exports` object, allowing you to customize it before the module is returned to JavaScript.

### Timing

- Runs **after** all `#[napi]` exports are registered
- Runs **during** `napi_register_module_v1` (Node.js module registration)
- Executes **once per Node.js context** (main thread + each worker thread)

### Signature

The function can return `()` or `Result<()>`. Each parameter, if present, must
be `Env`, `Object`, or a reference to one of those types. `Object` receives the
module's exports object, while `Env` receives the current Node-API environment.
The usual signatures are:

```rust
// With just the exports object
#[napi(module_exports)]
pub fn init(mut exports: Object) -> Result<()> {
  // customize exports
  Ok(())
}

// With exports and Env
#[napi(module_exports)]
pub fn init(mut exports: Object, env: Env) -> Result<()> {
  // customize exports with access to Env
  Ok(())
}
```

### When to Use

Use `#[napi(module_exports)]` for:

- **Adding custom properties** to the exports object
- **Creating symbols** that should be exported
- **Registering exports programmatically** (not via `#[napi]`)
- **Per-thread initialization** that needs the Node.js environment

### Example: Adding a Symbol

**lib.rs**

```rust
use napi::bindgen_prelude::*;

#[napi(module_exports)]
pub fn init(mut exports: Object) -> Result<()> {
  // Add a unique symbol to exports
  let symbol = Symbol::new("MY_MODULE_SYMBOL");
  exports.set_named_property("MY_SYMBOL", symbol)?;

  // Add a version string
  exports.set_named_property("VERSION", "1.0.0")?;

  Ok(())
}
```

**index.js**

```js
const native = require('./index.node')

console.log(native.MY_SYMBOL) // Symbol(MY_MODULE_SYMBOL)
console.log(native.VERSION) // "1.0.0"
```

## Key Differences

| Aspect                  | `#[napi_derive::module_init]` | `#[napi(module_exports)]`    |
| ----------------------- | ----------------------------- | ---------------------------- |
| **Execution time**      | At `.node` file load          | During module registration   |
| **Runs per**            | Native library/module load    | Node-API environment/context |
| **Receives exports**    | No                            | Yes                          |
| **Can modify exports**  | No                            | Yes                          |
| **Access to Env**       | No                            | Yes (Optional)               |
| **WebAssembly support** | Yes (Via our js binding)      | Yes                          |

## Using Both Together

These APIs are complementary and can be used together:

**lib.rs**

```rust
use napi::bindgen_prelude::*;

// Runs once at module load - setup tokio runtime
#[cfg(not(target_family = "wasm"))]
#[napi_derive::module_init]
fn setup_runtime() {
  let runtime = napi::tokio::runtime::Builder::new_multi_thread()
    .enable_all()
    .build();
  match runtime {
    Ok(rt) => create_custom_tokio_runtime(rt),
    Err(err) => eprintln!("failed to create custom Tokio runtime: {err}"),
  }
}

// Runs per thread - customize exports
#[napi(module_exports)]
pub fn customize_exports(mut exports: Object) -> Result<()> {
  exports.set_named_property("THREAD_SAFE_SYMBOL", Symbol::new("THREAD_SAFE"))?;
  Ok(())
}

// Regular export via #[napi]
#[napi]
pub async fn do_async_work() -> String {
  // This uses the tokio runtime set up in module_init
  napi::tokio::time::sleep(std::time::Duration::from_millis(100)).await;
  "done".to_string()
}
```

The custom-runtime examples require `napi`'s `async` (or `tokio_rt`) feature.
The sleep example additionally requires `tokio_time`:

**Cargo.toml**

```toml
[dependencies]
napi = { version = "3", features = ["async", "tokio_time"] }
napi-derive = "3"
```

## Worker Thread Behavior

When using worker threads in Node.js, the behavior differs between the two APIs:

**main.js**

```js
const { Worker } = require('worker_threads')

// Main thread loads module
const native = require('./index.node')
// -> module_init runs (first time)
// -> module_exports runs (main thread)

// Worker thread loads same module
new Worker(
  `
  const native = require('./index.node')
  // -> module_init does NOT run again (already ran)
  // -> module_exports DOES run again (new thread context)
`,
  { eval: true },
)
```

This means:

- Global resources (like tokio runtime) are initialized once and shared
- Per-thread state can be set up in `module_exports` for each context

::: info
The `#[napi_derive::module_init]` function runs via the `ctor` crate, which uses platform-specific mechanisms (`.init_array` on Unix, special constructor functions on Windows) to execute at dynamic library load time.

:::
