Appbricx
Docs
PricingStart free

Start here

OverviewGetting startedProjects & editorBring your own key

End-to-end walkthroughs

Lead intakeMulti-user TodoMulti-tenant SaaSReal-time ChatSlack on signup

AI & keys

AI keys — how resolution worksBYOK deep dive

SDK reference

Frontend SDK — @app/sdkBackend SDK — @app/backendUI kit — @app/ui

Integrations

Integrations catalog

Full-stack runtime

Runtime overviewNamed queriesAuth & RLSAuto CRUD REST APIWorkflowsWebhooks & schedulesTopics & CDCData templatesSecrets & env

Ship & own

Publish & domainsExport & GitHubSelf-host & deploy

Developers

Developer guide

Build a multi-user Todo app end-to-end

A complete walkthrough: prompt a fresh project, generate the UI, wire the data model with row-level security, expose auto CRUD, add a scheduled overdue-digest email, and publish to a subdomain. Every command below matches a real Appbricx endpoint.

0. Prep

  • Create a new project (Getting started). Pick the blank template so nothing pre-seeds.
  • Optional but recommended: attach BYOK (Bring your own key) so the generation step does not eat included AI credits.

1. The initial prompt

Open the editor chat and paste the prompt below. Keep it as one message — Appbricx generates a much cleaner backend when the data intent, RLS expectation, and UI shape land together.

Build a multi-user todo app.

Data:
- todos(id uuid pk, title text, notes text, due_at timestamptz,
        done boolean default false, created_by uuid, created_at timestamptz default now())
- Enable RLS so each signed-in user sees only rows where
  created_by = current app user.
- First signup in the project becomes admin.

UI:
- /login and /signup pages using db.auth.signup / db.auth.login.
- /todos as the main screen: an "Add todo" form (title + optional due date)
  and a list grouped into Overdue / Today / Upcoming / Done.
- Toggle done inline; delete with a trash icon.

Use runtime.api for create/update/delete and a named query "list_my_todos"
for the grouped read. No raw SQL in components.

The agent scaffolds React pages, a migration, one named query, and atables.json ACL. Watch the file tree — you should see files appear under .appbricx/backend/ as the generation completes.

2. Data model & RLS

Confirm the generated migration matches what you asked for. Open.appbricx/backend/migrations/001_todos.sql:

CREATE TABLE IF NOT EXISTS todos (
  id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  title       text NOT NULL,
  notes       text,
  due_at      timestamptz,
  done        boolean NOT NULL DEFAULT false,
  created_by  uuid NOT NULL,
  created_at  timestamptz NOT NULL DEFAULT now()
);

ALTER TABLE todos ENABLE ROW LEVEL SECURITY;

CREATE POLICY todos_owner_all ON todos
  USING      (created_by::text = current_setting('app.user_id', true))
  WITH CHECK (created_by::text = current_setting('app.user_id', true));

The data worker sets app.user_id from the app session on every request, so the same policy covers list, get, update, and delete. Background: Auth & RLS.

3. The generated UI

The list view calls a named query so grouping happens server-side — components stay dumb. Open .appbricx/backend/queries/list_my_todos.sql:

-- @name list_my_todos
SELECT
  id, title, notes, due_at, done,
  CASE
    WHEN done                              THEN 'done'
    WHEN due_at IS NULL                    THEN 'upcoming'
    WHEN due_at < now()                    THEN 'overdue'
    WHEN due_at::date = current_date       THEN 'today'
    ELSE 'upcoming'
  END AS bucket
FROM todos
ORDER BY done ASC, due_at ASC NULLS LAST;

The React screen at src/routes/todos.tsx is roughly:

import { useEffect, useState } from "react";
import { runtime } from "@appbricx/runtime";
import { db } from "@appbricx/data";

export default function Todos() {
  const [rows, setRows] = useState<any[]>([]);
  const [title, setTitle] = useState("");
  const [dueAt, setDueAt] = useState("");

  async function refresh() {
    const r = await runtime.queries.run("list_my_todos");
    if (r.ok) setRows(r.rows ?? []);
  }
  useEffect(() => { refresh(); }, []);

  async function add(e: React.FormEvent) {
    e.preventDefault();
    if (!title.trim()) return;
    const user = await db.auth.getUser();
    await runtime.api.create("todos", {
      title,
      due_at: dueAt || null,
      created_by: user!.id,
    });
    setTitle(""); setDueAt("");
    refresh();
  }

  async function toggle(row: any) {
    await runtime.api.update("todos", row.id, { done: !row.done });
    refresh();
  }

  async function remove(row: any) {
    await runtime.api.delete("todos", row.id);
    refresh();
  }

  const buckets = ["overdue", "today", "upcoming", "done"] as const;
  return (
    <div>
      <form onSubmit={add}>
        <input value={title} onChange={(e) => setTitle(e.target.value)}
               placeholder="What needs doing?" />
        <input type="datetime-local" value={dueAt}
               onChange={(e) => setDueAt(e.target.value)} />
        <button>Add</button>
      </form>
      {buckets.map((b) => (
        <section key={b}>
          <h3>{b}</h3>
          <ul>
            {rows.filter((r) => r.bucket === b).map((r) => (
              <li key={r.id}>
                <input type="checkbox" checked={r.done}
                       onChange={() => toggle(r)} />
                {r.title}
                <button onClick={() => remove(r)}>delete</button>
              </li>
            ))}
          </ul>
        </section>
      ))}
    </div>
  );
}

4. Auto CRUD — the REST surface

The agent creates .appbricx/backend/api/tables.json so onlytodos is exposed. Empty ACLs open every non-internal table — never skip this file.

{
  "allow": ["todos"],
  "tables": {
    "todos": {
      "expose": true,
      "methods": ["GET", "POST", "PATCH", "DELETE"]
    }
  }
}

Exercise it against the running project:

# Create
curl -sS -X POST "$ORIGIN/__appbricx/api/v1/todos" \
  -H "Authorization: Bearer $DATA_TOKEN" \
  -H "x-appbricx-data-api: 1" \
  -H "x-appbricx-app-session: $APP_SESSION" \
  -H "content-type: application/json" \
  -d '{"title":"Ship v1","due_at":"2026-09-01T17:00:00Z"}'

# List
curl -sS "$ORIGIN/__appbricx/api/v1/todos?limit=50" \
  -H "Authorization: Bearer $DATA_TOKEN" \
  -H "x-appbricx-data-api: 1" \
  -H "x-appbricx-app-session: $APP_SESSION"

# Toggle done
curl -sS -X PATCH "$ORIGIN/__appbricx/api/v1/todos/$ID" \
  -H "Authorization: Bearer $DATA_TOKEN" \
  -H "x-appbricx-data-api: 1" \
  -H "x-appbricx-app-session: $APP_SESSION" \
  -H "content-type: application/json" \
  -d '{"done":true}'

Without x-appbricx-app-session the RLS policy filters everything out — that is the desired behaviour for a shared surface. Full reference: Auto CRUD REST API.

5. Add the daily overdue-digest workflow

Ask the agent to add a scheduled workflow that emails each user their overdue items at 09:00 local time.

Add a workflow "overdue-digest" that:
- runs on cron "0 9 * * *" (UTC)
- pulls every user with at least one overdue todo
- calls ctx.messages.email once per user with a bullet list
- publishes topic "todos.digest.sent" with { user_id, count }
Register a schedule manifest for it.

You end up with two backend files. The workflow at .appbricx/backend/workflows/overdue-digest.workflow.js:

/** @param {import("@appbricx/runtime").WorkflowContext} ctx */
export async function run(ctx) {
  const r = await ctx.queries.run("overdue_by_user");
  if (!r.ok) throw new Error(r.error?.message ?? "overdue_by_user failed");

  let sent = 0;
  for (const group of r.rows ?? []) {
    const body = group.items
      .map((t) => `- ${t.title} (due ${t.due_at})`)
      .join("\n");

    await ctx.messages.email({
      to: group.email,
      subject: `You have ${group.items.length} overdue todos`,
      text: body,
    });

    await ctx.topics.publish("todos.digest.sent", {
      user_id: group.user_id,
      count: group.items.length,
    });
    sent += 1;
  }

  ctx.log.info(`digest sent to ${sent} users`);
  return { ok: true, sent };
}

And the cron manifest at .appbricx/backend/schedules/overdue-digest.json:

{
  "id": "overdue-digest",
  "cron": "0 9 * * *",
  "timezone": "UTC",
  "workflow": "overdue-digest",
  "enabled": true
}

Connect a Resend, SendGrid, or Gmail integration before the first fire —ctx.messages.email resolves through the same integration registry as any other action.

6. Test the workflow without waiting for 09:00

You can invoke any workflow on demand — the runner uses the same code path as the cron ticker.

# SDK
await runtime.workflows.invoke("overdue-digest", { payload: {} });

# HTTP
curl -sS -X POST "$ORIGIN/__appbricx/runtime/workflows/overdue-digest/run" \
  -H "Authorization: Bearer $DATA_TOKEN" \
  -H "x-appbricx-data-api: 1" \
  -H "content-type: application/json" \
  -d '{"payload":{}}'
# → { "ok": true, "runId": "…" }

# Inspect the run + logs
curl -sS "$ORIGIN/__appbricx/runtime/runs/$RUN_ID" \
  -H "Authorization: Bearer $DATA_TOKEN" \
  -H "x-appbricx-data-api: 1"

Details: Workflows · Webhooks & schedules.

7. Publish

  1. Open the editor → Deploy / Publish.
  2. Appbricx builds and serves the app at https://<subdomain>.apps.appbricx.com.
  3. Sign up two accounts from different browsers — each only sees their own todos, and the 09:00 cron fires per user.

Custom domain (Pro+): Publish & domains.

Internal map (what just happened)

Browser (todos.tsx)
  → /__appbricx/api/v1/todos          (auto CRUD, RLS by app.user_id)
  → /__appbricx/runtime/queries/run   (list_my_todos, grouped read)
  ↑
Schedule ticker (every ~15s)
  → app_runtime_schedules due? enqueue "overdue-digest"
  → runner loads overdue-digest.workflow.js
  → ctx.queries.run → ctx.messages.email → ctx.topics.publish
Rebuild tip. Every step in this walkthrough is a single prompt. If a screen or workflow does not match this doc, ask the agent to reconcile — it can rewrite files in place without breaking the RLS policy or the schedule manifest.
Appbricx

Full-stack AI app builder for teams. Hosted cloud or private deploy into your account — multi-tenant, sandboxed, credit-metered.

Product

How it worksDemoCapabilitiesPricingPrivate cloudBYOKFAQ

Resources

DocumentationBlogFor freelancersFor agenciesSupport

Company

ContactPrivate cloud / agencyPrivacyTerms

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use