Use PloyDB from your own app
Read and write PloyDB records from a backend, serverless function, mobile app, or another non-browser client.
Do not call PloyDB from browser JavaScript. The gateway does not send CORS headers, and a key shipped to a browser can be copied by anyone. Keep the key in a server-side secret. React Native and other native mobile clients are not subject to browser CORS, but you still need to protect the key.
Before you start
- Access key: Open your database, choose Manage access, and create a key. Grant read or write access only to the tables your app needs. The value is shown once.
- Database ID: Copy the
pdb_...value from the Ploy URL while the database is open. - Gateway origin: Use
https://ploydb-do-production.ploy.workers.devfor production.
One access key reaches exactly one database.
Base URL
https://ploydb-do-production.ploy.workers.dev/site/v1/ploydbs/{pdb_id}Send the key as a bearer token on every request:
Authorization: Bearer {access_key}1. Discover tables and fields
Start by listing the tables your key can read. Row values are keyed by immutable field IDs such as fld_..., so this response maps those IDs to the column labels you recognize.
const gateway = "https://ploydb-do-production.ploy.workers.dev";
const databaseId = process.env.PLOYDB_ID;
const accessKey = process.env.PLOYDB_KEY;
const baseUrl = `${gateway}/site/v1/ploydbs/${databaseId}`;
const headers = {
authorization: `Bearer ${accessKey}`,
"content-type": "application/json",
};
const response = await fetch(`${baseUrl}/tables`, { headers });
if (!response.ok) throw new Error(await response.text());
const { tables } = await response.json();{
"ok": true,
"tables": [
{
"tableId": "tbl_...",
"tableName": "Posts",
"schemaVersion": 3,
"fields": [
{ "fieldId": "fld_...", "label": "Title", "columnType": "TEXT" },
{ "fieldId": "fld_...", "label": "Body", "semanticType": "LONG_TEXT" }
]
}
]
}A key sees only tables in its read scope. Renaming a table or column changes its display name or label, but the tbl_... and fld_... IDs stay the same.
2. Query rows
Use the table ID and field IDs returned by the schema request.
const tableId = "tbl_...";
const response = await fetch(`${baseUrl}/tables/${tableId}/query`, {
method: "POST",
headers,
body: JSON.stringify({
filters: [{ field: "fld_status", op: "eq", value: "Published" }],
sort: [{ field: "created_at", dir: "desc" }],
limit: 20,
}),
});
if (!response.ok) throw new Error(await response.text());
const result = await response.json();Each row has an id, timestamps, a version, and a values object keyed by field ID. Query pages can contain up to 500 rows. Send the returned cursor in the next request to continue when a query is paginated.
3. Create a row
const response = await fetch(`${baseUrl}/tables/${tableId}/rows`, {
method: "POST",
headers,
body: JSON.stringify({
values: {
"fld_title": "My first post",
"fld_status": "Draft"
},
mutationId: crypto.randomUUID()
}),
});
if (!response.ok) throw new Error(await response.text());
const { row } = await response.json();A successful insert returns 201 Created. A mutationId makes a retried insert idempotent, so a lost response does not create a duplicate row.
4. Update a row
const rowId = "row_...";
const response = await fetch(`${baseUrl}/tables/${tableId}/rows/${rowId}`, {
method: "PATCH",
headers,
body: JSON.stringify({
values: { "fld_status": "Published" },
mutationId: crypto.randomUUID()
}),
});
if (!response.ok) throw new Error(await response.text());5. Delete a row
const response = await fetch(
`${baseUrl}/tables/${tableId}/rows/${rowId}?mutationId=${crypto.randomUUID()}`,
{ method: "DELETE", headers }
);
if (!response.ok) throw new Error(await response.text());Permissions and response data
Read and write access are independent for each table.
| Key scope | What the response includes |
|---|---|
| Read | Table schemas and row data for tables in the read scope. |
| Read and write | The created, updated, or deleted row, including its values. |
| Write only | An insert returns the new row's ID, timestamps, and version. Update and delete return no row body. |
Write-only behavior prevents an app from extracting existing data through mutation responses. A key that needs to read a row after changing it must also have read access to that table.
Errors, limits, and retries
- Errors use a JSON object with an
errormessage. - Query requests accept up to 50 filters, 10 sort fields, and 500 rows per page.
- A single stored string value can contain up to 128 KB.
- Single-row create and update request bodies can contain up to 2 MB.
- Writes are rate-limited. Back off on
429and retry temporary5xxresponses.
Roll or revoke a key
- Roll: Creates a new value and retires the old value immediately. Update the secret in your app before its next request.
- Revoke: Deletes the key and stops all requests that use it.
- Edit access: Name and scope changes apply on the next request without rolling the key.
Return to Database for tables, records, access keys, version history, and restore.

Do not call PloyDB from browser JavaScript. The gateway does not send CORS headers, and a key shipped to a browser can be copied by anyone. Keep the key in a server-side secret. React Native and other native mobile clients are not subject to browser CORS, but you still need to protect the key.