Bases (Databases)
A base is a database. It holds your tables, and each table has fields (columns), rows (records), and views. A base lives in your account and is linked into a site to give that site its data.
You can set up your tables and bases just by chatting
You don't have to build your tables and bases by hand — describe what you want and AI does it for you. Use the built-in Build chat inside Serenities, or connect Claude, ChatGPT, or any MCP client over MCP and do it from your own AI. You can still set it all up by hand — this page is the reference either way.
Where a base sits: a base is an account-level resource — you create and edit it in the Base Editor, independent of any one site. You then link it to a site. Pages reach the base’s tables through the SDK, which fully enforces your access rules. Backend functions reach it through ctx.tables instead, which applies the table’s own read rule to whoever called the function (see below). A site has one base: its pages and functions reach only that base. For another base’s data, use an account function.
The shape of a base
Account
└─ Base (a database) ← lives in your account
└─ Table (e.g. "Customers") ← a sheet of typed data
├─ Fields (columns: Name, Email, Status…)
├─ Rows (records: one customer per row)
└─ Views (saved filters/sorts/layouts over the same rows)
Site ──links to──▶ Base ← the site uses the base's tablesTables
Each table is like a spreadsheet with typed columns. A base can have many.
Views
Filtered/sorted windows over the same rows — no data copied.
Linked to a site
A site links to a base; the base gives that site its data layer.
Creating & linking a base
- Open the Base Editor from your dashboard and create a base.
- Add tables, then fields (columns) with the right types, and rows of data.
- In your site’s Config → Base, link the base to the site.
- Read/write it from pages via the SDK, or from backend functions with
ctx.tables.
Reading rows in a backend function:
// Inside a backend function — ctx.tables reads the linked base
const { rows } = await ctx.tables.getRows('Customers', { limit: 50 });
// Filtered read — a separate method; getRows has no filter option
const { rows: active } = await ctx.tables.filterRows('Customers', {
Status: { $eq: 'active' },
}, { limit: 50 });Rules apply to the caller: when a member or visitor calls the function, ctx.tables applies the table’s read rule to them (the same rows the site’s data API would give them), and a table with no read rule is read by you only: their read is refused with a message saying so (set a “creator” read rule to let members read the rows they created). Its field rules apply to each row too, as the site’s pages get them. Your own runs, schedules and webhooks see every row. ctx.service.tables deliberately ignores the rules, and only works after the Owner or an Admin turns on Full data access for that function in the dashboard.
The full field-type list, SDK read/write methods, filtering, and views are in the Tables guide.