Appbricx
Docs
PricingStart free

Start here

OverviewGetting startedProjects & editorBring your own key

Build with your agent

Coding agents quickstartAccess tokensMCP serverApp contractCustom UI & brandingBefore-write rulesDeveloper CLIDeveloper HTTP API

End-to-end walkthroughs

Lead intakeMulti-user TodoMulti-tenant SaaSSlack on signup

AI & keys

AI keys — how resolution worksBYOK deep dive

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

Custom components & branding

Blocks are the fast path. When an app needs its own look or interaction (a swipe deck, a chat thread, a profile editor), your agent writes the React component itself and puts it in the contract. It can also set an exact brand colour and replace the sign-in screen.

Code components

Declare a component in components with "kind": "code" and place it on a page with a custom layout node:

"components": [{
  "name": "SwipeStack",
  "kind": "code",
  "purpose": "Discovery deck: one profile at a time, like / pass",
  "props": { "title": { "type": "text" } },
  "sample": { "title": "Discover" },
  "source": "<the whole TSX file>"
}],
"pages": [{
  "name": "discover", "route": "/discover", "title": "Discover", "personas": ["member"],
  "layout": { "kind": "custom", "custom": "SwipeStack", "props": { "title": "Discover" } }
}]
KeyMeaning
namePascalCase. The default export must use it
kind"code" for a component with source
purposeOne line, for whoever edits it next
propsThe declared props and their types
sampleProps the render gate uses
sourceThe whole TSX file, up to 40 KB

A custom node can be the whole page layout or sit inside a stack or grid next to blocks. Prop values are literals, or bindings such as { "$each": { "entity": "member" } }, which gives a records prop its rows. The component ships as src/components/<Name>.tsx and stays in the contract, so get_contract returns it with its source.

The app prop

Every component has one default export and receives app: AppHandle next to its declared props. Import the type and declare it as optional (it is always set at runtime; the render gate passes a stub with no data).

import type { AppHandle } from "@app/kit/primitives";

type SwipeStackProps = { title?: string; app?: AppHandle };

export default function SwipeStack(props: SwipeStackProps) { ... }
MemberWhat it does
usersignedIn, name, email?, role (persona id or null), refs (my row id per persona table)
me(entity)My own row id in a persona table
paramsRoute params (:id pages: params.id)
rows(entity), loading(entity)The working set (newest ≤ 200 readable rows); re-renders as rows change
revision(entity)Changes when the entity changes; use as a useEffect dependency
query(entity, q?), get(entity, id)Server-side filtered, sorted page (where, sort, limit, offset) with a total; one row
create, update, removeWrites. Owner columns are stamped by the server
transition(entity, id, field, to)Advance a lifecycle enum field
upload(file)Upload a file; store the returned url in an image or attachment field
live(entity)Follow live changes; call in useEffect and return the unsubscribe
navigate(route)Go to another page
can(cap, entity?)UI hint for create / edit / delete; the server enforces it either way
authSign-in, sign-up and sign-out (below)

Every call goes through the app's data API, so row-level security decides what the signed-in user may do, whatever the component tries. Never pass owner columns (user_id, created_by). For a ref that means "me" (sender_id, swiper_id), declare it as the entity's actor_ref so the server stamps it.

Gates

New or changed source runs through four gates on apply. Run them yourself first with check_component (MCP) or POST /dev/v1/components/check. It takes name, source and sample, needs no project, changes nothing, and answers in seconds.

  1. static — imports only from react, @app/kit/primitives and lucide-react (icons from the icon set). No fetch, XMLHttpRequest, WebSocket, eval, dynamic import, storage, cookies, dangerouslySetInnerHTML, <script>, <iframe> or <form> (use <button onClick>).
  2. types — strict TypeScript against the real kit types.
  3. compile.
  4. render — a sandboxed server render with the sample props and with empty props. Guard every array and show an empty state; no Math.random or Date.now during render.
// pass
{ "ok": true, "passed": ["static", "types", "compile", "render"] }

// fail
{ "ok": false, "stage": "types", "errors": ["..."] }

Styling

Use Tailwind utility classes, including arbitrary values like h-[62vh], aspect-[3/4] or -rotate-6. Appbricx compiles exactly the classes each component names when it is applied. Theme tokens (bg-primary, bg-card, text-foreground, text-muted-foreground, border-border) follow the app theme and the brand colour; prefer them over literal colours. style works too. Design for a phone first (about 390×844).

Full-screen pages

Add "chrome": "none"to a page and it renders without the app's header and navigation. Your component owns the whole screen and draws its own header and tab bar. Navigate with app.navigate("/matches"); verify counts those links when it checks that every page is reachable. Keep the role's other pages reachable from your own nav, and add a Sign out button yourself (app.auth.signOut()).

{ "op": "set_page", "page": "discover", "chrome": "none" }   // full screen
{ "op": "set_page", "page": "discover", "chrome": null }     // framed again

Brand colour

"app": { "name": "Hive", "tagline": "Make the first move", "brand_color": "#FFC629" }
  • The exact colour becomes the primary colour over any theme: buttons, links, focus rings and the platform's own sign-in screen, in light and dark mode.
  • Text on it is near-black (#111111) or white, whichever has the higher WCAG contrast ratio. Yellow gets near-black text; navy gets white.
  • It is rendered into the preview's server-side styles, so it applies before any app code runs, and compiled into the app's CSS.
  • Accepts #RGB or #RRGGBB. Change it with { "op": "set_app", "brand_color": "#FFC629" }; "" removes it.

Custom sign-in screen

Write a code component and name it in the top-level authsection. It replaces the platform's sign-in and sign-up screen.

"auth": { "screen": "HiveLogin" }

The component gets app.auth:

MemberWhat it does
mode, setMode(mode)Which form to show: login, signup or forgot
canSignUpPublic sign-up is open for this app
signIn(email, password)Sign in
signUp({ name, email, password })Create an account
requestReset(email)Send a password reset
skip?()Continue without signing in, when the app allows browsing
signOut()Sign out. This is the only verb that works inside the app; the others reject there
Keep the labels.Label the inputs "Email" and "Password" and keep a "Sign in" button. verify and screenshot sign in through them. If auth.screennames a component that doesn't exist, the check drops it with a fix. If your component throws, the platform's screen shows instead, so users are never locked out.

A minimal sketch (run it through check_component):

import { useState } from "react";
import type { AppHandle } from "@app/kit/primitives";

type HiveLoginProps = { headline?: string; app?: AppHandle };

export default function HiveLogin(props: HiveLoginProps) {
  const app = props.app;
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState<string | null>(null);

  const submit = async () => {
    if (!app) return;
    setError(null);
    try {
      await app.auth.signIn(email, password);
    } catch (e) {
      setError(e instanceof Error ? e.message : "Sign-in failed");
    }
  };

  return (
    <div className="min-h-screen bg-background px-6 flex flex-col justify-center">
      <h1 className="text-3xl font-bold text-foreground">{props.headline ?? "Welcome back"}</h1>
      <label htmlFor="email" className="mt-8 text-sm text-muted-foreground">Email</label>
      <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)}
        className="mt-1 rounded-full border border-border bg-card px-4 py-3" />
      <label htmlFor="password" className="mt-4 text-sm text-muted-foreground">Password</label>
      <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)}
        className="mt-1 rounded-full border border-border bg-card px-4 py-3" />
      {error ? <p className="mt-3 text-sm text-destructive">{error}</p> : null}
      <button type="button" onClick={() => void submit()}
        className="mt-6 rounded-full bg-primary py-3 font-semibold text-primary-foreground">
        Sign in
      </button>
      {app?.auth.canSignUp ? (
        <button type="button" onClick={() => app.auth.setMode("signup")} className="mt-3 text-sm text-foreground">
          Create account
        </button>
      ) : null}
    </div>
  );
}

A real screen also renders the signup and forgot modes. Change the section later with { "op": "set_app", "auth": { "screen": "HiveLogin" } } (auth: null goes back to the platform screen).

Changing components later

  • set_component adds or replaces a whole component by name.
  • edit_component changes part of the source: { "op": "edit_component", "name": "ChatThread", "edits": [{ "find": "text-sm", "replace": "text-base" }] }. Each find must occur exactly once; add surrounding text to make it unique. Edited source is gated again on apply.
  • set_app changes theme, direction, nav_style, density, brand_color and auth without resending the contract.

After any visual change, call screenshot on each screen (and as: "guest" for the sign-in screen) before reporting it done.

Next

  • App contract — pages, patch ops
  • Before-write rules
  • MCP server reference — screenshot and check_component
Appbricx

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

Product

For coding agentsHow it worksDemoCapabilitiesPricingPrivate cloudBYOKFAQ

Resources

DocumentationBlogFor freelancersFor agenciesSupport

Company

ContactPrivate cloud / agencyPrivacyTerms

© 2026 Appbricx. All rights reserved.

TermsPrivacyCookiesAcceptable Use