Lua Action Scripts
The Action Editor is Plinken’s built-in Lua script editor. Scripts are .lua files stored in the project’s action/ folder — the filename is the identity (no manifest, no UUIDs).
Opening the Action Editor
Section titled “Opening the Action Editor”The Action Editor is part of Plinken’s panel system. It does not have a default shortcut — toggle it from the Action menu or execute the command:
| Action | Command ID |
|---|---|
| Toggle Action Editor | lua:toggle-visible |
Managing Scripts
Section titled “Managing Scripts”| Action | Command ID | Description |
|---|---|---|
| Create Script | lua:create | Create a new .lua file with default template |
| Save Script | lua:save | Save the current script to disk |
| Load Script | lua:load | Load a script by filename |
| Execute Script | lua:execute | Run a script (saved or inline) |
| List Scripts | lua:list | List all script filenames |
| Delete Script | lua:delete | Delete a script from project and disk |
Creating a Script
Section titled “Creating a Script”When you create a new script, Plinken generates a file with the default template:
-- Plinken Action Script-- Use pl.cmd("category:action", { payload }) to execute commands-- Use pl.log("message") to print to the output console
pl.log("Hello from Lua!")
-- Example: get transport state-- pl.cmd("transport:toggle-play")
-- Example: mute a track-- pl.cmd("track:toggle-mute", { trackId = 0 })Scripts are stored in {project}/action/{filename}.lua. The filename is the script ID — renaming the file renames the script.
Renaming
Section titled “Renaming”Rename a script by changing its filename. Plinken updates:
- The in-memory script registry (BTreeMap, alphabetically sorted)
- The selected script reference (if the renamed script was selected)
- The file on disk (atomic rename)
If the target filename already exists, the rename is rejected.
Deleting
Section titled “Deleting”Deleting a script:
- Removes it from the in-memory registry
- Clears the selection if the deleted script was selected
- Deletes the
.luafile from disk
Executing Scripts
Section titled “Executing Scripts”From the Action Editor
Section titled “From the Action Editor”Select a script in the editor and press Run to execute it. Output appears in the console pane.
From the Command System
Section titled “From the Command System”Execute a saved script by filename:
pl.cmd("lua:execute", { scriptId = "my-script.lua" })Execute inline Lua source code (no saved file needed):
pl.cmd("lua:execute", { source = 'pl.log("Hello from inline Lua!")'})The source parameter is used by Plinky (the AI assistant) to run generated code without creating a file.
Execution Result
Section titled “Execution Result”Every execution returns a LuaExecResult:
| Property | Type | Description |
|---|---|---|
scriptId | string | Filename or “inline” |
success | boolean | Whether the script completed without errors |
output | string[] | Lines of output from pl.log() and print() |
error | string? | Error message if success is false |
durationMs | number | Execution time in milliseconds |
The result is stored in the Lua Script Module and displayed in the editor’s output panel.
The plinken Global
Section titled “The plinken Global”Every Lua script has access to the plinken global table. Full reference at Lua Scripting.
Core Functions
Section titled “Core Functions”| Function | Description |
|---|---|
pl.cmd(id, payload?) | Execute a DAW command (deferred to next render frame) |
pl.log(msg) | Print to the script output console |
pl.sleep(ms) | Pause execution (max 30,000 ms per call) |
pl.get_context() | Get session context (project, account, space, team, video) |
pl.get_transport() | Get transport state (tempo, playing, time sig, cycle, record) |
plinken.list_commands() | List all registered command IDs |
pl.exec(program, args?) | Run an allowlisted external program |
plinken.allowed_programs() | List programs available to exec |
pl.get_setting(key) | Read a setting from the SQLite database |
pl.get_all_settings() | Read all settings as a key-value table |
print(...) | Redirected to the output buffer |
Context Table
Section titled “Context Table”pl.get_context() returns:
{ projectName = "My Song", account = { email = "user@example.com", displayName = "User", userId = "...", deviceId = "..." }, space = { -- nil if not in a Space moniker = "my-space", title = "My Space", isOwner = true }, team = { isInCall = false, callState = "Idle", isPerformanceMode = false, performerUserId = nil, peers = { ... } }, video = { isStudioMode = false, isBroadcasting = false }}Transport Table
Section titled “Transport Table”pl.get_transport() returns:
{ isPlaying = false, tempo = 120.0, timeSignature = "4/4", timeSigNum = 4, timeSigDen = 4, cycleEnabled = false, cycleStartTicks = 0, cycleEndTicks = 3840, metronomeEnabled = false, metronomeVolume = 0.8, countInEnabled = false, countInBars = 1, recordArmed = false}External Program Execution
Section titled “External Program Execution”pl.exec() runs allowlisted programs with no shell involvement (safe from injection):
local result = pl.exec("ffprobe", { "-v", "quiet", "-print_format", "json", "-show_format", "audio/my-track.wav"})
pl.log("Exit code: " .. result.code)pl.log("Output: " .. result.stdout)Allowlisted programs:
| Category | Programs |
|---|---|
| 3D / VFX | blender, houdini, hython, hbatch |
| Game Engines | unity, Unity |
| Video | resolve (DaVinci Resolve) |
| Media Tools | ffmpeg, ffprobe, imagemagick, convert, sox |
| Music / Audio | lilypond, csound, sonic-pi |
| Scripting | python3, python, node |
Programs with paths (/usr/bin/python3) are rejected — only bare names are allowed, resolved via $PATH.
Storage & Persistence
Section titled “Storage & Persistence”| Aspect | Details |
|---|---|
| File location | {project}/action/*.lua |
| Loading | On project open, all .lua files in action/ are scanned and loaded |
| Saving | Individual scripts or all scripts can be saved to disk |
| Persistence | Per-project (scripts live in the project directory) |
| Selected script | Stored as a module parameter, restored on reload |
Examples
Section titled “Examples”Toggle playback based on state
Section titled “Toggle playback based on state”local t = pl.get_transport()if t.isPlaying then pl.cmd("transport:stop") pl.log("Stopped")else pl.cmd("transport:toggle-play") pl.log("Playing at " .. t.tempo .. " BPM")endBatch mute tracks
Section titled “Batch mute tracks”for i = 0, 7 do pl.cmd("track:toggle-mute", { trackId = i })endpl.log("Toggled mute on tracks 0-7")Run ffmpeg from Lua
Section titled “Run ffmpeg from Lua”local result = pl.exec("ffmpeg", { "-i", "audio/input.wav", "-af", "aecho=0.8:0.88:60:0.4", "audio/echo.wav"})
if result.code == 0 then pl.log("Echo effect applied!") pl.cmd("assets:import", { sourcePath = "audio/echo.wav" })else pl.log("Error: " .. result.stderr)endSee Also
Section titled “See Also”- Lua Scripting — Full Lua API reference
- Lua Actions — Detailed function reference
- Notes — Rich text notes alongside your project
- Keyboard Shortcuts — Full shortcut reference