How to import CSV files in Saleor

7 min read
Learn how to build a CSV import feature in Saleor. Step-by-step guide for developers integrating spreadsheet uploads in SaaS applications.

How to Import CSV Files into Saleor Using CSVBox

Bulk CSV import remains a core operational feature for storefronts and internal tooling in 2026. This guide shows engineers and SaaS product teams how to add a reliable CSV import flow to Saleor using CSVBox — a validated CSV uploader widget — so non-technical users can upload spreadsheets safely and engineers retain control over validation, mapping, and GraphQL mutations.

Target audience: developers, full‑stack engineers, and technical founders building admin tooling or extending the Saleor dashboard.

At a glance: file → map → validate → submit. Use CSVBox as the UI and validation layer; send only cleaned rows to your backend, then call Saleor GraphQL from a secured server process.


Why Add CSV Import to Saleor?

Common operational needs that justify a CSV importer:

  • Bulk-creating or updating thousands of SKUs
  • Importing customers, price lists, or inventory snapshots from spreadsheets
  • Enabling non-engineers (merchandisers, operations) to manage catalog data
  • Migrating data into Saleor during onboarding or replatforming

Without an uploader and validation layer, teams either expose expensive admin privileges or build brittle scripts. CSVBox reduces manual work by adding a drag-and-drop uploader, header mapping, validation, and a structured payload you can trust before hitting the Saleor API.


What CSVBox Provides (Short)

CSVBox is a front-end widget + SaaS service that helps teams:

  • Present a visual drag/drop uploader and spreadsheet preview
  • Enforce header templates and per-column validation
  • Provide immediate feedback (invalid rows, duplicates)
  • Deliver a normalized row payload to your app via callbacks or webhooks

For Saleor teams, CSVBox functions as the “filter” between raw spreadsheets and your GraphQL layer, reducing bad requests and developer support.


Step-by-Step Saleor Integration (Developer-focused)

You can integrate CSVBox inside the Saleor Dashboard (React), a custom admin UI, or a standalone import tool. This section focuses on the practical developer flow and common pitfalls.

Prerequisites

  • Running Saleor instance (hosted or self‑managed)
  • Ability to edit the React frontend you use for admin actions
  • A secure backend endpoint that can call Saleor’s GraphQL API with an admin token
  • CSVBox account and a configured widget (sign up at csvbox.io)

1. Configure a CSVBox Widget

In the CSVBox dashboard:

  • Create a widget for the data type (Products, Inventory, Customers)
  • Define required headers and data types (e.g., title, sku, price, inventory)
  • Set validation rules, required fields, and duplicate detection
  • Save and copy your client_key and widget_hash for frontend initialization

These settings enforce the schema users must adhere to before any data reaches your backend.


2. Embed the CSVBox Uploader in your Saleor Frontend

Add the CSVBox script to your admin HTML (ensure only authorized admin pages include it):

<script src="https://js.csvbox.io/widget.js"></script>

Example React button that launches the widget and receives validated rows (adjust names to match your widget settings):

<button
  onClick={() => {
    window.CSVBox && window.CSVBox.show({
      client_key: "your_client_key",
      widget_hash: "your_widget_hash",
      user: {
        // optional: attach admin id/email for audit
        user_id: "admin@example.com"
      },
      metadata: {
        source: "saleor-dashboard"
      },
      onUploadDone: function (result) {
        // result typically contains normalized rows (inspect the payload)
        console.log("CSVBox upload result", result);
        // result.row_data is the cleaned rows array — adapt to your mapping
        processCSVRows(result.row_data);
      },
      onUploadError: function (err) {
        console.error("CSVBox upload error", err);
      }
    });
  }}
>
  Import Products via CSV
</button>

Notes:

  • Keep this button inside authenticated admin routes only.
  • Include useful metadata (user id, role) so server-side logs and audit trails are meaningful.
  • Inspect the result object in the browser console to confirm the row shape before sending to your API.

3. Map CSV Rows to Saleor Input Shape

CSVBox returns validated, normalized rows. Convert column names to the Saleor input shape before sending. Keep mapping simple and explicit; avoid sending raw CSV headers as-is.

Example client-side mapping handler:

const processCSVRows = async (rowData) => {
  const products = rowData.map(row => ({
    name: row.title,
    sku: row.sku,
    // Convert strings to correct types and handle empty values
    basePrice: row.price ? parseFloat(row.price) : null,
    quantity: row.inventory ? parseInt(row.inventory, 10) : 0,
  }));

  // POST to your server-side endpoint that holds admin credentials
  const resp = await fetch("/api/bulk-create-products", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ products })
  });

  const body = await resp.json();
  if (!resp.ok) {
    console.error("Server rejected import", body);
    // Show user-friendly error UI
  } else {
    // Show import summary (created, failed rows)
    console.log("Import result", body);
  }
};

Client should only forward cleaned, validated rows and minimal metadata. Do not embed admin credentials or secret tokens in frontend code.


4. Secure Backend: Call Saleor GraphQL from a Server Process

Best practice: your backend receives validated rows and performs GraphQL calls to Saleor using an admin API token stored server-side. Avoid calling Saleor directly from the browser.

Example using graphql-request (Node/Express). This example shows a safe pattern: validate the incoming payload server-side, then create Products by calling Saleor’s createProduct mutation for each row. Adapt the mutation to your Saleor schema and required fields.

const express = require('express');
const { GraphQLClient, gql } = require('graphql-request');
const app = express();
app.use(express.json());

const saleorClient = new GraphQLClient('https://your-saleor-host/graphql/', {
  headers: {
    authorization: 'Bearer YOUR_ADMIN_TOKEN', // keep this secret on the server
  },
});

const CREATE_PRODUCT = gql`
  mutation CreateProduct($input: ProductCreateInput!) {
    productCreate(input: $input) {
      errors {
        field
        message
      }
      product {
        id
        name
        slug
        // adjust return fields as needed
      }
    }
  }
`;

app.post('/api/bulk-create-products', async (req, res) => {
  const products = req.body.products || [];
  if (!Array.isArray(products) || products.length === 0) {
    return res.status(400).json({ errors: ['No products provided'] });
  }

  // Basic server-side validation: ensure required fields exist
  const tasks = products.map(async (p) => {
    const input = {
      name: p.name,
      // map other required fields (slug, productType, etc.) as your Saleor instance requires
      // generate slug if missing, or validate productType id before calling
    };

    try {
      const data = await saleorClient.request(CREATE_PRODUCT, { input });
      return { success: true, result: data.productCreate };
    } catch (err) {
      return { success: false, error: err.message };
    }
  });

  // Run requests in parallel but consider concurrency limits in production
  const results = await Promise.all(tasks);
  // Aggregate results and return a summary to the frontend
  res.status(200).json({ results });
});

Production notes:

  • Customize the mutation and required fields to match your Saleor configuration (product type, attributes, channel listings, etc.).
  • Use concurrency control (p-limit or a job queue) for large imports to avoid rate limits.
  • For large datasets, enqueue a background job (Redis/Sidekiq, Bull, etc.) and return an import id to the UI for status tracking.
  • Store import logs and row-level errors for re-processing and auditability.

Handling Errors, Validation, and Edge Cases

Key defensive practices:

  • Don’t trust client-side validation alone — re-validate required fields server-side.
  • Use CSVBox validation rules to reduce common mistakes (header mismatches, invalid numbers).
  • Provide a clear error report (row index, field, problem) back to users when rows fail.
  • For partial failures, return a summary with counts: created, updated, skipped, failed.
  • Use idempotency keys or deduplication to prevent duplicate product creation when retrying.

Quick Troubleshooting (Common Questions)

  • Upload button doesn’t open:

    • Ensure the CSVBox script is loaded before calling window.CSVBox.show.
    • Confirm the client_key and widget_hash are correct and active.
  • “Invalid headers” or mapping errors:

    • Confirm the CSV uses the headers defined in your CSVBox template and that column names are spelled exactly.
  • GraphQL 400 or schema errors:

    • Check your mapped input matches Saleor’s ProductCreateInput and required fields for your instance.
    • Inspect the server logs and returned GraphQL errors for the failing field.
  • Rows fail silently:

    • Log the full CSVBox result object on the client and the payload your server received. Add row-level error handling and return clear messages to the UI.

Tip: Console.log(result) immediately after onUploadDone to see the exact payload shape before mapping.


Why Teams Use CSVBox with Saleor (SEO-friendly phrasing)

If you’re searching for how to upload CSV files in 2026, or how to add “CSV import validation” and “map spreadsheet columns” into your Saleor workflow, CSVBox helps by:

  • Enforcing header templates and column types up-front
  • Providing a user-friendly spreadsheet preview and mapping UI
  • Delivering a normalized row payload to your server, reducing parsing work
  • Giving you callbacks/webhooks to integrate with background processing

This pattern keeps your Saleor GraphQL API stable and reduces support requests from non-technical users.


Next Steps to Harden a Production Import Flow

  • Secure endpoints with proper auth (admin token, middleware, scopes)
  • Move large imports to background jobs and add progress tracking
  • Persist import records and row-level errors for audit and retry
  • Add observability: metrics, alerts, and usage dashboards
  • Optionally use CSVBox webhooks for server-side processing when you prefer push notifications from the widget service

Helpful links:


Conclusion

Integrating CSVBox with Saleor gives your team a robust import UX while keeping engineering in full control of data that lands in your store. The safe pattern is: validate and normalize on the client/UI (CSVBox) → map and forward cleaned rows to a secure server endpoint → run GraphQL mutations from the server (or background jobs) with admin credentials. This minimizes broken imports and empowers non-technical users to manage catalog and inventory data confidently.

If you want, the next iteration of this guide can include a background-job example (Bull/Redis) for large imports and a sample import status UI in React.

Related Posts