Documentation

Working with data

Server functions

Server functions are trusted JavaScript that runs next to your data, never in the browser. They power form submit handlers, dynamic option loading, computed behaviors and page preloads.

The isolation rule

Function source is stored server-side and executed server-side. What reaches the browser is only a reference — the client says "run submit handler of form X", the server looks the code up, runs it in a sandbox and returns the result. Client code can never read (or tamper with) the function body.

Writing a submit handler

Form and widget configs accept function fields. A form submit handler:

async (ctx) => {
  const v = ctx.values;

  // Validate on the server — the browser is not a trust boundary.
  if (!v.title || v.title.length < 3) {
    return { ok: false, alert: { type: "error", message: "Title is too short" } };
  }

  // ctx.datacore: the data API bound to the caller's identity and roles.
  await ctx.datacore.table("core", "task").create({
    title: v.title,
    done: false,
  });

  return {
    ok: true,
    alert: { type: "success", message: "Task created" },
    navigate: "refresh",
  };
}

Loading options dynamically

Select/cardselect fields can compute their options server-side:

async () => {
  return [
    { value: "low", label: "Low", icon: "ArrowDown" },
    { value: "high", label: "High", icon: "ArrowUp" },
  ];
}

Calling database functions

The ctx.datacore.action(schema, fn) helper wraps the gated RPC: schema USAGE + function EXECUTE are checked, then the function runs under the caller's database role.

await ctx.datacore.action("core", "iam_client_create").execute({
  p_rep: JSON.stringify({ clientId: "my-spa", publicClient: true }),
});

Where server functions appear

Place Hook
Forms submit.fn, per-field option loaders, visibility rules
Pages widget preloads, row/page actions
Tables computed columns, bulk actions