---
title: Data with crow.db
description: Save and share JSON documents from the browser with crow.db, set who can read and write them, take entries from signed-out visitors, and watch for changes.
---

# Data with crow.db

`crow.db` gives every app its own database of JSON documents, used straight from the page. There's no schema to set up and no keys: create a collection by adding to it, and set who can read and write in [crow.json](/crow-json).

## Load the library

```html
<script src="/__crow/sdk.js"></script>
```

It's served by the app's own address, so it always matches. It defines `window.crow`; every call returns a Promise.

## Who is using the app

```js
const me = await crow.me();   // { id, email, name } for a signed-in viewer, or null when signed out
if (!me) crow.signIn();       // sends them to sign in and back to this page
```

## Add, read, change, remove

```js
const tasks = crow.db.collection("tasks");

const doc = await tasks.add({ title: "Ship it", done: false });   // the saved document with id, createdBy, createdAt, updatedAt
const one = await tasks.get(doc.id);                             // the document, or null
await tasks.update(doc.id, { done: true });                      // merges the fields you pass
await tasks.remove(doc.id);
```

Documents are up to 64 KB each. Crow Nest sets four fields you can read but not change: `id`, `createdBy`, `createdAt` and `updatedAt` (epoch milliseconds).

## Query

```js
const page = await tasks.list({
  where: [["done", "=", false], ["priority", ">=", 2]],   // up to 4 filters: = != < <= > >=
  order: "-createdAt",                                     // one field; "-" for descending
  limit: 50,                                               // 1 to 200, default 50
});
// page = { docs: [...], cursor }. Pass { ...the same query, cursor: page.cursor } for the next page; cursor is null at the end.
```

## Watch for changes

```js
const stop = tasks.watch({ order: "-createdAt" }, (page, err) => {
  if (err) return console.error(err);
  render(page.docs);
});
// stop() ends it.
```

`watch` updates live when people change the collection, and falls back to checking every 5 seconds when live updates aren't available.

## Who can read and write

Set rules per collection in `crow.json`. Collections you don't list default to `read: "viewers"` and `write: "creator"`.

```json
{
  "data": {
    "tasks":    { "read": "viewers", "write": "creator" },
    "settings": { "read": "viewers", "write": "owner" }
  }
}
```

| `read` | Who sees documents |
| --- | --- |
| `public` | Anyone who can open the app, signed out too |
| `viewers` | Signed-in people who can open the app |
| `creator` | Each person sees only their own |
| `owner` | Only the app's owner |

| `write` | Who adds and changes |
| --- | --- |
| `viewers` | Anyone signed in adds and changes anything |
| `creator` | Anyone signed in adds; people change only their own |
| `owner` | Only the app's owner |
| `public` | Signed-out visitors add, and change their own entry |
| `public-add` | Signed-out visitors can only add |

### Finding "mine"

By default other readers see only an author's id: `createdBy` is `{ id }`. Compare it with the viewer:

```js
const me = await crow.me();
const mine = page.docs.filter((d) => d.createdBy.id === me.id);
```

To show more, set `showCreator` to `name` or `email` on the collection (not allowed with `read: "public"`).

## Entries from signed-out visitors

On a `public` app, a collection can take entries from people who aren't signed in: a booking page, a contact form, a game's high scores.

```json
{
  "data": {
    "rsvps":  { "read": "owner", "write": "public", "fields": { "name": "string:80", "email": "email?", "slot": "string:40" }, "unique": ["slot"], "editFor": "7d" },
    "scores": { "read": "public", "write": "public-add", "fields": { "name": "string:30", "score": "integer" }, "dailyLimit": 2000 }
  }
}
```

- `fields` is required: entries must match it exactly. Types are `string`, `string:N`, `number`, `integer`, `boolean`, `email`, `date`, `datetime`, with `?` for optional.
- `unique` stops two entries sharing the same values, like one booking per slot. A clash answers `conflict`.
- With `public`, a visitor can change their own entry later from the same browser, for as long as `editFor` allows. `collection.editLink(id)` gives a link that lets them do it from another device.
- The library runs the bot check itself. Signed-out visitors can add 5 entries a minute and 50 a day each, and `dailyLimit` caps the collection.
- The owner can block a visitor's entries from the dashboard's Data tab.

## Errors

Calls throw an `Error` with a `code`: `sign_in_required` (a change by a signed-out visitor sends them to sign in), `forbidden`, `not_found`, `bad_request`, `too_large`, `limit_reached`, `rate_limited`, `conflict`, `edit_closed` or `payment_required`. See [Error codes](/errors).

## Limits

60 writes a minute per person; 100 MB of data per app (1 GB for companies). See [Limits](/limits).

## Seeing what's saved

The dashboard's Data tab shows each collection with its count, rules and recent documents, and exports everything as JSON. Your AI can use `tool_data` for the same view.
