supa table

Component from Waghubinger Registry

Back to Registry

Option A: Via components.json

Requires REGISTRY_TOKEN in .env.local

pnpm dlx shadcn add @waghubinger/supa-table

Option B: Direct URL with Token

For quick testing or one-time installation

pnpm dlx shadcn add http://localhost:3000/r/supa-table.json?token=reO2TJ1CDL8vO-IxZatQTNV1POL6pqKi4e-GxVBKYIcNfhj2nVBl-hXNQkZXPwm

SupaTable

Advanced data table component with Supabase integration, featuring pagination, filtering, sorting, Excel export, and saved views.

Features

  • Server-side pagination - Handle large datasets efficiently
  • Advanced filtering - Column-specific filters, date ranges, global search
  • Sorting - Multi-column sorting with Supabase integration
  • Excel export - Export filtered data with formatting
  • Saved views - Save and share filter/sort configurations
  • Column visibility - Show/hide columns dynamically
  • Row selection - Single or multi-row selection with "Select All Filtered"
  • Responsive design - Mobile-friendly with horizontal scroll
  • TypeScript - Full type safety with comprehensive interfaces

Installation

pnpm dlx shadcn add https://registry.tools.asscompact.at/r/supa-table.json?token=YOUR_TOKEN

This will install:

  • Components (10 files)
  • Hooks (2 files)
  • Helper functions (3 files)
  • Types (1 file)
  • API routes (4 template files)
  • Database migration (1 SQL file)

Quick Start

1. Database Setup

Run the migration to create required tables:

# Migration is installed to: supabase/migrations/XXX_supa_table_setup.sql
supabase db push

This creates:

  • table_export_sessions - Temporary storage for large exports
  • user_table_views - Saved filter/sort configurations per user

2. Supabase Client Setup

Create Supabase server clients in your project:

// lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'

export async function createClient() {
  const cookieStore = await cookies()
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    { cookies: () => cookieStore }
  )
}

export async function createServiceClient() {
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    { cookies: { getAll: () => [], setAll: () => {} } }
  )
}

3. Setup API Routes (Manual Step Required)

⚠️ API routes are NOT automatically installed to prevent accidental overwrites during updates.

Option A: Use Claude Code (Recommended)

If you're using Claude Code, simply ask:

"Claude, read the API_ROUTES.md file and create all SupaTable API routes for me"

Claude Code will:

  • ✅ Read the complete API route code from components/supa-table/API_ROUTES.md (installed with component)
  • ✅ Create all 4 API routes with correct import paths
  • ✅ Replace Supabase client placeholders automatically
  • ✅ Verify syntax and fix errors

Option B: Manual Setup

See the complete documentation in API_ROUTES.md which includes:

  • Full source code for all 4 routes
  • Detailed setup instructions
  • Import path adjustments
  • Test commands
  • Troubleshooting guide

Required routes:

  • app/api/supa-table/route.ts - Main data fetching
  • app/api/supa-table/selection-ids/route.ts - "Select All Filtered" functionality
  • app/api/supa-table/export-session/route.ts - Create export sessions
  • app/api/supa-table/export/route.ts - Excel export generation

Why not auto-installed? API routes require project-specific configuration (Supabase clients, auth). Auto-installing would overwrite your customizations on every update. The API_ROUTES.md file is installed with the component so you (or Claude) always have the correct version available.

4. Basic Usage

'use client'

import { SupaTable } from "@/components/supa-table"
import { SupaTableFieldConfig } from "@/types/supa-table"
import { createClient } from "@/lib/supabase/client"

const tableFields: SupaTableFieldConfig[] = [
  {
    field: "id",
    label: "ID",
    type: "number",
    contentWidth: 80,
    visible: true,
  },
  {
    field: "name",
    label: "Name",
    type: "text",
    contentWidth: 200,
    visible: true,
    enableFiltering: true,
    enableSorting: true,
  },
  {
    field: "email",
    label: "Email",
    type: "text",
    contentWidth: 250,
    visible: true,
    enableFiltering: true,
  },
  {
    field: "created_at",
    label: "Created",
    type: "date",
    contentWidth: 180,
    visible: true,
    enableSorting: true,
  },
]

export default function UsersTable() {
  const supabase = createClient()

  return (
    <SupaTable
      supabaseClient={supabase}
      dbTable="users"
      tableFields={tableFields}
      rowIdAccessor="id"
      enableExcelExport={true}
      enableSavedViews={true}
      enableColumnVisibility={true}
    />
  )
}

Component API

SupaTable Props

PropTypeRequiredDescription
supabaseClientSupabaseClientYesSupabase client instance
dbTablestringYesDatabase table name
tableFieldsSupaTableFieldConfig[]YesColumn configuration
rowIdAccessorstringYesField name for unique row ID
dbQueryStringstringNoCustom Supabase select query (default: "*")
enableExcelExportbooleanNoEnable Excel export functionality
enableSavedViewsbooleanNoEnable saved views functionality
enableColumnVisibilitybooleanNoEnable column show/hide toggle
enableRowSelectionbooleanNoEnable row selection checkboxes
adminModebooleanNoUse service role client (bypasses RLS)
globalFiltersGlobalFilter[]NoPre-applied filters from parent
onSelectionChange(ids: (string|number)[]) => voidNoCallback when selection changes
customActionsReact.ReactNodeNoCustom toolbar actions

SupaTableFieldConfig

interface SupaTableFieldConfig {
  field: string                    // Database column name (supports dot notation for joins)
  label: string                    // Display label
  type: FieldType                  // "text" | "number" | "date" | "bool" | "json"
  contentWidth?: number            // Column width in pixels
  visible?: boolean                // Show/hide column
  enableFiltering?: boolean        // Enable filter for this column
  enableSorting?: boolean          // Enable sorting for this column
  valueMap?: Record<string, any>   // Map database values to display values
  valueManipulation?: (val: any) => any  // Transform value before display
  formattingInfo?: {
    formatNumber?: "currency" | "percentage"
    formatDate?: "short" | "long"
  }
}

Advanced Features

Custom Queries with Joins

const tableFields: SupaTableFieldConfig[] = [
  { field: "id", label: "ID", type: "number" },
  { field: "user.name", label: "User", type: "text" }, // Dot notation for joins
  { field: "user.email", label: "Email", type: "text" },
]

<SupaTable
  dbTable="orders"
  dbQueryString="*, user:users(name, email)" // Supabase join syntax
  tableFields={tableFields}
  rowIdAccessor="id"
  supabaseClient={supabase}
/>

Value Mapping

const statusField: SupaTableFieldConfig = {
  field: "status",
  label: "Status",
  type: "text",
  valueMap: {
    "active": "Active",
    "inactive": "Inactive",
    "pending": "Pending",
  },
}

Value Manipulation

const priceField: SupaTableFieldConfig = {
  field: "price",
  label: "Price",
  type: "number",
  formattingInfo: { formatNumber: "currency" },
  valueManipulation: (val) => val / 100, // Convert cents to dollars
}

Global Filters

Pre-filter data from parent component:

const globalFilters: GlobalFilter[] = [
  {
    field: "status",
    value: "active",
    operator: "eq",
  },
  {
    field: "created_at",
    value: new Date(2024, 0, 1).toISOString(),
    operator: "gte",
  },
]

<SupaTable
  dbTable="users"
  globalFilters={globalFilters}
  {...otherProps}
/>

Excel Export

SupaTable supports exporting large datasets (1000+ rows) efficiently:

  1. Small datasets (<500 rows): Direct export
  2. Large datasets (>500 rows): Session-based export via database

Users can choose:

  • Formatted export: Apply value maps, date formatting, number formatting
  • Raw export: Export database values as-is

Saved Views

Users can save their current filter/sort configuration:

// Enable saved views
<SupaTable
  enableSavedViews={true}
  dbTable="users"
  supabaseClient={supabase}
  {...otherProps}
/>

Saved views are stored in user_table_views table and include:

  • Filter values
  • Sort configuration
  • Column visibility
  • View name

Row Selection

<SupaTable
  enableRowSelection={true}
  onSelectionChange={(selectedIds) => {
    console.log("Selected IDs:", selectedIds)
  }}
  {...otherProps}
/>

"Select All Filtered" functionality:

  • Automatically handles large datasets (1000+ rows)
  • Uses pagination to fetch all filtered IDs
  • Shows progress during fetch

API Routes

SupaTable requires 4 API routes for server-side operations:

  • app/api/supa-table/route.ts - Main data fetching endpoint
  • app/api/supa-table/selection-ids/route.ts - Bulk selection support
  • app/api/supa-table/export-session/route.ts - Export session management
  • app/api/supa-table/export/route.ts - Excel export generation

⚠️ Manual Setup Required

IMPORTANT: API routes are NOT automatically installed. This prevents accidental overwrites when updating the supa-table component.

Complete setup instructions are available in API_ROUTES.md, which includes:

  • ✅ Full source code for all 4 routes
  • ✅ Step-by-step setup guide
  • ✅ Import path adjustments
  • ✅ Supabase client configuration
  • ✅ Test commands
  • ✅ Troubleshooting guide
  • ✅ Special instructions for Claude Code (auto-setup)

Quick Setup with Claude Code:

"Claude, read the API_ROUTES.md file and create all SupaTable API routes for me"

Manual Setup:

  1. Read API_ROUTES.md for complete code
  2. Create 4 API route files in app/api/supa-table/
  3. Adjust import paths (registry → project paths)
  4. Configure Supabase clients
  5. Test endpoints

Why manual? API routes require project-specific configuration and updates would overwrite your customizations. The API_ROUTES.md file is installed with the component so you always have the correct version available.

Environment Variables

Required in your project's .env.local:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key

# For admin mode / service client
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Troubleshooting

API Routes Return 500 Error

Problem: API routes fail with "Cannot read property 'from' of null"

Solution: Make sure you've replaced the placeholder Supabase clients:

// ❌ Wrong - Placeholder still active
const supabase = null as any; // PLACEHOLDER

// ✅ Correct - Import and use your client
import { createClient } from "@/lib/supabase/server";
const supabase = await createClient();

Export Fails for Large Datasets

Problem: Export times out or fails for 1000+ rows

Solution: SupaTable automatically uses session-based export for >500 rows. Ensure:

  1. Migration created table_export_sessions table
  2. API route app/api/supa-table/export-session/route.ts is configured
  3. Session cleanup runs (automatic after 1 hour)

Filtering Not Working

Problem: Column filters don't affect data

Solution: Check that:

  1. enableFiltering: true is set on the field config
  2. API route app/api/supa-table/route.ts is properly configured
  3. Database column exists and has correct type

RLS Policies Block Data

Problem: No data shows even though database has rows

Solution:

  • Check RLS policies on your table
  • Use adminMode={true} for testing (bypasses RLS)
  • Verify user is authenticated when using user client

TypeScript Errors

Problem: Import errors or type mismatches

Solution:

# Regenerate types from your Supabase schema
npx supabase gen types typescript --project-id YOUR_PROJECT_ID > types/supabase.ts

Performance Tips

Optimize Large Tables

  1. Add database indexes on frequently filtered/sorted columns:
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_users_status ON users(status);
  1. Use dbQueryString wisely - Only select needed columns:
dbQueryString="id, name, email, created_at" // Don't use * for large tables
  1. Limit joins - Each join increases query complexity:
// ✅ Good - Single level join
dbQueryString="*, user:users(name)"

// ❌ Slow - Multiple nested joins
dbQueryString="*, user:users(*, profile:profiles(*, company:companies(*)))"

Reduce Re-renders

IMPORTANT: Always wrap your tableFields configuration with useMemo to prevent unnecessary re-renders.

Large field configurations (20+ fields) can cause performance issues if recreated on every render:

export default function MyTable() {
  const supabase = createClient()

  // ⚠️ REQUIRED: Use useMemo for tableFields
  const tableFields = useMemo<SupaTableFieldConfig[]>(() => [
    {
      field: "id",
      label: "ID",
      type: "number",
      searchable: true,
      searchType: "number",
      defaultEnabled: true,
    },
    // ... rest of your fields (can be 50+ fields)
  ], []) // Empty dependency array - config never changes

  return (
    <SupaTable
      supabaseClient={supabase}
      tableFields={tableFields}  // Now stable across renders
      // ... other props
    />
  )
}

Why this matters:

  • Large configs (50+ fields with custom formatters/icons) can be expensive to recreate
  • Without useMemo, React creates a new array reference on every render
  • SupaTable internally uses React Table which triggers full recalculation when fields change
  • For complex tables with 1000+ rows, this can cause noticeable lag

Migration from GenericDataTable

If you're migrating from the old GenericDataTable component:

Component Rename

// Old
import { TableWithDataLayer } from "@/components/Tables/GenericDataTable"

// New
import { SupaTable } from "@/components/supa-table"

Props Changes

// Old
<TableWithDataLayer
  supabaseAdmin={supabase}
  dbTable="users"
  tableDbFields={fields}
  useServerActionForFiltering={true}
/>

// New
<SupaTable
  supabaseClient={supabase}     // Renamed from supabaseAdmin
  dbTable="users"
  tableFields={fields}           // Renamed from tableDbFields
  adminMode={false}              // Replaces useServerActionForFiltering
/>

Type Changes

// Old
import { ITableDbFields } from "@/components/Tables/GenericDataTable"

// New
import { SupaTableFieldConfig } from "@/types/supa-table"

Contributing

Found a bug or want to contribute? This component is part of the Waghubinger Registry.

  1. Test your changes locally
  2. Update this README if adding features
  3. Update CHANGELOG.md with your changes
  4. Rebuild registry: pnpm registry:build

License

MIT License - Part of Waghubinger Registry

Support

For issues or questions:

  • Check this README first
  • Review API route setup in app/api/supa-table/
  • Check Supabase client configuration in lib/supabase/
  • Verify database migration ran successfully

Version: 1.1.1 Last Updated: 2025-11-04 Registry: https://registry.tools.asscompact.at