Reference

API Reference

Complete API documentation for ImportCSV

CSVImporter Props

PropTypeDefaultDescription
schemaz.ZodSchema<T>-Zod schema for type-safe validation and inference (recommended)
columnsColumn[]-Manual column definitions (use schema instead for better DX)
onComplete(data: any) => void-Callback when import completes
isModalbooleantrueShow as modal vs embedded
modalIsOpenbooleantrueControl modal state
modalOnCloseTriggered() => void-Modal close callback
modalCloseOnOutsideClickbooleanfalseClose on outside click
themeThemeConfig | string-Theme configuration or preset name
darkModebooleanfalseEnable dark theme (backward compatibility)
primaryColorstring#2563ebPrimary color (hex)
customStylesobject | string-CSS variable overrides
classNamesobject-Custom class names for components
showDownloadTemplateButtonbooleantrueShow template download
skipHeaderRowSelectionbooleanfalseAuto-select first row as header
waitOnCompletebooleanfalseWait for async operations on complete
invalidRowHandling'include' | 'exclude' | 'block''block'How to handle rows with validation errors
includeUnmatchedColumnsbooleanfalseInclude CSV columns not defined in schema
languagestring-Language for internationalization
customTranslationsobject-Custom translation resources
dynamicColumnsColumn[]-Customer-specific dynamic columns (output nested under _custom_fields)
importerKeystring-Importer key for backend mode
backendUrlstring-Backend API URL (for backend mode)
userobject-User context for backend mode
metadataobject-Additional metadata for import
demoDataobject-Demo data for testing

Use Zod to define your schema for automatic TypeScript inference:

import { z } from 'zod';

const schema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Invalid email'),
  age: z.number().int().positive().optional()
});

<CSVImporter
  schema={schema}
  onComplete={(data) => {
    // data is typed as: { name: string; email: string; age?: number }[]
  }}
/>

See Schema Definition Guide for all validation patterns.

Column Definition (Manual)

If you need dynamic columns or can't use Zod, define columns manually:

Each column in the columns array should have the following structure:

interface Column {
  id: string;              // Unique identifier for the column
  label: string;           // Display name for the column
  type?: 'string' | 'number' | 'email' | 'date' | 'phone' | 'select';  // Default: 'string'
  validators?: Validator[];      // Validation rules
  transformations?: Transformer[]; // Transformation rules
  options?: string[];            // For select type
  description?: string;          // Optional helper text
  placeholder?: string;          // Input placeholder
}

Validator Types

type Validator =
  | { type: 'required'; message?: string }
  | { type: 'unique'; message?: string }
  | { type: 'regex'; pattern: string; message?: string }
  | { type: 'min'; value: number; message?: string }
  | { type: 'max'; value: number; message?: string }
  | { type: 'min_length'; value: number; message?: string }
  | { type: 'max_length'; value: number; message?: string };

Transformer Types

type Transformer =
  | { type: 'trim' }
  | { type: 'uppercase' }
  | { type: 'lowercase' }
  | { type: 'capitalize' }
  | { type: 'remove_special_chars' }
  | { type: 'normalize_phone' }
  | { type: 'normalize_date'; format?: string }
  | { type: 'default'; value: string }
  | { type: 'replace'; find: string; replace: string }
  | { type: 'custom'; fn: (value: any) => any };

onComplete Callback Data Structure

The onComplete callback receives an ImportResult object:

interface ImportResult<T = Record<string, unknown>> {
  rows: Array<T & {
    _custom_fields?: Record<string, unknown>;  // Dynamic column values
    _unmatched?: Record<string, unknown>;      // Unmapped CSV columns
  }>;
  columns: {
    predefined: Column[];   // Columns from `columns` prop
    dynamic: Column[];      // Columns from `dynamicColumns` prop
    unmatched: string[];    // CSV column names that weren't mapped
  };
}

Example Usage

import { CSVImporter, ImportResult } from '@importcsv/react';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  email: z.string().email()
});

type User = z.infer<typeof schema>;

<CSVImporter
  schema={schema}
  dynamicColumns={[{ id: 'department', label: 'Department' }]}
  onComplete={(result: ImportResult<User>) => {
    // Access validated rows
    result.rows.forEach(row => {
      console.log(row.name, row.email);           // Typed from schema
      console.log(row._custom_fields?.department); // Dynamic columns
    });

    // Access column metadata
    console.log(`Predefined: ${result.columns.predefined.length}`);
    console.log(`Dynamic: ${result.columns.dynamic.length}`);
    console.log(`Unmatched: ${result.columns.unmatched.join(', ')}`);
  }}
/>

Row Structure

FieldTypeDescription
[column_id]anyValues from predefined columns (top-level)
_custom_fieldsRecord<string, unknown>Values from dynamicColumns prop
_unmatchedRecord<string, unknown>Values from unmapped CSV columns (when includeUnmatchedColumns is true)

Columns Metadata

FieldTypeDescription
predefinedColumn[]Columns defined via columns or schema prop
dynamicColumn[]Columns from dynamicColumns prop
unmatchedstring[]CSV header names that weren't mapped to any column

Import Behavior Options

Invalid Row Handling

The invalidRowHandling prop controls how the importer handles rows that fail validation:

ModeDescriptionUse Case
'block' (default)Prevents import if ANY row has validation errorsCritical data imports where integrity is paramount
'exclude'Filters out invalid rows, imports only valid onesClean data imports, removing problematic entries
'include'Imports all rows with warnings for invalid onesFlexible imports where you'll handle errors later

Examples

// Strict mode - blocks import on any error (default)
<CSVImporter
  columns={columns}
  invalidRowHandling="block"
  onComplete={(data) => console.log(data)}
/>

// Filter mode - imports only valid rows
<CSVImporter
  columns={columns}
  invalidRowHandling="exclude"
  onComplete={(data) => {
    // data contains only rows that passed validation
    console.log(`Imported ${data.num_rows} valid rows`);
  }}
/>

// Permissive mode - imports everything with warnings
<CSVImporter
  columns={columns}
  invalidRowHandling="include"
  onComplete={(data) => {
    // data contains all rows, check for errors separately
    console.log(`Imported ${data.num_rows} rows (may include invalid data)`);
  }}
/>

Include Unmatched Columns

The includeUnmatchedColumns prop allows importing columns from the CSV that aren't defined in your schema:

// Example CSV with extra columns:
// Name,Email,Department,Manager,Office
// John,john@example.com,Sales,Jane Smith,Building A

const columns = [
  { id: 'name', label: 'Name' },
  { id: 'email', label: 'Email' },
  { id: 'department', label: 'Department' }
];

// With includeUnmatchedColumns=true
<CSVImporter
  columns={columns}
  includeUnmatchedColumns={true}
  onComplete={(result) => {
    result.rows.forEach(row => {
      // Defined columns at top level
      console.log(row.name, row.email, row.department);

      // Unmapped columns under _unmatched
      console.log(row._unmatched?.Manager);
      console.log(row._unmatched?.Office);
    });

    // List of unmapped column names
    console.log(result.columns.unmatched); // ['Manager', 'Office']
  }}
/>

This is useful for:

  • Dynamic CSV structures where users might add custom columns
  • Importing data where you want to preserve all information
  • Flexible schemas that adapt to user needs

Dynamic Columns

For customer-specific fields that vary per user (e.g., custom properties, tenant-specific attributes), use the dynamicColumns prop:

// Fetch customer-specific fields at runtime
const customerFields = await fetchCustomerFields(customerId);
// Returns: [{ id: 'location', label: 'Location' }, { id: 'department', label: 'Department' }]

<CSVImporter
  columns={[
    { id: 'name', label: 'Name', validators: [{ type: 'required' }] },
    { id: 'email', label: 'Email', type: 'email' },
  ]}
  dynamicColumns={customerFields}
  onComplete={(result) => {
    result.rows.forEach(row => {
      // Predefined fields at top level
      console.log(row.name, row.email);

      // Dynamic fields nested under _custom_fields
      console.log(row._custom_fields?.location);
      console.log(row._custom_fields?.department);
    });
  }}
/>

Output Structure

When using dynamicColumns, the output is structured as follows:

  • Predefined columns (columns prop) → values at top level of each row
  • Dynamic columns (dynamicColumns prop) → values nested under _custom_fields
  • Unmatched CSV columns → values nested under _unmatched (when includeUnmatchedColumns is true)
// Example result structure
{
  rows: [
    {
      name: 'John Doe',           // Predefined column
      email: 'john@example.com',  // Predefined column
      _custom_fields: {           // Dynamic columns
        location: 'New York',
        department: 'Engineering'
      },
      _unmatched: {               // Unmatched columns (if includeUnmatchedColumns=true)
        extra_col: 'some value'
      }
    }
  ],
  columns: {
    predefined: [{ id: 'name', label: 'Name' }, { id: 'email', label: 'Email' }],
    dynamic: [{ id: 'location', label: 'Location' }, { id: 'department', label: 'Department' }],
    unmatched: ['extra_col']
  }
}

Use Cases

  • Multi-tenant SaaS: Each customer has unique custom fields
  • Configurable imports: Admins define additional fields per import type
  • Integration mapping: Different integrations require different custom attributes

Column Priority in Mapping

When the user maps CSV columns, they see columns in this order:

  1. Predefined columns (from columns prop)
  2. Dynamic columns (from dynamicColumns prop)

This keeps the core required fields prominent while allowing flexibility for custom fields.

Backend Mode

When using importerKey and backendUrl, the importer operates in backend mode. In this mode:

  1. Schema is fetched from the backend - No need to pass columns or schema prop
  2. Data is sent directly to your backend API - Not passed to onComplete
  3. AI features are enabled - Smart column mapping, natural language transforms
<CSVImporter
  importerKey="your-importer-key"
  backendUrl="https://api.yoursite.com"
  onComplete={() => {
    // In backend mode, this is called when the import completes
    // but data is sent to your backend, not passed here
    console.log('Import completed - data sent to backend');
  }}
/>

Backend Mode vs Local Mode

FeatureLocal ModeBackend Mode
Schema sourceschema or columns propFetched from backend API
Data destinationonComplete callbackBackend API endpoint
AI column mapping
Natural language transforms
Requires backend setup

When to Use Backend Mode

Use backend mode when you need:

  • AI-powered column matching (fuzzy matching, synonyms)
  • Natural language data transformations
  • Centralized importer configuration (via admin dashboard)
  • Background processing for large files

Use local mode (default) when you want:

  • Simple, frontend-only imports
  • Full control over data handling
  • No backend dependencies

Column Interface

interface Column {
  id: string;                      // Unique identifier
  label: string;                   // Display name
  type?: 'string' | 'number' | 'email' | 'date' | 'phone' | 'select';
  validators?: Validator[];        // Validation rules
  transformations?: Transformer[]; // Data transformations
  options?: string[];              // For select type
  description?: string;            // Help text
  placeholder?: string;            // Example value
}

Validators

type Validator =
  | { type: 'required'; message?: string }
  | { type: 'unique'; message?: string }
  | { type: 'regex'; pattern: string; message?: string }
  | { type: 'min'; value: number; message?: string }         // For number fields
  | { type: 'max'; value: number; message?: string }         // For number fields
  | { type: 'min_length'; value: number; message?: string }  // For string fields
  | { type: 'max_length'; value: number; message?: string }; // For string fields

Examples

validators: [
  { type: 'required' },
  { type: 'unique', message: 'Must be unique' },
  { type: 'regex', pattern: '^[A-Z]+$', message: 'Uppercase only' },
  { type: 'min', value: 0 },
  { type: 'max', value: 100 },
  { type: 'min_length', value: 3 },
  { type: 'max_length', value: 50 }
]

Transformers

Transformations are applied after validation, before data is returned. They process data silently without showing errors to users.

type Transformer =
  | { type: 'trim' }                    // Remove whitespace from start/end
  | { type: 'uppercase' }                // Convert to UPPERCASE
  | { type: 'lowercase' }                // Convert to lowercase
  | { type: 'capitalize' }               // Capitalize First Letter Of Each Word
  | { type: 'remove_special_chars' }     // Keep only alphanumeric and spaces
  | { type: 'normalize_phone' }          // Format as +1234567890
  | { type: 'normalize_date'; format?: string }  // Parse and format dates
  | { type: 'default'; value: string }   // Replace empty with default value
  | { type: 'replace'; find: string; replace: string }  // Find and replace
  | { type: 'custom'; fn: (value: any) => any }  // Custom transformation

Transformation Details

TypeDescriptionExample InputExample Output
trimRemoves leading/trailing whitespace" hello ""hello"
uppercaseConverts to uppercase"Hello""HELLO"
lowercaseConverts to lowercase"Hello""hello"
capitalizeCapitalizes first letter of each word"john doe""John Doe"
remove_special_charsKeeps only letters, numbers, spaces"hello@123!""hello123"
normalize_phoneFormats as US phone number"555-123-4567""(555) 123-4567"
normalize_dateParses and formats dates"01/15/24""2024-01-15"
defaultReplaces empty values"""N/A"
replaceString replacement"hello world""hello_world"
customCustom functionanyany

Examples

// Chain multiple transformations
transformations: [
  { type: 'trim' },                      // First: remove whitespace
  { type: 'lowercase' },                 // Then: convert to lowercase
  { type: 'default', value: 'unknown' }  // Finally: set default if empty
]

// Phone number normalization
transformations: [
  { type: 'normalize_phone' }  // "555-123-4567" → "(555) 123-4567"
]

// Date formatting
transformations: [
  { type: 'normalize_date', format: 'YYYY-MM-DD' }  // "1/15/24" → "2024-01-15"
]

// Custom transformation
transformations: [
  {
    type: 'custom',
    fn: (value) => {
      // Remove currency symbol and parse as number
      return parseFloat(value.replace(/[$,]/g, ''));
    }
  }
]

// Complex example: SKU formatting
transformations: [
  { type: 'trim' },
  { type: 'remove_special_chars' },
  { type: 'replace', find: ' ', replace: '-' },
  { type: 'uppercase' }
]  // "  prod #123  " → "PROD-123"

Transformation Order

Transformations are applied in the order they are defined:

// Order matters!
column: {
  id: 'email',
  transformations: [
    { type: 'trim' },        // 1st: "  JOHN@EXAMPLE.COM  "
    { type: 'lowercase' }    // 2nd: "john@example.com"
  ]
}

Response Format

interface ImportData {
  rows: Array<{
    index: number;
    values: Record<string, any>;
  }>;
  columns: Array<{
    id: string;
    label: string;
  }>;
  num_rows: number;
  num_columns: number;
  // When includeUnmatchedColumns is true:
  mappedData?: any[];      // Data for defined columns
  unmappedData?: any[];    // Data for extra columns
}

Example Response

{
  "num_rows": 2,
  "num_columns": 2,
  "columns": [
    { "id": "name", "label": "Name" },
    { "id": "email", "label": "Email" }
  ],
  "rows": [
    {
      "index": 0,
      "values": {
        "name": "John Doe",
        "email": "john@example.com"
      }
    },
    {
      "index": 1,
      "values": {
        "name": "Jane Smith",
        "email": "jane@example.com"
      }
    }
  ]
}

Performance Considerations

Large Datasets (1,000+ rows)

ImportCSV automatically enables performance optimizations for large files:

  • Virtual Scrolling: Only renders visible rows (~20) in the DOM
  • Progressive Validation: Validates first 50 rows instantly, rest asynchronously
  • Memory Management: Efficient data structures and cleanup
// No special configuration needed - optimizations are automatic
<CSVImporter
  columns={columns}
  onComplete={handleComplete}
/>

Performance Tips

  1. Use built-in validators over complex regex patterns
  2. Minimize transformations for better performance
  3. Keep validators simple for faster validation
  4. Consider server-side processing for files > 10,000 rows

See the Handling Large Files guide for detailed information.

Component Class Names

Customize component classes with the classNames prop:

classNames={{
  root: 'my-importer',
  modal: 'my-modal',
  header: 'my-header',
  stepper: 'my-stepper',
  content: 'my-content',
  footer: 'my-footer',
  button: 'my-button',
  input: 'my-input',
  table: 'my-table',
  dropzone: 'my-dropzone'
}}

Custom Styles

Override CSS variables:

customStyles={{
  'font-family': 'Inter',
  'font-size': '14px',
  'border-radius': '8px',
  'color-primary': '#3B82F6',
  'color-primary-hover': '#2563EB',
  'color-border': '#E5E7EB',
  'color-text': '#111827',
  'color-background': '#FFFFFF',
  'color-background-modal': '#F9FAFB'
}}

All CSS Variables

--font-family
--font-size
--base-spacing
--border-radius
--color-primary
--color-primary-hover
--color-secondary
--color-secondary-hover
--color-tertiary
--color-tertiary-hover
--color-border
--color-text
--color-text-soft
--color-text-on-primary
--color-text-on-secondary
--color-background
--color-background-modal
--color-input-background
--color-input-background-soft
--color-background-menu-hover
--color-importer-link
--color-progress-bar

CSS Injection

Starting from v0.2.0, CSS is automatically injected when the component loads. No separate CSS import is needed:

// Just import the component - CSS is included!
import { CSVImporter } from '@importcsv/react';

// No need for: import '@importcsv/react/dist/style.css' ❌

The CSS is scoped using the .importcsv class to prevent style conflicts.

TypeScript

import { CSVImporter } from '@importcsv/react';
import type { Column, Validator, Transformer } from '@importcsv/react';

const columns: Column[] = [
  {
    id: 'email',
    label: 'Email',
    type: 'email',
    validators: [{ type: 'required' }] as Validator[]
  }
];

Methods (JavaScript SDK)

const importer = CSVImporter.createCSVImporter(config);

importer.showModal();   // Open modal
importer.closeModal();  // Close modal