---
title: 'Class'
description: Define and export Rust structs as JavaScript classes with NAPI-RS.
---

# Class

::: tip
There is no concept of a class in Rust. We use `struct` to represent a
JavaScript `Class`.

:::

## Choose the right JavaScript shape

`#[napi]` on a struct creates a JavaScript class with native identity and methods. Other struct attributes create value shapes instead:

| Rust declaration                         | JavaScript representation                        | Use it for                                                 |
| ---------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------- |
| `#[napi] struct`                         | Class instance backed by one Rust value          | Stateful native objects, methods, identity, and references |
| `#[napi(object)] struct`                 | Plain object copied to/from an owned Rust struct | Records, options, and configuration shapes                 |
| `#[napi(transparent)] struct Wrapper(T)` | The inner value `T`                              | Rust newtypes that should not add a JavaScript wrapper     |
| `#[napi(array)]` tuple struct            | JavaScript Array / TypeScript tuple              | Fixed positional data                                      |

See [Type conversions](/docs/concepts/type-conversions) for direction and ownership rules and [`#[napi]` attributes](/docs/concepts/napi-attributes) for the complete shape controls.

## `Constructor`

### Default `constructor`

If all fields in a `Rust` struct are `pub`, then you can use `#[napi(constructor)]` to make the `struct` have a default `constructor`.

**lib.rs**

```rust
#[napi(constructor)]
pub struct AnimalWithDefaultConstructor {
  pub name: String,
  pub kind: u32,
}
```

**index.d.ts**

```ts
export class AnimalWithDefaultConstructor {
  name: string
  kind: number
  constructor(name: string, kind: number)
}
```

Every public field is part of the JavaScript API: napi-rs generates a getter and, unless the field is `#[napi(readonly)]`, a setter. Its Rust type must therefore support the generated JavaScript conversion direction. Keep native-only state private, as `count` is in the custom-constructor example below.

### Custom `constructor`

If you want to define a custom `constructor`, you can use `#[napi(constructor)]` on your constructor `fn` in the struct `impl` block.

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(constructor)]
  pub fn new() -> Self {
    JsQueryEngine { count: 0 }
  }
}
```

**index.d.ts**

```ts
export class QueryEngine {
  constructor()
}
```

::: warning
**NAPI-RS** does not currently support `private constructor`. Your custom
constructor must be `pub` in Rust.

:::

## Factory

Besides `constructor`, you can also define factory methods on `Class` by using `#[napi(factory)]`.

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { count }
  }
}
```

**index.d.ts**

```ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
}
```

::: warning
If no `#[napi(constructor)]` is defined in the `struct`, and you attempt to
create an instance (`new`) of the `Class` in JavaScript, an error will be
thrown.

:::

**test.mjs**

```js {3}
import { QueryEngine } from './index.js'

new QueryEngine() // Error: Class contains no `constructor`, cannot create it!
```

## `class method`

You can define a JavaScript class method with `#[napi]` on a struct method in **Rust**.

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { count }
  }

  /// Class method
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    Ok(format!("{query}: {}", self.count))
  }

  #[napi]
  pub fn status(&self) -> napi::Result<u32> {
    Ok(self.count)
  }
}
```

**index.d.ts**

```ts
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  query(query: string): Promise<string>
  status(): number
}
```

::: warning
`async fn` needs the `napi4` and `tokio_rt` features to be enabled.

:::

::: tip
Any `fn` in `Rust` that returns `Result<T>` will be treated as `T` in JavaScript/TypeScript. If the `Result<T>` is `Err`, a JavaScript Error will be thrown.

:::

## `Getter`

Define [JavaScript class `getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) using `#[napi(getter)]`. The Rust `fn` must be a struct method, not an associated function.

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { count }
  }

  /// Class method
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    Ok(format!("{query}: {}", self.count))
  }

  #[napi(getter)]
  pub fn status(&self) -> napi::Result<u32> {
    Ok(self.count)
  }
}
```

**index.d.ts**

```ts {4}
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
}
```

## `Setter`

Define [JavaScript class `setter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set) using `#[napi(setter)]`. The Rust `fn` must be a struct method, not an associated function.

**lib.rs**

```rust
#[napi(js_name = "QueryEngine")]
pub struct JsQueryEngine {
  count: u32,
}

#[napi]
impl JsQueryEngine {
  #[napi(factory)]
  pub fn with_initial_count(count: u32) -> Self {
    JsQueryEngine { count }
  }

  /// Class method
  #[napi]
  pub async fn query(&self, query: String) -> napi::Result<String> {
    Ok(format!("{query}: {}", self.count))
  }

  #[napi(getter)]
  pub fn status(&self) -> napi::Result<u32> {
    Ok(self.count)
  }

  #[napi(setter)]
  pub fn count(&mut self, count: u32) {
    self.count = count;
  }
}
```

**index.d.ts**

```ts {5}
export class QueryEngine {
  static withInitialCount(count: number): QueryEngine
  constructor()
  get status(): number
  set count(count: number)
}
```

## Class as argument

`Class` is different from [`Object`](./object). The Rust value is wrapped by a JavaScript instance and managed by that environment's garbage collector. Pass the instance back to Rust as `&T` for shared access or `&mut T` for mutable access; the value is not cloned from a plain object.

Only public struct fields become JavaScript properties. They are writable by default because napi-rs generates both accessors; `#[napi(readonly)]` suppresses the setter, and `#[napi(skip)]` suppresses both accessors. Private fields remain native implementation details. A writable field needs both `ToNapiValue` and `FromNapiValue`; a readonly field needs only `ToNapiValue`. See the [field attribute reference](/docs/concepts/napi-attributes#fields), including the `#[napi(constructor)]` shorthand limitation.

**lib.rs**

```rust {1,5}
#[napi]
pub fn accept_class(engine: &QueryEngine) {
  // ...
}

#[napi]
pub fn accept_class_mut(engine: &mut QueryEngine) {
  // ...
}
```

**index.d.ts**

```ts
export function acceptClass(engine: QueryEngine): void
export function acceptClassMut(engine: QueryEngine): void
```

For nested class instances, arrays of class instances, and `ClassInstance<T>`, see the [class section of the conversion reference](/docs/concepts/type-conversions#objects-classes-and-custom-shapes).

## Property attributes

The default Property attributes are `writable = true`, `enumerable = true` and `configurable = true`. You can control the Property attributes over the `#[napi]` macro:

**lib.rs**

```rust {20}
use napi::bindgen_prelude::*;
use napi_derive::napi;

// A complex struct that cannot be exposed to JavaScript directly.
#[napi]
pub struct QueryEngine {
  num: i32,
}

#[napi]
impl QueryEngine {
  #[napi(constructor)]
  pub fn new() -> Result<Self> {
    Ok(Self {
      num: 42,
    })
  }

  // writable / enumerable / configurable
  #[napi(writable = false)]
  pub fn get_num(&self) -> i32 {
    self.num
  }
}
```

In this case, the `getNum` method of `QueryEngine` is not writable:

**main.mjs**

```js {4}
import { QueryEngine } from './index.js'

const qe = new QueryEngine()
qe.getNum = function () {} // TypeError: Cannot assign to read only property 'getNum' of object '#<QueryEngine>'
```

## Custom Finalize logic

**NAPI-RS** will drop the Rust struct wrapped in the JavaScript object when the JavaScript object is garbage collected. You can also specify custom finalize logic for the Rust struct.

**lib.rs**

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

#[napi(custom_finalize)]
pub struct CustomFinalize {
  width: u32,
  height: u32,
  inner: Vec<u8>,
}

#[napi]
impl CustomFinalize {
  #[napi(constructor)]
  pub fn new(mut env: Env, width: u32, height: u32) -> Result<Self> {
    let inner_size = u64::from(width)
      .checked_mul(u64::from(height))
      .and_then(|pixels| pixels.checked_mul(4))
      .and_then(|bytes| usize::try_from(bytes).ok())
      .ok_or_else(|| Error::new(Status::InvalidArg, "image dimensions are too large"))?;
    let external_size = i64::try_from(inner_size)
      .map_err(|_| Error::new(Status::InvalidArg, "image dimensions are too large"))?;

    let mut inner = Vec::new();
    inner.try_reserve_exact(inner_size).map_err(|err| {
      Error::new(
        Status::GenericFailure,
        format!("failed to allocate image buffer: {err}"),
      )
    })?;
    inner.resize(inner_size, 0);
    env.adjust_external_memory(external_size)?;
    Ok(Self {
      width,
      height,
      inner,
    })
  }
}

impl ObjectFinalize for CustomFinalize {
  fn finalize(self, mut env: Env) -> Result<()> {
    let external_size = i64::try_from(self.inner.len())
      .map_err(|_| Error::new(Status::InvalidArg, "image buffer is too large"))?;
    env.adjust_external_memory(-external_size)?;
    Ok(())
  }
}
```

First, you can set `custom_finalize` attribute in `#[napi]` macro, and NAPI-RS will not generate the default `ObjectFinalize` for the Rust struct.

Then, you can implement `ObjectFinalize` yourself for the Rust struct.

In this case, the `CustomFinalize` struct increases external memory in the **constructor** and decreases it in `fn finalize`.

## `instance of`

There is `fn instance_of` on all `#[napi]` class:

**lib.rs**

```rust {9}
use napi::bindgen_prelude::*;
use napi_derive::napi;

#[napi(constructor)]
pub struct NativeClass {}

#[napi]
pub fn is_native_class_instance(env: &Env, value: Unknown) -> Result<bool> {
  NativeClass::instance_of(env, &value)
}
```

**main.mjs**

```js
import { NativeClass, isNativeClassInstance } from './index.js'

const nc = new NativeClass()
console.log(isNativeClassInstance(nc)) // true
console.log(isNativeClassInstance(1)) // false
```
