Appbricx
Docs
FeaturesPricingStart free

Get started

OverviewWhat Appbricx isQuickstart with a coding agentQuickstart with the builderCore concepts

Guides

Data and tablesAccess and rolesBusiness rulesSign-inCustom screensRealtime and topicsWorkflows, schedules and webhooksServer functionsAI in your appPaymentsFilesMessagesIntegrationsVerifyPublishing, domains and rollbackOperating your appExport and self-hostSelf-host install

Tutorials

Build a booking app with Claude CodeMulti-user todo listClient portalSlack on new sign-upTemplates

Reference

Reference homeFrontend SDKHTTP APIMCP toolsCLIIntegrations catalogContract formatAI key resolutionRun Appbricx locally

Business rules

Some things must never happen, whoever tries: booking a full class, logging a workout in the future, a price below cost. A rule is a small check that runs on the server before a row is saved. If it says no, nothing is written and your user sees your message.

When to use it

  • The condition depends on the data: capacity, dates, totals, who wrote first.
  • It must hold for every write, including scripts that call the API directly.

Use accessfor "who may touch which rows", a rule for "what may be in them", and a workflowfor what should happen after a write (a workflow can't stop one).

Step by step

  1. Describe the rule to your agent in plain words, or add it to the entity's rules list.
  2. Give it an id, when it runs (create, update or both), a message, and a check: the body of an async function.
  3. Return true to allow, false to refuse with the message, or a string to refuse with that string.
  4. Check and apply. A rule that doesn't compile is reported by the check and the contract isn't applied.

Code

The booking app refuses a booking for a class that is full or has started:

"rules": [{
  "id": "seats-left",
  "on": "create",
  "message": "That class is full. Try another time.",
  "check": "const r = await ctx.db.query('select capacity, booked, starts_at from session where id = $1', [row.session_id]);\nconst rows = Array.isArray(r) ? r : (r as { rows?: Array<Record<string, unknown>> }).rows ?? [];\nconst s = rows[0];\nif (!s) return 'That class is no longer on the schedule.';\nif (new Date(String(s.starts_at)).getTime() < ctx.now().getTime()) return 'That class has already started.';\nreturn Number(s.booked ?? 0) < Number(s.capacity);"
}]

The same check, formatted:

const r = await ctx.db.query(
  "select capacity, booked, starts_at from session where id = $1",
  [row.session_id],
);
const rows = Array.isArray(r) ? r : (r as { rows?: Array<Record<string, unknown>> }).rows ?? [];
const s = rows[0];
if (!s) return "That class is no longer on the schedule.";
if (new Date(String(s.starts_at)).getTime() < ctx.now().getTime()) return "That class has already started.";
return Number(s.booked ?? 0) < Number(s.capacity);   // false: "That class is full."

What the check gets

NameValue
rowThe row as it would be written (on update, merged with the patch). Owner and actor_ref fields are already set
ctx.oldThe current row on update, null on create
ctx.user{ id, role } of the writer
await ctx.me("member")The writer's own row id in a persona table
await ctx.db.query(sql, params)One read-only SELECT, run as the writer
ctx.now()The current time

What the user sees

HTTP/1.1 422
{ "ok": false, "error": { "code": "RULE_REFUSED", "message": "That class is full. Try another time." } }

In a component, app.createrejects with that message. Catch it and show it, as the booking app's schedule does.

Gotchas

  • Rules fail closed. A rule that throws, times out (3 seconds) or returns something odd refuses the write.
  • Queries run as the writer, so a rule only sees rows the writer may read. Keep what it needs readable.
  • No writes and no network inside a check: one SELECT, in a sandbox.
  • Up to 6 rules per entity, run in order; the first refusal wins. Checks are up to 4,000 characters.
  • Workflows don't run rules. Rules guard data-API writes; platform code acting for the app (workflows, seeds) skips them.
  • Replace an entity's rules with { "op": "set_entity", "entity": "booking", "rules": [...] }; [] removes them.

Related

  • Access and roles
  • Workflows, schedules and webhooks
  • Contract format reference

Product overview: Row-level security, what it does and where it runs in the live apps.

PreviousAccess and rolesNextSign-in
Appbricx

The backend, runtime and hosting for apps your coding agent builds. On our cloud, in yours, or on your own servers.

Features

Coding agents and MCPVerify testingBuilder consolePostgres databaseData APIRealtimeRow-level securitySign-in and authPaymentsWorkflows and jobsAI in your appIntegrationsBackups and rollbackHosting and domainsSelf-host and exportAll features

Product

ShowcasePricingFor coding agentsTemplatesPrivate cloudBring your own AI keyFAQ

Compare

Appbricx vs SupabaseLovable alternativeBolt alternativeFor freelancersFor agencies

Resources

DocumentationReferenceBlogSupportContact

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use