> ## Documentation Index
> Fetch the complete documentation index at: https://docs.capsule.new/llms.txt
> Use this file to discover all available pages before exploring further.

# Collections And Settings

> Collections, scopes, columns, and settings.

Collections and settings are easy to mix up at first because they both store data. The difference is simpler than it sounds.

Collections hold the records your app works on. Settings hold the knobs people use to control how the app behaves.

Once that distinction clicks, the rest of the page reads much more naturally.

## Collection scopes

Collections and settings are related, but they solve different problems:

* collections are for records
* settings are for configuration

## Worked example

This is a common production shape:

```python theme={null}
threads = app.collection(
    "threads",
    columns=[
        cpsl.Column("subject"),
        cpsl.Column("sender", type="email"),
        cpsl.Column("status", type="status"),
        cpsl.Column("priority"),
    ],
    scope="owner",
    sortable=True,
    filterable=True,
    paginate=25,
)

app.setting("show_closed_threads", scope="owner", type=bool, default=False)
app.setting(
    "default_tab",
    scope="owner",
    type=str,
    default="all",
    options=["all", "needs-review", "waiting", "closed"],
)
```

Here the collection holds the workflow records and the settings hold operator preferences. That is the split most apps want.

Capsule collections support four scopes:

| Scope     | Meaning                                |
| --------- | -------------------------------------- |
| `app`     | Shared across the whole app            |
| `user`    | Isolated per authenticated app user    |
| `owner`   | Shared across an owner or organization |
| `session` | Isolated to a single session           |

A practical way to read the scopes:

* `app` for a shared lead table
* `user` for saved bookmarks for one user of this app
* `owner` for organization-wide reports
* `session` for temporary scratch notes

## `cpsl.Column`

Use `cpsl.Column` when the table needs more than a bare field name. It lets you tell Capsule how a column should be understood and rendered.

```python theme={null}
cpsl.Column("status", type="status")
```

### Supported column types

* `text`
* `number`
* `currency`
* `date`
* `link`
* `email`
* `status`
* `tags`
* `boolean`

### Column fields

| Field    | Meaning                          |
| -------- | -------------------------------- |
| `key`    | Stored field name                |
| `type`   | Column type                      |
| `label`  | Optional display label           |
| `format` | Optional display formatting hint |

Example collection declaration:

```python theme={null}
contacts = app.collection(
    "contacts",
    columns=[
        cpsl.Column("name"),
        cpsl.Column("email", type="email"),
        cpsl.Column("website", type="link"),
        cpsl.Column("status", type="status"),
    ],
    scope="app",
    sortable=True,
    filterable=True,
    paginate=20,
)
```

## `CollectionRef`

`CollectionRef` is returned by `app.collection(...)`.

### Common methods

| Method                                          | Purpose                   |
| ----------------------------------------------- | ------------------------- |
| `insert_one(document)`                          | Insert one document       |
| `insert_many(documents)`                        | Insert multiple documents |
| `find_one(filter=None)`                         | Find one document         |
| `find(filter=None, limit=0, skip=0, sort=None)` | Query many documents      |
| `update_one(filter, update)`                    | Patch one document        |
| `delete_one(filter)`                            | Delete one document       |
| `count(filter=None)`                            | Count matching documents  |

Inserted documents get both `_id` and `id`.

Typical CRUD flow:

```python theme={null}
created = await threads.insert_one(
    {
        "subject": "Renewal review",
        "sender": "ops@example.com",
        "status": "needs-review",
        "priority": "high",
    }
)

row = await threads.find_one({"_id": created["_id"]})
rows = await threads.find({"status": "needs-review"}, limit=25, sort={"priority": 1})
count = await threads.count()
```

## Update behavior

Plain update dicts are treated as patches and automatically wrapped in `$set`:

```python theme={null}
await threads.update_one({"_id": thread_id}, {"status": "waiting"})
```

Operator documents pass through unchanged:

```python theme={null}
await threads.update_one({"_id": thread_id}, {"$inc": {"review_count": 1}})
```

Do not mix plain keys and operator keys in one update document.

## `session.db`

Inside a live session, `session.db` gives you scoped collection access:

```python theme={null}
await session.db.bookmarks.insert_one({"title": "Capsule"})
```

This is especially convenient for `user`, `owner`, and `session` collections, where the live session already knows which scope to use.

Example:

```python theme={null}
@app.message()
async def handle(session: cpsl.Session, msg: cpsl.Message):
    await session.db.review_notes.insert_one({"note": msg.text})
    total = await session.db.review_notes.count()
    await session.reply(f"Review notes in this scope: {total}")
```

## Settings declarations

Declare settings with `app.setting(...)`:

```python theme={null}
app.setting("daily_goal", scope="user", type=int, default=3)
```

### Arguments

| Argument  | Meaning                                 |
| --------- | --------------------------------------- |
| `name`    | Unique setting key                      |
| `scope`   | `app`, `owner`, or `user`               |
| `type`    | `bool`, `str`, `int`, or `float`        |
| `default` | Default value when unset                |
| `options` | Allowed values for select-like settings |
| `label`   | Human-readable UI label                 |

A fuller example:

```python theme={null}
app.setting("daily_goal", scope="user", type=int, default=3, label="Daily goal")
app.setting("show_completed", scope="user", type=bool, default=True, label="Show completed")
app.setting(
    "default_view",
    scope="user",
    type=str,
    default="table",
    options=["table", "chart"],
)
```

## `app.settings`

The settings accessor methods are async:

```python theme={null}
value = await app.settings.get("daily_goal")
await app.settings.set("daily_goal", 5)
all_values = await app.settings.get_all()
```

Settings are stored in a reserved backing collection and scoped using the same identity rules as collections. In practice, you mostly notice this because settings and pages fit together cleanly:

```python theme={null}
@app.page("Preferences")
def preferences():
    return cpsl.ui.Page(
        [
            cpsl.ui.Toggle("Show completed", setting="show_completed"),
            cpsl.ui.NumberInput("Daily goal", setting="daily_goal", min=1, max=20),
            cpsl.ui.Select("Default view", setting="default_view"),
        ]
    )
```

## Practical guidance

Use collections for records the app needs to query later. Use settings for configuration that users or operators should be able to tweak. Use `session.data` for conversational scratch state that only matters inside the current session. Add typed `Column` metadata when the table needs richer rendering or clearer labels.

## See also

* [Add Data And State](/build/add-data-and-state)
* [Add Pages](/build/add-pages)
* [UI And Pages Reference](/reference/ui-and-pages)
