Skip to main content

· 5 min read

Part 1: Extism in Elixir

Since I built Elixir support into Extism, I've been thinking about how to demonstrate the power of combining the two.

I wanted to explore wrapping a Wasm module in an Erlang process and see what kind of interesting things you could create when you leverage OTP. Along the way, I discovered that the programming model of an Extism plugin maps almost perfectly to the GenServer behavior.

This has some powerful implications! In this two-part post, I'm going to show why. Our goal is to extend this awesome idea of a turn-based game engine created by With just a couple invocations of the Extism library, we will allow users to create and upload their own turn based games.

Part 1 of this post will be explaining how to use Extism in Elixir and how to create this magic Extism GenServer.

Setting up Extism

Install Depdendencies

All you need to get started is rustup and elixir. If you have these installed you can skip to the next section.

Use Docker

If you want a temporary Dockerized environment, run these commands:

mkdir /tmp/elixir-blogpost
cd /tmp/elixir-blogpost
curl > Dockerfile
docker build -t game_box .
docker run -it game_box bash

Create an Elixir project

Now let's create a new Elixir project with mix:

mix new game_box
cd game_box

Add Extism as a dependency in mix.exs

  defp deps do
# ...
{:extism, "~> 0.1.0"},

Fecth the deps and compile:

mix do deps.get, compile

This should kick off a rust build of the Extism package and runtime and it should end something like this:

Finished release [optimized] target(s) in 3m 39s
Generated extism app
Getting Support

If you do run into a problem at any point please file an issue or reach out on Discord.

Running a plugin

Let's test this out in a repl. Before we do that, let's pull down a compiled wasm plugin to execute: This is our "count-vowels" example plugin:

curl > code.wasm

Now open an IEx repl:

iex -S mix

Paste in this code. This creates an Extism Context and loads the Plugin. Then it calls the count_vowels function on the plugin with a test string and returns the output of the plugin (which in this case is a JSON encoded result).

ctx =
manifest = %{ wasm: [ %{ path: "./code.wasm" } ]}
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, false)
{:ok, output} =, "count_vowels", "this is a test")

If all is working you'll see {"count" => 4} in the output string:

iex(5)> JSON.decode!(output)
%{"count" => 4}

Utilizing OTP

Okay, how do we turn this into a GenServer? There would be two approaches:

  1. Wrap the Extism.Context in the GenServer
    • Store the Context as the state
    • Provide callbacks to load and unload plugins
    • Provide callbacks to lookup and call those plugins
  2. Wrap the Extism.Plugin in the GenServer
    • Store both the context and the plugin as state
    • Provide callbacks to load and reload the plugin code
    • Provide callbacks to call functions on the plugin
What is a Context?

You can think of a Context as an arena of plugins. When a plugin is loaded, the context owns that plugin and is responsible for freeing it. Freeing a context frees all its plugins.

I went with #2 here because I think it's more granular and allows for more flexibility and concurrency. There isn't much overhead to have one context per plugin so it's okay to do it this way.

With that in mind, I implemented this barebones GenServer in lib/game_box.ex. Go ahead and copy paste it into that file:

defmodule GameBox.PluginServer do
use GenServer

@impl true
def init(_init_arg) do
ctx =
# as our state we will store a {Extism.Context, Extism.Plugin} tuple
{:ok, {ctx, nil}}

# This special call is for loading or reloading a plugin given a manifest
@impl true
def handle_call({:new, manifest, wasi}, _from, {ctx, plugin}) do
# if we have an existing Plugin let's free it
if plugin do
# Load a new plugin given the manifest and store it in the new state
{:ok, plugin} = Extism.Context.new_plugin(ctx, manifest, wasi)
{:reply, {:ok, plugin}, {ctx, plugin}}

# this is a generic way to proxy messages to the underlying Extism.Plugin module
# we're mostly going to use `call` here:
# e.g. call_details = {:call, "count_vowels", "this is a test"}
@impl true
def handle_call(call_details, _from, {ctx, plugin}) do
[func_name | args] = Tuple.to_list(call_details)
response = apply(Extism.Plugin, func_name, [plugin | args])
{:reply, response, {ctx, plugin}}

Okay, let's try the count vowels example again but now with a GenServer. Start the repl again:

$ iex -S mix

iex(1)> {:ok, pid} = GenServer.start_link(GameBox.PluginServer, nil)
{:ok, #PID<0.220.0>}
iex(2)>, {:new, %{wasm: [%{path: "./code.wasm"}]}, false})
ctx: %Extism.Context{ptr: #Reference<0.584822572.876216322.21881>},
plugin_id: 0
iex(3)>, {:call, "count_vowels", "this is a test"})
{:ok, "{\"count\": 4}"}

Now we're doing the same thing as before, but instead of invoking a function on the plugin, we're invoking it on a pid. What does this inversion give us exactly? Well, because it's a pid, it can live anywhere in your cluster. And it can be supervised and registered by OTP.

This also means we can replace any GenServer in our application, or in a framework like Phoenix, with a Wasm module. This would allow your customers to extend your application or maybe allow you to write part of your Elixir application in a language better suited to the task of the GenServer you are replacing.

In the next post we will show how we can use this PluginServer like a Phoenix.LiveView module.

We're open-sourcing Part 2 and invite you to get in on the action! Join us on Discord to learn more.

· 7 min read

Hello, World!

Today, we are excited to officially announce Extism, the universal plug-in system. Over the past few months, we have focused on building an embeddable, safe, performant runtime that is as easy to use from as many programs as possible. Extism brings extensibility to software of all shapes and sizes in a way which we believe to have been previously unacheivable.

Extism's goal is simple: make all software programmable. We've released the project under the permissive BSD-3 license, and you can see all of its components on GitHub.

Extism, universal plug-in system

Being "Universal"

The power of a plug-in system is that it enables software end-users to dream up new capabilities and functionality of a program, which the original authors couldn't foresee, or didn't want to add to the core of a product. Keeping programs as simple as possible often leads to higher quality, better performance, and easier maintainability. It's important to us that software creators using any language have the same opportunity to have these benefits, and also be able to give their users rich extensibility.

As of today, Extism can be easily embedded into 13+ languages, using our official SDKs:

  • Browser
  • C
  • C++
  • Elixir
  • Erlang
  • Go
  • Haskell
  • Java
  • .NET
  • Node
  • OCaml
  • PHP
  • Python
  • Ruby
  • Rust
  • Zig

Embed Extism into any of your projects: web apps (using our browser runtime), databases, API servers, command-line apps, smart TVs, IoT, SaaS... you name it! Extism meets your code wherever it's at.

Extism, at its core, is a code runtime built in Rust. Underneath the hood, we run WebAssembly code as the plug-in execution format. This makes it safe to execute untrusted, 3rd party plug-in code even while it's directly embedded within the same OS process as your program. WebAssembly has a battle-tested sandbox isolation architecture, and is in use across the software industry from browsers, edge platforms, cloud environments, and more. In addition to its security benefits, WebAssembly is a compilation target that is already supported by many different programming languages. This enables plug-in authors to use the language that they prefer to write their plug-in, and as of today, Extism plug-ins can be written in 5 languages, using our official plug-in development kits (PDKs):

  • Rust
  • JavaScript
  • Go
  • Haskell
  • AssemblyScript
  • C
  • Zig

We plan to add more language support to our SDKs and PDKs over the coming months & years, so if you don't see your favorite language listed above, please join our Discord or file and issue and we can prioritize it or help you contribute!

How We Built It

Staying true to our goal to make all software programmable, we knew Extism would need to be embedded into several languages. It needs to be able to go anywhere. Other projects with similar goals like sqlite or openssl, are low-level system components exposed to a multitude of language environments through FFI, the foreign function interface. Extism is no different. Choosing Rust was an obvious choice to help build a reliable and performance-sensitive core runtime, and with Rust's fantastic FFI support, we could expose runtime APIs which can be called from almost any language used today.

All of our official SDKs provide idiomatic wrappers over bindings to Extism, so users will feel right at home working within a language they know and love.

The runtime is only half of the equation though. What about plug-in authors? What kind of features and functionality should they get from the runtime? How do we expose these features to the plug-in environment? This is where WebAssembly really shines. Providing a standard ABI to WebAssembly modules is very straightforward thanks to its simple import/export architecture. Extism defines a set of functions which are linked from the host runtime to the .wasm module when it is instantiated, and our PDKs provide idiomatic wrappers over bindings to these native functions. From a .wasm module's perspective, these functions are "imports", provided to it from its host. Some of these include the ability for plug-ins to make network calls via HTTP, persist variables in between plug-in invocations, read host configuration data, and most importantly read & write complex data between the host and plug-in.

In addition to the imports provided by Extism, a host can elect to enable WASI and offer plug-ins a rich POSIX-like system interface, which allows many existing codebases and libraries to be compiled to .wasm modules and used within a plug-in. It's important to keep safety and security in mind, and as such, we've decided to hold off on enabling direct disk/filesystem access from plug-ins, and instead opt for a more explicit requirement to pass file data in and out of a plug-in directly. We're experimenting with approaches here and would appreciate your feedback.

Let's See Some Code

Head over to the SDK documentation for dozens of examples of embedding Extism into all of our supported host languages, or if you're interested in compiling plug-ins to WebAssembly, check out many examples in the PDK documentation for plug-in code in each supported language.

As a fully open-source project, we also invite you to head to our GitHub repository and see how everything works. In the main repository, you will find all of the runtime code as well as each of the host SDKs. Each PDK is split into its own repository within the Extism GitHub Organization.

For a quick glance at some simple examples, see below:

Node.js Host SDK Example

const { withContext, Context } = require('@extism/extism');
const { readFileSync } = require('fs');

withContext(async function (context) {
// get plug-in code from anywhere (disk, network, cache, database etc.)
let wasm = readFileSync('../wasm/code.wasm');
// construct a plug-in, to use WASI, pass `true` to constructor (see docs for more options)
let plugin = context.plugin(wasm);

// simple call any function from the plug-in and pass it any complex data,
// many SDKs provide options to pass strings, raw bytes, etc.
let buf = await'count_vowels', 'this could be any data!');

// parse or decode the returned data from the plug-in however your app needs to

// plug-ins will be automatically freed where possible, but you can ensure cleanup is done;

Go Plugin PDK Example

package main

import (


//export count_vowels
func count_vowels() int32 {
// read input from the host and use it from within the plug-in
input := pdk.Input()

count := 0
for _, a := range input {
switch a {
case 'A', 'I', 'E', 'O', 'U', 'a', 'e', 'i', 'o', 'u':

// for demonstration, use persisted variables which can be accessed
// between plug-in invocations
if pdk.GetVar("a") == nil {
pdk.SetVar("a", []byte("this is var a"))
varA := pdk.GetVar("a")

// for demonstration, access key-value based configuration data
// provided by the host
thing, ok := pdk.GetConfig("thing")
if !ok {
thing = "<unset by host>"

// prepare some data to write back to the host
output := fmt.Sprintf("Counted %d vowels!", count)
mem := pdk.AllocateString(output)

// zero-copy output to host

// most PDKs return a status code to hosts, `0` here indicates success.
return 0

What's Next?

Over the coming weeks and months, we plan on working with interested users and incorporating feedback, adding more language support wherever possible, and expanding our documentation and demos for users to learn from. Your input is critical and always appreciated, so please join us on Discord where we hang out and chat. Use each repository's issue tracker to file any experience report or bug you encounter. No feedback is too small, and we thank you for the time it takes to help us make Extism as great as it can be!

Spread the word!