API Reference
Complete API documentation for ImportCSV
CSVImporter Props
| Prop | Type | Default | Description |
|---|---|---|---|
schema | z.ZodSchema<T> | - | Zod schema for type-safe validation and inference (recommended) |
columns | Column[] | - | Manual column definitions (use schema instead for better DX) |
onComplete | (data: any) => void | - | Callback when import completes |
isModal | boolean | true | Show as modal vs embedded |
modalIsOpen | boolean | true | Control modal state |
modalOnCloseTriggered | () => void | - | Modal close callback |
modalCloseOnOutsideClick | boolean | false | Close on outside click |
theme | ThemeConfig | string | - | Theme configuration or preset name |
darkMode | boolean | false | Enable dark theme (backward compatibility) |
primaryColor | string | #2563eb | Primary color (hex) |
customStyles | object | string | - | CSS variable overrides |
classNames | object | - | Custom class names for components |
showDownloadTemplateButton | boolean | true | Show template download |
skipHeaderRowSelection | boolean | false | Auto-select first row as header |
waitOnComplete | boolean | false | Wait for async operations on complete |
invalidRowHandling | 'include' | 'exclude' | 'block' | 'block' | How to handle rows with validation errors |
includeUnmatchedColumns | boolean | false | Include CSV columns not defined in schema |
language | string | - | Language for internationalization |
customTranslations | object | - | Custom translation resources |
dynamicColumns | Column[] | - | Customer-specific dynamic columns (output nested under _custom_fields) |
importerKey | string | - | Importer key for backend mode |
backendUrl | string | - | Backend API URL (for backend mode) |
user | object | - | User context for backend mode |
metadata | object | - | Additional metadata for import |
demoData | object | - | Demo data for testing |
Schema Definition (Recommended)
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
| Field | Type | Description |
|---|---|---|
[column_id] | any | Values from predefined columns (top-level) |
_custom_fields | Record<string, unknown> | Values from dynamicColumns prop |
_unmatched | Record<string, unknown> | Values from unmapped CSV columns (when includeUnmatchedColumns is true) |
Columns Metadata
| Field | Type | Description |
|---|---|---|
predefined | Column[] | Columns defined via columns or schema prop |
dynamic | Column[] | Columns from dynamicColumns prop |
unmatched | string[] | 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:
| Mode | Description | Use Case |
|---|---|---|
'block' (default) | Prevents import if ANY row has validation errors | Critical data imports where integrity is paramount |
'exclude' | Filters out invalid rows, imports only valid ones | Clean data imports, removing problematic entries |
'include' | Imports all rows with warnings for invalid ones | Flexible 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 (
columnsprop) → values at top level of each row - Dynamic columns (
dynamicColumnsprop) → values nested under_custom_fields - Unmatched CSV columns → values nested under
_unmatched(whenincludeUnmatchedColumnsis 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:
- Predefined columns (from
columnsprop) - Dynamic columns (from
dynamicColumnsprop)
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:
- Schema is fetched from the backend - No need to pass
columnsorschemaprop - Data is sent directly to your backend API - Not passed to
onComplete - 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
| Feature | Local Mode | Backend Mode |
|---|---|---|
| Schema source | schema or columns prop | Fetched from backend API |
| Data destination | onComplete callback | Backend 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 fieldsExamples
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 transformationTransformation Details
| Type | Description | Example Input | Example Output |
|---|---|---|---|
trim | Removes leading/trailing whitespace | " hello " | "hello" |
uppercase | Converts to uppercase | "Hello" | "HELLO" |
lowercase | Converts to lowercase | "Hello" | "hello" |
capitalize | Capitalizes first letter of each word | "john doe" | "John Doe" |
remove_special_chars | Keeps only letters, numbers, spaces | "hello@123!" | "hello123" |
normalize_phone | Formats as US phone number | "555-123-4567" | "(555) 123-4567" |
normalize_date | Parses and formats dates | "01/15/24" | "2024-01-15" |
default | Replaces empty values | "" | "N/A" |
replace | String replacement | "hello world" | "hello_world" |
custom | Custom function | any | any |
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
- Use built-in validators over complex regex patterns
- Minimize transformations for better performance
- Keep validators simple for faster validation
- 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-barCSS 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