XLua2


General Info

XLua2 is an evolution of the original XLua X-Plane plugin that allows aircraft authors to create text based Lua programs to customize their aircraft without having to endure the complex process of compiling a native C or C++ plugin. The XLua2 plugin supports a new API/syntax aligned with Laminar's C based SDK/API, but it still supports the original legacy XLua syntax folks are used to. From here on out, we will refer to the original API syntax as XLua1 and the new XPLM based API syntax as XLua2, and when we simply say XLua, then we are generically referring to concepts that are independent of the API syntax.

The XLua1 / XLua2 API syntax are not compatible. You cannot mix/match them within the same XLua module; however, you CAN mix/match XLua & XLua2 modules within the same aircraft project. Whether or not you choose to do so depends on your variable scope requirements, your own programming skills, and your understanding of X-Plane's plugin architecture and dataref/command systems. There are situations where mixing/matching could be appropriate depending on your goals.

XLua1 is suitable for beginners and novices and is perfectly useful when working with datarefs and commands; however, XLua1 have no capability to create custom EFIS graphics or pop-up windows. XLua2 on the other hand, can be used to create pop-up windows and EFIS graphics, but is more complex and advanced. XLua1 scripts can be migrated to XLua2 scripts as described here

You can peruse each API syntax reference at the following links:


XLua2 Scripts Directory

XLua scripts are loaded per aircraft, and the scripts must reside within the same directory as the aircraft itself (*.acf). In addition to the text based scripts file(s), the XLua plugin also resides in the aircraft folder. The folder hierarchy and naming schema is very specific. XLua expects the directory layout shown below. Entries beginning with the prefix my_ are user created and named as required, while all other folder/file names are reserved and expected by XLua. The file, init.lua is required and in general, should not be modified.

my_Aircraft_folder/
└── plugins/
    └── xlua/
        ├── lin_x64/xlua2.xpl
        ├── mac_x64/xlua2.xpl
        ├── win_x64/xlua2.xpl
        ├── init.lua
        └── scripts/
            └── my_script-1/
                └── my_script-1.lua
            └── my_script-2/
                └── my_script-2.lua


XLua2 modules

User created scripts are to reside within the scripts/ folder. At minimum, one Lua file must reside within a folder of the same name as the Lua file. This paradigm is illustrated above. The Lua file of the same name as the folder is called the master script, and is the entry point for code execution. If your create additional Lua files in the same directory, they will have to be loaded by the master script via Lua's dofile or require techniques. XLua will ONLY run the master script file.

A script folder, its master script file, and any other optional Lua files within the folder, together, comprise what we call an XLua Module. In the above example, there are two XLua modules illustrated. my_script-1 and my_script-2


Loading and isolation

XLua modules cannot share data with one another, they are completely isolated, each in its own Lua environment. Lua symbols (variables / tables / functions) in one module cannot be accessed by other modules. Communication and data sharing between modules is effected through X-Plane's datarefs and commands system, e.g. one XLua module may create a dataref, which other module may then find and read. X-Plane acts as a go-between in this scenario.

Script modules are loaded when the aircraft is loaded, and unloaded when the aircraft is unloaded. Depending on configuration, a script may also be reloaded when the user starts a new flight. Whether that happens automatically or has to be opted into is version-specific; see the per-version files.


Lifecycle events

A script sees the following lifecycle, in roughly this order. The bullets below name each concept neutrally; the spelling — which functions to define, what entry points to register, which callback signatures to use — is in the per-version files.

A script is only required to implement the callbacks it cares about. Unused callbacks can be omitted entirely.


Interacting with X-Plane: the two primitives

Nearly every script is built on two building blocks. Neither is specific to XLua — they are how every X-Plane plugin talks to the sim.


Datarefs

A dataref is a named, typed value the sim publishes. Examples: the aircraft's indicated airspeed, the state of the battery master switch, the position of the flaps handle. Datarefs are identified by a path-like string such as sim/cockpit2/electrical/battery_on[0]. Each has a type (number, integer array, float array, byte block / string) and may be read-only or writable. Plugins can also publish their own datarefs so their state is visible to other plugins, the instrument panel, or X-Plane's animation system.

A script typically looks up its datarefs once at load time and reuses the resulting handles for the life of the script. Reading or writing is cheap; looking up by string is comparatively expensive.

The catalogue of datarefs published by X-Plane ships with the sim at Resources/plugins/DataRefs.txt and is documented at https://developer.x-plane.com/datarefs/.


Commands

A command is a named, invokable action. Examples: sim/magnetos/magnetos_up_1, sim/starters/engage_starter_1. X-Plane publishes thousands of built-in commands, and plugins and scripts can publish their own.

The same command can be triggered from many sources. A keyboard shortcut, a click on a cockpit panel hot spot, a button or axis on a joystick or yoke, a USB hardware device (radio panels, throttle quadrants, switch panels), a VR controller action, another plugin, or another Lua script — all of these funnel into the same command and look identical to anything handling it. A script that registers logic against a command does not need to — and cannot — tell one trigger source from another. This is what makes commands the right way to expose any user-actionable behaviour: attach the logic to a command once, and every input path the user has configured triggers it for free.

A command has three phases: begin (the trigger fires), continue (the trigger is held), and end (the trigger is released). A script can:

As with datarefs, lookup is done once and the handle is reused.

Custom datarefs and custom commands

Most non-trivial aircraft scripts create their own datarefs and commands in addition to consuming the built-in ones.

Custom datarefs are the normal way to expose a script-computed value to other parts of the aircraft. A smoothly animated needle, a knob position, a custom "autopilot is armed" flag — the script computes the value each frame and writes it into its dataref. The instrument panel and .obj animations read the same dataref by name, with no need to know anything about Lua.

Custom commands are how a script exposes a button or switch action in a way the user can rebind. Rather than hard-wiring a keystroke to Lua behaviour, the script creates a command with a stable path (for example laminar/c172/ignition_up) and installs a handler. The user — or a panel click spot — can then trigger that command from anywhere.


Timed and per-frame work

Scripts commonly need to run a piece of logic on a schedule: animate a needle, time out a warning, run a periodic check. Every version provides a mechanism for this; the spelling is covered in the per-version files.

If a script needs to run every frame, the idiomatic answer is always a per-frame / flight-loop callback rather than a self-rescheduling timer.


Initialisation and cleanup

Setup and teardown follow the same shape in every syntax version. The rule is simple: do one-off work once, from the appropriate lifecycle callback for the version you are using; do per-frame work per frame. The per-version documents name the exact callbacks to use for each stage.

Look up references once, at initialisation. Looking up a dataref or command by its string name is comparatively expensive, and the returned handle does not change over the life of the script. Do the lookup at script or aircraft load and keep the result in a local or script-level variable for reuse. Never look up by string inside a per-frame callback.

Create assets once, at initialisation. Custom datarefs, custom commands, command handlers installed on built-in commands, flight loops, menus — anything the script registers with the runtime — should be created during the same initialisation step as the lookups above. Never create them inside a per-frame callback.

Release every asset the script created, at shutdown. If the script registered it, the script must tear it down: custom datarefs unregistered, custom commands destroyed, installed command handlers unregistered, flight loops destroyed. This is not optional. X-Plane does try to clean up any abandoned assets but this can still leave the sim in a state that only a full restart clears, or cause runtime crashes.

Clear overrides too. If the script set a sim/operation/override/* dataref to take control of a sim behaviour, clear it again at shutdown so the sim resumes normal operation. The override is not an asset the script "owns", but it has the same lifecycle rule. You should almost certainly clear override for the disable stage and re-enable them in the enable stage, so that if the script or entire plugin is disabled, your overridden behaviour does not remain in place.

Dataref and command lookups do not need to be released — those handles are owned by the runtime, not by the script. Only things the script explicitly created need explicit teardown.


Debugging

Log.txt is the first place to look for anything a script does or gets wrong. Scripts share X-Plane's main log file — there is no separate Lua log and no runtime call to arrange. Anything a script passes to Lua's standard print() appears in Log.txt with a timestamp and an I/LUA tag:

0:01:05.103 I/LUA: Running scripts/A333.math/A333.math.lua
0:01:05.103 I/LUA: Running scripts/A333.sound/A333.sound.lua
0:01:05.103 I/LUA: Running scripts/A333.switches/A333.switches.lua
0:01:05.103 I/LUA: Running scripts/A333.systems/A333.systems.lua

Uncaught Lua errors and exceptions — a nil indexed as a table, a bad argument to a runtime call, a syntax error at script load — are logged the same way, with the error message and any available traceback tagged I/LUA. A script that is misbehaving will almost always have left an explanation in Log.txt; check there before anywhere else.

print() is the primary debugging tool — reach for it first. For stack context at an error site, use Lua's standard debug.traceback(); a typical idiom inside an error handler is print(debug.traceback()).

The standard workflow for diagnosing a script problem is to watch Log.txt live while working on the aircraft. On macOS and Linux:

tail -f "<X-Plane folder>/Log.txt"

On Windows, PowerShell's Get-Content -Wait -Tail 200 Log.txt does the same job. Whichever tool you use, the principle is the same: keep the log in view, reload the aircraft or exercise the script, and the error — with its trace — will appear inline with the rest of the sim's output.

Lua's standard pcall is available if a script needs to catch an error itself rather than let it propagate to the runtime — for example, when loading an optional file that may not exist.

Other debugging approaches are possible. In particular, XLua 1.5 and later make Dear ImGui available to scripts, which can be used to build interactive debug windows (live variable inspectors, button-driven state tweaks, and so on). Building those is out of scope for this document.

StackTracePlus (https://github.com/ignacio/StackTracePlus) is an optional third-party library that produces richer traces than the stock debug.traceback. To enable it, drop StackTracePlus.lua next to the runtime's init.lua and uncomment the short activation block at the top of that file.


LuaJIT tuning

XLua runs on LuaJIT. The compiler's default parameters are tuned for the kind of scripts that typically ship with X-Plane and as a rule should be left alone.

Two known exceptions:

Unless one of the above applies, leave LuaJIT parameters at their defaults.


Files that ship with XLua

The XLua deployment tree contains four things a script author may care about:


Gotchas

Short points that apply across every syntax version. The per-version documents note anything version-specific.


Choosing a syntax version

For any new aircraft, use the latest supported syntax version. Older syntax versions remain supported so existing aircraft continue to work without modification, and there is no obligation to update a working script just because a new syntax version has appeared.

If you do choose to update an older script to a newer syntax version, see the migration guide(s) — currently migrating-v1-to-v2.md.


Further reading