Data Export (Export CSV, Excel & Data)

Export displayed data, checked rows, or full source data to Excel (.xlsx) or CSV without external dependencies, or extract a logical model for external libraries.

#export#context-menu#row-selection#sorting-filtering#column-visibility
Available since: v1.0.13+Last reviewed: 2026-09-14
LIVE DEMOData Export (Export CSV, Excel & Data) interactive example
Loading the interactive demo…
GitHub
import { t } from './i18n';
import * as React from 'react';
import { Button, Space, Tag } from 'antd';
import { Download, FileSpreadsheet } from 'lucide-react';
import {
  BGrid,
  type BGridColumn,
  type BGridDataItem,
  type BGridRef,
} from 'beautiful-grid';
import DataGridContainer from '../components/DataGridContainer';
import { useContainerSize } from '../hooks/useContainerSize';

interface Order {
  orderNo: string;
  customer: string;
  product: string;
  category: string;
  price: number;
  quantity: number;
  status: string;
  orderDate: string;
}

const customers = ['AxisJ Studio', 'Northwind', t('서울 물류', 'Seoul Logistics'), 'Mono Office', 'Global Tech'];
const products = [
  t('클라우드 서버 패키지', 'Cloud Server Package'),
  t('데이터베이스 라이선스', 'Database License'),
  t('보안 컨설팅 서비스', 'Security Consulting'),
  t('엔터프라이즈 모니터링', 'Enterprise Monitoring'),
  t('네트워크 방화벽 장비', 'Network Firewall Unit'),
];
const categories = ['Infrastructure', 'Software', 'Security', 'DevOps', 'Hardware'];
const statuses = [t('결제 완료', 'Paid'), t('배송 중', 'Shipping'), t('처리 완료', 'Completed'), t('보류', 'Pending')];

const data: BGridDataItem<Order>[] = Array.from({ length: 40 }, (_, index) => {
  const price = (index % 5 + 1) * 150000;
  const quantity = (index % 4) + 1;
  const month = String((index % 12) + 1).padStart(2, '0');
  const day = String((index % 28) + 1).padStart(2, '0');

  return {
    values: {
      orderNo: `ORD-2026-${String(index + 1).padStart(4, '0')}`,
      customer: customers[index % customers.length],
      product: products[index % products.length],
      category: categories[index % categories.length],
      price,
      quantity,
      status: statuses[index % statuses.length],
      orderDate: `2026-${month}-${day}`,
    },
  };
});

const columns: BGridColumn<Order>[] = [
  {
    id: 'orderNo',
    key: 'orderNo',
    label: t('주문 번호', 'Order No'),
    width: 140,
    exportHeader: 'Order Number',
    toolbox: true,
  },
  {
    id: 'customer',
    key: 'customer',
    label: t('고객사', 'Customer'),
    width: 140,
    toolbox: true,
  },
  {
    id: 'product',
    key: 'product',
    label: t('주문 품목', 'Product'),
    width: 180,
    toolbox: true,
  },
  {
    id: 'category',
    key: 'category',
    label: t('분류', 'Category'),
    width: 120,
    toolbox: true,
  },
  {
    id: 'price',
    key: 'price',
    label: t('단가', 'Unit Price'),
    width: 120,
    align: 'right',
    toolbox: true,
    itemRender: ({ value }) => `₩${Number(value).toLocaleString()}`,
    getExportValue: ({ value }) => Number(value),
  },
  {
    id: 'quantity',
    key: 'quantity',
    label: t('수량', 'Quantity'),
    width: 80,
    align: 'right',
    toolbox: true,
  },
  {
    id: 'status',
    key: 'status',
    label: t('상태', 'Status'),
    width: 110,
    align: 'center',
    toolbox: true,
    exportHeader: col => `${String(col.id).toUpperCase()} (STATUS)`,
  },
  {
    id: 'orderDate',
    key: 'orderDate',
    label: t('주문 일자', 'Order Date'),
    width: 120,
    toolbox: true,
  },
];

export default function ExportExample() {
  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width, height } = useContainerSize(containerRef);
  const gridRef = React.useRef<BGridRef<Order>>(null);
  const [checkedRowKeys, setCheckedRowKeys] = React.useState<React.Key[]>([]);

  const handleExportAllExcel = React.useCallback(() => {
    gridRef.current?.exportExcel({
      fileName: 'all-orders.xlsx',
      rows: 'source',
      columns: 'all',
    });
  }, []);

  const handleExportAllCsv = React.useCallback(() => {
    gridRef.current?.exportCsv({
      fileName: 'all-orders.csv',
      rows: 'source',
      columns: 'all',
    });
  }, []);

  const handleExportCheckedExcel = React.useCallback(() => {
    gridRef.current?.exportExcel({
      fileName: 'selected-orders.xlsx',
      rows: 'checked',
      columns: 'visible',
    });
  }, []);

  const handleExportCheckedCsv = React.useCallback(() => {
    gridRef.current?.exportCsv({
      fileName: 'selected-orders.csv',
      rows: 'checked',
      columns: 'visible',
    });
  }, []);

  return (
    <div className='flex min-h-0 flex-col gap-3'>
      <div className='flex flex-wrap items-center justify-between gap-3 rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm text-slate-700'>
        <Space wrap>
          <Button icon={<FileSpreadsheet size={14} />} type='primary' onClick={handleExportAllExcel}>
            {t('전체 로드 데이터 Excel', 'Export all loaded rows (Excel)')}
          </Button>
          <Button icon={<Download size={14} />} onClick={handleExportAllCsv}>
            {t('전체 로드 데이터 CSV', 'Export all loaded rows (CSV)')}
          </Button>
          <Button
            icon={<FileSpreadsheet size={14} />}
            disabled={checkedRowKeys.length === 0}
            onClick={handleExportCheckedExcel}
          >
            {t('선택 행 Excel', 'Export checked rows (Excel)')} ({checkedRowKeys.length})
          </Button>
          <Button
            icon={<Download size={14} />}
            disabled={checkedRowKeys.length === 0}
            onClick={handleExportCheckedCsv}
          >
            {t('선택 행 CSV', 'Export checked rows (CSV)')} ({checkedRowKeys.length})
          </Button>
        </Space>

        <div className='flex items-center gap-2'>
          <Tag color='blue'>{t('우클릭 메뉴 지원', 'Context Menu Supported')}</Tag>
          <span className='text-xs text-slate-500'>
            {t('툴박스 필터·정렬 및 컬럼 표시 설정 반영', 'Reflects toolbox filters, sorting, and visibility')}
          </span>
        </div>
      </div>

      <DataGridContainer ref={containerRef} minHeight={480}>
        <BGrid<Order>
          ref={gridRef}
          width={width}
          height={height}
          columns={columns}
          data={data}
          rowKey='orderNo'
          rowChecked={{
            checkedRowKeys,
            onChange: (_indexes, keys) => setCheckedRowKeys(keys),
          }}
          columnVisibility={true}
          contextMenuOptions={{
            items: () => [
              {
                id: 'export-all-excel',
                label: t('전체 로드 행 Excel 내보내기', 'Export all loaded rows to Excel'),
                icon: <FileSpreadsheet size={14} />,
                onSelect: () => handleExportAllExcel(),
              },
              {
                id: 'export-all-csv',
                label: t('전체 로드 행 CSV 내보내기', 'Export all loaded rows to CSV'),
                icon: <Download size={14} />,
                onSelect: () => handleExportAllCsv(),
              },
              {
                type: 'separator',
                id: 'export-sep',
              },
              {
                id: 'export-checked-excel',
                label: t('체크된 행 Excel 내보내기', 'Export checked rows to Excel'),
                icon: <FileSpreadsheet size={14} />,
                disabled: checkedRowKeys.length === 0,
                onSelect: () => handleExportCheckedExcel(),
              },
              {
                id: 'export-checked-csv',
                label: t('체크된 행 CSV 내보내기', 'Export checked rows to CSV'),
                icon: <Download size={14} />,
                disabled: checkedRowKeys.length === 0,
                onSelect: () => handleExportCheckedCsv(),
              },
            ],
          }}
        />
      </DataGridContainer>
    </div>
  );
}

1. Overview

BeautifulGrid provides a built-in export system without external dependencies (such as SheetJS or ExcelJS).

Because BeautifulGrid uses virtual scrolling, only visible rows are rendered in the DOM. Attempting to scrape DOM nodes causes data loss. BeautifulGrid exports data directly from its logical state model (store.data, store.sourceData, sourceIndexByVisibleIndex) ensuring reliable and complete exports regardless of scroll position.

  • gridRef.current.exportExcel(options): Generates standard OpenXML .xlsx files and triggers browser download with zero external dependencies
  • gridRef.current.exportCsv(options): Serializes data to CSV and triggers a browser file download (RFC 4180 compliant, UTF-8 BOM, formula injection prevention)
  • gridRef.current.getExportData(options): Extracts logical column and cell 2D arrays (compatible with JSON, SheetJS, ExcelJS, etc.)

2. Row Scope

Specify the target rows using the rows option (default: 'displayed').

rows Option Target Data Description
'displayed' Visible logical rows Current rows with filters, sorting, and tree folding applied. Preserves exact display order regardless of virtualization.
'checked' Selected rows Rows selected via rowChecked in sourceData order. Includes checked rows currently hidden by filters.
'source' Loaded source rows All rows currently loaded in the client-side sourceData.
// Export current filtered and sorted rows
gridRef.current?.exportCsv({
  fileName: 'orders.csv',
  rows: 'displayed',
});

// Export only checked rows
gridRef.current?.exportCsv({
  fileName: 'checked-orders.csv',
  rows: 'checked',
});

[!WARNING] Server-side Pagination Notice
rows: 'source' refers to the rows currently loaded in sourceData on the client, not the entire remote database table. BeautifulGrid does not automatically fetch subsequent pages during export. For full database exports, use a server-side export endpoint.

3. Column Scope

Specify which columns to export using the columns option (default: 'visible').

  • 'visible': Currently visible columns after column visibility projection
  • 'all': All columns including hidden columns (in pivot mode, all generated pivot columns)
  • readonly string[]: Explicit list of column IDs in specified order (unmatched IDs are ignored)
// Export all columns including hidden columns
gridRef.current?.exportCsv({
  columns: 'all',
});

// Export specific columns in specified order
gridRef.current?.exportCsv({
  columns: ['orderNo', 'customer', 'price'],
});

4. Column Export Configuration (BGridColumn)

Configure exclusion, custom headers, and value converters on column definitions:

const columns: BGridColumn<Order>[] = [
  {
    id: 'orderNo',
    key: 'orderNo',
    label: <span>Order No</span>,
    exportHeader: 'Order Number', // Custom header string instead of ReactNode
  },
  {
    id: 'price',
    key: 'price',
    label: 'Price',
    getExportValue: ({ value }) => Number(value), // Normalize value
  },
  {
    id: 'actions',
    key: 'orderNo',
    label: 'Actions',
    exportable: false, // Always excluded from export
  },
];

Header Resolution Order

  1. column.exportHeader: string or callback function receiving the column definition
  2. column.label: if string or number, String(label)
  3. Column identifier (columnId)

Cell Value Resolution

itemRender output is not used for export. Raw values are resolved from nested row keys (getCellValueByRowKey). If column.getExportValue is provided, its return value is used; otherwise, the raw value is exported.

5. Context Menu Integration

Use contextMenuOptions to add export actions to the right-click menu:

<BGrid<Order>
  ref={gridRef}
  columns={columns}
  data={data}
  contextMenuOptions={{
    items: () => [
      {
        id: 'export-all-excel',
        label: 'Export all loaded rows to Excel',
        onSelect: () => {
          gridRef.current?.exportExcel({
            fileName: 'all-orders.xlsx',
            rows: 'source',
            columns: 'all',
          });
        },
      },
      {
        id: 'export-all-csv',
        label: 'Export all loaded rows to CSV',
        onSelect: () => {
          gridRef.current?.exportCsv({
            fileName: 'all-orders.csv',
            rows: 'source',
            columns: 'all',
          });
        },
      },
      { type: 'separator', id: 'export-sep' },
      {
        id: 'export-checked-excel',
        label: 'Export checked rows to Excel',
        disabled: checkedRowKeys.length === 0,
        onSelect: () => {
          gridRef.current?.exportExcel({
            fileName: 'selected-orders.xlsx',
            rows: 'checked',
            columns: 'visible',
          });
        },
      },
      {
        id: 'export-checked-csv',
        label: 'Export checked rows to CSV',
        disabled: checkedRowKeys.length === 0,
        onSelect: () => {
          gridRef.current?.exportCsv({
            fileName: 'selected-orders.csv',
            rows: 'checked',
            columns: 'visible',
          });
        },
      },
    ],
  }}
/>

6. Excel (.xlsx) Export

You can export directly to standard OpenXML Excel (.xlsx) using gridRef.current.exportExcel without installing any heavy external dependencies:

// Export current visible data to Excel
gridRef.current?.exportExcel({
  fileName: 'orders.xlsx',
  sheetName: 'Orders', // Sheet tab name (default: 'Sheet1', max 31 characters)
  rows: 'displayed',   // 'displayed' | 'checked' | 'source' (default: 'displayed')
  columns: 'visible',  // 'visible' | 'all' | string[] (default: 'visible')
  includeHeader: true, // Whether to include header row (default: true)
});

Numbers and boolean values are preserved as native Excel number and boolean cell types, enabling immediate formulas and calculations without manual type casting.

7. CSV Formatting and Security

exportCsv supports standard RFC 4180 escaping, UTF-8 BOM, and formula injection mitigation out of the box:

gridRef.current?.exportCsv({
  fileName: 'export.csv',
  delimiter: ',', // Default ',' (supports '\t', ';', etc.)
  newline: '\r\n', // Default '\r\n'
  bom: true, // Default true (prevents UTF-8 Korean/special char corruption in Excel)
  preventFormulaInjection: true, // Default true (prefixes strings starting with '=', '+', '-', '@' with ')
});

8. Integration with SheetJS / ExcelJS

When generating binary Excel (.xlsx) files, pass getExportData() output to an external library:

SheetJS (xlsx) Example

import * as XLSX from 'xlsx';

const exportData = gridRef.current?.getExportData({
  rows: 'displayed',
  columns: 'visible',
});

if (exportData) {
  const aoa = [
    exportData.columns.map(c => c.header),
    ...exportData.rows.map(row => row.cells),
  ];

  const ws = XLSX.utils.aoa_to_sheet(aoa);
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
  XLSX.writeFile(wb, 'orders.xlsx');
}