Building extensions
Everything you can do from an Extension: menu actions, the object model,
transactions, and UI.
The Extension object
app = Extension("My Tool", author="you", version="0.1.0")
# ...register handlers...
app.run() # connect, register everything, and stay alive handling events
It also exposes services: app.song, app.ui, app.resources,
app.environment.
Context-menu actions
Register a function against a scope — the type of object the user right-clicked. Your function receives the resolved, typed object.
@app.context_menu("MidiClip", "Quantize to scale")
def quantize(clip: MidiClip):
...
Available scopes: MidiClip, AudioClip, MidiTrack,
AudioTrack, ClipSlot, Scene, Simpler,
Sample, DrumRack.
Commands & selection
@app.command("do_thing") # invocable by id
def do_thing(): ...
@app.on_selection("ArrangementSelection") # acts on the current arrangement selection
def on_sel(selection): ...
The object model
Everything is a Pythonic wrapper. Properties map to reads; setters write; methods do actions.
track = app.song.tracks[0]
track.name = "Drums"
clip = track.arrangement_clips[0]
clip.looping = True
clip.color = 0xFF8800
device = track.insert_device("Reverb", 0)
device.parameters[1].value = 0.5
parent = clip.parent # navigate up: clip → track
See the full API reference.
Transactions = one undo step
Wrap several mutations so Live records them as a single undo.
with app.transaction():
c1 = track.create_midi_clip(0, 4)
c2 = track.create_midi_clip(4, 4)
c3 = track.create_midi_clip(8, 4)
# Ctrl+Z removes all three at once
Dialogs
For the common cases, ask for input in pure Python — no HTML, no JavaScript:
name = app.ui.prompt("Clip name", default=clip.name) # text input; None if cancelled
if app.ui.confirm("Delete all clips?"): ... # yes / no -> bool
scale = app.ui.choose("Scale", ["Major", "Minor", "Dorian"]) # dropdown
Custom dialogs
Need a bespoke UI? Live renders dialogs as HTML webviews (same as Ableton's own SDK). Pass raw
HTML to app.ui.modal(html, w, h) — a send(value) function is
injected for you, so your markup just calls send(...) to close and
return a value. No postMessage plumbing.
Progress dialogs
Show a progress bar for long work; the callback gets an update function and a cancel signal.
def work(update, abort):
for i in range(5):
if abort.is_set(): break
update(f"Step {i+1}/5", (i+1)*20)
...
app.ui.progress("Working…", work)
Resources: audio in & out
path = app.resources.import_into_project("/tmp/loop.wav") # copy a file into the Set
clip = audio_track.create_audio_clip(path, 0.0)
wav = app.resources.render_pre_fx_audio(audio_track, 0.0, 4.0) # render a range to WAV