Skip to content

Repository files navigation

lua-state - Native Lua & LuaJIT bindings for Node.js

Embed real Lua (5.1-5.5) and LuaJIT in Node.js with native N-API bindings. Create Lua VMs, execute code, share values between languages - no compiler required when using prebuilt binaries.

npmNodeLicense: MIT

FeaturesQuick StartInstallationUsageAPIMappingCLIPerformance

⚙️ Features

  • Multiple Lua versions - Supports Lua 5.1–5.5 and LuaJIT
  • 🧰 Prebuilt Binaries - Lua 5.4.8 included for Linux/macOS/Windows
  • 🔄 Bidirectional integration - Call Lua from JS and JS from Lua
  • 📦 Rich data exchange - Objects, arrays, functions in both directions
  • 🎯 TypeScript-ready - Full type definitions included
  • 🚀 Native performance - Built with N-API (no WebAssembly)

⚡ Quick Start

npm install lua-state
const{ LuaState }=require("lua-state");constlua=newLuaState();lua.setGlobal("x",10);constresult=lua.eval("return x * 2");console.log(result);// 20lua.close();

Lua runs synchronously in the same thread as Node.js and blocks the event loop during execution. This means long-running Lua code will block all JavaScript execution.

📦 Installation

Prebuilt binaries are currently available for Lua 5.4.8 and downloaded automatically from GitHub Releases. If a prebuilt binary is available for your platform, installation is instant - no compilation required. Otherwise, it will automatically build from source.

Requires Node.js 18+, tar (system tool or npm package), and a valid C++ build environment (for node-gyp) if binaries are built from source.

Tip: if you only use prebuilt binaries you can reduce install size with npm install lua-state --no-optional.

🧠 Basic Usage

constlua=newLuaState();

Get Current Lua Version

lua.getVersion();// "Lua 5.4.8" or "LuaJIT 2.1.0-beta3"

Evaluate Lua Code

lua.eval("return 2 + 2");// 4lua.eval('return "a", "b", "c"');// ["a", "b", "c"]

Share Variables

// JS → Lualua.setGlobal("user",{name: "Alice",age: 30});// Lua → JSlua.eval("config = { debug = true, port = 8080 }");lua.getGlobal("config");// { debug: true, port: 8080 }lua.getGlobal("config.port");// 8080lua.getGlobal("config.missing");// undefined (path exists but value is missing)lua.getGlobal("missing");// null (global variable does not exist)

Call Functions Both Ways

// Call Lua from JSlua.eval("function add(a, b) return a + b end");constadd=lua.getGlobal("add");add(5,7);// 12// Call JS from Lualua.setGlobal("add",(a,b)=>a+b);lua.eval("return add(3, 4)");// 12// JS function with multiple returnslua.setGlobal("getUser",()=>["Alice",30]);lua.eval("name, age = getUser()");lua.getGlobal("name");// "Alice"lua.getGlobal("age");// 30// JS function that throws an errorlua.setGlobal("throwError",()=>{thrownewError("Something went wrong");});const[success,err]=lua.eval(` local success, err = pcall(throwError); return success, err`);success;// falseerr.message;// "Something went wrong"

Get Table Length

lua.eval("items = { 1, 2, 3 }");lua.getLength("items");// 3

File Execution

-- config.luareturn {
title="My App",
features= { "auth", "api", "db" }
}
constconfig=lua.evalFile("config.lua");config.title;// "My App"

Lua Errors

// All errors are instances of LuaError// Syntax errortry{lua.eval("return 1+");}catch(err){errinstanceofLuaError;// trueerr.message;// [string "return 1+"]:1: unexpected symbol near <eof>}// String errortry{lua.eval('error("foo")');}catch(err){err.message;// [string "error(\"foo\")"]:1: fooerr.stack;// Lua-style stack trace (not a JavaScript stack)}// Table error (non-string)try{lua.eval('error({ foo = "bar" })');}catch(err){err.message;// ""err.cause;// { foo: "bar" }}

🕒 Execution Model

All Lua operations in lua-state are synchronous by design. The Lua VM runs in the same thread as JavaScript, providing predictable and fast execution. For asynchronous I/O, consider isolating Lua VMs in worker threads.

  • await is not required and not part of API - calls like lua.eval() block until completion
  • Lua coroutines work normally within Lua, but are not integrated with the JavaScript event loop
  • Asynchronous bridging between JS and Lua is intentionally avoided to keep the API simple, deterministic, and predictable.

⚠️Note: Lua 5.1 and LuaJIT have a small internal C stack, which may cause stack overflows when calling JS functions in very deep loops. Lua 5.1.1+ uses a larger stack and does not have this limitation.

🧩 API Reference

LuaState Class

Represents an isolated, synchronous Lua VM instance.

newLuaState(options?: {libs?: string[]|null// Libraries to load, use null or empty array to load none (default: all)})

Available libraries:base, bit32, coroutine, debug, io, math, os, package, string, table, utf8

Methods

MethodReturnsDescription
eval(code)LuaValueExecute Lua code
evalFile(path)LuaValueRun Lua file
setGlobal(name, value)thisSet global variable
getGlobal(path)LuaValue | null | undefinedGet global value
getLength(path)number | null | undefinedGet length of table
getVersion()stringGet Lua version
close()voidClose Lua VM

⚠️Note on close():
Lua VM memory is not managed by the JavaScript garbage collector.
It is recommended to call close() when the instance is no longer needed to avoid holding native memory.
Calling close() multiple times has no effect.
Any method call after close() will throw an error.

LuaError Class

Errors thrown from Lua are represented as LuaError instances.

Properties

PropertyTypeDescription
name"LuaError"Error name
messagestringError message (empty if a non-string value was passed to error(...))
stackstring | undefinedLua stack traceback (not a JavaScript stack trace)
causeunknown | undefinedValue passed to error(...) when it is not a string

🔄 Type Mapping (JS ⇄ Lua)

When values are passed between JavaScript and Lua, they’re automatically converted according to the tables below. Circular references are preserved during conversion.

JavaScript → Lua

JavaScript TypeBecomes in LuaNotes
stringstringUTF-8 encoded
numbernumber64-bit double precision
booleanboolean
datenumberMilliseconds since Unix epoch (not converted back to Date)
undefinednil
nullnil
functionfunctionCallable from Lua
objecttableRecursively copies enumerable fields. Non-enumerable properties are ignored
arraytableIndexed from 1 in Lua
bigintstring

Lua → JavaScript

Lua TypeBecomes in JavaScriptNotes
stringstringUTF-8 encoded
numbernumber64-bit double precision
booleanboolean
nilnull
tableobjectConverts to POJO (array-like tables are NOT converted to JavaScript arrays)
functionfunctionCallable from JS

⚠️Note: Conversion is not always symmetrical - for example,
a JS Date becomes a number in Lua, but that number won’t automatically
convert back into a Date when returned to JS.

⚠️ When Lua returns multiple values, they are returned as an array in JavaScript.

🧩 TypeScript Support

This package provides full type definitions for all APIs.
You can optionally specify the expected Lua value type for stronger typing and auto-completion:

import{LuaState}from"lua-state";constlua=newLuaState();constanyValue=lua.eval("return { x = 1 }");// LuaValue | undefinedconstnumberValue=lua.eval<number>("return 42");// number

🧰 CLI

install If you need to rebuild with a different Lua version or use your system Lua installation, you can do it with the included CLI tool:
npx lua-state install [options]

Options:

The build system is based on node-gyp and supports flexible integration with existing Lua installations.

OptionDescriptionDefault
-m, --modedownload, source, or systemdownload
-f, --forceForce rebuildfalse
-v, --versionLua version for download build5.4.8
--source-dir, --include-dirs, --librariesCustom paths for source/system builds-

Examples:

# Rebuild with Lua 5.2.4
npx lua-state install --force --version=5.2.4
# Rebuild with system Lua
npx lua-state install --force --mode=system --libraries=-llua5.4 --include-dirs=/usr/include/lua5.4
# Rebuild with system or prebuilt LuaJIT
npx lua-state install --force --mode=system --libraries=-lluajit-5.1 --include-dirs=/usr/include/luajit-2.1
# Rebuild with custom lua sources
npx lua-state install --force --mode=source --source-dir=deps/lua-5.1/src

⚠️Note: LuaJIT builds are only supported in system mode (cannot be built from source).

run

Run a Lua script file or code string with the CLI tool:

npx lua-state run [file]

Options:

OptionDescriptionDefault
-c, --code <code>Lua code to run as string-
--jsonOutput result as JSONfalse
-s, --sandbox [level]Run in sandbox mode (light, strict)-

Examples:

# Run a Lua file
npx lua-state run script.lua
# Run Lua code from string
npx lua-state run --code "print('Hello, World!')"# Run and output result as JSON
npx lua-state run --code "return { name = 'Alice', age = 30 }" --json
# Run in sandbox mode (light restrictions)
npx lua-state run --sandbox light script.lua
# Run in strict sandbox mode (heavy restrictions)
npx lua-state run --sandbox strict script.lua

🌍 Environment Variables

These variables can be used for CI/CD or custom build scripts.

VariableDescriptionDefault
LUA_STATE_MODEBuild mode (download, source, system)download
LUA_STATE_FORCE_BUILDForce rebuildfalse
LUA_VERSIONLua version (for download mode)5.4.8
LUA_SOURCE_DIRLua source path (for source mode)-
LUA_INCLUDE_DIRSInclude directories (for system mode)-
LUA_LIBRARIESLibrary paths (for system mode)-

🔍 Compared to other bindings

PackageLua versionsTypeScriptAPI StyleNotes
fengari5.2 (WASM)Pure JSBrowser-oriented, slower
lua-in-js5.3 (JS interpreter)Pure JSNo native performance
wasmoon5.4 (WASM)Async/PromiseNode/Browser compatible
node-lua5.1Native (legacy NAN)Outdated, Linux-only
lua-native5.4 (N-API)Native N-APIActive project, no multi-version support
lua-state5.1–5.5, LuaJITNative N-APIMulti-version, prebuilt binaries, modern API

⚡ Performance

lua-state uses native N-API bindings and provides low-overhead communication between JavaScript and Lua.

Performance depends heavily on the type of data being exchanged:

  • Primitive values are extremely fast
  • Flat objects are moderately fast
  • Deep or large object graphs are significantly more expensive to serialize

To run the benchmark locally: npm run bench

🧪 Quality Assurance

Each native binary is built and tested automatically before release.
The test suite runs JavaScript integration tests to ensure stable behavior across supported systems.

🪪 License

MIT License © quaternion

🌐 GitHub📦 npm

Releases

Packages

Contributors

Languages