Bring in CSV data, map columns, preview transformations, correct invalid cells and import only after validation passes.
Bring your data in
Map columns. Correct rows. Preview before importing.
pnpm dlx shadcn@latest add @latheui/import-workbenchPart of Lathe Pro. Set up your project once with your license key: npx latheui init --key <key>. See Lathe Pro.
Import Workbench is part of Lathe Pro
The preview is live and free to try. The source, the install and every assembly come with Pro: $79 once, with lifetime updates.
Depends on
| Prop | Type | Default | Description |
|---|---|---|---|
| fields* (required) | readonly ImportField[] | — | Unique key, label, optional required flag, pure transform and validation callbacks. A validator returns an error string or undefined. |
| onImport* (required) | (records: ImportRecord[], signal: AbortSignal) => void | Promise<void> | — | Persist validated records. Reject to preserve corrections and show retry feedback. Honor signal for cancellation. |
| initialCsv | string | empty string | Initial text in the CSV editor; parsing begins with Preview columns. |
| maxRows | number | 500 | Maximum number of nonblank data rows. |
| maxBytes | number | 1000000 | Byte limit applied to uploaded and pasted UTF-8 data. |
| disabled | boolean | false | Disable editing, file selection and import. |
| className | string | — | Classes for the section. |
Replace the demo data and callbacks with your application. Honor AbortSignal in asynchronous callbacks so cancellation also stops transport work.
"use client"
import * as React from "react"
import { Checkbox } from "@/components/ui/checkbox"
import { ImportWorkbench } from "@/components/import-workbench"
const fields = [
{ key: "name", label: "Name", required: true },
{
key: "email",
label: "Email",
required: true,
transform: (value: string) => value.trim().toLowerCase(),
validate: (value: string) =>
/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(value)
? undefined
: "Enter a valid email.",
},
{ key: "team", label: "Team" },
]
const csv =
'name,email,team\nMaya Chen,maya@example.com,Design\nLeo Park,not-an-email,Engineering\n"River, Sam",sam@example.com,Research'
export default function ImportDemo() {
const [fail, setFail] = React.useState(false)
const [count, setCount] = React.useState(0)
return (
<div className="w-full max-w-2xl space-y-3">
<ImportWorkbench
fields={fields}
initialCsv={csv}
onImport={async (records, signal) => {
await new Promise((resolve) => setTimeout(resolve, 500))
if (signal.aborted) return
if (fail) {
setFail(false)
throw new Error(
"Connection interrupted. Your corrections are still here. Try again."
)
}
setCount(records.length)
}}
/>
<div className="flex flex-wrap items-center justify-between gap-2 text-xs text-muted-foreground">
<Checkbox
label="Fail next import"
checked={fail}
onCheckedChange={(checked) => setFail(checked === true)}
className="text-xs"
/>
<span>{count} rows received by the demo</span>
</div>
</div>
)
}