Import CSV or Excel with client-side parse and validation.
uploader provides CommonUploaderButton: a client-side dialog workflow to download a template, choose a format, pick a spreadsheet, validate it, and submit parsed records or the raw file to your API.
format → template → file → parse (records mode) → validate → submit → history/redirectThe component owns dialog state, file selection, parsing feedback, and upload feedback. Your application owns persistence, authorization, server-side validation, and import-job status.
When to use it
- Bulk create or update flows driven by a spreadsheet template
- Imports where users must fix row-level errors before submit
- Optional file transport when the backend prefers the raw XLSX/CSV instead of parsed rows
Installation
Registry CLI
Requires a one-time components.json registry entry for @feature-kit/uploader.
pnpm dlx shadcn@latest add https://feature-kits.vercel.app/r/uploader.jsonNamespaced: npx shadcn@latest add @feature-kit/uploader
Manual installation
Monorepo consumers can depend on workspace:* packages instead of the registry CLI.
pnpm add uploader form uiDependencies
// apps/your-app/package.json
"dependencies": {
"uploader": "workspace:*"
"form": "workspace:*"
"ui": "workspace:*"
}Transpile (Next.js)
// next.config.ts
transpilePackages: ["uploader", "form", "ui"]Import
import { CommonUploaderButton, type UploaderConfig } from "@/lib/feature-kit/uploader"The data-table registry item includes export helpers when you need template downloads.
Usage
Column definition
Each UploaderColumn maps a spreadsheet header to a record key:
| Field | Purpose |
|---|---|
key | Property on parsed UploaderRecord |
header | Expected column header text |
requiredIn | "create" / "update" modes that require a value |
description | Shown in template / error copy |
sample | Example cell in generated template |
Modes and transport
defaultMode— selects thecreateorupdatevalidation mode. The current button does not render a mode switcher; render separate configured buttons if users need both modes.transport: "records"(default) — parse the file in the browser, validate required columns and custom rules, then callonUpload.transport: "file"— skip record parsing and callonUploadFilewith the originalFileand selectedformatCode. This is useful when the server owns parsing or large-file processing.
In records mode, upload is disabled when parsing is pending, no records were found, or any parse/validation issue exists. In file mode, upload requires an accepted file, a selected format, and onUploadFile.
Cross-row validation
validateParsedRecords: (records, mode) => {
if (mode === "create" && records.length > 500) {
return [{ row: 1, message: "Max 500 rows per import" }]
}
return []
}Flat validation reports spreadsheet rows using one-based spreadsheet numbering (the first data row is normally row 2). Cross-row validation can return a row-level error without a column. The UI displays up to five issues inline and reports the remainder as truncated.
Templates, formats, and history
downloadUploaderTemplate generates an .xlsx workbook from columns, using each header and optional sample. Pass onDownloadTemplate when templates are generated by the server or depend on the selected format. loadFormatCodes or loadFormatOptions can provide dynamic formats; loadFormatOptions supplies user-facing labels.
After a successful upload, the default behavior navigates to config.historyHref (default /file-processing/import-history). Set redirectOnSuccess={false} for an embedded flow. When buildSuccessRedirectHref is present and a filename is available, its returned URL replaces historyHref, which lets the host filter import history by filename and format.
API reference
UploaderConfig
| Prop | Type | Default | Description |
|---|---|---|---|
| entity | string | — | Stable entity code for APIs and analytics. |
| entityLabel | string | — | Human label for toasts; defaults to entity. |
| title / description | string | — | Dialog copy. |
| fileFormat | string | — | Format code passed to template/download handlers. |
| templateFileName | string | — | Download filename stem. |
| acceptedFileTypes | string | — | Input accept attribute, e.g. .xlsx,.csv. |
| columns | UploaderColumn[] | — | Required for records transport. |
| transport | "records" | "file" | "records" | Parse client-side or upload raw file. |
| defaultMode | "create" | "update" | "create" | Validation mode used for this button. |
| validateParsedRecords | (records, mode) => UploaderParseError[] | — | Cross-row rules after column validation. |
| historyHref | string | "/file-processing/import-history" | History link and fallback success destination. |
| buildSuccessRedirectHref | (ctx) => string | — | Optional redirect after success. |
CommonUploaderButton
| Prop | Type | Default | Description |
|---|---|---|---|
| config | UploaderConfig | — | Required dialog configuration. |
| triggerLabel | string | — | Button label opening the dialog. |
| triggerVariant | "default" | "outline" | "outline" | Trigger button variant. |
| onUpload | ({ mode, records }) => Promise<UploaderSubmitResult> | — | Records transport handler; required for a usable records-mode upload. |
| onUploadFile | ({ file, formatCode }) => Promise<UploaderSubmitResult> | — | File transport handler. |
| onDownloadTemplate | (formatCode) => Promise<void> | — | Custom template download. |
| loadFormatCodes / loadFormatOptions | async loaders | — | Dynamic format dropdown; options take precedence over codes. |
| redirectOnSuccess | boolean | true | Navigate using config redirect builder. |
Gotchas
Column headers must match
Parsed rows use exact header strings from the file. Trim mismatches in templates users download from your app.
Large files
Client parsing runs in the browser—gate file size in onUpload / server validation for production imports.
Server validation still applies
Browser validation improves feedback but is not authoritative. Revalidate records or the raw file on the server before writing data.
Live preview with sample data in the docs. Uploader preview — full column config in apps/web/src/components/examples/uploader/demo-uploader-columns.ts.
Accessibility
The dialog exposes labeled format and file controls, supports drag-and-drop plus the native file picker, and announces parse/upload failures in an alert. Keep triggerLabel, title, and description specific to the entity being imported. Test keyboard access to the dropzone and template/download controls.