{"id":13611888,"url":"https://github.com/extism/zig-pdk","last_synced_at":"2025-04-08T07:32:22.161Z","repository":{"id":65090733,"uuid":"581896152","full_name":"extism/zig-pdk","owner":"extism","description":"Extism Plug-in Development Kit (PDK) for Zig","archived":false,"fork":false,"pushed_at":"2024-05-21T22:01:49.000Z","size":64,"stargazers_count":23,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2024-05-22T12:39:29.728Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extism.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-24T19:11:45.000Z","updated_at":"2024-06-24T02:52:22.318Z","dependencies_parsed_at":"2024-01-04T01:12:23.765Z","dependency_job_id":"c162b66a-0e57-4088-8aee-ac8712eb7ba9","html_url":"https://github.com/extism/zig-pdk","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fzig-pdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fzig-pdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fzig-pdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fzig-pdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extism","download_url":"https://codeload.github.com/extism/zig-pdk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247419857,"owners_count":20936012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T19:02:17.366Z","updated_at":"2025-04-08T07:32:22.152Z","avatar_url":"https://github.com/extism.png","language":"Zig","funding_links":[],"categories":["Zig"],"sub_categories":[],"readme":"# Extism Zig PDK\n\nThis library can be used to write\n[Extism Plug-ins](https://extism.org/docs/concepts/plug-in) in Zig.\n\n## Install\n\nCreate a new Zig project:\n\n```bash\nmkdir my-plugin\ncd my-plugin\nzig init\n```\n\nAdd the library as a dependency. Git ref should be the hash of the latest\ncommit:\n\n```sh\nzig fetch --save https://github.com/extism/zig-pdk/archive/refs/tags/v1.1.0.tar.gz\n```\n\nChange your `build.zig` so that it references `extism-pdk`:\n\n```zig\nconst std = @import(\"std\");\n\npub fn build(b: *std.Build) void {\n    const optimize = b.standardOptimizeOption(.{});\n    const target = b.standardTargetOptions(.{\n        // if you're using WASI, change the .os_tag to .wasi\n        .default_target = .{ .abi = .musl, .os_tag = .freestanding, .cpu_arch = .wasm32 },\n    });\n    const pdk_module = b.dependency(\"extism-pdk\", .{ .target = target, .optimize = optimize }).module(\"extism-pdk\");\n    var plugin = b.addExecutable(.{\n        .name = \"my-plugin\",\n        .root_source_file = .{ .path = \"src/main.zig\" },\n        .target = target,\n        .optimize = optimize,\n    });\n    plugin.rdynamic = true;\n    plugin.entry = .disabled; // or add an empty `pub fn main() void {}` to your code\n    plugin.root_module.addImport(\"extism-pdk\", pdk_module);\n\n    b.installArtifact(plugin);\n    const plugin_example_step = b.step(\"my-plugin\", \"Build my-plugin\");\n    plugin_example_step.dependOn(b.getInstallStep());\n}\n```\n\n## Getting Started\n\nThe goal of writing an\n[Extism plug-in](https://extism.org/docs/concepts/plug-in) is to compile your\nZig code to a Wasm module with exported functions that the host application can\ninvoke. The first thing you should understand is creating an export. Let's write\na simple program that exports a `greet` function which will take a name as a\nstring and return a greeting string. Zig has excellent support for this through\nthe `export` keyword:\n\n```zig\nconst std = @import(\"std\");\nconst extism_pdk = @import(\"extism-pdk\");\nconst Plugin = extism_pdk.Plugin;\n\nconst allocator = std.heap.wasm_allocator;\n\nexport fn greet() i32 {\n    const plugin = Plugin.init(allocator);\n    const name = plugin.getInput() catch unreachable;\n    defer allocator.free(name);\n\n    const output = std.fmt.allocPrint(allocator, \"Hello, {s}!\", .{name}) catch unreachable;\n    plugin.output(output);\n    return 0;\n}\n```\n\n\u003e Note: if you started with the generated project files from `zig init`, you\n\u003e should delete `src/root.zig` and any references to it if they are in your\n\u003e `build.zig` file.\n\nThen run:\n\n```sh\nzig build\n```\n\nThis will put your compiled wasm in `zig-out/bin`. We can now test it using the\n[Extism CLI](https://github.com/extism/cli)'s `call` command:\n\n```bash\nextism call ./zig-out/bin/my-plugin.wasm greet --input \"Benjamin\"\n# =\u003e Hello, Benjamin!\n```\n\n\u003e **Note**: We also have a web-based, plug-in tester called the\n\u003e [Extism Playground](https://playground.extism.org/)\n\n### More Exports: Error Handling\n\nSuppose want to re-write our greeting module to never greet Benjamins. We can\nuse `Plugin.setError`:\n\n```zig\nexport fn greet() i32 {\n    const plugin = Plugin.init(allocator);\n    const name = plugin.getInput() catch unreachable;\n    defer allocator.free(name);\n\n    if (std.mem.eql(u8, name, \"Benjamin\")) {\n        plugin.setError(\"Sorry, we don't greet Benjamins!\");\n        return 1;\n    }\n\n    const output = std.fmt.allocPrint(allocator, \"Hello, {s}!\", .{name}) catch unreachable;\n    plugin.output(output);\n    return 0;\n}\n```\n\nNow when we try again:\n\n```bash\nextism call ./zig-out/bin/my-plugin.wasm greet --input=\"Benjamin\"\n# =\u003e Error: Sorry, we don't greet Benjamins!\necho $? # print last status code\n# =\u003e 1\nextism call ./zig-out/bin/my-plugin.wasm greet --input=\"Zach\"\n# =\u003e Hello, Zach!\necho $?\n# =\u003e 0\n```\n\n### Json\n\nExtism export functions simply take bytes in and bytes out. Those can be\nwhatever you want them to be. A common and simple way to get more complex types\nto and from the host is with json:\n\n```zig\nexport fn add() i32 {\n    const Add = struct {\n        a: i32,\n        b: i32,\n    };\n\n    const Sum = struct {\n        sum: i32,\n    };\n\n    const plugin = Plugin.init(allocator);\n    const input = plugin.getInput() catch unreachable;\n    defer allocator.free(input);\n\n    const params = std.json.parseFromSlice(Add, allocator, input, std.json.ParseOptions{}) catch unreachable;\n    const sum = Sum{ .sum = params.value.a + params.value.b };\n\n    const output = std.json.stringifyAlloc(allocator, sum, std.json.StringifyOptions{}) catch unreachable;\n    plugin.output(output);\n    return 0;\n}\n```\n\nTo use a json helper, you can accomplish the same as the above with:\n\n```zig\nexport fn add() i32 {\n    const Add = struct {\n        a: i32,\n        b: i32,\n    };\n\n    const Sum = struct {\n        sum: i32,\n    };\n\n    const plugin = Plugin.init(allocator);\n    // automatically deserialize \u0026 alloc/free the json input to `Add` struct\n    const add = plugin.getJson(Add) catch unreachable;\n    const sum = Sum{ .sum = add.a + add.b };\n    // automatically serialize and alloc/free the `Sum` struct to json and send it to the output\n    plugin.outputJson(sum, .{}) catch unreachable;\n    return 0;\n```\n\n```bash\nextism call ./zig-out/bin/my-plugin.wasm add --input='{\"a\": 20, \"b\": 21}'\n# =\u003e {\"sum\":41}\n```\n\n## Configs\n\nConfigs are key-value pairs that can be passed in by the host when creating a\nplug-in. These can be useful to statically configure the plug-in with some data\nthat exists across every function call. Here is a trivial example using\n`Plugin.getConfig`:\n\n```zig\nexport fn greet() i32 {\n    const plugin = Plugin.init(allocator);\n    const user = plugin.getConfig(\"user\") catch unreachable orelse {\n        plugin.setError(\"This plug-in requires a 'user' key in the config\");\n        return 1;\n    };\n\n    const output = std.fmt.allocPrint(allocator, \"Hello, {s}!\", .{user}) catch unreachable;\n    plugin.output(output);\n    return 0;\n}\n```\n\nTo test it, the [Extism CLI](https://github.com/extism/cli) has a `--config`\noption that lets you pass in `key=value` pairs:\n\n```bash\nextism call ./zig-out/bin/my-plugin.wasm greet --config user=Benjamin\n# =\u003e Hello, Benjamin!\n```\n\n## Variables\n\nVariables are another key-value mechanism but it's a mutable data store that\nwill persist across function calls. These variables will persist as long as the\nhost has loaded and not freed the plug-in.\n\n```zig\nexport fn count() i32 {\n    const plugin = Plugin.init(allocator);\n    const input = plugin.getInput() catch unreachable;\n    defer allocator.free(input);\n\n    var c = plugin.getVarInt(i32, \"count\") catch unreachable orelse 0;\n\n    c += 1;\n\n    plugin.setVarInt(i32, \"count\", c) catch unreachable;\n\n    const output = std.fmt.allocPrint(allocator, \"{d}\", .{c}) catch unreachable;\n    plugin.output(output);\n    return 0;\n}\n```\n\nTo test it, the [Extism CLI](https://github.com/extism/cli) has a `--loop`\noption that lets you pass call the same function multiple times:\n\n```sh\nextism call ./zig-out/bin/my-plugin.wasm count --loop 3\n1\n2\n3\n```\n\n\u003e **Note**: Use the untyped variants\n\u003e `Plugin.setVar(self: Plugin, key: []const u8, value: []const u8)` and\n\u003e `Plugin.getVar(self: Plugin, key: []const u8) !?[]u8` to handle your own\n\u003e types.\n\n## Logging\n\nBecause Wasm modules by default do not have access to the system, printing to\nstdout won't work (unless you use WASI). Extism provides a simple logging\nfunction that allows you to use the host application to log without having to\ngive the plug-in permission to make syscalls.\n\n```zig\nexport fn log_stuff() i32 {\n    const plugin = Plugin.init(allocator);\n    plugin.log(.Info, \"An info log!\");\n    plugin.log(.Debug, \"A debug log!\");\n    plugin.log(.Warn, \"A warning log!\");\n    plugin.log(.Error, \"An error log!\");\n\n    return 0;\n}\n```\n\nFrom [Extism CLI](https://github.com/extism/cli):\n\n```bash\nextism call ./zig-out/bin/my-plugin.wasm log_stuff --log-level=debug\n2023/11/22 14:00:26 Calling function : log_stuff\n2023/11/22 14:00:26 An info log!\n2023/11/22 14:00:26 A debug log!\n2023/11/22 14:00:26 A warning log!\n2023/11/22 14:00:26 An error log!\n```\n\n\u003e _Note_: From the CLI you need to pass a level with `--log-level`. If you are\n\u003e running the plug-in in your own host using one of our SDKs, you need to make\n\u003e sure that you call `set_log_file` to `\"stdout\"` or some file location.\n\n## HTTP\n\nSometimes it is useful to let a plug-in [make HTTP calls].\n[see: Extism HTTP library](src/http.zig)\n\n```zig\nconst http = extism_pdk.http;\n\nexport fn http_get() i32 {\n    const plugin = Plugin.init(allocator);\n    // create an HTTP request via Extism built-in function (doesn't require WASI)\n    var req = http.HttpRequest.init(\"GET\", \"https://jsonplaceholder.typicode.com/todos/1\");\n    defer req.deinit(allocator);\n\n    // set headers on the request object\n    req.setHeader(allocator, \"some-name\", \"some-value\") catch unreachable;\n    req.setHeader(allocator, \"another\", \"again\") catch unreachable;\n\n    // make the request and get the response back\n    const res = plugin.request(req, null) catch unreachable;\n    defer res.deinit();\n\n    if (res.status != 200) {\n        plugin.setError(\"request failed\");\n        return @as(i32, res.status);\n    }\n\n    // get the bytes for the response body\n    const body = res.body(allocator) catch unreachable;\n    // =\u003e { \"userId\": 1, \"id\": 1, \"title\": \"delectus aut autem\", \"completed\": false }\n    const Todo = struct {\n        userId: u32,\n        id: u32,\n        title: []const u8,\n        completed: bool,\n    };\n    const todo = std.json.parseFromSlice(Todo, allocator, body, .{}) catch |err| {\n        plugin.setError(std.fmt.allocPrint(allocator, \"parse error: {any}\", .{err}) catch unreachable);\n        return 1;\n    };\n    defer todo.deinit();\n\n    // format a string with the todo data\n    const t = todo.value;\n    const tmpl = \"[id={d}] '{s}' by user={d} is complete: {any}\\n\";\n    const args = .{ t.id, t.title, t.userId, t.completed };\n    const output = std.fmt.allocPrint(allocator, tmpl, args) catch unreachable;\n\n    // allocate space for the output data\n    const outMem = plugin.allocateBytes(output);\n\n    // `outputMemory` provides a zero-copy way to write plugin data back to the host\n    plugin.outputMemory(outMem);\n\n    return 0;\n}\n```\n\nBy default, Extism modules cannot make HTTP requests unless you specify which\nhosts it can connect to. You can use `--allow-host` in the Extism CLI to set\nthis:\n\n```\nextism call ./zig-out/bin/my-plugin.wasm http_get --allow-host='*.typicode.com'\n# =\u003e { \"userId\": 1, \"id\": 1, \"title\": \"delectus aut autem\", \"completed\": false }\n```\n\n## Imports (Host Functions)\n\nLike any other code module, Wasm not only let's you export functions to the\noutside world, you can import them too. Host Functions allow a plug-in to import\nfunctions defined in the host. For example, if you host application is written\nin Python, it can pass a Python function down to your Zig plug-in where you can\ninvoke it.\n\nThis topic can get fairly complicated and we have not yet fully abstracted the\nWasm knowledge you need to do this correctly. So we recommend reading our\n[concept doc on Host Functions](https://extism.org/docs/concepts/host-functions)\nbefore you get started.\n\n### A Simple Example\n\nHost functions have a similar interface as exports. You just need to declare\nthem as extern. You only declare the interface as it is the host's\nresponsibility to provide the implementation:\n\n```zig\npub extern \"extism:host/user\" fn a_python_func(u64) u64;\n```\n\nWe should be able to call this function as a normal Zig function. Note that we\nneed to manually handle the pointer casting:\n\n```zig\nexport fn hello_from_python() i32 {\n    const plugin = Plugin.init(allocator);\n\n    const msg = \"An argument to send to Python\";\n    const mem = plugin.allocateBytes(msg);\n    defer mem.free();\n\n    const ptr = a_python_func(mem.offset);\n    const rmem = plugin.findMemory(ptr);\n\n    const buffer = plugin.allocator.alloc(u8, @intCast(rmem.length)) catch unreachable;\n    rmem.load(buffer);\n    plugin.output(buffer);\n\n    // OR, you can directly output the memory\n    // plugin.outputMemory(rmem);\n\n    return 0;\n}\n```\n\n### Testing it out\n\nWe can't really test this from the Extism CLI as something must provide the\nimplementation. So let's write out the Python side here. Check out the\n[docs for Host SDKs](https://extism.org/docs/concepts/host-sdk) to implement a\nhost function in a language of your choice.\n\n```python\nfrom extism import host_fn, Plugin\n\n@host_fn()\ndef a_python_func(input: str) -\u003e str:\n    # just printing this out to prove we're in Python land\n    print(\"Hello from Python!\")\n\n    # let's just add \"!\" to the input string\n    # but you could imagine here we could add some\n    # applicaiton code like query or manipulate the database\n    # or our application APIs\n    return input + \"!\"\n```\n\nNow when we load the plug-in we pass the host function:\n\n```python\nmanifest = {\"wasm\": [{\"path\": \"/path/to/plugin.wasm\"}]}\nplugin = Plugin(manifest, functions=[a_python_func], wasi=True)\nresult = plugin.call('hello_from_python', b'').decode('utf-8')\nprint(result)\n```\n\n```bash\npython3 app.py\n# =\u003e Hello from Python!\n# =\u003e An argument to send to Python!\n```\n\n## Generating Bindings\n\nIt's often very useful to define a schema to describe the function signatures\nand types you want to use between Extism SDK and PDK languages.\n\n[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source\nframework to generate PDK bindings for Extism plug-ins. It's used by the\n[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform\nto define any Extism compatible plug-in system.\n\n### 1. Install the `xtp` CLI.\n\nSee installation instructions\n[here](https://docs.xtp.dylibso.com/docs/cli#installation).\n\n### 2. Create a schema using our OpenAPI-inspired IDL:\n\n```yaml\nversion: v1-draft\nexports: \n  CountVowels:\n      input: \n          type: string\n          contentType: text/plain; charset=utf-8\n      output:\n          $ref: \"#/components/schemas/VowelReport\"\n          contentType: application/json\n# components.schemas defined in example-schema.yaml...\n```\n\n\u003e See an example in [example-schema.yaml](./example-schema.yaml), or a full\n\u003e \"kitchen sink\" example on\n\u003e [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).\n\n### 3. Generate bindings to use from your plugins:\n\n```\nxtp plugin init --schema-file ./example-schema.yaml\n    1. TypeScript                      \n    2. Go                              \n    3. Rust                            \n    4. Python                          \n    5. C#                              \n  \u003e 6. Zig                             \n    7. C++                             \n    8. GitHub Template                 \n    9. Local Template\n```\n\nThis will create an entire boilerplate plugin project for you to get started\nwith:\n\n```zig\n/// returns VowelReport (The result of counting vowels on the Vowels input.)\npub fn CountVowels(input: []const u8) !schema.VowelReport {\n    // TODO: fill out your implementation here\n    _ = input;\n    return error.PluginFunctionNotImplemented;\n}\n```\n\nImplement the empty function(s), and run `xtp plugin build` to compile your\nplugin.\n\n\u003e For more information about XTP Bindgen, see the\n\u003e [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and\n\u003e the official\n\u003e [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).\n\n## Reach Out!\n\nHave a question or just want to drop in and say hi?\n[Hop on the Discord](https://extism.org/discord)!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fzig-pdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextism%2Fzig-pdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fzig-pdk/lists"}