VOOZH about

URL: https://blog.logrocket.com/how-to-create-a-deno-plugin-in-rust/

⇱ How to create a Deno plugin in Rust - LogRocket Blog


2021-06-02
1335
#deno#rust
Anshul Goyal
52727
πŸ‘ Image

See how LogRocket's Galileo AI surfaces the most severe issues for you

No signup required

Check it out

Deno is a new JavaScript runtime built with Rust and V8 that enables you to run JavaScript outside the browser. Deno is more secure than Node.js because it limits network and file system access by default.

πŸ‘ Rust and Deno Logos

One of the cool things about Deno is that you can write plugins in Rust and use them within Deno code. In this tutorial, we’ll show you how to create Deno plugins in Rust.

We’ll cover the following:

πŸš€ Sign up for The Replay newsletter

The Replay is a weekly newsletter for dev and engineering leaders.

Delivered once a week, it's your curated guide to the most important conversations around frontend dev, emerging AI tools, and the state of modern software.

Why write Deno plugins in Rust?

Plugins in Deno generally improve performance and provide access to a wider range of tools.

Due to their performant nature, plugins are often used in calculations for heavy tasks such as image processing. Plugins also give you access to a variety of libraries written in other languages, including high-quality Rust crates.

Deno plugin project structure

The plugin project structure is the same as any Deno module. For the purpose of this tutorial, you can use this boilerplate:

git clone https://github.com/anshulrgoyal/deno-rust-starter.git my_module

First, build the Rust boilerplate for the plugin:

cd my_module/native
cargo build

Next, run a test to verify that Deno is picking up the correct library:

cd my_module/native
deno test --unstable --allow-plugin

The boilerplate includes a Rust project in the native directory and a Deno module in the root.

Building a Rust project

The Rust project compiles a dynamic library that is loaded by the Deno runtime. The file type and name of the library depend on the operating system. The Rust project may compile to a so file β€” dylib or dll β€” and the name of the compiled file may also be different. The boilerplate can handle three major platforms: Linux, macOS, and Windows.

[package]
name = "native"
version = "0.1.0"
authors = ["anshul <[email protected]>"]
edition = "2018"

[lib]
name = "native"
crate-type = ["cdylib"]

[dependencies]
deno_core = "0.75.0"


β”œβ”€β”€ README.md
β”œβ”€β”€ deps.ts
β”œβ”€β”€ mod.ts
β”œβ”€β”€ mod_test.ts
β”œβ”€β”€ native
β”‚ β”œβ”€β”€ Cargo.lock
β”‚ β”œβ”€β”€ Cargo.toml
β”‚ β”œβ”€β”€ src
β”‚ └── lib.rs
β”œβ”€β”€ test.ts
β”œβ”€β”€ test_deps.ts
└── tsconfig.json

The mod.ts file is the main file imported by another application using your module.

Adding Rust code

For this tutorial, we’ll show you how to build a PNG optimizer using an oxipng crate. Every Deno plugin must export the deno_plugin_init function and register all the methods that the plugin exports.

The #[no_mangle] attribute tells the compiler not to change the name of the function:

#[no_mangle]
pub fn deno_plugin_init(interface: &mut dyn Interface) {
 // register the function. Pass name and function to register method
 interface.register_op("hello_world",hello_world);
}

Creating the optimizer function

Each exported function has the same signature. Deno plugins can only export functions. These functions can be sync or async, depending on the return type.

fn optimise(_interface: &mut dyn Interface,
 zero_copy: &mut [ZeroCopyBuf],
) -> Op {
 // get first argument
 let first=zero_copy.first().unwrap();
 let opts: oxipng::Options = Default::default();
 // convert vector
 let result = oxipng::optimize_from_memory(&first.to_vec(), &opts).unwrap();
 // move to heap so that deno can use it
 Op::Sync(Box::from(result))
}

The second argument of the function contains an array of buffers. Each buffer in the array represents the argument passed to the exported function when called. These buffers are serialized to strings or other data types based on requirements.

The above code takes the first element of zero_copy and passes it to optimize_from_memory. The first element of the array is the file passed to the optimize function when called from the Deno code. The file is passed as bytes. The function processes the file and returns the result as a Box. The return type is Op enum with two variants sync and async.

Build the code using the cargo build command. Now this plugin can be used in Deno.


Over 200k developers use LogRocket to create better digital experiences

πŸ‘ Image
Learn more β†’

Loading a Rust plugin in Deno

Now that the plugin is compiled, let’s load it using Deno.

The plugin is still in development and is a part of unstable APIs, so the --unstable flag is required, as is --allow-plugin.

let path = ""
// check the type of OS to load correct file
if (Deno.build.os === "linux") {
// linux file emited by rust compiler
 path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
// windows file emited by rust compiler
 path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
// macos file emited by rust comipler
 path = "./native/target/debug/libnative.dylib"
}
// load plugin from file system
const rid = Deno.openPlugin(path);
// Get available methods on plugin
//@ts-Expect-Error
const { optimise:optimise_native } = (Deno as any).core.ops();

export async function optimise(fileName: string): Promise<Uint8Array> {
// reading a file
 const file = await Deno.open(fileName);
// getting content
 const value = await Deno.readAll(file)
// closing file
 await Deno.close(file.rid)
// running the native plugin method using Deno dispatch method
 return (Deno as any).core.dispatch(optimise_native, value)
}

Each plugin is loaded using the openPlugin method. Then, the ops method is used to get the method identifier, which executes the code exported by the plugin.

dispatch is used to run code exported by the native plugin. The first argument is the method identifier; the rest are passed for the native function. In this case, the file is passed.

Writing async plugins

Since Deno is single-threaded, it’s not wise to block the main thread. Deno allows you to return a future from the native function, which you can use with OS threads to write a function that doesn’t block the main thread.

fn optimise_async(_interface: &mut dyn Interface,
 zero_copy: &mut [ZeroCopyBuf],
) -> Op {
// get first argument
 let first=zero_copy.first().unwrap();
 let opts: oxipng::Options = Default::default();
 let arg=first.to_vec();
// create a new future
 let fut = async move {
// create a channel to send result once done to main thread
 let (tx, rx) = futures::channel::oneshot::channel::<oxipng::PngResult<Vec<u8>>>();
// create a new thread
 std::thread::spawn(move || {
// perform work
 let result = oxipng::optimize_from_memory(&arg, &opts);
// send result to main thread
 tx.send(result).unwrap();
 });
// receive the result
 let result=rx.await;
// create a boxed slice
 let result_box = result.unwrap().unwrap().into_boxed_slice();
// return boxed slice from the future
 result_box
 };
// return the future
 Op::Async(fut.boxed())
}

A future is created using the async block and returned as a boxed future. Deno handles the completion of the future and informs the Deno side of the plugin. A channel is used to communicate between the new thread and the main thread.

The Deno code doesn’t need much updating β€” just a new asyncHandler to handle the completion of the task:

let path = ""
if (Deno.build.os === "linux") {
 path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
 path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
 path = "./native/target/debug/libnative.dylib"
}
const rid = Deno.openPlugin(path);

const { optimise_async } = (Deno as any).core.ops();

export async function optimise(fileName: string){
 const file = await Deno.open(fileName);
 const value = await Deno.readAll(file);
 await Deno.close(file.rid);
// new handler
 (Deno as any).core.setAsyncHandler(optimise_async, (response:any) => {
 Deno.writeFile("l.png",response)
 });
// executing the native code.
 (Deno as any).core.dispatch(optimise_async,value);
}
await optimise("t.png")

await Deno.close(rid);

Conclusion

In this tutorial, we covered how to build a simple Deno plugin using Rust as well as how to create an async plugin using Rust futures and the deno_core crate.


More great articles from LogRocket:


Rust has a large ecosystem with high-quality crates. You can use all these crates in Deno by creating plugins. Whether it’s an image processing plugin, database connector, etc., access to Rust plugins helps to expand the Deno ecosystem.

LogRocket: Full visibility into web frontends for Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.

LogRocket lets you replay user sessions, eliminating guesswork around why bugs happen by showing exactly what users experienced. It captures console logs, errors, network requests, and pixel-perfect DOM recordings β€” compatible with all frameworks.

LogRocket's Galileo AI watches sessions for you, instantly identifying and explaining user struggles with automated monitoring of your entire product experience.

πŸ‘ LogRocket Dashboard Free Trial Banner

Modernize how you debug your Rust apps β€” start monitoring for free.

πŸ‘ Image
πŸ‘ Image
πŸ‘ Image

Stop guessing about your digital experience with LogRocket

Get started for free

Recent posts:

Stop hardcoding LLM SDKs: Dynamic LLM routing with OpenRouter and Next.js

Build dynamic LLM routing in Next.js with OpenRouter, TanStack AI, task classification, model fallbacks, and cost-aware routing.

πŸ‘ Image
Chizaram Ken
Jun 16, 2026 β‹… 13 min read

What is TSRX?: What JSX would look like if it were designed today

TSRX adds first-class control flow, conditional hooks, and scoped styles to React via a TypeScript compiler extension β€” no new framework required.

πŸ‘ Image
Ikeh Akinyemi
Jun 12, 2026 β‹… 6 min read

How to add authentication to a React Native app with Better Auth

Learn how to build a full React Native auth system using Better Auth and Expo β€” with email/password login, Google OAuth, session persistence, and protected routes.

πŸ‘ Image
Chinwike Maduabuchi
Jun 9, 2026 β‹… 13 min read

AI dev tool power rankings & comparison [June 2026]

Compare the top AI development tools and models of June 2026. View updated rankings, feature breakdowns, and find the best fit for you.

πŸ‘ Image
Chizaram Ken
Jun 8, 2026 β‹… 11 min read
View all posts

Would you be interested in joining LogRocket's developer community?

Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.

Sign up now