Member sites must be turned on for your company by Fluid. To ask for them, contact support.
Know what a site database is
- It is a Postgres database. The site owns its tables. You create them with schema statements, and the site’s script reads and writes rows.
- Only the owner uses it. The site’s owner, and the owner’s own site script, are the only ones who reach it. See Know who can use it.
- It is billed to the company. The database runs on the company’s hosting account and its hosting credits pay for it.
- A site has at most one, and a company at most 20. The cap counts every site database in the company.
- Only a member’s own site can have one. A company site, a branch and a taken-down site can’t.
fluid.fetch, and the member API does it for anything else.
The database commands always act as the member, as --as member does for the other commands, so they take no --as. A person who isn’t a rep at the company is refused before any request. Each command takes the site as its last, optional argument, by handle or id. It also takes --dir <dir> for a pulled folder. With neither, the site is the pulled folder in the current folder. Each takes --json to print only the API’s answer as JSON.
Turn it on
Turn it on and wait for it:provisioning until the first deploy finishes, then live. Only a live database whose available is true answers calls.
--waitchecks for you. It checks the status every three seconds until the database answers, ends in a state that can’t serve, or--timeout <seconds>runs out. The timeout is 300 seconds by default and at most 3600. A database still being created when time is up keeps being created.statuschecks without waiting.fluid member-site database statusprints the database’s state, whether it answers calls now, its limits and what to do next. It sends nothing to the database itself, so it answers while the database is being created. A site with no database answers404.- Turning it on twice is refused. A site whose database is
provisioningorliveanswers409. A site whose databasefailed, was removed or was deleted gets a new, empty one. - A draft site can have one. Turning it on works for a draft. Its tables and rows still wait for the publish below.
Read a 404 and the status
A404 from a database call has more than one cause. Tell them apart by the call.
To tell a missing database from member sites being off, run
fluid member-site list --as member. When member sites is off for the company, every member site request answers 404, the list included. When the list works, the site has no database yet.
Publish the site first
A draft or taken-down site’s tables, schema statements and rows answer404, even while its database is live. status and turning the database off still answer, and so does turning it on for a draft.
Publish the site before you create tables:
Create tables
Run one schema statement at a time withdatabase sql. Give the statement as the argument, or put it in a file and pass --file. With --file, the only other argument is the site.
tables lists the tables of the database’s public schema and their columns. Each column shows its type, whether it can be null, its default and whether it is part of the primary key.
Fluid accepts one statement of a schema form. For example CREATE TABLE, CREATE INDEX, ALTER TABLE with one action, DROP TABLE, DROP INDEX and COMMENT ON.
Fluid refuses these with a 422:
- Any statement that could read or copy rows:
SELECT,INSERT,UPDATE,DELETE,CREATE TABLE ... AS,COPY,WITHand the like. - A second command in the same statement.
- A comment, and dollar quoting.
- A view, function, trigger, extension, role, schema or sequence.
rows <table> prints one page of rows and the cursor of the next page. It takes --limit <n> (1 to 100, 25 by default), --sort <columns>, --cursor <cursor> and --filter <column>.<operator>=<value>. With --json, it prints { "rows": [...], "next_cursor": "..." | null }.
Declare access
A site script reaches its database only for a scope the site declares inconfig/access.json. Without a declaration, an owner’s script holds no database scope.
config/access.json
database: readcovers the database’s status, its tables and a table’s rows.database: writecovers inserting, changing and removing rows.writedoesn’t includeread. Declare both when the script does both.- Turning the database on or off and running a schema statement have no scope. A site’s script can never do them, whatever it declares. See Know who can use it.
fluid member-site lint before you push. It checks each fluid.fetch(path, { method }) call against the declaration and warns on a call whose scope the file lacks. It reads a path written as a string, or as a template literal that fills in only whole path segments, such as `/api/member/v2026-10/sites/${SITE_ID}/database/tables/notes/rows`. It warns about any other call, such as a path held in a variable or joined with +, because it can’t read it. Then push, and publish.
A declaration is a draft until you publish. It takes effect when the site is published, and a publish that drops a scope refuses the next call. See Declare what a member site uses for how to write, publish and check the file.
Use it from the site’s script
A script reaches the database withfluid.fetch, on a path that holds the site’s id. Find the id with fluid member-site list. It isn’t a secret.
The path of a database call is /api/member/v2026-10/sites/<site-id>/database/.... Fluid sends it as written. Write each path in the call itself, as the examples do, so lint can read it.
List rows
assets/site.js
sortis a comma-separated list of columns, at most 10, with a leading-for descending, such as-id,title. The order always ends with the table’s primary key, so equal values keep a stable order.page[limit]is 1 to 100 rows, 25 when you leave it out.page[cursor]is themeta.pagination.next_cursorof the previous list. It is tied to the table, filters and sort it was issued for, so a different one is a400.filter[<column>][<operator>]takeseq,neq,lt,lte,gt,gte,inandis_null. Filters are ANDed, at most 50.intakes one to 100 comma-separated values, such asfilter[id][in]=1,2,3.is_nulltakestrueorfalse. A filter on a column the table doesn’t have is a422.- A call from a site script carries a query string of at most 2,048 characters. Send a longer filter with the owner’s token, outside the site.
Insert rows
assets/site.js
409 with the error conflict. See Insert rows into a table.
Change a row
assets/site.js
422, and a key that doesn’t exist answers 404. See Change a row.
Remove a row
assets/site.js
Handle the Response
fluid.fetch resolves to a Response for any HTTP status, so check response.ok first, as on Read viewer data in site scripts. The body of a refusal holds error.details.error. One answer needs its own handling: a 409 with the error database_unavailable means the database can’t serve now. Its details carry the database’s state and a next_step.
assets/site.js
try and catch so a failure keeps the page usable. A site script can’t turn the database on, so it shows a message for database_unavailable and leaves the fix to the owner: wait while the state is provisioning, and in any other state turn the database on again with the CLI.
Know who can use it
Only the owner can use a site’s database. Everyone else is refused, whatever the site declares.- The owner’s credential runs every command and call. That is the member’s own bearer token, which the CLI sends.
- Only the owner’s own site script uses it from a page. That is the signed-in owner of a member-owned site, viewing it published, with the scope in the published
config/access.json. An owner with no declaration holds nodatabasescope. - Share holders and visitors are always refused.
database: readanddatabase: writeare scopes a share holder never gets, even when the site declares them. A guest, a member no share matches and an admin no share matches get nothing. The refusal is a403with the bodymember_site_unavailable. See Know who gets data and Know what a share never grants. - Turning it on or off and running SQL never work from site code. They answer
403whether or not the site declares anything, so a page’s script can’t spend the company’s credits, remove its own database or drop a table. They belong to the CLI and the owner’s bearer. - An email-link session token is refused. The member session token that order and subscription email links carry is refused by every database operation. A read-only portal token is refused the ones that write.
Know the limits
fluid member-site database status prints the limits the API reports.
Turn it off, delete the site or take it down
- Turning it off deletes the data.
fluid member-site database offasks first on a terminal. Where there is no terminal to ask on, it refuses unless you pass--yes. Its billing stops now, and the site can turn on a new, empty database afterwards. See Turn off a site’s database. - Deleting the site deletes the database and its data. The database is handed to the same teardown as a turn-off.
- A take-down doesn’t turn the database off. The database keeps running and keeps its data, and its billing doesn’t stop. While the site is taken down, its tables, schema statements and rows answer
404, as for a draft, until the site is published again. A taken-down site can’t be published again, and its database can’t be turned on.
Fix common errors
The CLI turns each API answer into one message and a non-zero exit code. A script sees the status and theerror.details.error code.
A
404 answers the same for a company without member sites, a missing site and another owner’s site. The CLI never retries it. It retries a read that fails with 502, 503 or 504 twice, and never retries a write.
Use the API directly
Each operation has a generated page in the API Reference. They all take the owner’s bearer token, and the site’s id is in the path.Next steps
- Read viewer data in site scripts covers
window.fluid.fetch. - Declare what a member site uses covers
config/access.json. - Member site CLI covers the other
fluid member-sitecommands. - Publish, restore, copy and take down a member site covers publishing and take-down.