CloudAuroDocsDashboard →

Data and resources

Build a custom database

CloudAuro guide · 9 min read

Build the data your website needs#

A CloudAuro database is an isolated collection of custom tables stored in PostgreSQL. You choose the tables, fields, types, and website access rules. You do not need to install a database server, write SQL, or expose a secret key in your website. This is a restricted data API, not an arbitrary SQL server: joins, SQL triggers, uploaded server code, foreign-key relationships, and unique-field constraints are not supported in this release.

1. Create your project database#

Open Dashboard → your project → Database. Choose Empty database, Contact messages, or Newsletter subscribers, give it a name, and select Create & connect database. One database connects to one project; your plan's database allowance is shared across projects. Existing databases remain compatible and start private.

2. Create any table#

In Your custom data, name a table, for example products, bookings, contacts, or notes. Add up to 20 fields. Names use lowercase letters, numbers, and underscores and start with a letter. Each field can be optional or required, and can have a default value. Types: text, email, website URL, whole number, decimal number, yes/no, date, date/time with timezone, and structured JSON. Date uses YYYY-MM-DD; the API datetime format includes a timezone, for example 2026-10-26T12:00:00Z.

Use the Contact messages or Newsletter preset for a starting point, then customize its fields before creating it. These presets select Form inbox access. The API validates values on the server even when a visitor changes the browser code. Use Staff login table for private inbox access; do not store passwords in ordinary records.

3. Choose who can access it#

Private: only your management dashboard and authorized server tokens can read or change records. This is the default.

Public read: anyone visiting the site can read every record. Only you or an authorized server token can change it. Use this for a product catalog or published information, never private contact details.

Form inbox: anyone can add a record, but visitors cannot list, read, edit, or delete submissions. You read them in your project dashboard, or assign staff logins to read this inbox on your website. Public forms allow 20 submissions per IP per project per hour and 300 per project per hour. These are abuse limits, not spam filtering or email delivery. Saving a contact message does not send an email.

Every table starts private unless you explicitly choose otherwise. Public access can only be enabled on an empty table to avoid exposing existing private records. If a populated table needs a public view, create a separate public table and copy only information you intend to publish. HTML, CSS, and JavaScript files themselves remain public.

4. Add and edit records without JSON#

Choose a table under Tables & records. Fill in its generated form and select Save record. Use Edit or Delete next to a record. You can page through results and view approximate stored JSON size. Delete actions use a confirmation popup. Advanced JSON tools and server tokens remain available under Manage table rows and API access.

Edit fields & access lets you rename a table or add optional/default fields. Populated fields cannot be removed or have their definitions changed. Online field additions support up to 1000 existing records; larger schema migrations need a controlled export/import plan. Existing records are preserved, and defaults fill newly added fields. A table can hold 10000 rows of up to 64 KiB each; each database supports 20 tables with 20 fields each. All data also counts toward account storage. These are API limits, not a promise of unlimited data.

5. Connect a public contact form#

Create a contacts table using the Contact messages preset and Form inbox access. It should contain name (required text), email (required email), and message (required text). Upload these two files at your website root along with its index.html. The same complete files are provided in the release under examples/custom-database/contact.html and contact.js.

html
<!-- contact.html -->
<form id="contact-form">
  <label>Name <input name="name" required maxlength="100"></label>
  <label>Email <input name="email" type="email" required></label>
  <label>Message <textarea name="message" required maxlength="5000"></textarea></label>
  <button type="submit">Send</button>
</form>
<p id="status" role="status"></p>
<script src="/__cloudauro/data.js" defer></script>
<script src="contact.js" defer></script>
javascript
// contact.js
const form = document.querySelector('#contact-form');
const status = document.querySelector('#status');
form.addEventListener('submit', async event => {
  event.preventDefault();
  if (!form.reportValidity()) return;
  const button = form.querySelector('button');
  if (button.disabled) return;
  button.disabled = true;
  try {
    await CloudAuroData.table('contacts').insert(
      Object.fromEntries(new FormData(form))
    );
    status.textContent = 'Your message was saved. Thank you!';
    form.reset();
  } catch (error) {
    status.textContent = error.message;
  } finally {
    button.disabled = false;
  }
});

Use your published website address, not a file:// URL or the management dashboard. The API binds the database to the published project and its verified domains. No API token belongs in this example. Preview and test using a test website. Saving data persists it; use sample information and delete it afterward.

## Query, filter, and page through data

javascript
const products = CloudAuroData.table('products');
const page = await products.list({
  page: 1,
  limit: 25,
  where: {available: true},
  sort: '-price'
});
console.log(page.items, page.hasMore);
// Fetch page 2 only when page.hasMore is true.

One equality filter on a non-JSON field is supported. Ascending sort uses the field name; descending adds a leading minus. Responses return {items, page, limit, hasMore}. Default limit is 25; maximum is 100. get(id) fetches one record, insert(values) creates it, update(id, values) merges provided fields and preserves others, and remove(id) deletes it. Explicit null clears an optional field. Required fields cannot be null. Unknown fields are rejected. The website API is limited to 120 requests per IP per project per minute, with additional public submission limits.

Create custom databases directly with the management API#

Your own dashboard or trusted management client can create a database and several tables in one authenticated request. Obtain a management CSRF token from /api/auth/csrf, keep your management session, and POST this body to /api/websites/SITE_ID/database (or /api/databases with siteId included):

json
{
  "name": "Shop data",
  "template": "empty",
  "tables": [
    {
      "name": "products",
      "access": "public_read",
      "columns": {
        "title": {"type": "text", "required": true, "maxLength": 200},
        "price": {"type": "number", "required": true},
        "available": {"type": "boolean", "default": true}
      }
    }
  ]
}

This requires a customer account and hosting entitlement; it is not callable by anonymous website visitors. Table management uses /api/databases/DATABASE_ID/tables and /tables/TABLE_ID. GET /api/databases/DATABASE_ID/schema returns your table definitions and approximate JSON usage. PATCH table settings changes name, columns, or access. DELETE requires {"confirmation":"DELETE"} and deletes the table's rows. These owner operations require normal management authentication and CSRF protection.

Direct website REST API#

The SDK uses /__cloudauro/data/TABLE_NAME/rows and /rows/ROW_ID on the same published website. GET lists or fetches; POST inserts; PATCH merges; DELETE deletes. Mutation bodies contain {"values": {...}}, except DELETE which sends {}. Mutations require same-origin JSON and X-CloudAuro-Data: 1. The browser sets Origin automatically. Member mode requires the website session cookie. Cross-origin access is denied; management cookies and server bearer tokens do not substitute for visitor sign-in here.

The envelope is {"data": ..., "error": null} on success or {"data": null, "error": {"code": ..., "message": ...}} on errors. Expect 400 for invalid fields, 401 for visitor sign-in, 403 for disabled access/origin denial, 404 for missing or unowned rows, 409 for unsafe schema changes, 413 for size/storage limits, and 429 for rate limits. OpenAPI is available at /api/openapi.json.

Trusted server integrations#

The existing /api/data/DATABASE_ID/tables/TABLE_ID/rows API still requires a permission-limited 30-day bearer token. Tokens cover their database and are not limited by visitor row ownership: they are for trusted server integrations only. Keep all such tokens outside public HTML/JavaScript. Revocation is immediate. The separate website SDK is the beginner-friendly browser choice.

Troubleshooting#

404: publish the project, connect its database, check the table name, and install the complete backend update. A frontend-only deployment cannot add these routes.

403: check table access. Private denies visitors, Public read denies writes, and Form inbox denies reading unless an active staff login is assigned to that inbox. A form submission must originate from its own published site.

401: sign in with a manager-created staff login before reading an assigned Form inbox. Sessions are hostname-specific, so logging in on a subdomain does not also log you in on a custom domain.

Validation errors name the field to fix. Wrong types, missing required fields, extra fields, bad dates, and overly long values are rejected. API data is untrusted text; render it with textContent. The operator's PostgreSQL backup includes custom tables and ownership metadata. Your frontend release rollback does not roll back database records. Customer data snapshots include table access and opaque row ownership IDs, but do not include visitor password hashes or sessions; use a reviewed full PostgreSQL/files backup to recover the complete authentication system. Successful website data/SDK responses count toward monthly hosting bandwidth; bandwidth failures roll back writes rather than silently saving a failed submission. Table management changes create audit entries without storing record values. Renaming a table changes the SDK table name; update your website code to match.

## Create a staff login directly in the dashboard

Select a Form inbox under Database > Tables & records. In Staff login table, enter a name, email, and unique password of 12–128 characters, then Add login. The protected staff_login table is created automatically by the server migration: do not create a custom password table or store passwords in ordinary records. No public signup or separate grant is needed. Each staff row can read only its assigned Form inbox.

The website POSTs {email,password} to /__cloudauro/staff/login with X-CloudAuro-Staff: 1, same-origin JSON and browser credentials. GET /__cloudauro/staff/me checks the session; POST /__cloudauro/staff/logout signs out. After staff login, use the existing data SDK/list endpoint to read the assigned inbox. Cookies are host-only, HttpOnly and SameSite=Strict, secure in production, and expire in 12 hours. Login has per-IP and per-email rate limits. There is no staff signup endpoint.

In Staff login table, use Reset password or Disable login as needed. These actions revoke existing sessions. Changing the table away from Form inbox disables staff rows; relinking a database removes its old staff logins. The owner can manage these rows through /api/databases/DATABASE_ID/tables/TABLE_ID/staff-logins (GET/POST), or PATCH /staff-logins/LOGIN_ID with active and/or password, using normal management authentication and CSRF. Password hashes are never returned. Maximum 20 staff logins per database; each counts toward account storage. Customer snapshots omit these credentials; operator full-database backups include them. Existing visitor accounts remain separate and are not automatically converted to staff rows.