Log inGet started

userfile

Updated October 20, 2026

The files of the person at the screen, both ways. pick opens their own file picker (native OS dialog / Android / web browser) and brings the chosen file(s) into the engine; save puts a file the engine holds onto their device (a download in the browser).

require("@builtin::modules.api.engine.userfile") -- userfile (also available as global 'userfile')

The userfile namespace: 9 functions.

globals/userfile/pick

userfile.pick(opts: PickOpts?) -> PickResult

Open the user's system file picker and bring the chosen file(s) into the engine. Yields until the user finishes (call from a coroutine / task, like any task.await) and returns { cancelled, files = {{ name, mime, size, bytes?, vfsPath? }} }. Without writeTo each file carries bytes (a binary-safe string); with writeTo each carries vfsPath (read it with vfs.read). Cancelling returns { cancelled = true, files = {} }; a genuine failure (e.g. a lost browser user-activation gesture) raises an error.

Parameters

  • opts PickOpts (optional) — Picker options (optional): multiple, folder, title, filters, writeTo.

Returns PickResult — The decoded result table.

local r = userfile.pick({ filters = {{ name = "Images", extensions = {"png","jpg"} }} })
if not r.cancelled then vfs.write("/source/textures/wall.png", assert(r.files[1].bytes)) end

globals/userfile/pickFolder

userfile.pickFolder(opts: PickOpts?) -> PickResult

Convenience for userfile.pick({ folder = true }) — pick a whole directory tree. Yields until the user finishes and returns the same result table as pick. On the web this degrades to a multi-file selection.

Parameters

  • opts PickOpts (optional) — Picker options (optional); folder is forced true.

Returns PickResult — The decoded result table.

local r = userfile.pickFolder({ writeTo = "/source/imported/" })

globals/userfile/save

userfile.save(path: string, opts: SaveOpts?) -> (boolean, string, SaveReport?)

Save a file the engine holds onto the device of the person at the screen: the way a person keeps a recording, a capture or an export. The person decides where it lands. In the browser the file downloads at once when the save rides a click or key press of the person at the screen (call it from a button's click handler), and otherwise the page shows them an offer they save with a click of their own, since a browser starts a download only for a person who just acted. A native engine opens the host's save dialog in their downloads folder, with the name filled in, and writes the file where they choose. An engine with no window has no person at its screen and refuses. The call waits for the first answer, so run it where it can yield (task.spawn from a callback). Returns true, a sentence saying where the file went, and the report { state, detail, name, size, path } whose state is "saved" (the file is on the device) or "offered" (it is in front of the person, as the page's offer or the save dialog, and lands when they act); or false and the reason the save was refused: no file at the path, a folder, a name with a folder in it, no window, or a file too large for the browser to hold (which the page also shows).

Parameters

  • path string — VFS path of the file to save.
  • opts SaveOpts (optional) — { name }, the file name the device gets (the file's own when omitted).

Returns (boolean, string, SaveReport?) — Whether the device took the file. Where it went, or why it was refused. The save report, when the device took the file.

local ok, where = userfile.save("/zero/runtime/recordings/take.mp4")
local ok, where, report = userfile.save("/zero/source/export/level.json", { name = "level-3.json" })

modules/userfile/pick

pick(opts: PickOpts?): PickResult

Open the user's system file picker and bring the chosen file(s) into the engine. Yields until the user finishes (call from a coroutine / task, like any task.await) and returns { cancelled, files = {{ name, mime, size, bytes?, vfsPath? }} }. Without writeTo each file carries bytes (a binary-safe string); with writeTo each carries vfsPath (read it with vfs.read). Cancelling returns { cancelled = true, files = {} }; a genuine failure (e.g. a lost browser user-activation gesture) raises an error.

Parameters

  • opts PickOpts? (optional) — Picker options (optional): multiple, folder, title, filters, writeTo.
local r = userfile.pick({ filters = {{ name = "Images", extensions = {"png","jpg"} }} })
if not r.cancelled then vfs.write("/source/textures/wall.png", assert(r.files[1].bytes)) end

modules/userfile/pickFolder

pickFolder(opts: PickOpts?): PickResult

Convenience for userfile.pick({ folder = true }) — pick a whole directory tree. Yields until the user finishes and returns the same result table as pick. On the web this degrades to a multi-file selection.

Parameters

  • opts PickOpts? (optional) — Picker options (optional); folder is forced true.
local r = userfile.pickFolder({ writeTo = "/source/imported/" })

modules/userfile/save

save(path: string, opts: SaveOpts?): (boolean, string, SaveReport?)

Save a file the engine holds onto the device of the person at the screen: the way a person keeps a recording, a capture or an export. The person decides where it lands. In the browser the file downloads at once when the save rides a click or key press of the person at the screen (call it from a button's click handler), and otherwise the page shows them an offer they save with a click of their own, since a browser starts a download only for a person who just acted. A native engine opens the host's save dialog in their downloads folder, with the name filled in, and writes the file where they choose. An engine with no window has no person at its screen and refuses. The call waits for the first answer, so run it where it can yield (task.spawn from a callback). Returns true, a sentence saying where the file went, and the report { state, detail, name, size, path } whose state is "saved" (the file is on the device) or "offered" (it is in front of the person, as the page's offer or the save dialog, and lands when they act); or false and the reason the save was refused: no file at the path, a folder, a name with a folder in it, no window, or a file too large for the browser to hold (which the page also shows).

Parameters

  • path string — VFS path of the file to save.
  • opts SaveOpts? (optional) — { name }, the file name the device gets (the file's own when omitted).
local ok, where = userfile.save("/zero/runtime/recordings/take.mp4")
local ok, where, report = userfile.save("/zero/source/export/level.json", { name = "level-3.json" })

typed/builtin//modules/api/engine/userfile/userfile/pick

userfile.pick(opts: PickOpts?) -> PickResult

Open the user's system file picker and bring the chosen file(s) into the engine. Yields until the user finishes (call from a coroutine / task, like any task.await) and returns { cancelled, files = {{ name, mime, size, bytes?, vfsPath? }} }. Without writeTo each file carries bytes (a binary-safe string); with writeTo each carries vfsPath (read it with vfs.read). Cancelling returns { cancelled = true, files = {} }; a genuine failure (e.g. a lost browser user-activation gesture) raises an error.

Parameters

  • opts PickOpts (optional) — Picker options (optional): multiple, folder, title, filters, writeTo.

Returns PickResult — The decoded result table.

local r = userfile.pick({ filters = {{ name = "Images", extensions = {"png","jpg"} }} })
if not r.cancelled then vfs.write("/source/textures/wall.png", assert(r.files[1].bytes)) end

typed/builtin//modules/api/engine/userfile/userfile/pickFolder

userfile.pickFolder(opts: PickOpts?) -> PickResult

Convenience for userfile.pick({ folder = true }) — pick a whole directory tree. Yields until the user finishes and returns the same result table as pick. On the web this degrades to a multi-file selection.

Parameters

  • opts PickOpts (optional) — Picker options (optional); folder is forced true.

Returns PickResult — The decoded result table.

local r = userfile.pickFolder({ writeTo = "/source/imported/" })

typed/builtin//modules/api/engine/userfile/userfile/save

userfile.save(path: string, opts: SaveOpts?) -> (boolean, string, SaveReport?)

Save a file the engine holds onto the device of the person at the screen: the way a person keeps a recording, a capture or an export. The person decides where it lands. In the browser the file downloads at once when the save rides a click or key press of the person at the screen (call it from a button's click handler), and otherwise the page shows them an offer they save with a click of their own, since a browser starts a download only for a person who just acted. A native engine opens the host's save dialog in their downloads folder, with the name filled in, and writes the file where they choose. An engine with no window has no person at its screen and refuses. The call waits for the first answer, so run it where it can yield (task.spawn from a callback). Returns true, a sentence saying where the file went, and the report { state, detail, name, size, path } whose state is "saved" (the file is on the device) or "offered" (it is in front of the person, as the page's offer or the save dialog, and lands when they act); or false and the reason the save was refused: no file at the path, a folder, a name with a folder in it, no window, or a file too large for the browser to hold (which the page also shows).

Parameters

  • path string — VFS path of the file to save.
  • opts SaveOpts (optional) — { name }, the file name the device gets (the file's own when omitted).

Returns (boolean, string, SaveReport?) — Whether the device took the file. Where it went, or why it was refused. The save report, when the device took the file.

local ok, where = userfile.save("/zero/runtime/recordings/take.mp4")
local ok, where, report = userfile.save("/zero/source/export/level.json", { name = "level-3.json" })
  • api
  • reference