🚧 VeloxKit is pre-release software. APIs may change before v1.0. Get started →
Documentation
Guides
Native Extensions

Native Extensions Experimental

⚠️

Experimental — the native extension API may change before v1.0.

Native extensions let you write Rust code that your JavaScript can call directly. Use them for performance-critical work, C library bindings, or OS APIs not exposed by VeloxKit.

When to use native extensions

Use a native extension when you need to:

  • Process large files without blocking the JS thread
  • Bind to a C/C++ library (image processing, cryptography, etc.)
  • Access OS APIs not yet covered by VeloxKit's built-in bindings
  • Run CPU-intensive loops at native speed

For most apps, the built-in bindings cover everything needed.

Setup

You need Rust installed for native extensions (unlike the rest of VeloxKit):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Create an extension directory:

npx veloxkit-cli extension new my-extension

This scaffolds:

extensions/my-extension/
├── Cargo.toml
└── src/
    └── lib.rs

Write the extension

// extensions/my-extension/src/lib.rs
use velox_extension::prelude::*;
 
#[velox_command]
fn hash_file(path: String) -> Result<String, VeloxError> {
    use std::io::Read;
 
    let mut file = std::fs::File::open(&path)
        .map_err(|e| VeloxError::io(e))?;
 
    let mut hasher = sha2::Sha256::new();
    let mut buf = [0u8; 8192];
 
    loop {
        let n = file.read(&mut buf)?;
        if n == 0 { break; }
        hasher.update(&buf[..n]);
    }
 
    Ok(format!("{:x}", hasher.finalize()))
}
 
// Async extension (runs on background thread, non-blocking)
#[velox_command(async)]
async fn compress_file(input: String, output: String) -> Result<u64, VeloxError> {
    // ... compression logic
    Ok(bytes_written)
}

Call from JavaScript

import { velox } from 'veloxkit'
 
// Synchronous
const hash = velox.ext('my-extension', 'hash_file', '/path/to/file.txt')
 
// Async (non-blocking — runs on background thread)
const bytes = await velox.extAsync('my-extension', 'compress_file', {
  input: '/path/to/large.csv',
  output: '/path/to/large.csv.gz',
})

Types

Define TypeScript types for your extension:

// extensions/my-extension/index.d.ts
import { velox } from 'veloxkit'
 
export function hashFile(path: string): string
export function compressFile(input: string, output: string): Promise<number>

And create a wrapper module:

// extensions/my-extension/index.ts
import { velox } from 'veloxkit'
 
export const hashFile = (path: string) =>
  velox.ext<string>('my-extension', 'hash_file', path)
 
export const compressFile = (input: string, output: string) =>
  velox.extAsync<number>('my-extension', 'compress_file', { input, output })

Build

VeloxKit builds extensions automatically when you run veloxkit build or veloxkit dev. The Rust compilation happens once and is cached.

# Explicitly build the extension only
npx veloxkit-cli extension build my-extension

Cargo.toml

[package]
name = "my-extension"
version = "0.1.0"
edition = "2021"
 
[lib]
crate-type = ["cdylib"]
 
[dependencies]
velox-extension = "0.3"
sha2 = "0.10"