BaasixBaasix
Guides

App Builder Guide

← Back to Documentation Home

Table of Contents

  1. Overview
  2. Data Model
  3. The Grid
  4. Block Types
  5. Building a Page
  6. Block Config
  7. Permissions
  8. Public Pages
  9. Filters & Master-Detail
  10. Export & Import
  11. Build with AI (MCP)
  12. Best Practices

Overview

The App Builder turns your Baasix backend into a usable application without a second codebase. Admins compose pages from configurable blocks — a table over a collection, a kanban grouped by status, a form that creates records, a chart that aggregates on the server — and non-admin roles use those pages as their daily workspace, governed by the permissions you already have.

The builder and the end-user view are the same components: admins toggle an in-place "Editing" mode to add, configure, move, and delete blocks live on the page. All configuration is stored in the database, so it is versionable, exportable across instances, and editable through the UI, the SDK, or AI via MCP.

Data Model

Two system collections back the App Builder:

baasix_Page

FieldTypeNotes
nameStringDisplay name
slugStringUnique per tenant; used in the route ?slug=
iconStringLucide icon name for the menu
descriptionText (nullable)
parent_IdUUID (self-FK, null)Menu nesting
sortIntegerMenu ordering
isPublicBoolean (default false)Reachable without login via /p/?slug=
enabledBoolean (default true)Disabled pages are hidden from the menu and renderer
optionsJSONPage-level settings (e.g. menuGroup, homeFor)
rolesJSON (nullable)Role ids allowed to see the page in menus (null/empty = all)

baasix_Block

FieldTypeNotes
page_IdUUID FKOwning page (onDelete: cascade)
typeENUMOne of the 17 block types (below)
collectionString (null)Bound collection (null for collectionless blocks)
positionJSON{ row, col, span } on the 12-column grid
configJSONPer-type configuration
configVersionIntegerFor config-format migrations

Pages and blocks are ordinary rows — you read and write them with the same items API as any collection.

The Grid

Pages use a 12-column grid. Each block declares position: { row, col, span }:

  • row ≥ 0 — rows stack vertically
  • col 0–11 — starting column
  • span 1–12 — how many columns the block occupies

Blocks within a row share the 12 columns; there is no free positioning and no overlap. On mobile, rows collapse to a fully stacked layout (every block spans the full width).

Block Types

There are 17 block types:

BlockBinds to a collection?What it shows
tableyesData grid: columns, filters, sort, inline edit, actions
formyesCreate/edit records with sections and validation
detailsyesRead-only field view of a single record
kanbanyesDrag-to-update board grouped by a field
calendaryesRecords mapped to date fields (month/week/day)
chartyes9 chart kinds with server-side aggregation
cardlistyesCard/grid layout with title, image, fields
mapyesLeaflet map with markers, clustering, popups
geochartyesChoropleth world map shaded by an aggregated value
feedyesRealtime message/notes thread with a composer
mediayesImage/video/audio gallery from a file field
filteroptionalPublishes filter state to sibling blocks
markdownnoStatic markdown content
buttonsnoLink / workflow action buttons
iframenoEmbed an external URL
uploadnoFile upload widget
codeoptionalCode/JSON viewer (live record field or static)

Building a Page

The fastest way is the in-place UI editor: as an admin, open a page, flip the "Editing" toggle, and add blocks. You can also build pages programmatically over the SDK — pages and blocks are just collections:

// Create the page
const page = await baasix.items('baasix_Page').create({
  name: 'Operations',
  slug: 'operations',
  icon: 'gauge',
});

// Add a table block bound to the orders collection
await baasix.items('baasix_Block').create({
  page_Id: page.id,
  type: 'table',
  collection: 'orders',
  position: { row: 0, col: 0, span: 8 },
  config: {
    columns: [{ field: 'id' }, { field: 'customer' }, { field: 'total' }],
    filter: { status: { eq: 'open' } },
    sort: 'createdAt:desc',
    actions: { create: true, edit: true, view: true },
  },
});

// Add a status chart beside it
await baasix.items('baasix_Block').create({
  page_Id: page.id,
  type: 'chart',
  collection: 'orders',
  position: { row: 0, col: 8, span: 4 },
  config: {
    chartType: 'doughnut',
    aggregate: { count: { function: 'count', field: 'id' } },
    groupBy: ['status'],
  },
});

The page renders at /pages/?slug=operations (authenticated) — public pages render at /p/?slug=<slug>.

Block Config

Every block carries a JSON config. The shape depends on the block type, but the common envelope is { title?, description?, ...typeSpecific }. A few examples:

  • tablecolumns[{ field, label?, format? }], filter?, sort?, pageSize?, actions{ create?, edit?, delete?, view? }, search?, inlineEdit?
  • kanbangroupByField, cardTitleField, cardFields?[], allowDrag?
  • calendarstartField, endField?, titleField, views?[]
  • chartchartType, aggregate{ alias: { function, field } }, groupBy?[], filter?
  • formmode: "create"|"edit", fields[{ field, required?, section? }], successMessage?

Filter DSL — every filter value uses the same items-API filter DSL as queries and permissions (eq, neq, gt, in, contains, between, dwithin, …) including the dynamic variables $CURRENT_USER, $CURRENT_TENANT, and $NOW±DAYS_N. There is no second filter language anywhere.

Block config is validated server-side on every create and update against the block type and the bound collection's schema, so a page can never be saved into a broken state. You can pre-check a config without writing via the MCP baasix_validate_block_config tool.

Permissions

The App Builder introduces no new ACL system — it rides baasix_Permission:

  • All configuration is admin-only. Only administrators see the UI editor and can create/update/delete pages and blocks.
  • Other roles get read-only access. Row-level conditions decide which pages and blocks a role can see — the sidebar menu is simply "the pages this role can read".
  • Blocks never bypass data permissions. Every data block goes through the same collection permissions and row-level conditions as any other request. A block over a collection a role cannot read renders an empty/error state.

The roles field on a page controls menu visibility only; it is menu curation, not a security boundary — data access is always enforced by baasix_Permission.

Public Pages

Set isPublic: true to make a page reachable without login at /p/?slug=<slug>. Public pages are typically forms or dashboards, governed by the public-role permissions and the existing API rate limiting. Forms support an optional arithmetic human-check and a configurable success message / redirect.

Filters & Master-Detail

  • Filter blocks publish filter state to sibling data blocks on the page (target specific blocks or "all"), merged into each target's own config filter.
  • Master-detail — any data-block filter value may be the string $param.<name>, resolved from the page URL's query params at runtime. Pair a list block (whose row action sets the param) with a detail block filtered on that param to build selection-driven views.

Export & Import

Move apps between instances with page bundles:

# Export selected pages (or all) as a JSON bundle
GET /pages/export?pages=<id,id,...|all>

# Import — dry-run first to get a validation report
POST /pages/import?dryRun=true   { "bundle": { ... } }

# Then import for real, resolving any slug conflicts
POST /pages/import   { "bundle": { ... }, "resolutions": { "<slug>": "skip" | "overwrite" | { "rename": "<new-slug>" } } }

A bundle contains the pages, their blocks, and the collections they require. The dry-run report flags missing collections, missing fields, slug conflicts (with suggested slugs), unknown roles, and per-block validation errors — so you resolve everything before writing. New pages import disabled by default; roles are re-resolved by name on the target instance. In the admin app this is exposed as Import / export pages in the Pages sidebar.

Build with AI (MCP)

Because pages and blocks are plain config, AI assistants can build them for you. The MCP server exposes dedicated page-builder tools — baasix_create_page, baasix_create_block, baasix_update_block, baasix_validate_block_config, and more — plus a live block-config reference resource (baasix://docs/block-config) so the assistant generates valid configs from your schema.

Ask Claude or Copilot to "build an orders dashboard with a table of open orders and a doughnut chart of order status" and get a working, validated page.

Best Practices

  • Start from the data. Pick the collection first, then choose the block that fits the view (table for lists, kanban for status flows, chart for aggregates).
  • Keep pages focused. A page is a workspace for a task, not a dump of every block.
  • Lean on filters. Use a filter block to drive several sibling blocks instead of hardcoding the same filter into each.
  • Use role homepages. Set options.homeFor so each role lands on its own page after login.
  • Stage with enabled: false. Build and review a page disabled, then enable it once it is ready.
  • Validate before bulk edits. When scripting or letting AI build pages, run baasix_validate_block_config (or rely on the server-side validation) to catch bad field references early.

On this page