Skip to main content
Pages need to do things — collect a payment, send an email, store a file, query data, identify a signed-in user. Capabilities are how. A capability is a typed action a page invokes by name; the runtime validates the arguments against a Zod input schema, dispatches the call to a backend (a payment provider, the transactional email gateway, object storage, Postgres, the end-user identity service), and validates the result on the way back. By the end of this page you will know what a capability is, the six that ship with every workspace, how a page invokes one, and how versioning protects you from breaking changes.

A capability invocation, in 10 lines

Here is a Form block that emails the visitor a thank-you when they submit:
page.json
{
  "block": "Form",
  "props": {
    "schema_table": "leads",
    "fields": ["name", "email"],
    "on_submit": {
      "capability": "sendEmail",
      "args": {
        "template": "lead-thankyou",
        "to": "ada@example.com"
      }
    }
  }
}
The page declares the capability by name — sendEmail — along with the arguments. The runtime takes it from there: it validates the args against sendEmail’s input schema, dispatches the call to the transactional email gateway, and returns the result. The page author never writes an SDK call, never holds an email domain credential, never sees a network handle. The to value here is a literal address. In a real Form binding, you’d reference the submitted email field with template syntax — see the Form block reference for the full binding pattern.

The mental model: a typed bridge

A capability is a typed bridge between the declarative block tree and a real-world side effect. The block says what should happen (sendEmail with these args); the runtime decides how it happens (call the email gateway with this domain, these credentials, this template). Two properties fall out of that shape:
  • Pages stay declarative. A block describes its intent; it never imports an SDK or holds a secret. The capability name and args are all the page knows.
  • The runtime owns I/O. Validation, dispatching, and error handling live in one place. A call that fails validation never reaches the backend.
The analogy ends where flows get rich — capabilities can be retried, replayed in tests, and observed in logs the way an SDK call would be. Drop the bridge metaphor once the shape clicks.

The 6 capabilities

Every workspace ships with these. Each has a Zod-validated input schema and a Zod-validated output schema; the runtime rejects calls and responses that don’t match. The per-capability reference page is the source of truth for each one’s exact input and output shape.

Two ways a page invokes a capability

From a block (declarative)

Most invocations come from blocks. A Form block declares the capability that fires on submit; the runtime validates the arguments against the input schema before dispatching. The earlier example showed sendEmail; the same pattern works for collectPayment on a Cta button or storeFile on a file input.
page.json
{
  "block": "Form",
  "props": {
    "schema_table": "leads",
    "fields": ["name", "email"],
    "on_submit": {
      "capability": "sendEmail",
      "args": {
        "template": "lead-thankyou",
        "to": "ada@example.com"
      }
    }
  }
}

Across the iframe boundary

When a tenant page runs inside an iframe — for example, embedded on a marketing site — capability calls cross the boundary via a postMessage bridge. The bridge serializes each call to the edge proxy at {tenant}.gavai.run/_capability, which forwards it to the runtime. You do not write this plumbing; it is automatic. The shape of the call is identical to an in-page invocation; only the transport differs. A block that works at acme.gavai.app works embedded on acme.com without changes.

Versioning

Capabilities are semver’d. The runtime ships new versions as the platform evolves — sometimes adding optional fields, occasionally introducing breaking changes that get a new major version. If you want to pin a page to a specific release and avoid breakage when the platform ships a v2, add a version field to the invocation:
invocation.json
{
  "capability": "collectPayment",
  "version": "1.x",
  "args": {  }
}
Leave version unpinned and the runtime uses the latest stable version. Pin to 1.x and the runtime guarantees a 1.x release for as long as one exists — useful for long-lived page documents you don’t want to revisit when major versions ship.