Developer Docs

UI API

Windows, widget factories, toolbar items, song data, and preferences β€” everything you need to build plugin UI.

Event Subscriptions

plugin.on(event: string, handler: function) β†’ number

Subscribes to a named event. Returns a subscription ID you can pass to plugin.off to remove the subscription. Most plugins subscribe in on_init and never need to unsubscribe.

local id = plugin.on("note", function(e)
  -- e is a table of event fields
end)
plugin.off(event: string, id: number)

Removes the subscription with the given ID from the named event. No-op if the ID does not exist.

Windows

Plugins can open any number of floating windows. Windows are automatically closed when the plugin shuts down.

plugin.new_window(title: string) β†’ window

Creates a new floating window with the given title. The window is not visible until you call plugin.window_show. Returns an opaque window handle used by the other window functions.

local win = plugin.new_window("My Plugin")
plugin.window_set_content(win, my_widget)
plugin.window_show(win)
plugin.window_set_content(win: window, widget: widget)

Replaces the entire content of the window with the given widget. Can be called any number of times to swap content dynamically.

plugin.window_show(win: window)

Makes the window visible. On first call, this creates the underlying OS window. Safe to call multiple times β€” subsequent calls bring the window to the front.

plugin.window_hide(win: window)

Hides the window without destroying it. The window can be shown again later via plugin.window_show.

plugin.window_close(win: window)

Destroys the window. After calling this, the window handle is invalid and must not be used again.

plugin.window_on_closed(win: window, fn: function)

Registers a callback invoked when the user closes the window via the OS close button. Use this to track visibility state so your toolbar action can reopen the window.

local visible = true

plugin.window_on_closed(win, function()
  visible = false
end)

plugin.add_toolbar_item("My Plugin", function()
  visible = true
  plugin.window_show(win)
end)

Widget Factories

Widget factory functions return opaque widget handles. Pass them to plugin.window_set_content or to a container like plugin.new_vbox.

plugin.new_label(text: string) β†’ widget

Creates a read-only text label. Supports multi-line text β€” use \n to insert line breaks. Update the text at any time with plugin.set_label_text.

local lbl = plugin.new_label("Hello, world!")
-- Later, update it:
plugin.set_label_text(lbl, "Updated text")
plugin.set_label_text(label: widget, text: string)

Updates the text of a widget previously created by plugin.new_label. The widget refreshes immediately. Calling this with a non-label widget is a no-op.

plugin.new_button(label: string, action: function) β†’ widget

Creates a clickable button. action is called with no arguments when the button is tapped.

local btn = plugin.new_button("Reset", function()
  plugin.set_label_text(my_label, "β€”")
end)
plugin.new_entry(placeholder: string [, onChange: function]) β†’ widget

Creates a single-line text input. onChange is optional β€” if provided it is called with the current string value every time the user types.

local entry = plugin.new_entry("Search…", function(text)
  plugin.set_label_text(result_label, "Searching: " .. text)
end)
plugin.new_vbox(widget1, widget2, …) β†’ widget

Stacks any number of widgets vertically with equal spacing. The result is itself a widget and can be nested inside other containers or set as a window's content.

local layout = plugin.new_vbox(
  plugin.new_label("Track"),
  track_entry,
  plugin.new_button("Apply", on_apply)
)
plugin.window_set_content(win, layout)
plugin.new_hbox(widget1, widget2, …) β†’ widget

Lays out any number of widgets horizontally side by side with equal spacing. Like new_vbox, the result is itself a widget that can be nested inside other containers.

local btn_row = plugin.new_hbox(
  plugin.new_button("β—€ Prev", on_prev),
  plugin.new_button("β–Ά Play", on_play),
  plugin.new_button("Next β–Ά", on_next)
)
local layout = plugin.new_vbox(content_label, btn_row)
plugin.window_set_content(win, layout)
plugin.new_select(options: table, onChange: function) β†’ widget

Creates a dropdown select widget. options is a Lua array of strings (e.g. G). The onChange callback receives the selected string whenever the user changes the selection. The first option is selected by default.

local current_key = "Am"
local key_select = plugin.new_select(C, function(val)
  current_key = val
  render()  -- update display for new key
end)

local key_row = plugin.new_hbox(
  plugin.new_label("Key:"),
  key_select
)
plugin.new_slider(min: number, max: number, step: number, initial: number [, onChange: function]) β†’ widget

Creates a slider for numeric input. The onChange callback is optional and receives the current numeric value whenever the user moves the slider.

local bpm_label = plugin.new_label("Manual BPM: 120")
local bpm_slider = plugin.new_slider(40, 240, 1, 120, function(value)
  plugin.set_label_text(
    bpm_label,
    "Manual BPM: " .. tostring(math.floor(value + 0.5))
  )
end)

local controls = plugin.new_vbox(bpm_label, bpm_slider)
plugin.new_mono_label(text: string) β†’ widget

Creates a label rendered in a monospace font with word-wrap disabled. Ideal for displaying structured text such as guitar tab notation, ASCII diagrams, or code output where column alignment matters. Update the text with plugin.set_label_text.

local tab_display = plugin.new_mono_label("")

-- Later, update with formatted tab notation:
plugin.set_label_text(tab_display, [[
e|--5--8--|
B|--5--8--|
G|--5--7--|
D|--5--7--|
A|--5--7--|
E|--5--8--|]])
plugin.new_neck_diagram(toml: string) β†’ widget, err

Creates a guitar fretboard diagram widget (~200Γ—175 dp) that can be placed in any window layout alongside other controls. The diagram shows 6 strings and up to 5 frets. Its content is described by a TOML string with a starting_fret field, an optional muted array, and one [[dot]] table per marker. Returns the widget on success, or nil, error_string on parse failure.

Top-level TOML fields:

Field Type Description
starting_fretintFirst fret shown at the top (default 1). When > 1 the fret number appears beside the diagram and the nut line is not bolded.
mutedint[]Optional list of string numbers (1–6) that are not played. Draws an Γ— above the nut for each. E.g. muted = [5, 6].

Each [[dot]] supports:

Field Type Description
stringintString number β€” 1 = high e (thinnest), 6 = low E (thickest)
fretintAbsolute fret number; must be within [starting_fret, starting_fret + 4]
colorstringOptional CSS hex color for the dot, e.g. "#E74C3C". Defaults to the theme primary color.
labelstringOptional short text label drawn inside the dot, e.g. "R" or "3b".
numberintOptional numeric fallback label for backwards compatibility. Ignored when label is present.

Note: Because TOML uses [[section]] syntax, the TOML string inside Lua must use [==[ β€¦ ]==] long-string delimiters instead of [[ ]] to avoid a premature closing of the Lua string.

-- C major degrees (use [==[ ]==] delimiters, not [[ ]])
local diagram = plugin.new_neck_diagram([==[
starting_fret = 1
[[dot]]
string = 5
fret   = 3
label  = "R"
[[dot]]
string = 4
fret   = 2
label  = "3"
[[dot]]
string = 2
fret   = 1
label  = "5"
]==])

plugin.window_set_content(win, plugin.new_vbox(
  plugin.new_label("C major"),
  diagram
))
plugin.update_neck_diagram(diagram: widget, toml: string) β†’ true, err

Replaces the content of a diagram previously created by plugin.new_neck_diagram and immediately redraws it. Safe to call from event callbacks (runs on the UI thread internally). Returns true on success or nil, error_string on parse failure.

-- Switch between chords when the user picks one from a dropdown
local diagram = plugin.new_neck_diagram(C_TOML)

local selector = plugin.new_select(G, function(name)
  plugin.update_neck_diagram(diagram, CHORDS[name])
end)

Toolbar

plugin.add_toolbar_item(tooltip: string, action: function)

Appends a button to the plugin toolbar strip in the Tabatura UI. tooltip is shown on hover and acts as the button's accessible label. The typical use is to let users reopen a window they have closed.

plugin.add_toolbar_item("Note Display", function()
  plugin.window_show(win)
end)

Song Data

plugin.current_song() β†’ table | nil

Returns a snapshot of the currently loaded song's metadata, or nil if no file is open. The snapshot is taken at call time and does not update β€” call this again after a file.open or playback.start event to get fresh data.

Field Type Description
title string Song title
artist string Artist name
album string Album name
subtitle string Subtitle / transcriber
tempo number Tempo in BPM
time_signature string e.g. "4/4"
key_signature string e.g. "C Major"
track_names string[] Array of track name strings
plugin.on("file.open", function(_)
  local s = plugin.current_song()
  if s then
    for i, name in ipairs(s.track_names) do
      print(i .. ": " .. name)
    end
  end
end)

Persistent Preferences

Each plugin gets its own namespaced preference store, backed by the host app's preference system. Keys and values are plain strings. Preferences persist across restarts.

plugin.get_pref(key: string) β†’ string

Returns the stored preference value for key, or an empty string if not set.

local count = tonumber(plugin.get_pref("open_count")) or 0
plugin.set_pref(key: string, value: string)

Stores a preference value. Non-string values must be serialised to a string first (e.g. tostring).

-- Increment a persistent counter.
local count = tonumber(plugin.get_pref("open_count")) or 0
count = count + 1
plugin.set_pref("open_count", tostring(count))

Opening Files

Plugins can ask the host to open a GP file from the filesystem. Once loaded, Tabatura displays the tab and the user can play it using the built-in playback controls β€” no audio code needed in the plugin.

plugin.open_file(path: string) β†’ true | nil, error_string

Asks Tabatura to open the GP file at the absolute filesystem path. On success returns true. On failure returns nil plus an error string. Use plugin.plugin_dir() to build paths relative to your plugin's directory.

-- Open a bundled GP4 file and let Tabatura handle playback.
local path = plugin.plugin_dir() .. "/scales/box1-am.gp4"
local ok, err = plugin.open_file(path)
if ok then
  print("Tab loaded β€” press β–Ά in Tabatura to play")
else
  print("Failed to open file: " .. tostring(err))
end

Plugin Directory

plugin.plugin_dir() β†’ string

Returns the absolute filesystem path to this plugin's directory β€” the same directory that contains plugin.toml. Use this to locate assets (icons, SoundFont files, data files) bundled with your plugin.

local sf_path = plugin.plugin_dir() .. "/sounds/guitar.sf2"
plugin.load_soundfont(sf_path)