Log inGet started

Recording: filming the world and keeping the video

Updated September 27, 2026

The shortest thing that works

task.spawn(function()
    -- Record five seconds of what the player sees. The call waits until the
    -- encoder is open, so a path back means the take is running.
    local take, why = av.record("clip.mp4", { max_duration_sec = 5 })
    if not take then error(why) end

    -- A take is filed when it ends.
    while av.is_recording() do task.wait(0.1) end
    print(av.status().recordingEnd, av.status().recordingBytes)
end)

A relative name lands under /zero/runtime/recordings/. That folder lives as long as the engine does: in the browser it is gone when the tab closes, which is why a clip a player wants to keep has to be saved to their device.

In the browser a take under that folder is written into the page's private storage frame by frame as it records, and stays there once it is filed: the file reads, plays and saves like any other, and a long take holds none of itself in the engine's memory while it records or after. A take filed anywhere else (a world path under /zero/source/) is written into the VFS as bytes when it ends.

Keeping a clip: av.save

-- In the handler of the button the player clicks to keep the clip:
task.spawn(function()
    local ok, where, report = av.save("clip.mp4")
    if not ok then print("not saved: " .. where) end
end)

The person at the screen decides where the file lands:

  • In the browser, the clip downloads at once when the save rides the player's click or key press, which is why the call belongs in a button's click handler. A save with no such gesture behind it (a timer, the end of a take, an agent's call) makes the page show the player an offer, which they save with a click of their own: a browser starts a download only for a person who just acted. report.state reads "saved" for the first and "offered" for the second.
  • In a native engine, a save dialog opens in the player's downloads folder with the clip's name filled in, and the file is written where they choose (report.state is "offered"). An engine with no window refuses.

So a clip feature that downloads in one click has two controls: one that records the take, and one, shown once the take has ended, whose click saves it. Saving a take that is still recording is refused: it is filed when it ends. av.save answers false and a reason for that, for a path with no file, and for a clip too large for the browser to hold. The saved file is the take byte for byte. userfile.save(path) saves any other file the engine holds the same way.

In the editor, a take is saved without any code: the Files panel's right-click menu on the take (in /zero/runtime/recordings/) holds Save to this device, and that click is the gesture the download rides.

Choosing what the take holds

  • max_duration_sec or frames bound the take; without either it runs until av.stop_recording().
  • renderLayers = "all !EditorUI !debug" films the scene without the editor's chrome; add !ui to leave the HUD out too (the viewport shows what the take films while it runs).
  • camera names the camera the take is filmed from.
  • codec picks the video codec from av.status().codecs; "h264" is the one every player and site opens.
  • cadence = "frame" films a fixed-step timeline (a cutscene) at the length it runs to however slowly the engine draws it.

man modules/av/record and man modules/av/save hold the full reference, and av.status() reports how the last take ended and what it produced.

  • documentation
  • guide