Data Tables
Data Tables let you store and query structured, app-scoped data from your server. The Admin Package exposes them under memberstack.dataTables, with methods to read table schemas and to create, query, update, and delete records.
- Initialize the Admin Package with your secret key as shown in the Quick Start guide
- Create your tables and fields in the Memberstack dashboard (Data Tables) first — the Admin Package reads and writes records, it does not create tables or fields
- The Admin API has full access and ignores per-table access rules (createRule/readRule/etc.)
- Records are environment-scoped: a sandbox key (
sk_sb_…) only sees sandbox records
Overview
Every method takes a table (the table key from your dashboard, e.g. "contacts", or a table id like "tbl_…"). Record values live under a data object keyed by your field keys. Responses are wrapped in { data }, the same as the member methods.
| Method | Description |
|---|---|
dataTables.list() | List all tables and their fields |
dataTables.get() | Get one table by key or id |
dataTables.createRecord() | Create a record |
dataTables.getRecord() | Get one record by id |
dataTables.queryRecords() | Query records (findMany / findUnique) |
dataTables.updateRecord() | Update a record |
dataTables.deleteRecord() | Delete a record |
Field Types
Each table field has a type that determines how its value is stored and returned. Fields are defined in the dashboard (Data Tables); the type is reported on each field in list() / get().
| Type | Description |
|---|---|
| TEXT | Plain text (also TEXT_FILTERABLE, TEXT_UNIQUE, EMAIL, URL variants) |
| NUMBER | Integer value |
| DECIMAL | Decimal value (see the note on string vs number below) |
| BOOLEAN | true / false |
| DATE | ISO date-time string |
| REFERENCE | A link to one record in another table |
| REFERENCE_MANY | Links to many records in another table |
| MEMBER_REFERENCE | A link to a single member |
| MEMBER_REFERENCE_MANY | Links to many members |
List Data Tables
Retrieve every table in your app along with its field definitions.
const { data } = await memberstack.dataTables.list();
console.log(data.tables);Response:
Get a Data Table
Fetch a single table (and its fields) by key or id.
const { data } = await memberstack.dataTables.get({
table: "contacts"
});
console.log(data.key); // "contacts"
console.log(data.fields); // [{ key: "name", type: "TEXT", ... }, ...]Requesting a table that does not exist rejects with a 404 and the message "Data table not found".
Create a Record
Create a record by passing the table and a data object keyed by your field keys.
const { data: record } = await memberstack.dataTables.createRecord({
table: "contacts",
data: {
name: "Ada Lovelace",
score: 9.5,
active: true
},
// Optional: associate the record with a member (must exist in this app/env)
memberId: "mem_abc123"
});
console.log(record.id);Response:
{
data: {
id: "rec_xyz789",
tableKey: "contacts",
data: {
name: "Ada Lovelace",
score: "9.5", // DECIMAL is a string here — see note below
active: true
},
createdAt: "2026-01-15T10:30:00.000Z",
updatedAt: "2026-01-15T10:30:00.000Z",
internalOrder: 42
}
}- Required:
table— the table key or iddata— an object of field values (must be an object, not an array)
- Optional:
memberId— associate the record with a member; the member must exist in this app and environment
Get a Record
Fetch a single record by id. This is a convenience wrapper around a findUnique query, so the record is returned under data.record.
const { data } = await memberstack.dataTables.getRecord({
table: "contacts",
recordId: "rec_xyz789"
});
console.log(data.record.data.name); // "Ada Lovelace"
console.log(data.record.data.score); // 9.5 (a number on reads)If the record id does not exist, getRecord() rejects with a 404 and the message "Record not found".
Query Records
Use queryRecords() for filtering, sorting, pagination, relationships, and counts. It accepts a Prisma-like query with either findMany (many records) or findUnique (one record by id) — exactly one of the two.
const { data } = await memberstack.dataTables.queryRecords({
table: "contacts",
query: {
findMany: {
where: { active: { equals: true }, score: { gte: 5 } },
orderBy: { score: "desc" },
take: 10
}
}
});
console.log(data.records); // matching records
console.log(data.pagination); // { limit, endCursor, hasMore }const { data } = await memberstack.dataTables.queryRecords({
table: "contacts",
query: {
findUnique: { where: { id: "rec_xyz789" } }
}
});
console.log(data.record);// Return only the count of matching records
const { data } = await memberstack.dataTables.queryRecords({
table: "contacts",
query: { findMany: { _count: true } }
});
console.log(data._count); // e.g. 42findMany→{ data: { records, pagination } }(pagination is present whentakeis set)findManywith_count: true→{ data: { _count } }findUnique→{ data: { record } }
Query Options (findMany)
| Option | Type | Description |
|---|---|---|
where | object | Filter conditions (see operators below) |
include | object | Include related records or counts |
select | object | Select specific fields (cannot use with include) |
orderBy | object | array | Sort results |
take | number | Limit number of results (max 100) |
skip | number | Offset pagination (max 10000, cannot use with after) |
after | number | string | Cursor pagination — pass the previous endCursor (cannot use with skip) |
_count | boolean | Return only the count of matching records |
findUnique requires where.id and supports include / select only — pagination and ordering are not allowed.
Where Operators
Filter conditions support the following operators:
| Operator | Example |
|---|---|
equals | { score: { equals: 9.5 } } |
not | { status: { not: 'archived' } } |
in | { status: { in: ['active', 'pending'] } } |
notIn | { status: { notIn: ['deleted'] } } |
lt / lte | { score: { lte: 100 } } |
gt / gte | { score: { gte: 50 } } |
contains | { name: { contains: 'Ada' } } |
startsWith | { name: { startsWith: 'A' } } |
endsWith | { email: { endsWith: '@acme.com' } } |
Combine conditions with AND, OR, and NOT, e.g. where: { OR: [{ score: { gte: 90 } }, { active: { equals: true } }] }.
Update a Record
Update a record by id. Only the fields you include in data are changed.
const { data: record } = await memberstack.dataTables.updateRecord({
table: "contacts",
recordId: "rec_xyz789",
data: {
score: 8.25
}
});
console.log(record.data.score); // "8.25"Tips for updating records:
- Pass only the fields you want to change — other fields are left untouched
datamust be a non-empty object- For REFERENCE / MEMBER_REFERENCE fields, use
connect/disconnect(e.g.{ author: { connect: { id: "rec_…" } } })
Delete a Record
Delete a record by id. The deleted record is returned.
const { data: record } = await memberstack.dataTables.deleteRecord({
table: "contacts",
recordId: "rec_xyz789"
});
console.log(`Deleted record: ${record.id}`);Deleting a record is permanent and cannot be undone. Implement a confirmation step and back up important data if needed.
A Note on DECIMAL Fields
DECIMAL field values are returned as strings from createRecord(), updateRecord(), and deleteRecord(), but as numbers from getRecord() and queryRecords(). Coerce with Number(record.data.score) if you need a consistent numeric type.
Error Handling
Errors reject with an object containing a code and message. "Not found" cases return a 404 status; validation problems return 400.
try {
await memberstack.dataTables.getRecord({
table: "contacts",
recordId: "rec_missing"
});
} catch (error) {
// { code: "generic-message", message: "Record not found" }
console.error(error.message);
}| Message | When |
|---|---|
| Data table not found | The table key/id does not exist (404) |
| Record not found | The record id does not exist (404) |
| Member not found | createRecord memberId is not in this app/env (404) |
| data must be an object | createRecord/updateRecord data is not an object (400) |
| data cannot be empty | updateRecord data is an empty object (400) |
| Either query.findMany or query.findUnique parameter is required | queryRecords query has neither (400) |