Express CSV Logo

Quickstart

Follow these steps to set up ExpressCSV with the framework-agnostic SDK and receive validated rows in your backend.

Use this pre-built prompt to get started faster with TypeScript.

  1. Install the SDK

    npm install @expresscsv/sdk
  2. Get your publishable key

    You need a publishable key from the dashboard to connect the importer.

    New accounts start with two environments: Production and Development. For this example, grab the key from your Development environment.

  3. Define your schema and create an importer

    import { CSVImporter, x } from "@expresscsv/sdk";
    
    const productSchema = x.row({
      sku: x
        .string()
        .label("SKU")
        .refine(/^[A-Z]{3}-\d{4}$/, { message: "Format must be ABC-1234" }),
      name: x.string().min(1).label("Product Name"),
      price: x.number().min(0).currency("USD").label("Price"),
      categories: x
        .multiselect([
          { label: "Apparel", value: "apparel" },
          { label: "Electronics", value: "electronics" },
          { label: "Home Goods", value: "home-goods" },
        ])
        .min(1)
        .label("Category"),
    });
    
    const importer = new CSVImporter({
      schema: productSchema,
      publishableKey: "pk_test_...",
      importIdentifier: "product-import",
      title: "Import products",
    });
  4. Open the importer from your app

    document.querySelector("#import-products")?.addEventListener("click", () => {
      importer.open({
        chunkSize: 500,
        webhook: {
          url: "http://localhost:4000/webhooks/products-import",
          headers: {
            Authorization: "Bearer your-api-token",
          },
          metadata: {
            userId: "user-123",
          },
        },
        onComplete: () => {
          console.log("Product import delivered");
        },
        onError: (error) => {
          console.error("Product delivery error", error);
        },
      });
    });
  5. Receive webhook deliveries in your backend

    Here's an example Koa backend. In a real app, keep your schema in a shared or core package and use @expresscsv/schemas on the server so the webhook payload stays in sync with the importer:

    import Koa from "koa";
    import Router from "@koa/router";
    import type { InferWebhookPayload } from "@expresscsv/schemas";
    import { productSchema } from "../shared/product-schema";
    
    const app = new Koa();
    const router = new Router();
    
    router.post("/webhooks/products-import", async (ctx) => {
      const token = ctx.headers.authorization;
    
      if (token !== "Bearer your-api-token") {
        ctx.status = 401;
        ctx.body = { error: "Unauthorized" };
        return;
      }
    
      const payload = ctx.request.body as InferWebhookPayload<typeof productSchema>;
      const { records, metadata, delivery, chunkIndex, totalChunks } = payload;
    
      await db.products.insertMany({
        userId: metadata.userId,
        products: records,
      });
    
      console.log(
        `Processed chunk ${chunkIndex + 1}/${totalChunks} for ${delivery.importIdentifier}`
      );
    
      ctx.body = { ok: true };
    });
    
    app.use(router.routes());
    app.use(router.allowedMethods());
    app.listen(4000);

    Return a 2xx response for each chunk. 5xx and 429 responses are retried automatically, while other 4xx responses are treated as permanent failures.

  6. Optional: process chunks locally instead

    If you do not want webhook delivery, use onData:

    importer.open({
      chunkSize: 500,
      onData: async (chunk, next) => {
        const { records, currentChunkIndex, totalChunks, totalRecords } = chunk;
    
        console.log(
          `Processing chunk ${currentChunkIndex + 1}/${totalChunks} ` +
            `(${records.length} records, ${totalRecords} total)`
        );
    
        await saveImportedProducts(records);
        next();
      },
    });
  7. Optional: disable preloading

    By default, the SDK preloads the widget in a hidden iframe so it appears instantly when open() is called. To show a loading screen instead:

    const importer = new CSVImporter({
      schema,
      publishableKey: "pk_test_...",
      importIdentifier: "user-import",
      preload: false,
    });

What's Next

  • Types - define field types, validation rules, and TypeScript types
  • Styling - theme the widget to match your app
  • Webhooks - deliver records to your backend
  • API Reference - full constructor, open(), and lifecycle reference