One script tag. Any page, any framework.
No bundler, no build step, no package to install. Add the CSVbox script, construct an importer with your license key, and call openModal() from any element — in Svelte, Ember, Rails views, Laravel Blade, or plain server-rendered HTML.
- Under 10 lines to first import
- Validated rows only
- SOC 2 Type II + GDPR
<script src="https://js.csvbox.io/script.js"></script><button data-csvbox disabled onclick="importer.openModal();">
Import
</button>
<script src="https://js.csvbox.io/script.js"></script>
<script>
function callback(result, data) {
if (result) {
console.log("success");
console.log(data.row_success + " rows uploaded");
} else {
console.log("fail");
}
}
let importer = new CSVBoxImporter("YOUR_LICENSE_KEY_HERE", {}, callback);
importer.setUser({
user_id: "default123"
});
</script>Not every app has a bundler, and not every framework has an official SDK. The plain JavaScript integration is the escape hatch that covers both: it is a single script tag and a constructor, so it works anywhere a browser executes a script.
It is also the most complete surface. The React, Angular, and Vue packages are wrappers around this same importer, so every option and every event documented here is available in the framework SDKs too — just spelled as props instead of method calls.
Because there is no build step, this is usually the fastest way to prove the importer out. Paste the snippet into a page, swap in your license key, and you have a working import flow before you have decided where it belongs in your app.
- No npm install, no bundler, no build step — one script tag
- Works in Svelte, Ember, Rails views, Laravel Blade, and static HTML
- The full option and event surface the framework SDKs wrap
- Imperative API — construct once, then openModal() from anything
How it works
- 1Add the script
Drop the js.csvbox.io script tag into your page. Nothing else needs to change in your build, because there is no build.
- 2Construct the importer
Call new CSVBoxImporter(licenseKey, options, callback). The options object is where row limits, language, theme, and custom headers go.
- 3Identify the user
Call importer.setUser({ user_id: "..." }). user_id is mandatory; add any other attributes you want echoed back to your destination.
- 4Open it from any element
Call importer.openModal() from a click handler on whatever element you like. Your callback fires with (result, data) when the import completes.
No build step, no bundler. Drop the script tag in and call openModal() from any element.
Importer methods
| Method | Signature | What it does |
|---|---|---|
CSVBoxImporter | new CSVBoxImporter(licenseKey, options, callback, settings) | Constructs the importer. The callback receives (result, data) when an import finishes. |
setUser | setUser(userObject) | Attaches user attributes to the import. user_id is mandatory. |
setOptions | setOptions(optionsObject) | Updates options after construction — useful when they depend on state you do not have yet. |
openModal | openModal() | Opens the importer. Call it from any click handler. |
listen | listen(eventName, callback) | Subscribes to a lifecycle event: onReady, onLoadStart, onSubmit, onClose, or onImport. |
Options, events, and a custom trigger
<button id="import-btn">Import customers</button>
<script src="https://js.csvbox.io/script.js"></script>
<script>
var importer = new CSVBoxImporter(
"YOUR_LICENSE_KEY_HERE",
{
max_rows: 50000,
language: "en",
allow_invalid: false,
theme: "light",
lazy: true
},
function (result, data) {
if (result) {
console.log(data.row_success + " rows uploaded");
} else {
console.log("import failed");
}
}
);
importer.setUser({
user_id: "default123",
workspace_id: "ws_42"
});
importer.listen("onReady", function () {
document.getElementById("import-btn").disabled = false;
});
importer.listen("onClose", function () {
console.log("user closed the importer");
});
document
.getElementById("import-btn")
.addEventListener("click", function () {
importer.openModal();
});
</script>Any element can be the trigger — there is no CSVbox button to style around. Construct the importer once, then call openModal() from as many handlers as you need.
Commonly used options
| Option | What it controls |
|---|---|
max_rows / min_rows | Row-count bounds enforced before the user can submit. |
max_rows_allow_submit | Whether an over-limit file may still be submitted. |
max_rows_custom_message / min_rows_custom_message | The message shown when a row-count bound is breached. |
language | Interface language for the importer. |
allow_invalid | Whether rows that fail validation may still be submitted. |
request_headers | Extra HTTP headers sent with the import request. |
dynamic_list_request_headers | Headers used when fetching dynamic dropdown values. |
sample_template_url / sample_template_button_text | Link and label for a downloadable sample file. |
target_file_name | Name recorded for the uploaded file. |
upload_file_url / upload_file_worksheet_name | Pre-load a file, and the worksheet within it, instead of asking the user to pick one. |
theme | Colour theme applied to the importer UI. |
default_header_row | Which row to treat as the header by default. |
lazy | Defer loading the importer until it is opened. |
Frameworks without an official SDK
If we do not ship a package for your framework, this is the integration. It is the same importer the SDKs wrap, so nothing is missing.
Add the script to app.html and construct the importer inside onMount so it only runs in the browser.
Load the script in index.html and construct the importer from a component’s didInsertElement hook.
Add the script tag to your layout and construct the importer in the view that needs it — no JavaScript build required.
Paste the snippet into the page. There is nothing else to configure.
Common use cases
Drop the snippet into any page to see the full import flow against your real schema before you commit to where it lives.
Add a modern import experience to an app with no bundler and no front-end framework, without touching its build.
Rails, Laravel, and Django admin screens get the same importer as your main product, with one script tag.
Svelte, Ember, Alpine, htmx, or anything else — if it renders HTML, the snippet works.
JavaScript import questions, answered
Do I need npm or a bundler?
No. The plain JavaScript integration is a script tag and a constructor. Nothing is installed, nothing is compiled, and your existing build — if you have one — does not change.
Is the plain snippet less capable than the SDKs?
No, it is the other way round. The React, Angular, and Vue packages are wrappers around this same importer, so every option and event available here is available through them too.
How do I open the importer from a custom element?
Construct the importer once, then call importer.openModal() from any click handler. There is no CSVbox-rendered button to work around, so the trigger is entirely yours.
Can I change options after construction?
Yes. Call importer.setOptions() with a new options object. That is useful when a value — a row limit or a request header — depends on state you do not have at page load.
Which lifecycle events can I subscribe to?
Use importer.listen() with onReady, onLoadStart, onSubmit, onClose, or onImport to drive your own loading states, analytics, and post-import refreshes.
Which frameworks does this cover?
Any framework without an official SDK — Svelte, Ember, Alpine, htmx — plus server-rendered templates like Rails views and Laravel Blade, and plain static HTML.
JavaScript picks the SDK. Your back end picks the destination.
The SDK you install has nothing to do with where the data lands. Whatever you run server-side — Node, Rails, Django, Laravel, Go, .NET, or no server at all — CSVbox delivers validated rows to your webhook, database, or no-code tool.