Add CSV import to a Bolt.new React app
Add a CSV importer to a Bolt.new React application, with a copyable agent prompt, column mapping, editable validation, code and test files.
View as MarkdownWhat you will build
Start from the Bolt project that already contains your application. Identify its framework and backend before adding a package. This guide targets a React web frontend; it does not describe a React Native or Expo integration. The importer belongs in the browser, while any private database credentials belong in the server’s existing configuration.
CSVJetty supports React 18/19 and CSV files up to 20 MB. The free plan permits 10,000 rows per upload; paid permits 20,000. Configure at most 30 destination fields. Callback mode keeps row data out of CSVJetty servers while configuration and import metadata still use the hosted service. It is not a fully offline or self-hosted importer.
Integrate with Bolt’s current database connection
Inspect package.json and the existing data-access module in the Bolt project. Bolt supports different web frameworks and database configurations; a React interface does not tell you whether records are stored in Bolt Database, Supabase, or your own API. Mount ContactPreview on the current contacts page and verify delivery before touching the database connection.
If the app already uses Bolt Database, keep it. Connecting a separate Supabase project changes the database connection; it is not a prerequisite for CSVJetty. If the app already uses Supabase, pass its existing browser client to the SupabaseContactImport example. Otherwise, connect onData to the app’s existing authenticated save function.
Do not send a database password to the React component. For a Vite frontend, public values are bundled through import.meta.env; server credentials belong in the existing backend. The CSVJetty importer key is public configuration and can be passed as a component prop.
Configure CSVJetty first
Create an importer in the CSVJetty dashboard, choose OnData Callback, and copy its public importer key. Enable Require valid rows so customers must fix browser validation errors before delivery. Development Mode still calls your save handler; use test data and a test database. Dashboard preview does not deliver rows.

Paste this prompt into Bolt.new
Replace YOUR_IMPORTER_KEY before using the prompt. Let the agent inspect the existing app and preserve its architecture.
Inspect the existing Bolt project and identify its React web framework, package manager, contacts screen, authentication and database. Install csvjetty with the existing package manager. Add the default CSVJetty component to the contacts screen using importerKey YOUR_IMPORTER_KEY. Configure required name and email fields plus optional company. Use an onData callback, and keep the existing button and page styling. First show the delivered records in a clearly labeled in-memory table. For persistence, reuse the backend already selected by this app; do not create a new Bolt database or switch to Supabase unless requested. Validate and authorize on the trusted save path, enforce a unique account/email key, await saving and throw on failure. Return individual failures as { errors: [{ row: originalRowNumber, message }] }. Do not assume CSVJetty is an Excel parser. Test mapping, corrections and the deployed app.
A complete browser preview
If the project already uses Supabase, keep that integration rather than introducing a parallel database. If it uses another backend, connect onData to its existing authenticated endpoint. The CSVJetty key is public configuration; a database connection string is not. Test the published URL as well as the preview because hosting configuration can differ.
npm install csvjetty
Download all example files (ZIP) →
File: components/ContactPreview.jsx
"use client";
import { useRef, useState } from "react";
import CSVJetty from "csvjetty";
export const contactColumns = [
{ name: "name", label: "Full name", type: "text", required: true,
aliases: ["Full Name", "Contact Name"] },
{ name: "email", label: "Email", type: "email", required: true,
aliases: ["Email Address", "Work Email"] },
{ name: "company", label: "Company", type: "text" },
];
export default function ContactPreview({ importerKey = "YOUR_IMPORTER_KEY" }) {
const records = useRef(new Map());
const [rows, setRows] = useState([]);
const [message, setMessage] = useState("");
return (
<section aria-labelledby="contact-import-title">
<h2 id="contact-import-title">Import contacts</h2>
<p>This example keeps records in memory. Reloading clears them.</p>
<CSVJetty
importerKey={importerKey}
columns={contactColumns}
accentColor="#2563eb"
onData={({ rows: batch, importId, rowNumbers }) => {
batch.forEach((row, index) => {
records.current.set(`${importId}:${rowNumbers[index]}`, row);
});
setRows([...records.current.values()]);
return { errors: [] };
}}
onComplete={({ accepted, rejected }) => {
setMessage(`Review finished: ${accepted} accepted, ${rejected} rejected. Records below are not saved to a database.`);
}}
>
Import contacts
</CSVJetty>
<p role="status">{message}</p>
<div style={{ overflowX: "auto" }}>
<table>
<caption>Prepared contacts in this browser session</caption>
<thead><tr><th scope="col">Name</th><th scope="col">Email</th><th scope="col">Company</th></tr></thead>
<tbody>{rows.map((row, index) => (
<tr key={index}><td>{row.name}</td><td>{row.email}</td><td>{row.company}</td></tr>
))}</tbody>
</table>
</div>
</section>
);
}
Upload, map and correct a real file
Duplicate and invalid contacts sample →
Upload the clean sample and check the resulting field names. Upload the invalid sample and fix the email. Rename a header and map it manually. Close during an unfinished import and check the discard confirmation. If saving is connected, force a save failure, retry, and verify the database instead of relying on the modal’s completion message.



Connect the real save path
The Map in the preview uses importId plus original row number to avoid showing the same delivery twice. It does not deduplicate a second file upload by email, and nothing survives a reload. For persistence, use the database’s unique constraint and account ownership rules. Treat onComplete counts as importer outcomes, not proof that records were newly inserted.
Customer uploads into Supabase →
Watch the CSV upload and correction flow
Troubleshoot a Bolt CSV import
| Symptom | Likely cause | Next step |
|---|---|---|
| Import UI appears but records disappear on refresh | The browser-preview component is still in use | Wire onData to persistence and reload the app to verify saved records. |
| Existing app data disappears after an integration change | The project may now point at a different database | Inspect the database connection; do not recreate tables or switch projects as a CSVJetty fix. |
| Package cannot be resolved in preview | The package was not installed in the web app’s package directory | Install csvjetty with the existing package manager, check the lockfile, and restart the preview. |
| A mobile project cannot render the importer | The target is React Native or Expo rather than a browser app | This example targets React web. Do not assume a native file import integration. |
| Works in preview, fails after publication | Published auth or configuration differs | Inspect the deployed request and verify the same save path with a signed-in production account. |
Resources
Build your importer
Configure the fields your app expects, then connect CSVJetty to your application.
Create an importer →