Getting Started

Next.js App Router

Complete setup guide for ImportCSV in Next.js 15+ with App Router

Installation

npm install @importcsv/react zod
# Optional: for Excel support
npm install xlsx

Since ImportCSV uses client-side file processing, you need to mark components as client components:

app/import/page.tsx
'use client';

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

// Define your data schema
const contactSchema = z.object({
  name: z.string().min(1, 'Name is required'),
  email: z.string().email('Must be a valid email'),
  phone: z.string().optional(),
  company: z.string()
});

// TypeScript automatically infers the type
type Contact = z.infer<typeof contactSchema>;

export default function ImportPage() {
  const [isOpen, setIsOpen] = useState(false);

  const handleComplete = (contacts: Contact[]) => {
    console.log(`Imported ${contacts.length} contacts`);
    // contacts is fully typed: contacts[0].name, contacts[0].email, etc.
    setIsOpen(false);
  };

  return (
    <div className="p-8">
      <button
        onClick={() => setIsOpen(true)}
        className="px-4 py-2 bg-blue-500 text-white rounded"
      >
        Import Contacts
      </button>

      <CSVImporter
        modalIsOpen={isOpen}
        modalOnCloseTriggered={() => setIsOpen(false)}
        schema={contactSchema}
        onComplete={handleComplete}
      />
    </div>
  );
}

With API Route

Create an API route to handle the imported data:

app/api/contacts/import/route.ts
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db'; // Your database

const contactSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  phone: z.string().optional(),
  company: z.string()
});

export async function POST(request: Request) {
  try {
    const { contacts } = await request.json();

    // Validate all contacts
    const validated = contacts.map((c: unknown) =>
      contactSchema.parse(c)
    );

    // Insert into database
    const result = await db.contact.createMany({
      data: validated,
      skipDuplicates: true
    });

    return NextResponse.json({
      success: true,
      count: result.count
    });
  } catch (error) {
    return NextResponse.json(
      { error: 'Import failed' },
      { status: 400 }
    );
  }
}

Update your component to call the API:

app/import/page.tsx
const handleComplete = async (contacts: Contact[]) => {
  try {
    const response = await fetch('/api/contacts/import', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ contacts })
    });

    if (!response.ok) throw new Error('Import failed');

    const result = await response.json();
    console.log(`Successfully imported ${result.count} contacts`);
    setIsOpen(false);
  } catch (error) {
    console.error('Import error:', error);
  }
};

Server Actions Integration

Process imported data with Next.js Server Actions:

app/import/page.tsx
'use client';

import { CSVImporter } from '@importcsv/react';
import { z } from 'zod';
import { useState } from 'react';
import { importContacts } from './actions';

const contactSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  phone: z.string().optional(),
  company: z.string()
});

export default function ImportPage() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <CSVImporter
      modalIsOpen={isOpen}
      modalOnCloseTriggered={() => setIsOpen(false)}
      schema={contactSchema}
      onComplete={async (data) => {
        await importContacts(data);
        setIsOpen(false);
      }}
    />
  );
}
app/import/actions.ts
'use server';

import { db } from '@/lib/db';

export async function importContacts(contacts: any[]) {
  // Validate and insert into database
  await db.contacts.insertMany(contacts);
  return { success: true };
}

Inline Mode

Display the importer directly in the page without a modal:

app/import/page.tsx
'use client';

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

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

export default function ImportPage() {
  return (
    <div className="max-w-4xl mx-auto p-8">
      <h1 className="text-2xl font-bold mb-4">Import Contacts</h1>

      <CSVImporter
        isModal={false}
        schema={schema}
        onComplete={(data) => {
          console.log('Imported:', data);
        }}
      />
    </div>
  );
}

Theming

Match your app's design with custom theming:

<CSVImporter
  schema={schema}
  onComplete={handleComplete}
  theme="dark"
  primaryColor="#3b82f6"
/>

Available themes:

  • "default" - Clean, modern light theme
  • "dark" - Dark mode
  • "professional" - Enterprise-grade styling
  • Custom CSS variables (see Theming Guide)

Next Steps

Common Issues

Error: "window is not defined"

Make sure you're using 'use client' directive at the top of your file:

'use client';  // ← Required!

import { CSVImporter } from '@importcsv/react';

Excel files not working

Install the xlsx library:

npm install xlsx

Type errors with Zod schema

Ensure you're passing the schema correctly and the onComplete callback has proper types:

const schema = z.object({ name: z.string() });
type Data = z.infer<typeof schema>;

<CSVImporter
  schema={schema}
  onComplete={(data: Data[]) => {
    // data is typed correctly
  }}
/>