Skip to content

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).

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:

ActionCommand ID
Toggle Action Editorlua:toggle-visible
ActionCommand IDDescription
Create Scriptlua:createCreate a new .lua file with default template
Save Scriptlua:saveSave the current script to disk
Load Scriptlua:loadLoad a script by filename
Execute Scriptlua:executeRun a script (saved or inline)
List Scriptslua:listList all script filenames
Delete Scriptlua:deleteDelete a script from project and disk

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.

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 a script:

  • Removes it from the in-memory registry
  • Clears the selection if the deleted script was selected
  • Deletes the .lua file from disk

Select a script in the editor and press Run to execute it. Output appears in the console pane.

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.

Every execution returns a LuaExecResult:

PropertyTypeDescription
scriptIdstringFilename or “inline”
successbooleanWhether the script completed without errors
outputstring[]Lines of output from pl.log() and print()
errorstring?Error message if success is false
durationMsnumberExecution time in milliseconds

The result is stored in the Lua Script Module and displayed in the editor’s output panel.

Every Lua script has access to the plinken global table. Full reference at Lua Scripting.

FunctionDescription
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

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
}
}

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
}

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:

CategoryPrograms
3D / VFXblender, houdini, hython, hbatch
Game Enginesunity, Unity
Videoresolve (DaVinci Resolve)
Media Toolsffmpeg, ffprobe, imagemagick, convert, sox
Music / Audiolilypond, csound, sonic-pi
Scriptingpython3, python, node

Programs with paths (/usr/bin/python3) are rejected — only bare names are allowed, resolved via $PATH.

AspectDetails
File location{project}/action/*.lua
LoadingOn project open, all .lua files in action/ are scanned and loaded
SavingIndividual scripts or all scripts can be saved to disk
PersistencePer-project (scripts live in the project directory)
Selected scriptStored as a module parameter, restored on reload
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")
end
for i = 0, 7 do
pl.cmd("track:toggle-mute", { trackId = i })
end
pl.log("Toggled mute on tracks 0-7")
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)
end