Try it
A real form, a real mapping
Drop a CSV or spreadsheet — or click Load sample to import a deliberately messy roster. importly will auto-suggest matches; fix any it got wrong, then hit Continue to see what the form would post.
Form payload
Install & usage
Drop it in
One stylesheet, one script, one attribute, one JSON field schema. No build step. Plugin auto-mounts on DOMContentLoaded.
<link rel="stylesheet" href="path/to/importly.css">
<script src="path/to/importly.js"></script>
<form action="/leads/import" method="post" enctype="multipart/form-data">
<input type="file" name="leads"
accept=".csv,.xlsx,.xls"
data-importly
data-fields='[
{"key":"first_name","label":"First name","required":true},
{"key":"last_name","label":"Last name","required":true},
{"key":"email","label":"Email","required":true},
{"key":"phone","label":"Phone"},
{"key":"company","label":"Company"}
]'>
<button>Continue</button>
</form>
The plugin keeps the original <input> in the DOM and syncs its files list to whatever the user drops or picks. A sibling <input type="hidden" name="leads_mapping"> is added automatically and updated on every mapping change, so a normal form post carries both the file and the mapping JSON.
Override the hidden field name with data-mapping-name="my_map" if you need it.
XLSX / XLS support is provided by SheetJS, loaded lazily from a CDN the first time a non-CSV file is dropped. Pure-CSV usage never hits the network.
Field schema
Declare what your backend expects
Pass an array of field objects via data-fields. Only key is required; everything else shapes the UI.
{
"key": "email", // identifier your backend uses — required
"label": "Email", // shown to the user — defaults to key
"required": true, // missing required fields show a warning pill
"type": "email", // optional tag rendered next to the label
"description": "Primary contact" // shown under the label
}
importly's auto-suggest is intentionally simple: normalize both sides (lowercase, strip punctuation, collapse whitespace), match exact, then match by substring. first_name finds "First Name", "FirstName", "FIRST_NAME", and "first name " — that's about it. The user is the source of truth; auto-suggest just saves clicks.
JavaScript API
For when auto-init isn't enough
// Manual init (after injecting new file inputs)
Importly.initAll(scope); // scan a subtree for [data-importly]
Importly.init(inputElement); // mount one input
// Live instance
const inst = Importly.instances.get(inputElement);
inst.getMapping(); // -> { first_name: "First Name", ... }
inst.setMapping({ email: "E-mail Address" });
inst.reset(); // back to the dropzone
// Events bubble from the original input
input.addEventListener('importly:parsed', (e) => {
console.log(e.detail.headers, e.detail.rowCount, e.detail.mapping);
});
input.addEventListener('importly:mapping-changed', (e) => {
console.log(e.detail.mapping);
});
input.addEventListener('importly:reset', () => { ... });
input.addEventListener('importly:error', (e) => alert(e.detail.error.message));