Tinylib
DiscoverDocsSign in
Sign in
Tinylib
AboutDiscoverDocsMake an appPricingTermsPrivacyDeveloper TermsContact

Start

  • Quickstart
  • How Tinylib works

Guides

  • Data
    • How the user's data syncs
    • Who can read and write it
    • Save and read values
    • Decide what goes in one key
    • React to changes from elsewhere
    • Handle a full store
    • Keys only the rules write
    • Type your data
  • Rooms and rules
  • Games
  • Friends
  • Places and links
  • Reminders and notifications
  • Publishing
  • Errors and offline
  • Platforms

Client API

  • tinylib
  • tinylib.data
  • tinylib.rooms
  • tinylib.invitations
  • tinylib.friends
  • tinylib.places
  • tinylib.reminders
  • Types
  • tinylib-sdk/kits

Rules API

  • Kinds
  • ctx

Reference

  • tinylib.json
  • Command line
  • Limits

Plain text

  • llms.txt
  • llms-full.txt

Data

tinylib.data saves the user's data for your app as keys with JSON values. Every call is synchronous and works offline, and Tinylib syncs the data across the user's devices.

The user's device Tinylib's servers A friend's device Local data Your client Room Your rules The user's data Just them Friends too entry/… best rating wins A friend's client tinylib.friends.data sync reads, writes writes reads Your code Tinylib
The user's device Tinylib's servers A friend's device Your client Local data The user's data Just them Friends too entry/… best rating wins Room Your rules A friend's client tinylib.friends.data reads, writes sync writes reads Your code Tinylib

How the user's data syncs

A set or remove changes the device's copy at once, and Tinylib sends the write to its servers moments later. Offline, the write waits on the device until there's a connection. Nothing tells your client when a write has reached the servers.

When two devices change the same key, the write that reaches Tinylib's servers last wins, whenever it was made. A remove is a write like any other. If the user ticks off a to-do on their phone while offline, then removes it on their laptop, the to-do comes back when the phone reconnects. The device whose write lost gets the winning value, and onChange tells its client.

In npx tinylib-sdk dev, choose the same user at the top of both panes to watch a change reach another device. To make a conflict, turn one pane's Online switch off, change the same key in both panes, then turn it back on.

A guest, a user without a Tinylib account, has data like anyone, and it reaches no other device until they sign in.

Who can read and write it

WhoReadsWrites
Your client, on the user's devices
Reads
Every key
Writes
Every key except declared ones
Your rules, in a room the user is in
Reads
Declared keys
Writes
Declared keys
Your client, on a friend's device
Reads
Keys saved for friends, while online
Writes
Nothing
Other apps, and you as the developer
Reads
Nothing
Writes
Nothing

A value is private to the user unless it's saved with audience: 'friends'. A later save without audience keeps the value's audience, and a save with audience: 'me' makes it private again.

For data that several people change together, like a shopping list a couple shares, use a room.

The user can delete their data in your app from Tinylib. That deletes everything saved for them in your app, takes them out of its rooms, and closes your app without warning if it's open. If they come back, they start as a new user with a new id.

Save and read values

set saves a value under a string key, get reads it, and remove deletes it:

JavaScript
tinylib.data.set('entry/2026-10-09', 'Rain all day') tinylib.data.get('entry/2026-10-09') // 'Rain all day' tinylib.data.remove('entry/2026-10-01')

set stores the value as JSON.stringify writes it, so a Date comes back as a string. A value JSON can't hold, like undefined, throws a TypeError. get returns a new copy each time, so changing what it returns changes nothing until you set it.

To read related keys together, give them a common prefix. keys, values, and entries return the keys that start with it, sorted by key, so dates written as YYYY-MM-DD come back in date order:

JavaScript
tinylib.data.keys('entry/') // ['entry/2026-10-08', 'entry/2026-10-09'] tinylib.data.entries('entry/') // [['entry/2026-10-08', '…'], ['entry/2026-10-09', 'Rain all day']]

There's no query call. The whole store is in memory, so filter what entries returns.

Decide what goes in one key

A write replaces a key's whole value, and conflicts are settled key by key. Give each item in a list its own key, so edits to two items on two devices both survive:

JavaScript
const id = crypto.randomUUID() tinylib.data.set(`todo/${id}`, { text: 'Buy milk', done: false, createdAt: tinylib.now() })

To list them in order, keep each item's creation time in its value and sort by it:

JavaScript
const todos = tinylib.data.values('todo/').sort((a, b) => a.createdAt - b.createdAt)

Tinylib has no transaction across keys, though, so keep values that must change together in one key, like a puzzle's grid and its move count. Two devices that edit that key conflict over all of it.

React to changes from elsewhere

Your client opens on the device's copy of the data, and what changed elsewhere arrives just after. To redraw when the user's data changes on another device, in another tab, or in your rules, pass a function to onChange. It gets the keys that changed, and it isn't called for this client's own writes:

app.js
tinylib.data.onChange((keys, error) => { if (error) toast(error.message) if (keys.some((key) => key.startsWith('entry/'))) drawEntries() })

onChange also reports a write that failed after set returned, for example because writes from another device took the data past 1 MB. Then error is set and those keys go back to what Tinylib's servers have, so the user's change is gone from the device. Keep what the user typed on screen so they can try again. onChange lists each error.code.

Handle a full store

A set that would grow the data your client writes for the user past 1 MB throws a TinylibError with the code limit and saves nothing. A set that makes the data smaller still works, and so does remove:

app.js
try { tinylib.data.set('entry/2026-10-09', document.querySelector('#entry').value) } catch (error) { if (error.code !== 'limit') throw error toast(error.message) }

Keys only the rules write

A user can make their own client save anything. For a value other users should trust, like a count of wins, declare its key in rulesKeys in tinylib.json, and only your rules can write it. A name ending in / declares every key under it:

tinylib.json
{ "rulesKeys": ["wins", "rating", "games/"] }

Your rules write a declared key for a room member with ctx.data(id). Use add, min, or max rather than get then set: if two rooms finish at the same moment, add counts both, while get then set drops one.

rules.js
export const game = { // ... actions: { move(state, square, ctx) { // ...place the mark, and on a win set state.winner to the mark if (state.winner === state.turn) { ctx.data(ctx.user.id).add('wins', 1, { audience: 'friends' }) ctx.end() } }, }, }

Your client reads a declared key like any other, but its set and remove throw a TypeError for one.

A key stays declared for the app's life: no later version can drop it from rulesKeys. A new version also can't declare a key that already holds a value your client wrote for any user.

Type your data

In TypeScript, declare your keys on Register in a .d.ts file:

tinylib-env.d.ts
import type {} from 'tinylib-sdk' declare module 'tinylib-sdk' { interface Register { data: { wins: number; [key: `entry/${string}`]: string } } }

get and set then check each key's type, while entries and values still return unknown. Tinylib checks nothing at runtime, and values an earlier version wrote may have another shape.

PreviousHow Tinylib worksNextRooms and rules

On this page

  • How the user's data syncs
  • Who can read and write it
  • Save and read values
  • Decide what goes in one key
  • React to changes from elsewhere
  • Handle a full store
  • Keys only the rules write
  • Type your data