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.
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(())
}
⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️
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.
#[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}"))
}
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>.
#[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())
}
export function returnSomethingUnknown(): number
ts_type
Overwrite the generated ts-type of a field in a struct.
#[napi(object)]
pub struct TsTypeChanged {
#[napi(ts_type = "MySpecialString")]
pub type_override: String,
#[napi(ts_type = "object")]
pub type_override_optional: Option<String>,
}
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) ordtsHeaderFile(path to a.d.tsfile) in thenapiconfig.dtsHeaderFileis the better choice for complex headers with imports. - The CLI flags
--dts-headerand--no-dts-headerofnapi buildoverride 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.