Custom Pages
Build custom multi-panel pages with data sources, drag-and-drop, matching rules, and page actions.
Custom Pages
Custom pages extend the CMS beyond standard table views. Define split-panel layouts, matching rules for record reconciliation, drag-and-drop interactions, and page-level actions — all from a single definePage() call.
Defining a Page
Pages are defined in your feature directory under a pages/ folder:
quickback/definitions/features/
banking/
schema.ts
actions.ts
pages/
bank-reconciliation.tsEach page file exports a definePage() call:
import { definePage } from '@quickback/compiler';
export default definePage({
slug: 'bank-reconciliation',
title: 'Bank Reconciliation',
description: 'Match bank transactions to ledger entries',
icon: 'landmark',
feature: 'banking',
access: { roles: ['owner', 'admin'] },
dataSources: {
bankTransactions: {
table: 'bankTransaction',
defaultSort: { field: 'date', order: 'desc' },
displayColumns: ['date', 'description', 'amount'],
},
ledgerEntries: {
table: 'ledgerEntry',
defaultSort: { field: 'date', order: 'desc' },
displayColumns: ['date', 'memo', 'amount'],
},
},
layout: {
type: 'split-panel',
panels: [
{
id: 'bank',
title: 'Bank Transactions',
dataSource: 'bankTransactions',
position: 'left',
features: ['drag-source'],
},
{
id: 'ledger',
title: 'Ledger Entries',
dataSource: 'ledgerEntries',
position: 'right',
features: ['drop-target'],
},
],
},
matching: {
enabled: true,
confidenceThreshold: 0.5,
rules: [
{
name: 'Amount Match',
weight: 0.5,
condition: {
left: 'bankTransactions.amount',
right: 'ledgerEntries.amount',
operator: 'abs-equals',
},
},
{
name: 'Date Proximity',
weight: 0.3,
condition: {
left: 'bankTransactions.date',
right: 'ledgerEntries.date',
operator: 'within-days',
value: 7,
},
},
{
name: 'Description Match',
weight: 0.2,
condition: {
left: 'bankTransactions.description',
right: 'ledgerEntries.memo',
operator: 'fuzzy-match',
},
},
],
},
pageActions: {
reconcile: {
table: 'bankTransaction',
action: 'reconcile',
label: 'Reconcile',
icon: 'check-circle',
confirm: 'Match this bank transaction to the selected ledger entry?',
inputMapping: {
transactionId: 'bankTransactions.$id',
ledgerEntryId: 'ledgerEntries.$id',
amount: 'bankTransactions.$amount',
},
},
},
});Data Sources
Each page declares one or more named data sources that bind to tables in your schema:
dataSources: {
bankTransactions: {
table: 'bankTransaction', // Table name from your schema
defaultFilters: { status: 'pending' }, // Applied on load
defaultSort: { field: 'date', order: 'desc' },
displayColumns: ['date', 'description', 'amount'],
},
}| Field | Type | Description |
|---|---|---|
table | string | Table name to query (required) |
defaultFilters | Record<string, unknown> | Filters applied on initial load |
defaultSort | { field, order } | Default sort column and direction |
displayColumns | string[] | Columns shown in the panel (defaults to ["id"]) |
Layout
Pages use a layout configuration to arrange panels. Currently the only layout type is split-panel:
layout: {
type: 'split-panel',
panels: [
{ id: 'bank', title: 'Bank Txs', dataSource: 'bankTransactions', position: 'left', features: ['drag-source'] },
{ id: 'ledger', title: 'Ledger', dataSource: 'ledgerEntries', position: 'right', features: ['drop-target'] },
],
}Panel Features
Panels can enable interactive features:
| Feature | Description |
|---|---|
drag-source | Rows in this panel can be dragged |
drop-target | Rows in this panel accept drops |
When a row is dragged from one panel and dropped onto a row in the other panel, the first page action is triggered with both records as context.
Matching Engine
The matching engine compares records across two panels and computes confidence scores for potential matches. Matched rows display a colored confidence badge.
Rules
Each rule defines a comparison between fields from two data sources:
matching: {
enabled: true,
confidenceThreshold: 0.5, // Minimum score to show (0-1)
rules: [
{
name: 'Amount Match',
weight: 0.5, // Weight in final score (0-1)
condition: {
left: 'bankTransactions.amount', // dataSourceName.fieldName
right: 'ledgerEntries.amount',
operator: 'abs-equals',
},
},
],
}Operators
| Operator | Description | Returns |
|---|---|---|
abs-equals | Absolute values are equal | 1.0 if match, 0 otherwise |
within-days | Dates are within N days | 1 - (daysDiff / maxDays), clamped to 0 |
fuzzy-match | Word overlap between strings | Overlap ratio (0-1) |
The within-days operator uses the value parameter to set the maximum number of days (defaults to 7).
Confidence Scoring
The final confidence score is the weighted average of all rule scores:
score = sum(ruleScore * ruleWeight) / sum(ruleWeights)Only matches above confidenceThreshold are shown. Confidence badges are color-coded:
| Score | Color |
|---|---|
| 80%+ | Green |
| 50-79% | Yellow |
| Below 50% | Orange |
Page Actions
Page actions trigger table actions using data from both the source (dragged) and target (dropped-on) records:
pageActions: {
reconcile: {
table: 'bankTransaction', // Target table for the action
action: 'reconcile', // Action name on that table
label: 'Reconcile',
icon: 'check-circle',
confirm: 'Match this transaction?',
inputMapping: {
transactionId: 'bankTransactions.$id',
ledgerEntryId: 'ledgerEntries.$id',
amount: 'bankTransactions.$amount',
},
},
}Input Mapping
Input mapping resolves field references from the source and target records into the action's input fields. The format is "dataSourceName.$fieldName":
bankTransactions.$id— resolves to theidfield of the dragged record from thebankTransactionsdata sourceledgerEntries.$amount— resolves to theamountfield of the drop-target record from theledgerEntriesdata source
The $ prefix denotes a field reference on the record. The data source name determines which record (source or target) to read from.
Access Control
Restrict page access by role:
access: { roles: ['owner', 'admin'] }When set, only users with the specified roles can view the page. The page link is hidden from the sidebar for unauthorized users.
Sidebar Navigation
Custom pages appear in the CMS sidebar under their feature group. Each page is listed alongside the feature's tables, making them easily discoverable.
Validation
The compiler validates page definitions at compile time:
slugmust start with a lowercase letter and contain only lowercase letters, digits, and hyphens- At least one data source is required
- Every panel's
dataSourcemust reference a declared data source - Matching rule weights must be between 0 and 1
confidenceThresholdmust be between 0 and 1
Next Steps
- Dashboard — The CMS home page with schema stats
- Components Reference — Page-related component details
- Schema Format Reference — PageMeta type definition