Skip to main content
A member site can keep its own data: RSVPs, guestbook entries, a team’s goals. You declare its tables in db/schema.json, and your code reads and writes them through fluid.db. The browser never sends SQL and never holds a database credential. Each request goes to your site’s host, which checks it against your schema and your table rules before it touches a row.
Site databases are rolling out. Until they reach your company, a publish whose folder has a db/schema.json is refused with no_database, and nothing is published. A site without a db/ folder publishes as usual.

Declare the tables

db/schema.json lists the site’s tables. This one keeps RSVPs for the site’s events:
db/schema.json
A table or column name starts with a lowercase letter and holds lowercase letters, digits and underscores, at most 63 characters. It can’t start with _ or pg_, and a column can’t take a system column’s name. The whole file is at most 1 MiB.

Column types

One request’s body is at most 1 MiB (see Limits), so a row you write through fluid.db is smaller than that. A json column compares as a whole value: filter it with eq, neq, in or is only, and don’t order by it.

System columns

Every table has four columns you don’t declare. Your code can read them but never write them.

Choose who reads and writes

A rule applies to one table and one operation: read, insert, update or delete. An operation with no rule gets its default: So with no rules, signed-in visitors can add rows and change their own, and only the owner reads them. Set read to signed_in or public when visitors should see each other’s rows.
Your site’s code runs with each visitor’s own access. Whatever it writes to a table, whoever its read rule admits can read, created_by included. Don’t copy a visitor’s private data, such as their orders, into a table other people can read.

Query a table

fluid.db.from(table) starts a query. Await it to send one request. It resolves { data, error } whatever happens, and never throws for an answer.

Read rows

  • select() or select("*") reads every declared column, then the system columns.
  • select("name,guests", { count: "exact" }) also returns count, the number of rows that match.
  • limit(n) takes at most 1,000 rows. With no limit, a select returns 100.
  • range(0, 49) reads the first 50 rows, and range(50, 99) the next 50.
  • single() resolves the one row, and validation unless exactly one matches. maybeSingle() resolves null when none matches.

Write rows

  • insert takes one row or an array of up to 500.
  • update and delete need a filter, so they never touch every row.
  • Add .select() after a write to get the written rows back.
  • A creator rule adds “rows this visitor wrote” to the filter for you, so an update on another person’s row changes nothing.

Filter

Methods that fluid.db leaves out, such as upsert, rpc, textSearch and joins in select, throw a NotSupportedError that names the method.

Handle errors

error is null on success. Otherwise it has a code, a message, and sometimes details:

Limits

The row, storage and request limits of one site can be raised for your company.

Change the schema

Each publish compares your new db/schema.json with the schema the site’s database holds. A destructive change needs a migration in migrations, with an id unique in the site. Keep every other key of the table as it was: a rule you leave out goes back to its default, and changing a rule is applied with no confirmation. This example renames guests and drops note, and changes nothing else:
db/schema.json
Confirm each migration by its id when you publish:
Without the confirmation, the publish fails with destructive_change_unconfirmed, lists each unconfirmed migration, and changes nothing. A migration runs once: a later publish with the same migration doesn’t run it again. A schema change that fails leaves the database and the current version of the site as they were.