# A database for an app your agent built - scava.db

An app on Scava gets a shared database without any setup. The app calls `scava.db('name')` and stores entries that everyone with access to the app sees. There is no server to run, no connection string and no schema. The agent that builds the app needs nothing more than the four calls below.

## The four calls

```js
const db = scava.db('vacation');          // a collection; it exists once you write to it

const entries = await db.list();           // the entries this person may read, oldest first
const entry = await db.add({ from: '2026-12-22', to: '2026-12-31' });
await db.update(entry.id, { to: '2027-01-02' });
await db.remove(entry.id);
```

- A **collection** name uses letters, digits, `-` and `_`, up to 40 characters. An app can use as many collections as it wants. They are separate lists, like tables.
- An **entry** is a JSON object. Nested objects and arrays are fine.
- `add` returns the new entry. `update` merges the fields you send into the entry: fields you send replace the old values, fields you leave out stay as they are. A nested object you send replaces the old nested object as a whole. `remove` deletes the entry.

Every entry comes back in this shape:

```json
{
  "id": "k3x9...",
  "data": { "from": "2026-12-22", "to": "2026-12-31" },
  "created_by": "anna@company.com",
  "created_at": 1790000000000,
  "updated_by": null,
  "updated_at": null
}
```

`created_by` and `updated_by` are email addresses. The timestamps are milliseconds since 1970, as `Date.now()` returns them. The rules in `scava.json` use `created_by` to decide what "own" means; see [Permission rules](/docs/rules).

## A complete small app

Save this as `index.html` in a folder and publish it. It is a working vacation list.

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Team vacation</title>
</head>
<body>
  <h1>Team vacation</h1>
  <form id="form">
    <input id="from" type="date" required>
    <input id="to" type="date" required>
    <button>Add</button>
  </form>
  <ul id="list"></ul>
  <script>
    const vacation = scava.db('vacation');
    const list = document.getElementById('list');

    async function show() {
      const entries = await vacation.list();
      list.textContent = '';
      for (const e of entries) {
        const li = document.createElement('li');
        li.textContent = e.created_by + ': ' + e.data.from + ' to ' + e.data.to;
        list.append(li);
      }
    }

    document.getElementById('form').addEventListener('submit', async (event) => {
      event.preventDefault();
      await vacation.add({
        from: document.getElementById('from').value,
        to: document.getElementById('to').value,
      });
      event.target.reset();
      show();
    });

    show();
  </script>
</body>
</html>
```

The Scava script is loaded before your own scripts, so `scava` is always there. Show stored text with `textContent`, as above, or pass it through `scava.escape(text)` when you build HTML.

## Many entries: paging

One `list()` call returns one page. When there are more entries, the result has a `next` value. Pass it back as `after`:

```js
let page = await db.list({ limit: 500 });
const all = [...page];
while (page.next) {
  page = await db.list({ limit: 500, after: page.next });
  all.push(...page);
}
```

`db.list({ order: 'newest' })` starts with the newest entry; the default is `'oldest'`. `next` is `null` on the last page.

There is no query language. To find entries, list them and filter in the page, or keep separate collections for separate questions.

## Who sees what

Everyone with access to the app sees the same data, unless the rules in `scava.json` say otherwise. By default every person reads all entries and changes only the entries they created; the owner can do everything. The rules are enforced on Scava's server, so a changed page or a hand-made request cannot get around them. See [Permission rules](/docs/rules).

## What is not accepted

Scava cleans what is stored, so that an entry can never become code in another person's browser.

- Entries must be objects, at most 64 KB each. Extremely deep nesting is refused.
- Keys named `__proto__`, `constructor` or `prototype` are refused.
- Text that starts with `javascript:` or similar is refused.
- Invisible control characters and characters that reverse the reading direction are removed.

## Limits

| What | Limit |
|---|---|
| Size of one entry | 64 KB |
| Entries per app | 1,000,000 |
| Data per app | 2 GB |
| Changes per person and app | 300 in 10 minutes |
| Storage per account | Files and entries of all your apps together count against the storage of your plan: Starter 10 GB, Team 50 GB, Business 250 GB |

When the storage of the plan or a limit of the app is used up, further writes fail with "This app has reached its storage limit." More limits are in [Limits](/docs/limits).

## One database per app

Each app has a database of its own. An app cannot read the entries of another app, and one app cannot fill the space of another. The data lives in the EU unless the owner chose the US for new apps; see [Data location](/docs/data-location).

## Export and deletion

The owner of an app can download everything the app holds from the app page: the files of the live version, every collection as a JSON file, and every uploaded file, in one ZIP. The download needs a sign-in from the last 12 hours, works only in the browser, and is limited to 10 per app and hour. Deleting the app deletes its entries and files.

## Server code

Server code reads and writes the same collections with `env.SCAVA.db('vacation')`, which also offers `get(id)`. See [Server code](/docs/server-code).
