Canonical fields
These are the typed fields the template already understands across the widget, validation rules, dashboard config, and playground.
First name
first_name
Last name
last_name
Phone
phone
Company
company
Status
status
crm-leads
Use with destination tables shaped for prospects, contacts, or sales leads.
Give sales teams a credible lead-import flow without building CSV cleanup, mapping, and validation UX from scratch.
Who this is for
Best for CRM, outbound, and revenue-tool founders who need customer lead lists imported on day one.
Sample import idea
A sales manager uploads prospects from a spreadsheet export, confirms the lead fields, fixes a few bad emails, and finishes onboarding in minutes.
Sample headers
first_name, last_name, email, phone, company, status
These are the typed fields the template already understands across the widget, validation rules, dashboard config, and playground.
First name
first_name
Last name
last_name
Phone
phone
Company
company
Status
status
The same sequence appears in the hosted widget and the public playground.
Upload CSV or Excel
Upload a spreadsheet and start the browser-side flow immediately.
Confirm smart column mapping
Confirm the suggested mapping instead of rebuilding a schema by hand.
Review validation results
See what fails clearly and repair bad values inline.
Fix invalid values inline
Keep real imports safe with server authority and quotas.
Import with a clear summary
End with a simple inserted vs failed summary customers can trust.
After you create a kit, replace the example token with the public embed token from your kit.
'use client'
import { useEffect, useRef } from 'react'
const IMPORTFLOW_APP_ORIGIN = "https://your-importflow-domain.example"
const IMPORTFLOW_TOKEN = "if_pub_xxxxxxxxx"
const IMPORTFLOW_APPEARANCE = {} as const
const IMPORTFLOW_SCRIPT_ID = 'importflow-embed-loader'
type ImportFlowSummary = {
importJobId: string
requestedRows: number
validRows: number
insertedRows: number
failedRows: number
completedAt: string
}
type ImportFlowMount = {
destroy: () => void
}
type ImportFlowGlobal = {
mount: (options: {
token: string
target: HTMLElement
theme?: 'light' | 'dark' | 'system'
primaryColor?: string
surfaceColor?: string
textColor?: string
radius?: 'none' | 'sm' | 'md' | 'lg' | 'xl'
onComplete?: (summary: ImportFlowSummary) => void
onClose?: (event: { reason: string; phase: string }) => void
}) => ImportFlowMount
}
declare global {
interface Window {
ImportFlow?: ImportFlowGlobal
}
}
function clearTarget(target: HTMLElement) {
target.replaceChildren()
}
function loadImportFlowScript() {
if (window.ImportFlow) {
return Promise.resolve()
}
const existing = document.getElementById(IMPORTFLOW_SCRIPT_ID) as HTMLScriptElement | null
if (existing) {
return new Promise<void>((resolve, reject) => {
existing.addEventListener('load', () => resolve(), { once: true })
existing.addEventListener('error', () => reject(new Error('ImportFlow failed to load.')), {
once: true,
})
})
}
return new Promise<void>((resolve, reject) => {
const script = document.createElement('script')
script.id = IMPORTFLOW_SCRIPT_ID
script.src = new URL('/embed/v1/importflow.js', IMPORTFLOW_APP_ORIGIN).toString()
script.async = true
script.addEventListener('load', () => resolve(), { once: true })
script.addEventListener('error', () => reject(new Error('ImportFlow failed to load.')), {
once: true,
})
document.head.appendChild(script)
})
}
export function ImportFlowDropIn() {
const targetRef = useRef<HTMLDivElement | null>(null)
const mountRef = useRef<ImportFlowMount | null>(null)
useEffect(() => {
const target = targetRef.current
if (!target) {
return
}
let cancelled = false
mountRef.current?.destroy()
mountRef.current = null
clearTarget(target)
loadImportFlowScript()
.then(() => {
if (cancelled || !window.ImportFlow) {
return
}
mountRef.current?.destroy()
clearTarget(target)
mountRef.current = window.ImportFlow.mount({
token: IMPORTFLOW_TOKEN,
target,
...IMPORTFLOW_APPEARANCE,
onComplete(summary) {
console.log('ImportFlow complete', summary)
},
onClose(event) {
console.log('ImportFlow closed', event)
},
})
})
.catch((error: unknown) => {
if (!cancelled) {
console.error(error)
}
})
return () => {
cancelled = true
mountRef.current?.destroy()
mountRef.current = null
clearTarget(target)
}
}, [])
return <div ref={targetRef} />
}
The same real hosted loader works without React by mounting into a target element.
<div id="importflow-widget"></div>
<script src="https://your-importflow-domain.example/embed/v1/importflow.js"></script>
<script>
const importflowAppearance = {}
const importflow = window.ImportFlow.mount({
token: 'if_pub_xxxxxxxxx',
target: '#importflow-widget',
...importflowAppearance,
onReady(payload) {
console.log('ImportFlow ready', payload)
},
onComplete(summary) {
console.log('ImportFlow complete', summary)
},
onClose(event) {
console.log('ImportFlow closed', event)
},
})
// Optional cleanup if your app dynamically removes this view:
// importflow.destroy()
</script>
See it before you wire it
The playground uses the same browser-side parsing, mapping, review, and inline correction experience while keeping completion safely local.