Configuration
Transformations Example
Transform data during import with Zod
Basic Transformations
import { CSVImporter } from '@importcsv/react';
import { z } from 'zod';
const contactSchema = z.object({
// Trim and normalize name
name: z.string()
.transform(s => s.trim())
.transform(s => s.replace(/\s+/g, ' ')), // Remove extra spaces
// Lowercase and trim email
email: z.string()
.email()
.transform(s => s.trim().toLowerCase()),
// Normalize phone number
phone: z.string()
.transform(s => s.replace(/[^0-9]/g, '')) // Remove non-digits
.transform(s => s.length === 10 ? `+1${s}` : s) // Add country code
.optional(),
// Uppercase country code
country: z.string()
.transform(s => s.toUpperCase())
.default('US'),
// Parse date string
created_at: z.string()
.transform(s => new Date(s))
.optional()
});
type Contact = z.infer<typeof contactSchema>;
export default function ContactImporter() {
return (
<CSVImporter
schema={contactSchema}
onComplete={(contacts: Contact[]) => {
// All data is transformed and typed
console.log(contacts[0].email); // "john@example.com" (lowercased)
console.log(contacts[0].phone); // "+15550100" (normalized)
}}
/>
);
}Common Transformation Patterns
Currency to Number
const schema = z.object({
price: z.string()
.transform(s => s.replace(/[$,]/g, '')) // Remove $ and commas
.transform(s => parseFloat(s))
});Date Parsing
const schema = z.object({
// ISO date strings
iso_date: z.string().datetime(),
// Custom date format
custom_date: z.string()
.transform(s => {
// Parse "MM/DD/YYYY" format
const [month, day, year] = s.split('/');
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
})
});Enum Mapping
const statusMap = {
'active': 'ACTIVE',
'inactive': 'INACTIVE',
'pending': 'PENDING'
} as const;
const schema = z.object({
status: z.string()
.transform(s => s.toLowerCase())
.transform(s => statusMap[s as keyof typeof statusMap] || 'UNKNOWN')
});Conditional Transformations
const schema = z.object({
email: z.string()
.email()
.transform(s => {
// Add default domain if missing
return s.includes('@') ? s : `${s}@company.com`;
})
});Sample CSV
Name,Email,Phone,Country,Created At
John Doe ,JOHN@EXAMPLE.COM,(555) 010-0100,us,2024-01-15
Jane Smith ,jane@EXAMPLE.COM,555-010-0101,US,2024-02-01After Transformation
[
{
"name": "John Doe",
"email": "john@example.com",
"phone": "+15550100100",
"country": "US",
"created_at": "2024-01-15T00:00:00.000Z"
}
]Built-in Transformers (Column-based)
When using the columns prop instead of Zod schemas, you can use built-in transformers:
const columns = [
{
id: 'email',
label: 'Email',
transformations: [
{ type: 'trim' },
{ type: 'lowercase' }
]
},
{
id: 'name',
label: 'Name',
transformations: [
{ type: 'trim' },
{ type: 'capitalize' }
]
},
{
id: 'phone',
label: 'Phone',
transformations: [
{ type: 'normalize_phone' }
]
}
];
<CSVImporter columns={columns} onComplete={handleComplete} />Available Transformers
| Type | Description | Example Input | Example Output |
|---|---|---|---|
trim | Remove leading/trailing whitespace | " hello " | "hello" |
uppercase | Convert to uppercase | "Hello" | "HELLO" |
lowercase | Convert to lowercase | "Hello" | "hello" |
capitalize | Capitalize first letter of each word | "john doe" | "John Doe" |
remove_special_chars | Keep only letters, numbers, spaces | "hello@123!" | "hello123" |
normalize_phone | Format as phone number | "555-123-4567" | "(555) 123-4567" |
normalize_date | Parse and format dates | "01/15/24" | "2024-01-15" |
default | Replace empty values | "" | "N/A" |
replace | String replacement | "hello world" | "hello_world" |
custom | Custom function | any | any |
Transformation Stages
Transformers support an optional stage property to control when they run:
| Stage | When it runs | Use case |
|---|---|---|
'pre' | Before validation | Clean data before validating |
'post' | After validation (default) | Format validated data |
const columns = [
{
id: 'email',
label: 'Email',
validators: [{ type: 'required' }],
transformations: [
// Trim BEFORE validation so " " isn't considered valid
{ type: 'trim', stage: 'pre' },
// Lowercase AFTER validation
{ type: 'lowercase', stage: 'post' }
]
}
];When to Use Pre-Stage
Use stage: 'pre' when the transformation affects validation:
transformations: [
// Without 'pre', " john@example.com " would fail email validation
{ type: 'trim', stage: 'pre' }
]When to Use Post-Stage (Default)
Use stage: 'post' (or omit stage) for formatting:
transformations: [
// Format phone number after it's validated
{ type: 'normalize_phone', stage: 'post' }
]Custom Transformers
transformations: [
{
type: 'custom',
fn: (value) => {
// Parse currency string to number
return parseFloat(value.replace(/[$,]/g, ''));
}
}
]Zod vs Column Transformations
| Feature | Zod .transform() | Column transformations |
|---|---|---|
| Type inference | ✅ Full TypeScript inference | ❌ Limited |
| Custom logic | ✅ Any JavaScript | ✅ Via custom type |
| Built-in helpers | ❌ None | ✅ trim, uppercase, etc. |
| Stage control | ❌ Always post-parse | ✅ pre or post |
| Recommended for | New projects, strict typing | Simple use cases, migration |
Next Steps
- Validation - Add validation rules
- Schema Guide - All transformation patterns