TNKS Data Table
07 features

Data Export

Export table data to CSV and Excel with custom formatting and transformations

Data Export

Export your table data to CSV and Excel formats with powerful transformation and formatting capabilities.

Basic Export Setup

Create Export Configuration

src/app/(section)/users/utils/config.ts
import { useMemo } from "react";

export function useExportConfig() {
  const columnMapping = useMemo(() => ({
    id: "User ID",
    name: "Full Name",
    email: "Email Address",
    age: "Age",
    created_at: "Registration Date",
  }), []);

  const headers = useMemo(() => [
    "id",
    "name",
    "email",
    "age",
    "created_at",
  ], []);

  const columnWidths = useMemo(() => [
    { wch: 10 },  // ID
    { wch: 25 },  // Name
    { wch: 30 },  // Email
    { wch: 10 },  // Age
    { wch: 20 },  // Created At
  ], []);

  return {
    columnMapping,
    headers,
    columnWidths,
    entityName: "users",  // Used in filename: users_2024-01-15.xlsx
  };
}

Add to DataTable

import { useExportConfig } from "./utils/config";

<DataTable
  getColumns={getColumns}
  fetchDataFn={useUsersData}
  exportConfig={useExportConfig()}  // Add export config
  idField="id"
/>

Result

✅ Export button appears in toolbar ✅ Downloads as users_2024-01-15.xlsx or .csv ✅ Columns have readable headers ✅ Proper column widths in Excel

Export Configuration Options

Column Mapping

Maps API field names to human-readable headers:

columnMapping: {
  id: "Customer ID",           // id → "Customer ID"
  created_at: "Join Date",     // created_at → "Join Date"
  total_spent: "Lifetime Value", // total_spent → "Lifetime Value"
}

Headers Array

Specifies which columns to export and their order:

headers: [
  "id",
  "name",
  "email",
  "age",
  "created_at",
  // Columns not listed won't be exported
]

Column Widths (Excel Only)

Set column widths for Excel exports:

columnWidths: [
  { wch: 10 },  // 10 characters wide
  { wch: 25 },  // 25 characters wide
  { wch: 30 },  // 30 characters wide
]

Width is in characters (wch). Order matches headers array.

Entity Name

Used in the exported filename:

entityName: "customers"
// Downloads as: customers_2024-01-15.xlsx

Data Transformation

Transform data before export to format values or add calculated columns.

Basic Formatting

import { DataTransformFunction } from "@/components/data-table/utils/export-utils";
import { formatTimestampToReadable, formatCurrency } from "@/utils/format";

const transformFunction: DataTransformFunction<User> = (row: User) => ({
  ...row,
  // Format dates
  created_at: formatTimestampToReadable(row.created_at),
  // "2024-01-15T10:30:00Z" → "01/15/2024 10:30 AM"

  // Format currency
  total_spent: formatCurrency(row.total_spent),
  // "1234.56" → "$1,234.56"

  // Format phone numbers
  phone: formatPhoneNumber(row.phone),
  // "5551234567" → "(555) 123-4567"
});

Adding Calculated Columns

Add new columns that don't exist in your data:

const enhancedTransform: DataTransformFunction<User> = (row: User) => {
  const spentAmount = parseFloat(row.total_spent) || 0;
  const currentYear = new Date().getFullYear();
  const joinYear = new Date(row.created_at).getFullYear();

  return {
    ...row,
    // Format existing columns
    created_at: formatTimestampToReadable(row.created_at),
    total_spent: formatCurrency(row.total_spent),

    // NEW calculated columns
    customer_tier: spentAmount > 2000 ? "PREMIUM" :
                   spentAmount > 1000 ? "GOLD" :
                   spentAmount > 500 ? "SILVER" : "BRONZE",

    years_active: currentYear - joinYear,

    spending_category: spentAmount > 1000 ? "HIGH_SPENDER" :
                       spentAmount > 500 ? "MEDIUM_SPENDER" :
                       "LOW_SPENDER",

    risk_level: row.order_count < 2 ? "HIGH_RISK" : "LOW_RISK",
  };
};

Include New Columns in Export

export function useExportConfig() {
  return {
    headers: [
      // Original columns
      "id", "name", "email", "phone", "created_at", "total_spent",
      // NEW calculated columns
      "customer_tier", "years_active", "spending_category", "risk_level"
    ],

    columnMapping: {
      // Original columns
      id: "Customer ID",
      name: "Full Name",
      email: "Email",
      phone: "Phone",
      created_at: "Join Date",
      total_spent: "Total Spent",
      // NEW column headers
      customer_tier: "Tier",
      years_active: "Years Active",
      spending_category: "Spending Category",
      risk_level: "Risk Assessment",
    },

    transformFunction: enhancedTransform,  // Apply transformation
    entityName: "customers"
  };
}

Export Result

Your exported file will include:

Customer ID | Full Name | Email | Phone | Join Date | Total Spent | Tier | Years Active | Spending Category | Risk Assessment
1 | John Doe | john@example.com | (555) 123-4567 | 01/15/2023 10:30 AM | $2,450.75 | PREMIUM | 2 | HIGH_SPENDER | LOW_RISK

Available Formatting Functions

Built-in utilities from /utils/format.ts:

FunctionInputOutput
formatTimestampToReadable()"2024-01-15T10:30:00Z""01/15/2024 10:30 AM"
formatCurrency()"1234.56""$1,234.56"
formatPhoneNumber()"5551234567""(555) 123-4567"
formatToTitleCase()"john doe""John Doe"
formatBoolean()true"Yes"
formatNumber()1234567"1,234,567"
formatTruncatedText()"Long text...", 20"Long text..."

Controlling Export Columns

Hidden Columns

Hidden columns are automatically excluded from export:

// User hides "Email" column via column visibility toggle
// Export will not include email, only visible columns

Allow New Columns

Control whether transform function's new columns are exported:

config={{
  allowExportNewColumns: true,   // Include calculated columns (default)
  // or
  allowExportNewColumns: false,  // Strict mode - visible columns only
}}

When true:

  • ✅ Exports visible table columns
  • ✅ Exports new columns from transform function
  • ❌ Excludes hidden table columns

When false:

  • ✅ Exports visible table columns only
  • ❌ Excludes hidden table columns
  • ❌ Excludes new columns from transform function

Export Modes

Export Current Page

Export only the rows visible on current page:

// User clicks: "Export Current Page"
// Exports 10-20 rows (depending on page size)

Export All Selected

Export all selected rows across all pages:

// User selects rows on pages 1, 3, and 5
// Clicks: "Export Selected"
// Exports only selected rows, regardless of page

Export All Data

Export the entire dataset:

// User clicks: "Export All"
// Fetches and exports all rows (respects current filters/search)

Case Format Support

Export preserves your API's case format:

Snake_case API

// API Response: { user_name: "John", created_at: "..." }
headers: ["id", "user_name", "created_at"]
columnMapping: {
  user_name: "Full Name",      // ✅ Uses snake_case
  created_at: "Registration",
}

CamelCase API

// API Response: { userName: "John", createdAt: "..." }
headers: ["id", "userName", "createdAt"]
columnMapping: {
  userName: "Full Name",        // ✅ Uses camelCase
  createdAt: "Registration",
}

Export with Subrows

When using hierarchical data:

Flatten Subrows (Default)

exportConfig={{
  // ... other options
  flatten: true,  // Each row (parent + children) becomes separate export row
}}

Result:

Order # | Product | Quantity
ORD-001 | Widget A | 2
ORD-001 | Widget B | 1
ORD-002 | Gadget C | 3

Parent Rows Only

exportConfig={{
  // ... other options
  flatten: false,  // Export only parent rows
}}

Result:

Order # | Product | Quantity
ORD-001 | Widget A | 2
ORD-002 | Gadget C | 3

Complete Example

src/app/(section)/users/utils/config.ts
import { useMemo } from "react";
import { DataTransformFunction } from "@/components/data-table/utils/export-utils";
import { formatTimestampToReadable, formatCurrency } from "@/utils/format";
import { User } from "../schema";

const transformData: DataTransformFunction<User> = (row: User) => {
  const spentAmount = parseFloat(row.total_spent) || 0;

  return {
    ...row,
    // Format existing fields
    created_at: formatTimestampToReadable(row.created_at),
    total_spent: formatCurrency(row.total_spent),
    phone: formatPhoneNumber(row.phone),

    // Add calculated fields
    customer_tier: spentAmount > 2000 ? "PREMIUM" :
                   spentAmount > 1000 ? "GOLD" :
                   spentAmount > 500 ? "SILVER" : "BRONZE",
    years_active: new Date().getFullYear() - new Date(row.created_at).getFullYear(),
  };
};

export function useExportConfig() {
  const headers = useMemo(() => [
    "id",
    "name",
    "email",
    "phone",
    "age",
    "created_at",
    "order_count",
    "total_spent",
    "customer_tier",
    "years_active",
  ], []);

  const columnMapping = useMemo(() => ({
    id: "Customer ID",
    name: "Full Name",
    email: "Email Address",
    phone: "Phone Number",
    age: "Age",
    created_at: "Registration Date",
    order_count: "Total Orders",
    total_spent: "Lifetime Value",
    customer_tier: "Tier",
    years_active: "Years Active",
  }), []);

  const columnWidths = useMemo(() => [
    { wch: 12 },  // ID
    { wch: 25 },  // Name
    { wch: 30 },  // Email
    { wch: 18 },  // Phone
    { wch: 8 },   // Age
    { wch: 22 },  // Created
    { wch: 14 },  // Orders
    { wch: 16 },  // Spent
    { wch: 12 },  // Tier
    { wch: 14 },  // Years
  ], []);

  return {
    headers,
    columnMapping,
    columnWidths,
    transformFunction: transformData,
    entityName: "customers",
  };
}

Next Steps

How is this guide?