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

Storage

Your tool has four places to keep data, and most tools need no other persistence at all — no database, no accounts, no schema. Each is one JSON object that is entirely yours; Voshi never reads or interprets it.

// Save a student's answer
await this.api.storage.memberLocation.update({ q3: 'B', lastSaved: Date.now() })

// Read it back on the next visit
const work = await this.api.storage.memberLocation.get() // {} if nothing was ever saved

The four scopes​

ScopeOne object per…Typical use
api.storage.coursecourseCourse-wide settings the instructor made: term dates, which modules are open
api.storage.locationlocation × courseThe instructor's setup of one activity: the word list, the due date, the question pool
api.storage.memberperson × courseThe student's state across your whole tool in this course: preferences, cumulative progress
api.storage.memberLocationperson × location × courseThe student's work on one activity: answers, attempts, the saved draft

The two member scopes are the student's own. The course and location scopes are shared by everyone in the course, which is why only staff can write them.

Every scope points at the right object for this launch: this course, this location, this person. Your tool never says which course or which student it means — the launch already did.

Who can write what​

studentinstructor / assistant / manager
course, locationreadread and write
member, memberLocationread and write (their own)read and write

A student writing course or location throws a VoshiPermissionError. So course-wide state that students need to change has to live in their own member object instead.

Course staff read and write the same member and memberLocation objects — their own, in a staff launch. Reading a particular student's work from an instructor launch is not something the client does today; design an instructor's review view around the shared scopes, or around what the student's own launch wrote into them.

The methods​

Each scope has the same four methods. All are async and throw on failure.

get()Promise<object>

The stored object, or {} when nothing has been stored yet.

set(data)Promise<void>

Replace the whole object with data.

update(changes)Promise<void>

Merge changes into the object: each top-level key in changes is set, and other keys are left alone. A key whose value is undefined is removed. Prefer this over get-then-set for small changes — it is one request, and it does not overwrite a key another tab or the instructor changed in between.

patch(operations)Promise<void>

Apply a JSON Patch (RFC 6902): an array of operations like { op: 'add', path: '/answers/-', value: 4 }. For changes deeper than the top level, such as appending to a list or setting one key inside a nested object. Missing intermediate paths are created for you, and a remove or replace of a path that does not exist is tolerated.

// An instructor saves the activity's setup
if (this.api.isStaff) {
await this.api.storage.location.set({ words: ['asset', 'liability', 'equity'], shuffle: true })
}

// A student appends an attempt without touching anything else
await this.api.storage.memberLocation.patch([
{ op: 'add', path: '/attempts/-', value: { score: 0.8, at: Date.now() } },
])

Size​

The JSON object is limited to 512 KB on the two member scopes and 1 MB on the course and location scopes, measured as compact JSON. That is configuration and state, not a file store. Anything larger — an image, a recording, an essay as a PDF, a big dataset — belongs in files, which each scope keeps beside its object.

Things worth knowing​

  • Reads return exactly what you last wrote. No envelope, no metadata.
  • Writes are atomic. Two writes to the same object serialize; a patch or update never half-applies.
  • Copied courses keep their setup. When a school copies a course into a new term, the new course's course and location objects start as copies of the old course's. The instructor's setup carries forward; students' member objects do not, because the students are new.
  • Nothing is ever torn down. Objects persist for as long as the course does. There is no API to delete them; the Sandbox's Reset is the one place data is discarded.
  • Versions share storage. A course that upgrades to a newer version of your tool keeps its objects, so new code must read what old code wrote.