Better Tables
Type-safe tables from your database schema to React UI — filtering, sorting, pagination, and URL state included.
Better Tables builds both halves of a data table from one definition: define your columns once in TypeScript, let an adapter turn filter, sort, and page state into real queries, and render with React components you own.
Why Better Tables
What it covers end-to-end, and honest comparisons.
Quick Start
Install core + adapter, define a table, fetch a page of rows.
Architecture
How core, adapters, UI, and the CLI fit together.
Live Examples
Relationship filters, query groups, facets, and a 12k-row board.
BetterTable Props
Complete reference for the component.
Troubleshooting
Common failures and their fixes.
Packages
| Package | Role | Distribution |
|---|---|---|
@better-tables/core | Column builders, managers, adapter contracts | npm |
@better-tables/adapters-drizzle | Drizzle query generation (Postgres / MySQL / SQLite) | npm |
@better-tables/ui | React table & filter components | CLI copy (private) |
@better-tables/cli | better-tables init — copies UI into your app | npm |
Data flow: defineTable() column definitions → adapter executes queries → UI renders and manages client state, optionally synced to the URL.
import { betterTables, defineTable } from '@better-tables/core';
import { drizzleAdapter } from '@better-tables/adapters-drizzle';
export const tables = betterTables({
database: drizzleAdapter(db),
defaults: { pageSize: 20 },
});
export const ticketsTable = defineTable<typeof tables>()('tickets', (t) => ({
columns: [
t.text('subject').searchable().filterable().sortable(),
t.option('status').options([{ value: 'open', label: 'Open' }]).filterable(),
],
}));
const result = await tables.fetchData(ticketsTable, {
pagination: { page: 1, limit: 20 },
});AI-friendly docs
- Index:
/llms.txt - Full corpus:
/llms-full.txt - Any page as Markdown: append
.mdx(for example/docs/quick-start.mdx)