File Server API
Five file functions and two helpers are destructured from your app.js module argument. Unlike the synchronous data operations, the file operations are async — they touch the disk — so await them.
Server file operations
module.exports = ({ getFile, setFile, removeFile, listFiles,
fileStat, fileUrlFor, addFileTrigger, FILES_DIR, ... }) => {
// Write. content may be a Buffer, TypedArray or string.
const w = await setFile(["reports", "2026-08.csv"], csvString);
// → { status: "Success", size: 4821, type: "text/csv", name: "2026-08.csv" }
// Read. Resolves with a Buffer in .data.
const r = await getFile(["reports", "2026-08.csv"]);
// → { status: "Success", data: <Buffer>, name, size, type, mtime }
// → { status: "Path not found", data: null } if it isn't there
// List one level.
const l = await listFiles(["reports"]);
// → { status: "Success", data: [{ name, isDir, size, type, mtime }, …] }
// Capped at FILE_MAX_DIR_ENTRIES; a truncated listing sets .truncated = true
// Delete. Directories require { recursive: true }.
await removeFile(["reports", "2026-08.csv"]);
await removeFile(["reports"], "root", { recursive: true });
// Synchronous stat — no await, returns undefined if nothing is there.
const st = fileStat(["reports", "2026-08.csv"]);
// → { name, ext: ".csv", size, type, mtime, isDir: false }
// Build the URL a client would fetch.
fileUrlFor(["reports", "2026-08.csv"]); // "/_file/reports/2026-08.csv"
};
Server-side file calls run as root by default. Every one of these takes an optional admin argument that defaults to the string "root", which bypasses file rules entirely — exactly like get/set on the data side. To have a call checked against your rules, pass the caller's auth context through explicitly:
addCallable("uploadAvatar", async (data, admin) => {
// Checked against fileWrite/fileValidate as the *caller*, not as root
return await setFile(["avatars", admin.uid + ".png"], data.bytes, admin);
});