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

Server functions

Screens run in the browser and can't call other services or hold secret keys. A server function is a small piece of your own code that runs on the server when a screen asks: call a weather API, charge a card with your Stripe key, run a check the browser must not be trusted with. The screen gets back the JSON you return.

When to use it

  • A third-party API outside the integration catalog.
  • Anything that needs a secret key.
  • A calculation or check you want done on the server.

Step by step

  1. Declare a workflow with "on": { "call": true, "roles": [...] } and one custom step.
  2. Write the step as the body of async (ctx, input, user) => { … } and return any JSON value.
  3. If it needs a key, add it under Backend, Secrets and read it with ctx.secrets.get.
  4. Call it from a component with await app.call("id", input).

Code

The booking app checks the weather for garden classes with Open-Meteo (no key needed):

{
  "id": "class-forecast",
  "title": "Weather for a garden class",
  "on": { "call": true, "roles": ["member", "instructor"] },
  "do": [{ "action": "custom", "code": "<the body below>" }]
}
const id = String(input.session_id ?? "");
if (!id) throw new Error("Which class? Send a session_id.");
const session = await ctx.db.get("session", id);          // as the caller: access applies
if (!session) throw new Error("That class isn't on the schedule.");
const hour = new Date(String(session.starts_at)).toISOString().slice(0, 13) + ":00";
const day = hour.slice(0, 10);
const url = "https://api.open-meteo.com/v1/forecast?latitude=38.7223&longitude=-9.1393"
  + "&hourly=temperature_2m,precipitation_probability,weather_code&timezone=UTC"
  + `&start_date=${day}&end_date=${day}`;
const res = await ctx.http.fetch(url);
if (!res.ok) throw new Error(`The forecast service answered ${res.status}. Try again later.`);
const data = res.json<{ hourly?: { time?: string[]; temperature_2m?: number[];
  precipitation_probability?: number[]; weather_code?: number[] } }>();
const h = data?.hourly;
const i = h?.time ? h.time.indexOf(hour) : -1;
if (!h || i < 0) return { tempC: null, rainChance: null, summary: "No forecast yet" };
const code = h.weather_code?.[i] ?? 0;
const summary = code === 0 ? "Clear sky" : code <= 3 ? "Some cloud" : code <= 67 ? "Rain likely" : "Showers";
return { tempC: h.temperature_2m?.[i] ?? null, rainChance: h.precipitation_probability?.[i] ?? null, summary };

And from the screen:

type Forecast = { tempC: number | null; rainChance: number | null; summary: string };
try {
  const f = await app.call<Forecast>("class-forecast", { session_id: id });
  setForecast(`${f.summary}, ${Math.round(f.tempC ?? 0)}°C`);
} catch (e) {
  setForecast(e instanceof Error ? e.message : String(e));   // your thrown message
}

On the live app this answered {"tempC":16.6,"rainChance":0,"summary":"Some cloud"}.

With a secret key

const key = await ctx.secrets.get("WEATHER_API_KEY");
if (!key) throw new Error("Weather isn't set up yet");
const res = await ctx.http.fetch(url, { headers: { authorization: `Bearer ${key}` } });

What the code gets

NameValue
inputThe object the screen sent (up to 64 KB). Narrow it with String(input.x)
user{ id, name, email, role } of the caller, or null when signed out
ctx.dbget / list / create / update / remove, as the caller
ctx.http.fetchHTTPS requests, 30 seconds each. An object body is sent as JSON
ctx.secrets.getThe project's secrets (production values, in dev and prod)
Alsoctx.ai, ctx.integrations.call, ctx.notify, ctx.email, ctx.vars, ctx.log, ctx.now

Gotchas

  • Who may call: roles limits it to those personas; without roles any signed-in user may; add "public": true for signed-out visitors. The server decides from the session, never from the browser.
  • Never return or throw a key. Thrown messages are shown to the caller.
  • One attempt, 30 seconds. No retries; a slow call rejects with "didn't finish".
  • Each call is a workflow run: it counts against the app's daily run quota and shows in the logs with your ctx.log lines.
  • Third-party services fail. Open-Meteo answered 503 once while we built the tutorial; the screen showed the thrown message. Always handle a rejected app.call.
  • Up to 8,000 characters, strict TypeScript. A function that doesn't check is dropped and check_contract says why.
  • No global fetch, imports, timers or process.

Related

  • Workflows, schedules and webhooks
  • Secrets
  • Payments

Product overview: Workflows and jobs, what it does and where it runs in the live apps.

PreviousWorkflows, schedules and webhooksNextAI in your app
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