Express CSV Logo

Types

Use x.row() to define the schema you pass to useExpressCSV(). Each field controls how uploaded values are parsed, validated, labeled, and typed in your React app.

Basic Usage

import { useExpressCSV, x, type Infer } from "@expresscsv/react";

const userSchema = x.row({
  name: x.string().label("Full Name"),
  email: x.string().email().label("Email Address"),
  age: x.number().label("Age").min(18),
  startDate: x.date().label("Start Date"),
  active: x.boolean().label("Active").optional(),
});

type UserRow = Infer<typeof userSchema>;

export function ImportUsersButton() {
  const { open } = useExpressCSV({
    schema: userSchema,
    publishableKey: "pk_test_...",
    importIdentifier: "user-import",
  });

  return (
    <button
      type="button"
      onClick={() =>
        open({
          onData: async (chunk, next) => {
            const records: UserRow[] = chunk.records;
            await saveImportedUsers(records);
            next();
          },
        })
      }
    >
      Import users
    </button>
  );
}

Each key becomes a column the user maps to. The field type determines how values are parsed and validated before your onData callback or webhook runs.

Field Types

Custom Validation

Use Refine for custom validators with .refine(), .refineBatch(), and suggested fixes.

Dynamic Schemas

You can build the schema conditionally before passing it into useExpressCSV():

function buildSchema(includeVatId: boolean) {
  return x.row({
    companyName: x.string(),
    email: x.string().email(),
    ...(includeVatId ? { vatId: x.string() } : {}),
  });
}

const schema = buildSchema(true);

TypeScript Inference

Use Infer<typeof schema> to extract the validated row type from your schema:

import { x, type Infer } from "@expresscsv/react";

const schema = x.row({
  name: x.string(),
  age: x.number(),
  active: x.boolean().optional(),
});

type Row = Infer<typeof schema>;
// { name: string; age: number; active?: boolean }

When you use that schema with useExpressCSV(), chunk.records is inferred automatically inside onData, so you usually do not need manual type annotations.