
Undo and redo
Every edit, fill, paste, and delete steps back, for the whole session.
Add CSV, Excel, JSON, and XML import to a Next.js app as a Client Component. Files are parsed, mapped, and validated in the browser, and never reach Updog's servers.
Free in development. $19 per production domain per month.
The rest is configured with props, and every one of them is typed.
"use client";
import { useState } from "react";import { DataEditor, type DataEditorColumn } from "@updog/data-editor";import "@updog/data-editor/styles.css";
type Employee = { name: string; email: string };
const columns: DataEditorColumn[] = [ { id: "name", title: "Name", validators: [{ type: "required" }] }, { id: "email", title: "Email", validators: [{ type: "email" }, { type: "unique" }] },];
export default function ImportEmployees() { const [open, setOpen] = useState(false);
return ( <> <button type="button" onClick={() => setOpen(true)}> Open Importer </button> <DataEditor<Employee> apiKey="YOUR_KEY" open={open} onClose={() => setOpen(false)} columns={columns} primaryKey="email" onComplete={(result) => save(result.sources)} /> </> );}Version 0.1.80Published August 2026React 19App RouterTypeScript types included
Five steps. Someone who has never seen your product finishes them without instructions.
The person drops one file or several at once. An Excel workbook comes apart into one card per sheet, and empty sheets arrive unchecked.
Click to upload or drag and drop
Headers are matched against your schema by fuzzy matching and a synonym list you can extend. First Name, fname, and Contact First all land on firstName, and anything left over waits for the person.
A column with a fixed list of options gets a second pass over the values. Active, active, ACTIVE, and Currently active become one value from your list.
The primary key decides what happens when the same record arrives twice. Rows that already exist are updated in place, which makes a second import of a corrected file safe.
Every row becomes a new entry, even if it repeats one you already have.
The last step opens the full editor with the imported rows in it. Failed rules are marked on the cell with the message you wrote, and the person fixes them before anything reaches you.
The imported rows land in a full editor, the one everyone is used to.

Every edit, fill, paste, and delete steps back, for the whole session.

Drag the corner of a cell to copy the value down the column.

Click the header to take the column, or the row number to take the row.

Search the whole file or one column, then replace every match at once.

Sort by any column, and narrow the file by text, number range, or date range.

Trim, split, merge, or change the case of a whole column in one pass.
Real files are a mess. This is what that looks like.
A title line, a blank row, and an export timestamp above the real header. The header is found without help.
Semicolons, tabs, and pipes are detected from the file rather than assumed, which is what most European exports produce.
Text that arrives as mojibake is decoded before parsing, so names in Cyrillic, Japanese, or Chinese survive the trip.
A column holding 03/04/2024, 2024-04-03, and 3 Apr 2024 is read as dates, with day before month settled once for the whole file.
A currency symbol, a thousands separator, or a comma for the decimal point still becomes a number your app can store.
Those cells are marked and counted, the import carries on, and the person fixes them in the grid before you see the rows.
Old and non-Excel spreadsheets are read as well, including .xls, .xlsb, and .ods files.
Bundle size, rendering, theming, licences, and where the data goes.
createContext is not a function.ssr: false so it is never rendered on the server.No row cap, no per-import fee, and no price per seat.
Localhost and preview domains cost nothing, with every feature switched on. One extra domain, for staging, is free.
Get an API keyYes, as a Client Component. Mark the file with "use client" and keep the column schema in it, because the schema carries validator functions and callbacks that cannot cross the server boundary.
No. The grid draws on canvas and needs the browser DOM, so import it with next/dynamic and ssr: false.
The editor reached a Server Component. Next reports it as Failed to collect page data, and the cause is a React context created outside the browser. Mark the file with "use client" and keep the column schema in it.
Yes, and that is the normal way to load it. It has to come from a Client Component, because Next does not allow ssr: false anywhere else. TypeScript loses your row type through the dynamic wrapper, so type the onComplete handler instead of the component.
About 254 KB, brotli, on top of React. An importer opens on a click, so put it behind a lazy import and it arrives then, not on first load.
Yes. CSV, TSV, Excel, JSON, and XML import, and the Excel reader also handles .xls, .xlsb, and .ods files. An Excel workbook is split into one importable sheet per tab. For PDFs, scans, or other formats, you register your own parser and Updog imports the rows it returns.
An afternoon for a working import against a real schema. The minimal version is one component, a column array, and a result handler.
In the browser. Imported row data never reaches Updog's servers, and Updog has no server-processing mode. The only request that leaves the page is a licence check carrying your API key.
About 1 million rows at roughly 20 columns in the browser, limited by the user's machine memory. That is a practical ceiling rather than a promise, and a wide table with long text reaches it sooner than a narrow one.
No. Everything runs in the browser, and a file that does not fit in memory needs a server-side pipeline instead.
No. The package runs entirely in your users' browsers, so there is nothing to host, and the licence check is the only piece that depends on us.
No. Neither one applies to imported row data, because that data never reaches our servers and we never process it. If your procurement process requires a certificate covering the import vendor regardless, a hosted importer is the honest answer.
Yes. Pass loadData and the grid opens on rows from your backend, with import switched off if you want. Read-only mode is supported as well.
Install the package, add your columns, render the component. Free on localhost and preview domains, with every feature included.