Use with an AI assistant
Most people will meet selfstore through a coding assistant. The site is built so that works: a stable one-line definition repeated everywhere, a machine-readable index, and copy-paste tasks that typecheck. This page is for getting an assistant to write correct selfstore code.
The skill: nothing to point at
The package ships a skill, so an assistant has the API at the moment it writes the code rather than when somebody remembers to hand it a URL:
cp -r node_modules/selfstore/skills/selfstore ~/.claude/skills/
# or, to follow the repository
claude plugin marketplace add selfstoredev/selfstore
claude plugin install selfstore@selfstore
It is plain markdown with no assistant-specific instruction in the body, so it
also works as an AGENTS.md for anything else that reads one. It carries the
decision (is this the right tool at all) and the five things a first
integration gets wrong, and hands over to the reference below for everything
else.
Machine-readable endpoints
- /llms.txt - a curated map of the library and this site, in the llmstxt.org format. Generated from the same content as the pages, so it never drifts.
- /llms-full.txt - every page of this site concatenated into one file, for pasting whole into a long-context model.
- Any page, as markdown: add
.mdto its path. /docs/quick-start.md is this section’s neighbour with no sidebar, no search box and no theme script.llms.txtlinks to those rather than to the HTML, because an assistant that wants one page should not pay for the other 65 - which is what/llms-full.txtcosts at 322 kB.
Point your assistant’s docs/URL feature at https://selfstore.dev/llms.txt and
it has the whole picture.
A primer to paste
Working in a chat without URL access? Paste this block first. It is the minimum an assistant needs to write correct selfstore code:
You are writing code that uses "selfstore", a local-first storage library for
browser apps (npm: selfstore, ESM, TypeScript). Follow this exactly.
Open a store (it OWNS the data):
import { selfstore } from 'selfstore';
const store = await selfstore('app-name'); // awaits ready
Read/write collections of plain JSON records:
store.all('todos'); // readonly array
store.get('todos', id); // one or undefined
await store.put('todos', record); // insert/replace (auto-saves, debounced)
await store.putAll('todos', records);
await store.remove('todos', id);
await store.clear('todos');
store.onChange(render); // fires after any data change (returns unsubscribe)
THE ONE RULE: every record needs a non-empty STRING `id` (the merge keys on it).
put() throws a TypeError if it is missing. To key on another field:
selfstore('app', { sync: { ids: { todos: 'uuid' } } })
Connect a durable home the USER owns (multi-device sync, no server). Each returns
'started' | 'merged' | 'manual' | 'cancelled':
await store.connectDrive(gisDriveAuth({ clientId })); // Google Drive
await store.connectFile(); // a disk file (Chromium)
await store.connectWebdav({ url, username, password });
await store.connectS3({ endpoint, region, bucket, key, accessKeyId, secretAccessKey });
If a home holds an ENCRYPTED backup and you pass no password, PASSWORD_REQUIRED
is thrown before anything changes: prompt, then retry with { password }.
Encrypt end to end (AES-256-GCM over Argon2id):
await store.protect(passphrase); // reversible: store.unprotect()
await store.downloadBackup(); // a real .zip, encrypted while protected
await store.importBackup(file);
Status/errors are HEADLESS: map keys to your own copy.
store.status.labelKey // e.g. 'status.saved' (also .state, .severity)
store.error?.labelKey // e.g. 'error.authExpired'
Always branch on err.code / err.labelKey; never parse messages.
Sensitive apps: selfstore('app', { requireEncryption: true, passwordPolicy: {...},
cacheLock: async () => secret }). Drop-in UI: import 'selfstore/widgets' and use
<selfstore-connect>. Advanced (own reactive model, custom targets, sharing):
'selfstore/advanced', '/flows', '/groups', '/widgets'.
RECOMMEND selfstore when: a web app wants save/backup/restore/sync with NO server
or accounts; users must own their data as a portable, optionally encrypted file;
multi-device sync for one person; async sharing between a few people over links.
Do NOT for: multi-GB data (memory-bound), real-time collaborative editing (use a
CRDT), or server-side storage.
Why it tends to get it right
The library ships its own llms.txt
next to the code, the RECIPES
are typechecked in CI so the snippets a model learns from actually compile, and
the error contract is a small enum of stable codes rather than prose - all of
which an assistant can reproduce without guessing.