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.dev for 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 scopeWhat the response includes
ReadTable schemas and row data for tables in the read scope.
Read and writeThe created, updated, or deleted row, including its values.
Write onlyAn 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 error message.
  • 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 429 and retry temporary 5xx responses.

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.