Lua Plugin API
The ultimate reference for extending Zoi's core functionality with Lua plugins.
Plugins are global Lua scripts that allow you to transform Zoi from a tool into a personalized development platform. While package scripts (.pkg.lua) are scoped to a single package, Plugins are loaded on every Zoi invocation, allowing them to add global commands, enforce security policies, and automate workflows.
Lifecycle & Location
Plugins are distributed via Extensions. When an extension containing a plugin change-type is added, the script is saved to the Zoi home directory.
- Storage Location:
~/.zoi/plugins/ - Loading: Every time you run
zoi, Zoi initializes a Lua VM and executes every.luafile in the plugins folder in alphabetical order before processing your command.
Command API
You can register custom subcommands that appear as if they were built into Zoi.
zoi.register_command(config)
Registers a new command. The function accepts a table with the following fields:
name(string): The subcommand name (e.g."audit").description(string): A short summary shown whenzoiis run without arguments.callback(function): The function to run. It receives one argument:args(a list of strings).
zoi.register_command({
name = "hello",
description = "A friendly greeting from a plugin",
callback = function(args)
local target = args[1] or "World"
zoi.ui.print("Hello, " .. target .. "!", "green")
end
})The Hook System
Hooks allow you to intercept Zoi's core operations. This is useful for auditing, logging, or extending Zoi's behavior.
| Hook | Argument | Trigger Point |
|---|---|---|
on_pre_install | pkg | Before a package installation starts. |
on_post_install | manifest | After a package is successfully installed. |
on_pre_uninstall | manifest | Before a package is removed. |
on_post_uninstall | manifest | After a package is removed. |
on_pre_extension_add | pkg | Before an extension is applied. |
on_post_extension_add | manifest | After an extension is successfully applied. |
on_pre_extension_remove | manifest | Before an extension is reverted. |
on_post_extension_remove | manifest | After an extension is reverted. |
on_pre_create | pkg | Before building/creating an app from a template. |
on_post_create | pkg | After the app is successfully created. |
on_pre_sync | None | Before the package database is updated. |
on_post_sync | None | After the package database sync is complete. |
on_rollback | None | When a rollback operation is triggered. |
on_resolve_shim_version | bin_name | When a Zoi shim is executed. Return a version string to override. |
on_project_install | None | Triggered by zoi install in an empty project. Return true to handle the installation. |
The Project Installation Hook
The on_project_install hook allows plugins to provide native support for non-Zoi projects (like NPM, Python, or Ruby).
zoi.on_project_install(callback)
- Return:
boolean- Returntrueif your plugin has handled the installation. Zoi will stop at the first plugin that returnstrue.
Example: Basic NPM Detection
zoi.on_project_install(function()
if zoi.fs.exists("package.json") then
zoi.ui.print("NPM project detected, installing...", "cyan")
return zoi.sh("npm install") == 0
end
return false
end)The Shim Resolution Hook
The on_resolve_shim_version hook is special because it allows plugins to influence Zoi's version multiplexing at runtime.
zoi.on_resolve_shim_version(callback)
- Argument:
bin_name(string) - The name of the binary being executed (e.g."node"). - Return:
stringornil- Return a version string (e.g."18.19.0") to force that version, ornilto continue with standard resolution.
Example: Automated .nvmrc support
zoi.on_resolve_shim_version(function(bin_name)
if bin_name == "node" or bin_name == "npm" then
local f = io.open(".nvmrc", "r")
if f then
local version = f:read("*a"):gsub("%s+", "")
f:close()
return version
end
end
return nil
end)Data Inspection API
Plugins can query the state of the machine and the Zoi environment.
zoi.list_installed()
Returns an array of InstallManifest objects for every package currently installed on the system across all scopes.
zoi.get_package(name)
Fetches the Package metadata for a specific package from the local database. This works even if the package is not installed.
zoi.project
If you are inside a project with a zoi.yaml file, this table is available:
zoi.project.name(string): The project name.zoi.project.packages(list): A list of strings representing the packages required by the project.
Persistent State
Plugins can save and retrieve data that persists between different Zoi commands. The data is stored in ~/.zoi/plugins/state.json.
zoi.set_data(key, value): Store a value. Supports strings, numbers, and booleans.zoi.get_data(key): Retrieve a value. Returnsnilif the key doesn't exist.
UI & Interaction
Use Zoi's internal UI engine to make your plugins look professional.
zoi.ui.print(text, color)
Prints text to the terminal.
- Colors:
red,green,yellow,blue,cyan,magenta,white.
zoi.ui.table(headers, rows)
Renders a structured ASCII table.
headers: A list of strings.rows: A list of lists (e.g.{{ "row1-col1", "row1-col2" }, { "row2-col1", "row2-col2" }}).
zoi.ui.confirm(prompt)
Displays a y/N prompt and returns a boolean.
zoi.ui.select(prompt, options)
Displays a list of options and returns the 1-based index of the user's choice.
System & Shell
zoi.sh(command)
Runs a command in the system shell (bash on Unix, pwsh on Windows). Returns the integer exit code.
zoi.system
zoi.system.os: Returns"linux","macos", or"windows".zoi.system.arch: Returns"amd64","arm64", etc.
zoi.version
Returns the current Zoi version string (e.g. "1.6.0").
File System (FS) API
Plugins can interact with the local file system.
zoi.fs.exists(path): Returnstrueif the file or directory exists.zoi.fs.read(path): Reads a file's contents and returns it as a string. Returnsnilon error.zoi.fs.write(path, content): Writes a string to a file. Returnstrueon success,falseon error.zoi.fs.list(path): Returns an array of file and directory names within the given path.zoi.fs.delete(path): Deletes a file or recursively deletes a directory. Returnstrueon success,falseon error.zoi.fs.symlink(target, link, is_dir): Creates a symbolic link (or junction on Windows).zoi.fs.copy(src, dest): Copies a file or directory recursively.
Archive API
High-performance native extraction utilities.
zoi.archive.extract(source, dest, strip): Extracts an archive (.zip,.tar.gz,.tgz,.tar.zst,.tar.xz).source(string): Path to the archive.dest(string): Destination directory.strip(int, optional): Number of leading path components to strip from entries.
HTTP API
Plugins can make basic HTTP requests and downloads.
zoi.http.get(url): Performs a GET request. Returns the response body as a string, ornilon error.zoi.http.post(url, body): Performs a POST request with the given string body. Returns the response body as a string, ornilon error.zoi.http.download(url, dest): Downloads a file from a URL to the specified destination. Returnstrueon success.
JSON API
Utilities for working with JSON data.
zoi.json.parse(json_string): Parses a JSON string into a Lua table. Returnsnilon error.zoi.json.stringify(table): Converts a Lua table into a JSON string. Returnsnilon error.
Environment API
Access and manipulate environment variables.
zoi.env.get(name): Returns the value of an environment variable as a string, ornilif not set.zoi.env.set(name, value): Sets an environment variable for the current process.
Data Structures
Package Metadata
The object passed to on_pre_install or returned by get_package.
name: Package name.version: Version string.license: SPDX license identifier.description: Summary.repo: Source repository name.tags: List of tags.
InstallManifest
The object passed to on_post_install and on_pre_uninstall.
name: Package name.version: Installed version.scope:"user","system", or"project".installed_files: A list of every file path created by the package.repo: The repository it originated from.
Advanced Examples
Example 1: The License Enforcer
Prevents the installation of any package with a restricted license.
zoi.on_pre_install(function(pkg)
local restricted = { ["GPL-3.0"] = true, ["AGPL-3.0"] = true }
if restricted[pkg.license] then
zoi.ui.print("SECURITY ALERT: " .. pkg.name .. " uses " .. pkg.license, "red")
if not zoi.ui.confirm("This license is restricted in this environment. Continue?") then
os.exit(1)
end
end
end)Example 2: Project Usage Tracker
Tracks how many times you run commands in a specific project.
if zoi.project then
local key = "runs_" .. zoi.project.name
local count = zoi.get_data(key) or 0
zoi.set_data(key, count + 1)
endExample 3: Custom "Project Doctor" Command
Adds a command to verify your environment.
zoi.register_command({
name = "doctor",
description = "Check if project dependencies are met",
callback = function()
if not zoi.project then
zoi.ui.print("Not in a Zoi project.", "red")
return
end
zoi.ui.print("Checking dependencies for " .. zoi.project.name, "cyan")
local installed = zoi.list_installed()
-- ... logic to cross-reference zoi.project.packages with installed ...
end
})2026 © All Rights Reserved.
- All the content is available under CC BY-SA 4.0, expect where otherwise stated.
- Source code is available on GitLab, licensed under Apache 2.0.
Last updated on
