Skip to main content
Version: 2026-09-21 (current)

Files

Each of the four storage scopes also keeps files: data too large for the JSON object, or not JSON at all — a student's essay as a PDF, a photo of their lab setup, an audio recording, a map the instructor uploaded for everyone. Who can read and write them follows the same rules as the scope's object.

// A student uploads their submission
const input = this.shadowRoot.querySelector('input[type=file]')
await this.api.storage.memberLocation.files.put('essay.pdf', input.files[0])

// Later, show what they uploaded
const files = await this.api.storage.memberLocation.files.list() // [{ key, size, modified }]

// Display an image the instructor stored for the course
const { url } = await this.api.storage.course.files.info('diagram.png')
img.src = url // valid for a few minutes

Each file has a key your tool chooses — 1 to 128 characters, letters, digits, ., _, and -, starting with a letter or digit, no /. A put to a key that already holds a file replaces it.

The methods​

All are on api.storage.<scope>.files, all are async, and all throw on failure.

list()Promise<Array>

Every file on the scope: [{ key, size, modified }], with size in bytes and modified in milliseconds since the epoch. Empty when there are none.

info(key)Promise<object>

One file: { key, size, modified, content_type, url, expires }. url downloads the file until expires (milliseconds since the epoch, a few minutes away). Put it in an <img>, an <a>, or an <audio> right away; ask again for a fresh one rather than keeping it. Throws VoshiNotFoundError when no file has the key.

get(key)Promise<Blob>

The file's contents, as a Blob — for a file your code reads, such as JSON or text your tool saved earlier. For something the browser displays, info().url is cheaper.

put(key, file, options?)Promise<void>

Store file (a File from an input, or any Blob) at key, replacing what was there.

options
filenamestring

The name a download is saved as. The File's own name by default, else the key.

contentTypestring

The file's type. The Blob's type by default, else guessed from the name.

attachmentbooleandefault: false

Make downloads save the file rather than open it in the browser.

remove(key)Promise<void>

Delete the file. Removing a key that holds nothing is not an error.

Limits​

Limit
One file25 MB
Files on one scope100 files, 100 MB together

A put that would exceed the scope's quota throws before anything is uploaded.

How it works, briefly​

The bytes go straight between the browser and Voshi's file storage, not through the API, which is why info() hands you a short-lived URL instead of the file. Your tool does not need to know this — but it explains why a stored URL stops working after a few minutes, and why you should ask for a new one each time you render.

Saving something that is not a file​

A Blob can be made from anything:

const blob = new Blob([JSON.stringify(bigDataset)], { type: 'application/json' })
await this.api.storage.memberLocation.files.put('dataset.json', blob)

const back = JSON.parse(await (await this.api.storage.memberLocation.files.get('dataset.json')).text())

Use this when a student's work outgrows the storage object's size limit.