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-01

After 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

TypeDescriptionExample InputExample Output
trimRemove leading/trailing whitespace" hello ""hello"
uppercaseConvert to uppercase"Hello""HELLO"
lowercaseConvert to lowercase"Hello""hello"
capitalizeCapitalize first letter of each word"john doe""John Doe"
remove_special_charsKeep only letters, numbers, spaces"hello@123!""hello123"
normalize_phoneFormat as phone number"555-123-4567""(555) 123-4567"
normalize_dateParse and format dates"01/15/24""2024-01-15"
defaultReplace empty values"""N/A"
replaceString replacement"hello world""hello_world"
customCustom functionanyany

Transformation Stages

Transformers support an optional stage property to control when they run:

StageWhen it runsUse case
'pre'Before validationClean 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

FeatureZod .transform()Column transformations
Type inference✅ Full TypeScript inference❌ Limited
Custom logic✅ Any JavaScript✅ Via custom type
Built-in helpers❌ Nonetrim, uppercase, etc.
Stage control❌ Always post-parsepre or post
Recommended forNew projects, strict typingSimple use cases, migration

Next Steps