Add a CSV importer to JavaScript in 10 minutes
No framework, no bundler, no build step. Load the SDK from a script tag, define fields, and open the importer from a click handler. This works inside a Rails view, a Django template, a WordPress admin page, or a static HTML file.
<script src="https://cdn.jsdelivr.net/npm/dromo-uploader-js/dist/dromo-uploader-js.js"></script>
<button id="import-products" type="button">Import products</button>
<script>
const fields = [
{ label: "SKU", key: "sku", type: "string", validators: [{ validate: "required" }, { validate: "unique" }] },
{ label: "Name", key: "name", type: "string", validators: [{ validate: "required" }] },
{ label: "Price", key: "price", type: "number" },
{
label: "Category",
key: "category",
type: "select",
selectOptions: [
{ label: "Apparel", value: "apparel" },
{ label: "Footwear", value: "footwear" },
{ label: "Accessories", value: "accessories" },
],
},
];
const uploader = new DromoUploader(
"YOUR_LICENSE_KEY",
fields,
{ importIdentifier: "products", developmentMode: true },
{ id: document.body.dataset.userId }
);
uploader.onResults(async (rows, metadata) => {
await fetch("/products/import", {
method: "POST",
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
body: JSON.stringify({ importId: metadata.id, rows }),
});
window.location.reload();
});
document.getElementById("import-products").addEventListener("click", () => uploader.open());
</script>
If you do have a bundler, the npm package exposes the same class as an ES module:
npm install dromo-uploader-js
import DromoUploader from "dromo-uploader-js";
Everything else is identical. The constructor takes a license key, a fields array, a settings object, and a user object; onResults receives the validated rows and import metadata; open() shows the modal.
How it fits the JavaScript model
The SDK is a plain class with no opinion about how the rest of your page is built. It attaches its own modal to the document, so it coexists with jQuery plugins, Alpine directives, Web Components, and server-rendered HTML that never touches a virtual DOM. If your app is a Rails monolith with Turbo, initialize the uploader inside a turbo:load listener and it survives navigation.
Nothing in the flow depends on a router or a state library. Because the uploader is instantiated in your code, you decide when it is created: once at page load, or lazily on the first click if the script should not run until needed.
Vue and Svelte teams use the same package. Wrap the constructor in a composable or a store, call open() from a button, and treat the result the way you would any async event.
JavaScript gotchas Dromo handles for you
Encoding surprises. Excel on Windows exports CSV in a legacy code page, macOS adds a UTF-8 byte order mark, and a hand-rolled FileReader.readAsText call gets one of them wrong. Names with accents become question marks and nobody notices until a customer complains. Dromo handles encodings and lets you set a default for files that give no hints.
Excel is not CSV. The first support ticket after launching a "CSV" importer is a customer attaching an .xlsx. Supporting it yourself means pulling in a second parser and handling multi-sheet workbooks. XLS and XLSX work out of the box, alongside CSV and TSV.
A frozen tab. Parsing a large file synchronously blocks the main thread, and the browser eventually offers the user a "this page is unresponsive" dialog. Dromo processes the file inside the widget without stalling the page, and for files too large to correct in one session, the user can download the annotated workbook, fix it offline, and pick up where they left off.
Header rows that are not on row one. Exports from finance and inventory tools put a title, a date range, and a blank line above the real headers. A naive parser treats "Inventory Report 2026-08" as a column. Header row overrides handle it, and the user can confirm the header row visually during matching.
Delimiters that are not commas. European locales export with semicolons, some ERPs emit pipe-delimited text, and a parser hardcoded to , produces one giant column. Delimiter detection is configurable rather than assumed.
Dromo vs building it with PapaParse
| Build it yourself | Dromo | |
|---|---|---|
| Column mapping | Hand-built select elements and DOM plumbing for each header | Suggestions that match headers to your fields and persist for repeat imports |
| Validation | Regex and type checks written per field in your click handler | Declared once per field; custom validators can hit your API during the import |
| Error correction UI | Alert boxes, or a home-grown editable table | Errors shown inline in a spreadsheet-style step the user edits directly |
| Large files | Streaming callbacks and manual memory discipline | Processed inside the widget with offline fix-and-resume |
| Internationalization | Your own string table for every step | 30+ languages built in |
| Ongoing maintenance | Every new export format is code you own | Configuration, editable by a PM in Schema Studio |
PapaParse is excellent at what it does. If a script needs to read a predictable CSV and nothing more, use it and skip the vendor. Dromo becomes worth it when the file is written by someone outside your company and a human needs to be walked through fixing it. Parsing was never the bottleneck; the correction loop is.
Ship it to your users
White labeling comes with every plan at no extra cost. Set the colors and fonts to match a Bootstrap theme or a decade-old admin panel, edit any string in the flow, and the widget carries no Dromo branding.
The importer ships with translations for more than thirty languages, so a script tag on a page served in German or Japanese produces a German or Japanese import experience without extra files.
When customers refuse to let data leave their browser, Private Mode runs the whole import client-side with nothing retained by Dromo. The same class, the same onResults, one extra setting.

