Uploader

Uploader

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/redirect

The 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.json

Namespaced: 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 ui

Dependencies

// 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:

FieldPurpose
keyProperty on parsed UploaderRecord
headerExpected column header text
requiredIn"create" / "update" modes that require a value
descriptionShown in template / error copy
sampleExample cell in generated template

Modes and transport

  • defaultMode — selects the create or update validation 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 call onUpload.
  • transport: "file" — skip record parsing and call onUploadFile with the original File and selected formatCode. 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

UploaderConfig
PropTypeDescription
entitystringStable entity code for APIs and analytics.
entityLabelstringHuman label for toasts; defaults to entity.
title / descriptionstringDialog copy.
fileFormatstringFormat code passed to template/download handlers.
templateFileNamestringDownload filename stem.
acceptedFileTypesstringInput accept attribute, e.g. .xlsx,.csv.
columnsUploaderColumn[]Required for records transport.
transport"records" | "file"Parse client-side or upload raw file.
defaultMode"create" | "update"Validation mode used for this button.
validateParsedRecords(records, mode) => UploaderParseError[]Cross-row rules after column validation.
historyHrefstringHistory link and fallback success destination.
buildSuccessRedirectHref(ctx) => stringOptional redirect after success.

CommonUploaderButton

CommonUploaderButton props
PropTypeDescription
configUploaderConfigRequired dialog configuration.
triggerLabelstringButton label opening the dialog.
triggerVariant"default" | "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 / loadFormatOptionsasync loadersDynamic format dropdown; options take precedence over codes.
redirectOnSuccessbooleanNavigate 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.

On this page