Docs
Home

Creating a database

Spreadsheet-style databases that live next to the conversation in a space.

A canvas database is a structured table of records that lives inside a space, next to the channel and any document canvases. Each row is a record with typed cells, and the same data can be saved through multiple views (table, board, list, gallery, feed) — see Database views.

Use a database for anything you'd otherwise track in a spreadsheet: projects, customer accounts, bug reports, content pipelines, OKRs, release checklists.

Creating a database

  1. Open any space and click + in the canvas tab bar above the channel.
  2. Pick Database to create a fresh, empty table.
  3. Give it a name. The new database appears as a tab next to the channel; everyone in the space can see it immediately.

A new database starts with no columns and one default Table view.

Naming, icon, and description

At the top of every database — just like a document — you can set:

  • An icon. Click Add icon on the toolbar row above the title and pick an emoji. The icon then shows at full size above the title and helps the database stand out. Click an existing icon to change or remove it.
  • A title. Click the large title and type. The name updates everywhere instantly, including the database's tab in the space tab bar and any mention chips that point at it.
  • A description. Click Add a description… under the title to add a short line about what the database is for. Everyone in the space sees it.

The live indicator and the menu sit in the top-right corner above the title. All three edits save automatically and sync to everyone in the space in real time.

Column types

Every column has a kind that controls how the cell renders, what you can type into it, and how filters and sorts behave. There are 16 column kinds; the most common are listed first.

KindWhat it stores
StringFree-form text. Optionally rich-text (Markdown). Capped at 256 KiB per cell.
IntegerWhole numbers. Sortable as numbers.
DecimalFixed-point numbers with declared precision and scale.
BooleanA checkbox — on or off.
TimestamptzAn instant in time, stored with time-zone awareness.
Date range tzA start/end timestamp pair (e.g. an event window or a sprint).
Enum (single)One of a fixed list of options, each with a label and color.
Enum (multi)Any number of options from a fixed list.
URLA link. Renders as a clickable URL.
EmailAn email address. Click to open in your mail client.
UserA workspace member. Shows their avatar and name; click a cell to pick a member from a searchable list.
AttachmentA file uploaded to the workspace, referenced by ID.
Canvas object refA pointer to another canvas (document or database).
Database relationA typed link to another canvas database. Cardinality is one or many per column.
FormulaA computed value derived from other columns. See Formulas below.
DynamicA column whose payload type isn't known yet — useful during import. Narrow later.

Enum (select) columns

Enum is the most common column for kanban-style workflows. Each option has a label and a color, picked from a 9-entry palette (blue, green, yellow, purple, pink, orange, cyan, red, gray) that matches the rest of Monad. Single-select lets a row hold one option; multi-select lets it hold many.

A new Select or Multi-select column starts with no options — cells show an empty dash until options exist. To add your first option, click any cell in the column: the option picker opens, and you can type a name and press Enter to create an option inline. Options you create this way are immediately available to every cell in that column.

Cells whose stored option ID no longer exists in the column's option list (for example, after an option is deleted) also display rather than a raw identifier.

User columns

Add one from the + menu at the right edge of the column headers — name it and pick Person. Click a User cell (or start typing on it) to open a searchable member picker — the same kind of list you get from an @ mention. Type to filter by name or handle, then pick a member to assign them. Choose Unassigned to clear the cell. Each cell holds one member, shown as their avatar and name.

Dynamic columns

When you import data and Monad can't infer a type (mixed strings and numbers, mostly), the column lands as Dynamic. Each cell stores its own scalar value plus a tag describing what it is. When you're ready to commit to a type, the column can be narrowed to a strict kind with one of three policies:

  • Strict — fail the narrowing if any cell can't be coerced cleanly.
  • Coerce best-effort — coerce what you can; the rest stay as-is.
  • Coerce or null — coerce what you can; clear the rest to empty.

The narrowing keeps an audit trail so you can see what changed and why.

Reserved column names

Date-range columns expose two hidden sub-columns (<name>__start and <name>__end) under the hood, so you can't name another column with the __start / __end suffix.

Filters and sorts

Each view stores its own filter and sort configuration so that "open bugs", "this sprint", and "by owner" can each save their own query against the same rows.

Operators

The filter grammar is a closed catalog with the same semantics on the server (SQL) and on the client (offline replay):

  • Equality: eq, neq
  • Ordering: gt, gte, lt, lte, between (with from/to)
  • Set membership: in, not_in
  • Containment (lists / multi-select): contains, not_contains, has_member, not_has_member, set_eq
  • Text: starts_with, ends_with, contains, not_contains
  • Nullness: is_null, is_not_null

Filters compose with AND / OR groups; there's no top-level NOT — negate at the operator instead (e.g. not_contains). Cursor-based pagination keeps long lists responsive.

What the UI exposes today

The structured filter editor is the building block for every view — a visual builder that produces a schema-valid filter for the operator catalog above. The data model, the API, and the agent surface expose every operator today; saved filters round-trip correctly whether they're set by the visual builder, the agent's select_rows tool, or a direct API call.

Rows and cells

  • Add a row — click + Row in the toolbar above the grid.
  • Focus a cell — click it. Arrow keys move the focus.
  • Edit a cell — start typing on a focused cell, press Enter, press F2, or double-click. Enter commits, Escape cancels, Tab commits and moves right.
  • Select a range — Shift+click another cell, or hold Shift while pressing the arrow keys.
  • Select a whole row — click the row number on the left gutter. Shift-click another row number to extend.
  • Select a whole column — click the column header.
  • Reorder columns — drag a column header sideways and drop it on another header. A thin vertical bar shows where the column will land (left edge = before, right edge = after). The new order applies immediately on screen and saves in the background; other viewers see the new order once their browser receives the update. If you're offline, the reorder is queued and replays when you reconnect.
  • CopyCmd+C (macOS) / Ctrl+C (other) copies the selection. A single cell copies as plain text; a range copies as TSV (tab- separated values with newline row separators), ready to paste into another database, a spreadsheet, or any text input. Tabs and newlines embedded inside a cell value are escaped (\t / \n) so the format round-trips.
  • PasteCmd+V / Ctrl+V pastes from the clipboard. A single pasted value drops into the focused cell. A TSV block fans out starting at the focused cell, expanding rightward across columns and downward across rows. If the paste lands past the existing row count, new empty rows are inserted to receive the block. Pasting into a multi-cell selection repeats the block to fill the selection. Values are coerced to each column's kind (number, date, enum-by-label, etc.) — cells that can't be coerced are skipped and logged.
  • Clear cells — select cells (or a range) and press Delete or Backspace. Formula columns are skipped (they aren't writable).
  • Delete rows — select one or more whole rows (click the row gutter), then press Delete. Rows are soft-deleted; restore via version history.
  • Open the full record — click the chevron at the start of any row to open the row detail dialog. Every column appears as a labelled field, which is the easiest way to fill out a row with many columns or to edit kinds the grid can't inline-edit (date-range, attachment, canvas-object reference, database relation).

Every row tracks who last modified it and when, so you can always tell who owns a recent change.

Comments on rows and cells

Canvas databases support threaded comments anchored at four levels of precision:

  • Database — anchored on the table as a whole. Useful for "How do we want to use this database?" meta-discussion.
  • Row — anchored on a specific record.
  • Cell — anchored on a single cell (a column + row pair).
  • Cell substring — anchored on a range of characters inside a rich-text cell. The selection is preserved across edits via a resilient-token strategy, so the anchor stays attached even when the surrounding text changes.

Open a comment thread from the row detail dialog (or the comment icon in the cell editor); replies, resolve, and unresolve work the same way as canvas-document comments — see Canvas documents → Editor for the full thread UX.

Cross-posting a comment to a space

When you create a thread, you can cross-post the root message into one or more spaces. The crossposted message looks like a normal channel message in each target space, with a quote of the anchored text and a link back to the row. Replies, resolve, and unresolve stay scoped to the thread itself — they don't fan out to the cross-post targets.

Use cross-posting when:

  • You want a fast "heads-up, look at this row" in the channel without forcing everyone to open the database tab.
  • The row is a status check-in (sprint end, on-call handoff) where the conversation belongs in the channel but the anchor belongs on the data.

If you only need the comment on the row itself, leave the cross-post list empty.

Following a database

The Subscribe button (the bell, next to the page menu above the title) turns on notifications for this database — the same control canvas documents have. While subscribed you're notified when the database is updated and when someone posts a new comment on it, even if you've muted the space or set it to mentions-only. Click it again to unsubscribe. Anyone who can open the database can follow it.

Real-time collaboration

Everyone in the space sees edits as they happen. Each edit is recorded as a revision on the database; the live view applies them in order. If two people edit the same row at the same time, the second edit lands as a new revision and the first author sees the update.

Unlike a shared spreadsheet, the database is generation-aware: when you revert a revision range (see below) the database's generation bumps, and any in-flight edits made against the old generation are flagged so you can resolve them through a dialog instead of silently losing or duplicating work.

Database page menu

The "…" menu at the top-right of every database carries the page-level actions:

  • Version history — opens the revision side panel (see below).
  • Copy share link — copies a link to this database to your clipboard. It opens the database for anyone who can reach it through any space it's shared into — not just the one you copied it from. See Sharing a database for the details.
  • Export to CSV — downloads the current table as a CSV file. When you have a view selected, the export matches that view: only the columns visible in the view, in the view's order, with the view's filter and sort applied. With no view selected, you get the raw full table. Cell values starting with =, +, -, or @ are prefixed with a leading apostrophe so Excel / Sheets won't interpret them as formulas when the file is opened.

Version history and revert

Every op against the database is recorded as a revision with a kind (schema, data, view, comment, revert, narrowing, formula_recompute). From the database menu you can:

  • Revert a single revision — undoes that op and adds a new revision that reverses it.
  • Revert a range — replays the inverse of every revision in the range in reverse order.
  • See who did what, when — the revision list is the audit log.

A revert never deletes the old history — it appends new compensating revisions, so the audit trail stays intact and you can revert the revert.

Offline editing

The web client buffers edits while you're offline. The header pill at the top of the database shows whether you're live, syncing, connecting, or disconnected. Edits made while disconnected queue up; when the connection returns they replay against the latest server head.

If something on the server changed in a way that conflicts with your queued edits (a revert bumped the generation, for example), a dialog asks you whether to retry, discard, or review your queued ops before applying them.

Formulas

A formula column computes its value from other columns in the same row, using a familiar table-formula surface:

[@Status] = "Done" && [@DueDate] < now()
  • References to other columns use [@Name] (the column's display name). The expression compiles to a stable AST keyed off the column ID, so renaming a column doesn't break the formula — the surface re-renders against the new name automatically.
  • A formula declares its return type up front. If a dependency changes type and the formula no longer fits, the column is marked broken with a clear reason ("references column Owner which was dropped", "expected number, got string", etc.) and you can edit the expression to repair it.
  • Arithmetic + booleans + comparisons + if / and / or / not
    • concat + the aggregates sum, count, min, max, avg are supported, alongside as_string, as_integer, as_decimal, etc. for type coercion.

Formulas can't cycle — if you set up A = [@B] + 1 and B = [@A] + 1, the second commit is rejected with a clear error.

Cell-level formulas (a different cell of the same column overriding the column formula) and filtered rollups are planned but not yet shipped — the schema reserves room for both.

Working with agents

Agents in the space can read, query, and modify the database via a structured tool surface — they do not run raw SQL. They can refer to columns by name (not just internal id) and to dropdown options by their label, so a request like "set Status to Done for everything in this sprint" works without you spelling out any ids. The shipped tools are:

  • describe_database — look up the database's columns (name, type, and the available options for dropdown columns) before reading or editing it.
  • select_rows — read rows matching the structured filter DSL, with order, limit, cursor pagination, and column projection.
  • insert_rows — add up to 1000 new rows in a single call, each with the cell values you provide.
  • update_rows — update many rows in one go, each with its own values (for example, fill in a different headcount and founding year for every company). You can point at each row by a unique field like its name, so you don't have to look up row references first.
  • bulk_set_rows — set a single field to the same value on every row matching a filter (for example, mark everything in this sprint "Done", or tag every overdue task "urgent").
  • bulk_delete_rows — soft-delete every matching row (up to 1000 rows; narrow the filter if more match).
  • narrow_column_type — convert a dynamic column to a strict kind with a strict / coerce-best-effort / coerce-or-null policy.
  • revert_database_revision — undo a single revision, a range, or a branch snapshot.

Common asks:

  • "@agent create a database to track our quarterly OKRs with columns for Owner, Status, Quarter, and Target Date."
  • "@agent add the bug we just discussed to the bug tracker, with severity High and owner @alice."
  • "@agent which projects in our project tracker are still in Backlog past their due date?"
  • "@agent undo my last update on the bug tracker."

Because the agent surface is structured rather than free-form SQL, every action is type-checked end to end and the bulk gates protect you from a single mistaken filter blowing away thousands of rows.

Permissions

Like all canvases, a database inherits its access from the spaces it lives in:

  • Anyone who can read the space can read the database.
  • Anyone who can read a database can also edit it — adding rows, editing cells, adding or renaming columns, and creating new views all follow the same access as reading. If the database is linked into a public space, any workspace member can edit it.
  • Only workspace owners and admins (or the admin of a linked space) can delete the entire canvas.

There are no per-row or per-column permissions today. (Column-level permissions are stubbed in the schema for a future capability.)

Mentioning a database in a message

Type # in any message or canvas document and pick the database from the autocomplete menu to drop in a chip that opens the database when clicked. The chip is distinguishable from a document mention by its Circle Stack icon and Database badge, and the chip's label stays in sync as the database is renamed.

Tips

  • Start with one view, add more as you need them. It's easy to duplicate a view by adding a new one with the same kind and copying the filter.
  • Group by enum columns to get a board. If you find yourself filtering "by status", a board view is usually a better experience.
  • Use a timestamptz or date-range column for due dates — they sort and filter correctly, and you can run "due in the next week" queries.
  • Use a database relation column to link rows across databases — a row in your projects database can carry a one/many link to rows in a tasks database, and an agent can follow the link in one hop.
  • Database views — the five view kinds and their per-view state.
  • Canvas documents → Editor — the comment thread UX that database comments reuse.
  • Spaces → Overview for how canvases relate to the space they live in.
  • Agents → Concepts for what agents can do with your data.