fs.read(path, { as }) the file's contents
fs.write(path, { text | bytes | json | blob | secret, append, mode })
writes the file; returns the number of bytes written
fs.list(path, { deep }) the entries, by name: [{ name, kind, size, modified, mode }]
fs.stat(path) one entry
fs.exists(path) true if the path exists
fs.remove(path, { deep }) removes a file, a link or an empty directory; returns nothing
fs.mkdir(path) makes the directory and those above it; returns nothing
fs.move(path, { to }) moves or renames a file, a link or a directory; returns nothing
A path with no leading / is in the job's scratch directory (Scripts).
Options:
as"text", "bytes", "json" or "blob"; default "text"
secreta handle to a secret (secrets); the file's mode is then 0600
appendadds to the end of the file; default false
modethe mode of a new file; default 0o644
deeplists the directories below too, or removes a directory with its contents; default false
An entry's fields:
kind"file", "directory" or "link"
modifieda time in UTC, as "2026-10-13T20:02:07Z"
modethe permission bits, as a number
A file read or written as a blob may be any size (blobs).
Errors:
not_foundthe path does not exist
not_permittedthe operating system refuses the runner
bad_inputa file over 64 MiB read in a form other than "blob"; a file read as "json" that is not JSON
failedanything else, such as removing a directory that is not empty
Needs these permissions, at the path's real location after links are followed:
fs.read:pathread, list, stat, exists
fs.write:pathwrite, mkdir, remove, and move for both paths
secret:NAME@fs.write:patha secret written to the file
- fs.remove and fs.move act on the name: a link is removed or moved, not what it points to.
- A directory removed or moved needs fs.write:path for every entry inside, at the old path and the new. If one is refused, nothing is removed or moved.
- fs.list omits the entries that the job may not read.
This script writes a blob to a file and reads the file as a blob.
allow: [fs.write:in.csv, fs.read:in.csv]
const table = blobs.make("item,amount\ntea,12\n", { type: "text/csv" })
fs.write("in.csv", { blob: table }) // gives 19
return fs.read("in.csv", { as: "blob" }).size // gives 19This script makes the other calls.
allow: [fs.read:/srv/reports/**, fs.read:out/**, fs.write:out/**]
const text = fs.read("/srv/reports/latest.md")
fs.write("out/summary.txt", { text: text.slice(0, 200) }) // gives 52
fs.exists("out/summary.txt") // gives true
fs.stat("out/summary.txt").size // gives 52
fs.mkdir("out/2026")
fs.move("out/summary.txt", { to: "out/2026/q3.txt" })
fs.list("out", { deep: true }).map((entry) => entry.name) // gives ["2026", "2026/q3.txt"]
fs.remove("out/2026", { deep: true })This script writes a secret to a file.
allow: [fs.write:/srv/app/key, "secret:APP_KEY@fs.write:/srv/app/key"]
fs.write("/srv/app/key", { secret: secrets.get("APP_KEY") })