How plugins work
Lua 5.4 plugins that run in ShopFast's sandbox, with a typed host API, declared permissions you approve, versions and roll back.
Plugins let a store add what ShopFast does not ship: a payment provider, a key generator, a fraud rule, a webhook format. They are a few dozen lines of Lua, run next to the checkout in ShopFast’s sandbox, and never need a server.
Four kinds
| Kind | Hooks | Runs when |
|---|---|---|
| Payment method | create_payment, verify_callback |
A buyer picks the plugin’s method at checkout; the provider calls back |
| Delivery | deliver |
An order with one of the plugin’s products is paid |
| Checkout rule | validate_cart |
Before every checkout starts |
| Webhook transformer | transform |
Before an event is signed and sent to an endpoint it is attached to |
A plugin is two files: plugin.lua, which returns a table of hook functions, and manifest.json, which declares
what it is and what it may do.
local plugin = {}
function plugin.validate_cart(ctx, input)
if input.cart.emailDomain == "mailinator.com" then
return { action = "block", message = "Please use a permanent email address.", reason = "disposable inbox", points = 80 }
end
return { action = "allow" }
end
return plugin
The sandbox
Each run gets a fresh Lua 5.4 state (WebAssembly, in a worker thread) that is thrown away afterwards. Nothing leaks between runs, plugins or stores.
- Only safe libraries.
string,table,math,utf8,coroutineand the safe parts of the base library. Noio,os,debug,package,require,load,dofileorstring.dump; bytecode never runs. - No JavaScript objects. Plugin code only sees Lua values; host functions take and return strings and numbers.
- Budgets per run: VM instructions, CPU time (time spent waiting on the network does not count), heap size and a
wall-clock deadline after which the worker is terminated. A spent budget cannot be caught: every
pcall,xpcallandcoroutine.resumere-raises it. - No network except
http.fetchto hosts the manifest lists, through an SSRF-safe proxy: https only, no private or metadata addresses, no redirects, 5 s per request.
| Hook | Instructions | CPU | Memory | HTTP requests | Wall clock |
|---|---|---|---|---|---|
create_payment, verify_callback |
5M | 500 ms | 16 MB | 4 | 12 s |
deliver |
10M | 1 s | 32 MB | 4 | 15 s |
validate_cart |
2M | 200 ms | 8 MB | 1 | 3 s |
transform |
2M | 250 ms | 8 MB | 0 | 2 s |
Permissions you approve
The manifest’s permissions say what the plugin may do beyond computing: the hosts http.fetch may call and whether
it gets a key-value store. Secret settings, the kind’s own access (for example “start
payments at checkout”) and these permissions are shown to the store owner or an admin in plain words. A version runs
only after its exact set of permissions is approved; a new version that asks for more needs a new approval, and a
version whose permissions changed since the approval never runs.
Versions and roll back
Saving creates an immutable version with its source, manifest and SHA-256. Nothing changes until you activate one. Activating an older version is the roll back, and it is instant. Every live run records the version it ran.
Where to write them
Open Settings → Developers → Plugins: start from a template of each kind, or install a reviewed package from the catalog. The workbench has live linting in the sandbox, completions for the host API, a test runner with fixtures, live runs with their logs and metrics, settings and versions.