Skip to content

API Reference

Initialize PrefsDB instance

<script src="https://prefsdb.com/prefsdb.v1.min.js"></script>

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).

[Promise promise = ] .write(data [, options])

const w = await db.key('theme').write('dark');
w.settled.catch((err) => console.log('not saved:', err.code));
await db.key('theme').write('light', { ifVersion: w.version });   // CONFLICT if it changed meanwhile
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -H "Content-Type: text/plain" \
  --data-binary "data" "https://prefsdb.com/api/v1/write/?*="

.key

[Promise promise = ] .key('itemid').write(data [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ --data-urlencode "itemid=data"

.setItem

[Promise promise = ] .setItem('theme', value [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -H "Content-Type: text/plain" \
  --data-binary "value" "https://prefsdb.com/api/v1/write/?theme="

.putObject

[Promise promise = ] .putObject(file [, options])   // resolves to { file: "<file id>", ... }
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -F "file=@photo.jpg" https://prefsdb.com/api/v1/putobject/

.add

[Promise promise = ] .list('mylist').add(data [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ -d list=mylist --data-urlencode "*=data"

Read Data


         .read( [options] )

         .readAll( [options] )

         .pages( [options] )

         .entries( [options] )

         .getItem( key [, options] )

         .getObject( fileId [, options] )

         .getObjectUrl( fileId [, options] )

.read

[Promise promise = ] .read( [options] )
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d "*="

.readAll

[Promise promise = ] .readAll( [options] )
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d "**="

.pages

Reads in pages of limit values, newest first. See Reading in pages.

[Promise promise = ] .list('mylist').read({ limit: 100 })
[Promise promise = ] .list('mylist').read({ limit: 100, after: previous.next })
for await (const page of .list('mylist').pages({ limit: 100 })) { ... }
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d list=mylist -d "*=" -d __limit=100 --data-urlencode "__after=$NEXT"

.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
# __start=0 always answers with "values"
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d __map=inventory -d "*=" -d __start=0

.key

[Promise promise = ] .key('itemid').read( [options] )
[Promise promise = ] .keys(['itemid', 'other']).read( [options] )
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d "itemid="

curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d "itemid=" -d "other="

.match

[Promise promise = ] .list('mylist').match('pattern').read([options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d list=mylist -d "*=" --data-urlencode "__filter=pattern"

.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.

[Promise promise = ] .map('todos').changes({ since [, limit] })

const items = await db.map('todos').entries();
const res = await db.map('todos').changes({ since: items.changeSeq });
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d __map=todos -d "*=" -d __since=120

.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

[Promise promise = ] .getItem('theme' [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/read/ -d "theme="

.getObject

[Promise promise = ] .getObject(fileId [, options])   // resolves to { blob, filename, contentType }
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -o photo.jpg \
  -G https://prefsdb.com/api/v1/getobject/ -d file=$FILE_ID

.getObjectUrl

[Promise promise = ] .getObjectUrl(fileId [, options])   // resolves to a web address, e.g. for <img src>
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/getobject/ -d file=$FILE_ID -d url=1

Filtering results


         .match( 'pattern' )

.match

[Promise promise = ] .list('mylist').match('pattern').read([options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d list=mylist -d "*=" --data-urlencode "__filter=pattern"

Remove all values from a list named "mystuff" that begin with 2025.

await db.list("mystuff").match("2025%").delete();
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/delete/ \
  -d list=mystuff -d "*=" --data-urlencode "__filter=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

await db.project('myapp').key('data').write(data);
const resp = await db.project('myapp').key('data').read();
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/write/ \
  -d project=myapp --data-urlencode "data=..."
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d project=myapp -d "data="

.domain

await db.project('myapp').domain('display').key('settings').write(settings);
const resp = await db.project('myapp').domain('display').key('settings').read();
settings = resp.value;
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

await db.project('myapp')
    .domain('display')
    .subdomain('accessibility')
    .key('settings')
    .write(settings);

const resp = await db.project('myapp')
    .domain('display')
    .subdomain('accessibility')
    .key('settings')
    .read();
settings = resp.value;
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/read/ \
  -d project=myapp -d domain=display -d subdomain=accessibility -d "settings="

.map

[Promise promise = ] .map('inventory').put('apples', 12 [, options])
[Promise promise = ] .map('inventory').get('apples' [, options])
[Promise promise = ] .map('inventory').remove('apples' [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ -d __map=inventory -d apples=12

Delete data


         .delete( [options] )

         .deleteObject( fileId [, options] )

         .ttl( days )

         .expire( days [, options] )

.delete

Delete a entry (a value) assigned to a key named "nickname"

[Promise promise = ] .key("nickname").delete([options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/delete/ -d "nickname="

Remove a specific "value" from a list named "mystuff".

[Promise promise = ] .list("mystuff").match("value").delete([options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" -G https://prefsdb.com/api/v1/delete/ \
  -d list=mystuff -d "*=" --data-urlencode "__filter=value"

{ ifVersion } deletes a single key only if its version is still that one, and answers CONFLICT otherwise (see Several Devices).

See: Remove data

.deleteObject

[Promise promise = ] .deleteObject(fileId [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/deleteobject/ -d file=$FILE_ID

.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.

[Object storage = ] .storage()
[Function stop = ] .onStorage(({ used, max, plan, ratio }) => { ... })
# Reported as "storage" in getkey, putobject and deleteobject responses:
#   "storage": { "used": 41943040, "max": 52428800, "plan": "free" }

.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.

[Promise promise = ] .ttl(30).key('foobar').write('data' [, options])
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/write/ -d foobar=data -d ttl=30

.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().

Promise promise = .project('my-app').expire(30)
Promise promise = .project('my-app').domain('active-users').expire(1)
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/expire/ -d project=my-app -d days=30
curl -s -b cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/expire/ -d project=my-app -d domain=active-users -d days=1

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).

[Promise<PrefsDB> promise = ] PrefsDB.getkey('name', ['a', 'b', ...] [, options])

const db = await PrefsDB.getkey('my-app', ['a', 'b'], { email: 'you@example.com' });
curl -s -c cookies.txt -G https://prefsdb.com/api/v1/getkey/ \
  --data-urlencode "name=my-app" --data-urlencode "seed=a b" \
  --data-urlencode "namespace=$PREFSDB_NAMESPACE" --data-urlencode "email=you@example.com" \
  -d offline=1

PrefsDB.createkey

Creates a new key using parameters provided. Returns a failed response , if a key already exists.

[Promise<PrefsDB> promise = ] PrefsDB.createkey('name', ['a', 'b', ...] [, options])
curl -s -c cookies.txt -G https://prefsdb.com/api/v1/createkey/ \
  --data-urlencode "name=my-app" --data-urlencode "seed=a b" \
  --data-urlencode "namespace=$PREFSDB_NAMESPACE" --data-urlencode "email=you@example.com" \
  -d offline=1

.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.

if (db.emailVerificationRequired()) {
    showMessage("Check your inbox");
    await db.waitForVerification();
}
curl -s -H "X-Authorization: Token $KEY" https://prefsdb.com/api/v1/verify/status/
# {"success":true,"verified":false,"reason":"UNVERIFIED",...}

.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.

db.onSessionExpired(() => showSignIn());

.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.

const db = await PrefsDB.resume("todo-app") ?? await showSignIn();
curl -s -c cookies.txt https://prefsdb.com/api/v1/getkey/resume/ --data-urlencode "grant=$GRANT"

.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.

db.onSyncEvent((e) => { if (e.phase === "dropped") reloadTodos(); });

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.

const db = PrefsDB.client(apikey, { throwOnError: true });

.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.

[Promise<PrefsDB> promise = ] .rotate('name', ['new', 'seed', ...] [, options])
curl -s -b cookies.txt -c cookies.txt -H "X-Authorization: Token $KEY" \
  -G https://prefsdb.com/api/v1/rotatekey/ --data-urlencode "name=my-app" --data-urlencode "seed=new seed" \
  --data-urlencode "namespace=$PREFSDB_NAMESPACE"

.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:

  1. Deploy code on the old address that calls migrateOrigin() after each user's getkey().
  2. Keep the old address running until your users have visited it. Only keys that have been moved are found from the new address.
  3. 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.

[Promise<PrefsDB> promise = ] .migrateNamespace(namespace [, options])

const db = await PrefsDB.getkey("my-app", [userId], { namespace: oldNamespace });
await db.migrateNamespace(newNamespace);
# 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:

  1. Create a new credential in your dashboard's App Credentials panel.
  2. For each key: call getkey() through the old credential, then migrateAppcred() with the new one.
  3. Switch your server over to the new credential.
  4. Revoke the old credential.
[Promise<PrefsDB> promise = ] .migrateAppcred(appcred [, options])

const db = await PrefsDB.getkey("my-app", [userId], { appcred: oldCredential });
await db.migrateAppcred(newCredential);
# 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.

const key = PrefsDB.generateEncryptionKey();

.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().

await db.enableEncryption({ key });

.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

    onsuccess: (result) => {
        console.log("%o", result);
    }

onerror

    onerror: (error) => {
        console.log(error.message);
    }

timeout

    timeout: 60000 /* 1-minute request 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.

    autoRenew: false

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.

    const db = await PrefsDB.getkey("myapp", seed, { email, throwOnError: true });

array

An option of .read(): the response always has a values array, also when one value or none matched. See Always an array.

    const res = await db.list('todos').read({ array: true });