API Reference
Initialize PrefsDB instance
The script defines the PrefsDB class. Get an instance with PrefsDB.getkey() (see API Keys below); the examples call it db. Every method returns a Promise.
The cURL tabs show the raw HTTP call each method makes. They assume $KEY and cookies.txt from a getkey call — see Getting started.
Write Data
.write( data [, options])
.setItem( key, value [, options])
.putObject( file [, options])
.add( item [, options])
.write
Stores any value up to 1 MB (4 MB on Plus, 8 MB on Pro), sent as the request body. See Write Data. The answer's settled Promise (on every write and delete, including put(), add(), remove()) resolves once the change is on the server and rejects if it never will be; see Knowing when a write is saved. The answer's version is the value's new version: pass { ifVersion } to change a value only if nobody else has since (see Several Devices).
.key
.setItem
.putObject
.add
Read Data
.read( [options] )
.readAll( [options] )
.pages( [options] )
.entries( [options] )
.getItem( key [, options] )
.getObject( fileId [, options] )
.getObjectUrl( fileId [, options] )
.read
.readAll
.pages
Reads in pages of limit values, newest first. See Reading in pages.
.entries
The rows as a plain array of { key, value } objects (plus uid, modifiedt and the row's scope). Rejects with the error if the read fails. One read returns at most 1,000 rows on the free plan: the array's more is true when more matched and next continues from there. { max: n } reads further pages until it has n rows (Infinity for all). For a whole map it also works offline, from this browser's copy (cached: true on the array), and it shows changes not confirmed yet as pending: true. For one array in the usual response shape instead, pass { array: true } to .read(). See Always an array.
[Promise<Array> promise = ] .map('inventory').entries( [options] )
for (const { key, value } of await db.map('inventory').entries()) { ... }
const everything = await db.map('inventory').entries({ max: Infinity });
const res = await db.list('todos').read({ array: true }); // res.values is always an array
.key
.match
.changes
What changed in a map (or a key's scope) after a changeSeq, oldest first: { changes: [{ key, value, version } or { key, deleted: true, version }], changeSeq, more }. entries() and every read of keys or a map give the changeSeq to start from; since: 0 lists every value. Keys and maps only. See Several Devices.
.watch
Calls fn(changes, { reset, changeSeq }) when the map changes, asking changes() every interval milliseconds (10,000 by default). Without since, the first call has every value, with reset: true. Returns a function that stops it; db.logout() stops it too.
const stop = db.map('todos').watch((changes, { reset }) => { /* update the list */ }, { since: items.changeSeq });
.getItem
.getObject
.getObjectUrl
Filtering results
.match( 'pattern' )
.match
Remove all values from a list named "mystuff" that begin with 2025.
Organizing data
Each of these returns a new object and leaves the one you called it on as it was, so you can keep part of a chain and reuse it: const inventory = db.map('inventory'); then inventory.put(...), inventory.entries(). Always chain the scope into the call: db.project('x'); on a line of its own does nothing to the next call.
.project( 'name' )
.domain( 'name' )
.subdomain( 'name' )
.map( 'name' )
.project
.domain
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/write/ \
-d project=myapp -d domain=display --data-urlencode 'settings={"theme":"dark"}'
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
-d project=myapp -d domain=display -d "settings="
.subdomain
.map
Delete data
.delete( [options] )
.deleteObject( fileId [, options] )
.ttl( days )
.expire( days [, options] )
.delete
Delete a entry (a value) assigned to a key named "nickname"
Remove a specific "value" from a list named "mystuff".
{ ifVersion } deletes a single key only if its version is still that one, and answers CONFLICT otherwise (see Several Devices).
See: Remove data
.deleteObject
.storage / .onStorage
How much of the key's file storage is used: { used, max, plan, ratio }, in bytes, or null until getkey(), an upload or a file delete has reported it. max and ratio are null when the plan has no limit. .onStorage() calls your function every time this changes, and right away if it's already known. It returns a function that stops the calls. See Storage space.
.ttl
Set a time-to-live, in days, for the value written by the next .write(). On its own .ttl() does nothing; to change the TTL of values already stored, use .expire below.
.expire
Set the time-to-live, in days, of every value already stored in a scope. Scope levels you don't set match any value, so .project("my-app") covers every domain and subdomain under my-app; add .list(name) or .map(name) to limit it to one list or map. 0 or less removes expiry. A scope is required, and it can't be combined with .key().
API Keys / tokens
PrefsDB.getkey( 'name', seed [ ] [, options] )
PrefsDB.createkey( 'name', seed [ ] [, options] )
PrefsDB.client( apikey )
.waitForVerification( [options] )
.verificationStatus( [options] )
.onSessionExpired( fn )
.testKey( )
.rotate( 'name', seed [ ] [, options] )
.migrateOrigin( origin [, options] )
.migrateNamespace( namespace [, options] )
.migrateAppcred( appcred [, options] )
PrefsDB.generateEncryptionKey( )
.enableEncryption( options )
.disableEncryption( )
.encryptionEnabled( )
PrefsDB.getkey
Creates a new key using parameters provided or returns the key, if one already exists. A new key needs email in the options (see Getting started).
PrefsDB.createkey
Creates a new key using parameters provided. Returns a failed response , if a key already exists.
.waitForVerification
Resolves once the key's email address has been confirmed (the user clicked the link), so your app can carry on by itself. It checks every few seconds, and straight away when the user comes back to your tab. Options: timeout (milliseconds; then it rejects with TIMEOUT) and signal (an AbortSignal; rejects with ABORTED). .emailVerificationRequired() tells you whether you need to wait at all.
.verificationStatus
One check of the above: resolves to { success, verified } (and reason while not verified).
.onSessionExpired
The client keeps your key's session and renews it by itself (getkey() option autoRenew, on by default). fn(error) is called only when it can't: the key was made with autoRenew: false, it came from PrefsDB.client(), a Google/Microsoft/Apple/GitHub sign-in is more than 7 days old (SIGNIN_EXPIRED), or the key no longer exists. Ask the user to sign in again then. Returns a function that removes the listener.
.logout
Signs the key out of this browser: stops using its session, deletes what it kept in the browser (its copy of values, changes not sent yet), removes its onSyncEvent() listeners and stops its watch()es. On a remembered device it also forgets and revokes the device grant. Call it when your user signs out. { keepLocal: true } keeps the browser's copy. Works offline. See Signing out.
if (await db.pendingCount() > 0 && !confirm("Some changes haven't been saved yet. Sign out anyway?")) return;
await db.logout();
PrefsDB.resume
Gets back the key this browser remembered (remember: true on getkey() or a sign-in) under that name, without the passphrase or a sign-in. Resolves to null when there is nothing to resume: show your sign-in then. PrefsDB.canResume(name) says whether there's a grant to try, without a request; PrefsDB.forgetResume(name) forgets it.
.devices / .revokeDevice
db.devices() resolves to { devices: [{ id, label, created, lastUsed, expires, current }] }, the devices the key is remembered on. db.revokeDevice(id) signs one out at once; db.revokeAllDevices() signs out all of them. See Staying Signed In.
.onSyncEvent
fn(event) is called when something happens to one of this key's writes saved offline or made optimistically: queued, pushed, confirmed, optimistic-rollback, dropped and more, each with a phase and a key. Returns a function that removes the listener. See Offline and Optimistic Writes.
PrefsDB.onSyncEvent(fn) does the same for every key on the page.
.pendingCount
Resolves to how many of this key's writes are saved in the browser, waiting to be sent.
PrefsDB.sync
Sends the writes saved in the browser now. It also happens by itself when the connection comes back.
.testKey
true for a test key: one made with an email address at sandbox.prefsdb.com, which works without an email, has small limits, can't store files and stops working a day after it was made (.testKeyExpires(), a Date). See Testing without an inbox.
PrefsDB.client
Wraps a key you already have (for example one stored earlier, from db.apitoken()) without calling getkey(). Takes throwOnError and timeout as options.
.rotate
Replaces the key with a new one derived from a new name and seed, and moves all its data over. See API Key Rotation.
.migrateOrigin
Moves the key to a new website address, keeping the same key and all its data. Use it when your site changes domain.
A key belongs to the site it was created on. Once your site moves, getkey() called from the new address doesn't find your users' existing keys: it creates new, empty ones, with no error. Their data isn't deleted, but your app can't reach it any more. migrateOrigin() moves each key over so that getkey() from the new address finds it.
It has to run on the old address, with a key from getkey() there (it needs a current session, which the client renews by itself if needed). So plan the move before you switch:
- Deploy code on the old address that calls
migrateOrigin()after each user'sgetkey(). - Keep the old address running until your users have visited it. Only keys that have been moved are found from the new address.
- Don't send users to the new address before their key has moved. A
getkey()there creates a new, empty key for them, and the move is then refused (409), because the new address already has a key for that name and seed.
The new address is given as scheme://host[:port], with no path or trailing slash, e.g. https://app.example.com.
[Promise<PrefsDB> promise = ] .migrateOrigin(origin [, options])
// On the old site:
const db = await PrefsDB.getkey("my-app", ["a", "b"]);
await db.migrateOrigin("https://app.example.com");
There's no cURL version: this is for keys created in a browser, which belong to that site's address.
Keys that a site sponsors (created without an email) can't be moved to another address.
.migrateNamespace
Moves the key to a different namespace, keeping the same key and all its data. Use it when your server has to change its namespace, for example because the value leaked. Afterwards, getkey() finds the key only with the new namespace.
Call it on a key from getkey() with its current namespace (it needs a current session, which the client renews by itself if needed). To change the namespace for all your keys: generate a new value, move each key onto it, then switch your server over to the new value.
# Right after a getkey call for this key made with namespace=$OLD_NAMESPACE:
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
-G https://prefsdb.com/api/v1/migrateorigin/ \
--data-urlencode "namespace=$NEW_NAMESPACE"
# {"success":true,"originScoped":true,"message":"Key re-bound to new origin.",...}
To switch from a namespace to an app credential, do the same but call migrateAppcred() instead.
.migrateAppcred
Moves the key onto a different app credential, keeping the same key and all its data. Afterwards, getkey() finds the key only through the new credential. Call it right after a getkey() for the key made through its current credential (it needs a session from the last 60 seconds), and before you revoke the old credential: once that's revoked, you can't get the session this needs.
To replace a credential, for example one that has leaked:
- Create a new credential in your dashboard's App Credentials panel.
- For each key: call
getkey()through the old credential, thenmigrateAppcred()with the new one. - Switch your server over to the new credential.
- Revoke the old credential.
# Right after a getkey call for this key made with appcred=$OLD_APPCRED:
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
-G https://prefsdb.com/api/v1/migrateorigin/ \
--data-urlencode "appcred=$NEW_APPCRED"
# {"success":true,"originScoped":true,"message":"Key re-bound to new origin.",...}
If the new credential already has a different key for the same name and seed, the move is refused (409); an unknown or revoked credential gets 403 with APPCRED_INVALID.
PrefsDB.generateEncryptionKey
A new random encryption key, as a string, for client-side encryption. Make it once and keep it somewhere only you control: PrefsDB never receives it and cannot recover data if it is lost.
.enableEncryption
Turns on client-side encryption for this instance: values and files are encrypted in your page before they are sent, so PrefsDB cannot read them. Off by default, and a trade: read what you give up first. Give a key, or a passphrase with a salt; deterministic and strict are optional. Resolves to the instance. You can also pass the same object as encryption in the options of getkey() / createkey().
.disableEncryption
Turns it off again for this instance. Data already written stays encrypted on the server, and reads return it as stored.
.encryptionEnabled
true while client-side encryption is on for this instance.
Options
onsuccess
onerror
timeout
autoRenew
An option of getkey()/createkey()/rotate(), on by default: the client renews the key's session by itself when it runs out, keeping the hashed seed in memory to do so. With autoRenew: false it doesn't keep it, and onSessionExpired() tells you when the session has ended.
throwOnError
An option of getkey()/createkey(), the sign-ins and PrefsDB.client(), off by default: when the server refuses a request, the Promise rejects with a PrefsDBError instead of resolving with success: false. Every request made with the returned object follows it. See How the JavaScript client reports errors.
array
An option of .read(): the response always has a values array, also when one value or none matched. See Always an array.