Skip to content

Types Overwrite

In most cases, NAPI-RS generates the correct TypeScript types from the Rust signature. Override them only when the public TypeScript contract intentionally differs from the runtime conversion type, and keep the two behaviors aligned in tests.

ThreadsafeFunction is one example: a build_callback closure can transform owned Rust data into a different list of JavaScript callback arguments, so inference cannot always describe the final callback signature.

ts_args_type

Replace the complete comma-separated parameter list of the exported function. This changes only the generated declaration, not runtime conversion.

lib.rs
rust
use std::sync::Arc;
use std::thread;

use napi::{
  bindgen_prelude::*,
  threadsafe_function::{ThreadsafeCallContext, ThreadsafeFunctionCallMode},
};
use napi_derive::napi;

#[napi(ts_args_type = "callback: (err: null | Error, result: string) => void")] 
pub fn call_threadsafe_function(callback: Function<u32, ()>) -> Result<()> {
  let tsfn_builder = callback.build_threadsafe_function();
  let tsfn = Arc::new(
    tsfn_builder
      .callee_handled::<true>()
      .build_callback(
        move |ctx: ThreadsafeCallContext<u32>| Ok(format!("n: {}", ctx.value)),
      )?,
  );
  for n in 0..100 {
    let tsfn = tsfn.clone();
    thread::spawn(move || {
      tsfn.call(Ok(n), ThreadsafeFunctionCallMode::Blocking);
    });
  }
  Ok(())
}

⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️

index.d.ts
ts
export function callThreadsafeFunction(
  callback: (err: null | Error, result: string) => void,
): void

ts_arg_type

Replace one or more parameter types individually. NAPI-RS continues to infer the other parameters.

lib.rs
rust
#[napi] 
fn override_individual_arg_on_function(
  not_overridden: String,
  #[napi(ts_arg_type = "() => string")] f: Function<(), String>,
  not_overridden2: u32,
) -> Result<String> {
  let value = f.call(())?;
  Ok(format!("{not_overridden}-{value}-{not_overridden2}"))
}
index.d.ts
ts
export function overrideIndividualArgOnFunction(
  notOverridden: string,
  f: () => string,
  notOverridden2: number,
): string

ts_return_type

Replace the generated return type. For an async export, provide the complete public type, normally Promise<T>.

lib.rs
rust
#[napi(ts_return_type="number")] 
fn return_something_unknown<'env>(env: &'env Env) -> Result<Unknown<'env>> {
  env.create_uint32(42).map(|v| v.to_unknown())
}
index.d.ts
ts
export function returnSomethingUnknown(): number

ts_type

Overwrite the generated ts-type of a field in a struct.

lib.rs
rust
#[napi(object)] 
pub struct TsTypeChanged {
  #[napi(ts_type = "MySpecialString")]
  pub type_override: String,

  #[napi(ts_type = "object")]
  pub type_override_optional: Option<String>,
}
index.d.ts
ts
export interface TsTypeChanged {
  typeOverride: MySpecialString
  typeOverrideOptional?: object
}

Custom Type Definitions in Header

When NAPI-RS generates index.d.ts, it starts the file with a default header. You can replace that header with your own type aliases, imports, lint directives, or license comments. This is a file-level setting: unlike the ts_* attributes above, it is configured once for the whole package, not per export.

How it works:

  • Set dtsHeader (inline string) or dtsHeaderFile (path to a .d.ts file) in the napi config. dtsHeaderFile is the better choice for complex headers with imports.
  • The CLI flags --dts-header and --no-dts-header of napi build override the inline value or disable the header entirely, which is useful in CI.
  • A custom header replaces the default header completely. Include the /* auto-generated by NAPI-RS */ comment and the eslint directive in your own header if you want to keep them.

Precedence summary: a header file always wins over inline header text; --dts-header beats the inline dtsHeader config value; the default header is used when nothing is set; --no-dts-header skips the header entirely.

The full field reference, priority table, and examples live in NAPI Config: Declaration file header.

Last updated on
LongYinan