Reference · Files and processes

save_upload

signature
save_upload(map, str) -> str | error(str)

What it does

Writes a file the request uploaded to a path under the program's folder, making folders as needed, and gives the path back. The first argument is a file record from request.files ({ field, filename, type, size }, from a form with enctype="multipart/form-data"), or the file a component passed to a server fn. The bytes stay with the request until saved, so call it in the route or server fn that received them.

Examples

A page with a form, and the route it posts to. The form needs method="post" and enctype="multipart/form-data" (without it the browser sends only the file's name), and the file input's name is its key in request.files:

in a program
route GET /avatar =    <form method="post" action="/avatar" enctype="multipart/form-data">        <label>Your photo <input type="file" name="photo" accept="image/*" required/></label>        <button>Upload</button>    </form>route POST /avatar {    let f = request.files.photo    if f == null { reply 400 "Choose a photo" }                 // `required` can be skipped    if f.size > 2_000_000 { reply 413 "Photos up to 2 MB" }    let path = save_upload(f, `uploads/${uuid()}.png`)?        // into the program's folder    redirect(`/${path}`)}route GET /uploads/:name = file(`uploads/${name}`)

Several files from one input with multiple, and a text field beside them:

in a program
route GET /docs =    <form method="post" action="/docs" enctype="multipart/form-data">        <input type="text" name="title"/>        <input type="file" name="docs" accept=".pdf" multiple/>        <button>Send</button>    </form>route POST /docs { files: { docs: [map] } } {    for d in docs { save_upload(d, `uploads/${uuid()}.pdf`)? }    `${request.form.title}: ${len(docs)} saved`}

From a component, a file goes to a server fn as it is chosen, with no form:

in a program
server fn add_photo(file: map) = save_upload(file, `photos/${uuid()}.jpg`)component Photo() {    state saved = null    <input type="file" accept="image/*" @change={(e) => { saved = add_photo(e.target.files[0]) }}/>}

Edge cases

let photo = { field: "photo", filename: "me.png", type: "image/png", size: 3 }print(save_upload(photo, "uploads/a.png"))print(save_upload(photo, "uploads/a.png") | is_err)print(read_file("uploads/a.png") ?? "nothing was written")
output
err("save_upload: the current request has no files")truenothing was written

Errors

Having no file to save is an error value, which ? turns into the request's answer. A path that leaves the program's folder (.., or starting at /), or the empty path, is a runtime error, checked before anything else; so is a path that is not a string.

this one fails
print(save_upload({}, "../a.png"))
output
line 1: `save_upload` needs a path inside the program's folder, got "../a.png"
this one fails
fn keep(u) with time = save_upload(u, "uploads/a.txt")print(keep({ field: "f", filename: "a.txt", type: "text/plain", size: 1 }))
output
line 1: `save_upload` needs the fs effect, but `keep` is declared `with time`  in keep, called at main.qwe:2

Chaining

The file comes first in a pipeline, the path in the call:

in a program
route POST /docs {    let paths = request.files.docs | map((f) => f | save_upload(`docs/${uuid()}.pdf`))    paths}

See also

read_upload, write_file, image.info, image.convert, uuid

Thread
6Useful? Like it, or add to its thread below.

Thread 0 posts

On save_upload(): questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · every example's output was made by running it (tools/refcheck.mjs) · questions on Discord or r/quillo