Svelte CSV importer that runs entirely in the browser

Add CSV, Excel, JSON, and XML import to a Svelte or SvelteKit app with one custom element and a column schema. No compiler configuration, and files never reach Updog's servers.

Example app on GitHub

Free in development. $19 per production domain per month.

The whole integration is one component

The rest is configured with props, and every one of them is typed.

Importer.svelte
<script lang="ts">
import "@updog/data-editor-wc";
import "@updog/data-editor-wc/styles.css";
import type { UpdogEditorElement } from "@updog/data-editor-wc";
const columns = [
{ id: "name", title: "Name" },
{
id: "email",
title: "Email",
validators: [{ type: "email" }, { type: "unique" }],
},
];
let editorEl: UpdogEditorElement | undefined = $state();
$effect(() => {
if (!editorEl) return;
const el = editorEl;
el.configure({
apiKey: "YOUR_KEY",
columns,
primaryKey: "email",
onComplete: (result) => save(result.sources),
});
const onClose = () => el.hide();
el.addEventListener("close", onClose);
return () => el.removeEventListener("close", onClose);
});
function open() {
editorEl?.show();
}
</script>
<button type="button" onclick={open}>Open Importer</button>
<updog-editor bind:this={editorEl}></updog-editor>

Version 0.1.80Published August 2026Web ComponentNo setup required

What your users walk through

Five steps. Someone who has never seen your product finishes them without instructions.

  1. Select the files

    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


  2. Match the columns

    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.

    Imported columns5/6 matched
    Target columns

    name
    First name
    surname
    Last name
    country
    Country
    level
    Level
    email
    Email
    phone
    Select column
  3. Match the values

    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.

    Country5/5 matched
    Show matched

    Level4/5 matched
    Show matched

    associate
    Select value
    junior
    Junior
    middle
    Middle
    senior
    Senior
    staff
    Principal
  4. Choose what makes a row unique

    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.

    Add every row

    Every row becomes a new entry, even if it repeats one you already have.

  5. Review and fix

    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.

    First name
    Last name
    Email
    1
    Dagny
    Taggart
    2
    Rearden
    3
    Francisco
    d'Anconia
    4
    John
    Galt
    5
    Ragnar
    Danneskjöld
    6
    Eddie
    Willers
    7
    James
    Taggart
    8
    Lillian
    Rearden
    9
    Wesley
    Mouch
    10
    Hugh
    Akston
    11
    Quentin
    Daniels
    12
    Midas
    Mulligan

A real spreadsheet, not a preview table

The imported rows land in a full editor, the one everyone is used to.

Undo

Undo and redo

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

Fill handle

Fill handle

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

Whole row and column selection

Select a whole row or column

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

Find and replace across the file

Find and replace

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

Sorting and filtering

Sort and filter

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

Column formulas

Formulas

Trim, split, merge, or change the case of a whole column in one pass.

Files from real customers

Real files are a mess. This is what that looks like.

Rows above the header

A title line, a blank row, and an export timestamp above the real header. The header is found without help.

A delimiter that is not a comma

Semicolons, tabs, and pipes are detected from the file rather than assumed, which is what most European exports produce.

The wrong encoding

Text that arrives as mojibake is decoded before parsing, so names in Cyrillic, Japanese, or Chinese survive the trip.

Dates in three formats at once

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.

Numbers wearing costumes

A currency symbol, a thousands separator, or a comma for the decimal point still becomes a number your app can store.

Empty values in required fields

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.

What to know before you ship

Bundle size, rendering, theming, licences, and where the data goes.

Bundle
About 459 KB, brotli, since the element carries its own copy of React. Behind a dynamic import it arrives when someone opens the importer, not on first load.
Svelte setup
None. Svelte passes unknown tags straight through to the DOM, so the import is the whole setup.
SvelteKit
Import it inside the click handler. A top-level import returns 500 from the dev server with HTMLElement is not defined.
Your data
Never reaches our servers. One licence check leaves the page, carrying your API key.
Theming
Colours, spacing, radii, and fonts are CSS variables. No logo and no badge on any plan. Styling guide
Scale
About 1 million rows at 20 columns, drawn on canvas rather than in the DOM. Engineering post
Languages
Every string is overridable, and right-to-left is first class rather than a mirrored stylesheet.
Accessibility
An ARIA grid with keyboard navigation, focus management, and screen-reader support.

Free to build, paid in production

No row cap, no per-import fee, and no price per seat.

$19/domain/month

Localhost and preview domains cost nothing, with every feature switched on. One extra domain, for staging, is free.

Get an API key

Questions we get asked

Is this a native Svelte component?

No. It ships as a Web Component. @updog/data-editor-wc registers <updog-editor> as a standard custom element, and inside it runs the same React implementation the React package uses. That is why the element behaves identically in Angular, Vue, Svelte, and plain JavaScript, and why the API is one configure() call rather than framework-specific bindings.

What setup does Svelte need?

None. Svelte passes unknown tags straight through to the DOM, so importing the package and rendering <updog-editor> is enough. In SvelteKit, keep the import in client code because the element is browser only.

How do I pass columns and callbacks to the element?

Through configure(). Simple values like api-key, primary-key, variant, and locale can be written as HTML attributes, but columns, loadData, translations, and every callback go through editor.configure(). A callback written as a template binding silently does nothing in Vue and Svelte, and fails the build in Angular.

Does it work in SvelteKit?

Yes, on the client. A top-level import returns 500 from the dev server with ReferenceError: HTMLElement is not defined, because the element registers itself against the browser DOM. The production build survives, so this breaks in development only. Import the package inside the click handler and render the element once it resolves. Setting export const ssr = false in the page also works, but it costs you server rendering for the whole page.

How much does it add to my bundle?

About 459 KB, brotli, since the element carries its own copy of React. An importer opens on a click, so move the import into the click handler and it arrives then, not on first load.

Does it read Excel files as well as CSV?

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.

How long does the integration take?

An afternoon for a working import against a real schema. The minimal version is one component, a column array, and a result handler.

Where are the files processed?

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.

What is the largest file it can handle?

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.

Is there a server-side mode for files larger than that?

No. Everything runs in the browser, and a file that does not fit in memory needs a server-side pipeline instead.

Can it be self-hosted?

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.

Do you have SOC 2 or HIPAA certification?

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.

Can the editor load existing data without an import?

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.

Try for free

Install the package, add your columns, render the component. Free on localhost and preview domains, with every feature included.