| name | create-plugin |
| description | Create a new FrankenWASM plugin using the Extism PDK. Scaffolds source code, Makefile, and demo page in Go, Rust, or JavaScript. |
Create a FrankenWASM Plugin
Create a new Extism WASM plugin for FrankenWASM. The plugin name and language are provided as arguments.
References
Steps
- Create plugin directory:
plugins/<name>/
- Write plugin source using the appropriate Extism PDK (see templates below)
- Create a Makefile with a
build target that outputs ../NAME.wasm
- Add build step to root
Makefile under the plugins target
- Create demo page at
examples/<name>/index.php
- Add card to
examples/index.php and update navigation links
Go Plugin Template
Directory: plugins/<name>/
go.mod
module plugins/<name>
go 1.26.0
require github.com/extism/go-pdk v1.1.0
main.go
package main
import (
"encoding/json"
"github.com/extism/go-pdk"
)
type Params struct {
Input string `json:"input"`
}
func functionname() int32 {
params := &Params{}
if err := json.Unmarshal(pdk.Input(), params); err != nil {
pdk.SetError(err)
return 1
}
result := params.Input
pdk.OutputString(result)
return 0
}
Makefile
export MAKEFLAGS='--silent --environment-override'
.ONESHELL:
.PHONY: build
build:
echo "Building <name> plugin ..."
GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -tags std -o ../<name>.wasm
echo "Built <name>.wasm"
Key notes for Go plugins:
Rust Plugin Template
Directory: plugins/<name>/
Cargo.toml
[package]
name = "<name>"
version = "0.1.0"
edition = "2021"
[dependencies]
extism-pdk = "1.3.0"
serde_json = "1"
[lib]
crate-type = ["cdylib"]
src/lib.rs
use extism_pdk::*;
#[plugin_fn]
pub fn functionname(input: String) -> FnResult<String> {
let result = input;
Ok(result)
}
Makefile
export MAKEFLAGS='--silent --environment-override'
.ONESHELL:
.PHONY: build
build:
echo "Building <name> plugin ..."
cargo build --target wasm32-wasip1 --release
cp target/wasm32-wasip1/release/<name_underscored>.wasm ../<name>.wasm
echo "Built <name>.wasm"
Key notes for Rust plugins:
- Target:
wasm32-wasip1
- The
#[plugin_fn] macro handles input/output automatically
- Input comes as
String (already decoded from bytes)
- Return
FnResult<String> for string output
- For structured input, use
serde_json::from_str()
- The compiled filename uses underscores (Cargo convention), copy with hyphens to match plugin name
JavaScript Plugin Template
Directory: plugins/<name>/
src/index.js
function functionname() {
const input = JSON.parse(Host.inputString());
const result = input;
Host.outputString(JSON.stringify(result));
}
module.exports = { functionname };
src/index.d.ts
declare module "main" {
export function functionname(): I32;
}
package.json
{
"name": "<name>-plugin",
"private": true,
"dependencies": {
"your-lib": "^1.0.0"
},
"devDependencies": {
"esbuild": "^0.20.0"
}
}
esbuild.js
const esbuild = require('esbuild');
esbuild.build({
entryPoints: ['src/index.js'],
bundle: true,
outfile: 'dist/index.js',
format: 'cjs',
target: 'es2020',
mainFields: ['main', 'module'],
minify: true,
external: ['fs', 'path'],
}).catch(() => process.exit(1));
Makefile
export MAKEFLAGS='--silent --environment-override'
.ONESHELL:
.PHONY: build
build:
echo "Building <name> plugin ..."
set -e
npm install
node esbuild.js
extism-js dist/index.js -i src/index.d.ts -o ../<name>.wasm
echo "Built <name>.wasm"
Key notes for JS plugins:
- Uses QuickJS compiled to WASM (via
extism-js tool) — no Node.js APIs at runtime
- Input:
Host.inputString(), Output: Host.outputString()
- esbuild bundles npm dependencies into a single CJS file
extism-js compiles the bundle + type declarations into a .wasm file using Wizer
- Always use
mainFields: ['main', 'module'] in esbuild config
- Add Node builtins to
external if dependencies reference them: ['fs', 'path', 'url']
- Critical: Lazy-load libraries that call host functions or access heavy APIs during
require(). Move the require() inside the exported function.
- Declare all exported functions in
src/index.d.ts
Root Makefile Integration
Add to the plugins target in the root Makefile:
cd $(ROOT)/plugins/<name> && $(MAKE) build
Demo Page
Create examples/<name>/index.php following the patterns in existing demo pages. All demos share:
FrankenPHP\Wasm namespace usage
- Light/dark theme toggle with localStorage persistence
- Navigation links to adjacent demos
Wasm::metadata() for file size display
hrtime(true) timing around $plugin->call()