Quickback Docs

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.ts

Each page file exports a definePage() call:

features/banking/pages/bank-reconciliation.ts
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'],
  },
}
FieldTypeDescription
tablestringTable name to query (required)
defaultFiltersRecord<string, unknown>Filters applied on initial load
defaultSort{ field, order }Default sort column and direction
displayColumnsstring[]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:

FeatureDescription
drag-sourceRows in this panel can be dragged
drop-targetRows 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

OperatorDescriptionReturns
abs-equalsAbsolute values are equal1.0 if match, 0 otherwise
within-daysDates are within N days1 - (daysDiff / maxDays), clamped to 0
fuzzy-matchWord overlap between stringsOverlap 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:

ScoreColor
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 the id field of the dragged record from the bankTransactions data source
  • ledgerEntries.$amount — resolves to the amount field of the drop-target record from the ledgerEntries data 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.

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:

  • slug must start with a lowercase letter and contain only lowercase letters, digits, and hyphens
  • At least one data source is required
  • Every panel's dataSource must reference a declared data source
  • Matching rule weights must be between 0 and 1
  • confidenceThreshold must be between 0 and 1

Next Steps

On this page