
Catalog Import Export
- 69 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Bulk import and export a product catalog via native platform tools or apps with validation and scheduled sync, for supplier onboarding, mass updates, or product feeds.
About
A skill for bulk catalog import/export using platform-native CSV/JSON/XML tools with validation and scheduled sync. A developer uses it for supplier onboarding, mass price updates, or feeding merchant centers.
- Native import/export first; custom pipeline only when needed
- Validation and scheduled sync for large catalogs
Catalog Import Export by the numbers
- 69 all-time installs (skills.sh)
- Ranked #362 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill catalog-import-exportAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 69 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Bulk import and export a product catalog via native platform tools or apps with validation and scheduled sync, for supplier onboarding, mass updates, or product feeds.
Files
Catalog Import / Export
Overview
Bulk importing and exporting products is a core catalog management task — whether you're onboarding a new store from a supplier spreadsheet, doing mass price updates, or feeding products to Google Merchant Center. Every major platform has native import/export tools that handle this without custom code. Use them first; only build a custom pipeline if your volume, format, or validation requirements exceed what the platform offers.
When to Use This Skill
- When onboarding a new merchant whose catalog lives in a spreadsheet or ERP system
- When syncing product data from a supplier or PIM on a scheduled basis
- When merchants need to do mass price or inventory updates without coding
- When building a product feed export for Google Merchant Center, Amazon, or comparison shopping engines
Core Instructions
Step 1: Determine the platform and choose the right import tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Matrixify (Bulk Product Import Export) | Handles complex catalogs with metafields, variants, multiple images, and collections; supports scheduled sync and error reports |
| Shopify (simple) | Shopify's built-in CSV import | Free, sufficient for basic catalogs under a few hundred products with standard fields only |
| WooCommerce | WP All Import Pro | Most powerful WooCommerce import tool; supports CSV/XML, field mapping, scheduled imports, and conditional logic |
| WooCommerce (built-in) | WooCommerce Product CSV Importer | Ships with WooCommerce; handles products, variations, and images for simple use cases |
| BigCommerce | Built-in bulk import / Feedonomics | BigCommerce's native import handles most needs; Feedonomics for multi-channel feed management |
| Custom / Headless | Build a custom pipeline | Only if your platform has no native tools or your validation/transformation requirements are too complex |
---
Step 2: Prepare your import file
Regardless of platform, product import files follow a similar structure. Use the platform's sample CSV as your template:
- Shopify: Download the sample CSV from Products → Import → Download sample CSV
- WooCommerce: Download from Products → Import → Download sample CSV
- BigCommerce: Download from Products → Import & Export → Download template
Key fields almost every platform requires:
| Field | Notes |
|---|---|
| Handle / Slug | URL-safe unique identifier (e.g., blue-cotton-tshirt) |
| Title | Product name |
| Price | Decimal, e.g., 29.99 |
| SKU | Unique per variant |
| Inventory Quantity | Integer |
| Images | Comma-separated URLs or upload separately |
| Variant options | Size, Color, etc. |
Important formatting rules:
- Save CSV files as UTF-8 encoding (in Excel: Save As → CSV UTF-8)
- Use the exact column headers from the platform's sample template
- Dates in ISO format:
2026-03-12 - Boolean values:
true/false(notyes/noor1/0unless the platform specifies)
---
Step 3: Platform-specific setup
---
Shopify
Option A: Built-in CSV import (simple catalogs)
1. Go to Admin → Products → Import 2. Download the sample CSV to use as a template 3. Prepare your file following Shopify's column format exactly 4. Upload the CSV and click Import products 5. Shopify shows a summary of what will be created/updated; review before confirming
Limitations: No support for metafields, custom collections, or complex variant logic in the built-in importer.
Option B: Matrixify (recommended for complex catalogs)
1. Install Matrixify from the Shopify App Store 2. In Matrixify, click Export to download your current catalog as a reference spreadsheet 3. Use the exported format to prepare your import file — it includes all supported columns with examples 4. Upload your file and click Import 5. Matrixify shows a row-by-row error report — download it and fix errors before re-running 6. For scheduled sync: go to Matrixify → Schedules → Add schedule, set frequency (hourly, daily, etc.), and point to a Google Sheets URL or FTP path
Exporting for Google Merchant Center: 1. In Matrixify, go to Export 2. Choose Google Shopping as the export template 3. Download the feed XML and upload to Google Merchant Center, or use Matrixify's direct Google Sheets sync
---
WooCommerce
Option A: Built-in product importer
1. Go to WooCommerce → Products → Import 2. Upload your CSV file 3. On the column mapping screen, match your CSV columns to WooCommerce fields 4. Click Run the importer — WooCommerce shows a progress bar and summary
Option B: WP All Import Pro (recommended for advanced needs)
1. Install WP All Import Pro + the WooCommerce Add-On 2. Go to All Import → New Import 3. Upload your CSV or XML file, or enter a URL for scheduled imports 4. Use the drag-and-drop field mapper to connect your file's columns to WooCommerce product fields 5. Set up Scheduling: All Import → Manage Imports → Edit → Run automatically every X hours 6. Review the import log at All Import → History — errors are listed with row numbers
For Google Merchant Center feed export:
- Install Product Feed Pro for WooCommerce (free)
- Go to Product Feed Pro → Manage Feeds → Add Feed
- Select Google Shopping as the template
- Configure and publish the feed URL directly to Google Merchant Center
---
BigCommerce
Built-in bulk import:
1. Go to Products → Import & Export → Import Products 2. Download the CSV template 3. Prepare your file and upload 4. BigCommerce validates the file and shows a preview before committing 5. For images: host images at accessible URLs and include them in the Product Image URL column
Google Shopping feed:
- Go to Channel Manager → Google Shopping
- BigCommerce has native Google Shopping integration — connect your Google Merchant Center account and BigCommerce syncs the catalog automatically
For advanced multi-channel feeds: Install Feedonomics or GoDataFeed from the BigCommerce App Marketplace for Amazon, eBay, and comparison engine feeds.
---
Custom / Headless
For headless storefronts, build a pipeline that validates, transforms, and upserts products:
// lib/catalogImport.ts
import { z } from 'zod';
import { parse } from 'csv-parse';
import { createReadStream } from 'fs';
// Define and validate the import schema
const productRowSchema = z.object({
handle: z.string().min(1).regex(/^[a-z0-9-]+$/, 'Handle must be lowercase alphanumeric with hyphens'),
title: z.string().min(1).max(255),
price: z.coerce.number().positive(),
sku: z.string().min(1),
inventory_quantity: z.coerce.number().int().min(0).default(0),
image_url: z.string().url().optional(),
});
// Stream-parse large CSV files without loading into memory
export async function* parseCatalogCsv(filePath: string) {
const parser = createReadStream(filePath).pipe(
parse({ columns: true, skip_empty_lines: true, trim: true })
);
let rowIndex = 2;
for await (const rawRow of parser) {
const result = productRowSchema.safeParse(rawRow);
yield result.success
? { row: rowIndex, data: result.data, errors: null }
: { row: rowIndex, data: null, errors: result.error.issues.map(i => ({ field: i.path.join('.'), message: i.message })) };
rowIndex++;
}
}
// Process as an async job with upsert logic (idempotent, safe to re-run)
export async function runCatalogImport(filePath: string) {
let processed = 0;
const errors: { row: number; errors: { field: string; message: string }[] }[] = [];
for await (const { row, data, errors: rowErrors } of parseCatalogCsv(filePath)) {
if (rowErrors) { errors.push({ row, errors: rowErrors }); continue; }
// Upsert by handle — re-running the same file won't create duplicates
await db.products.upsert({ where: { handle: data!.handle }, create: data!, update: data! });
processed++;
}
return { processed, errorCount: errors.length, errors: errors.slice(0, 50) };
}For Google Merchant Center XML export:
import { create } from 'xmlbuilder2';
export async function exportGoogleFeed(products: Product[]) {
const root = create({ version: '1.0', encoding: 'UTF-8' })
.ele('rss', { version: '2.0', 'xmlns:g': 'http://base.google.com/ns/1.0' })
.ele('channel');
for (const product of products) {
const item = root.ele('item');
item.ele('g:id').txt(product.sku);
item.ele('g:title').txt(product.title);
item.ele('g:price').txt(`${product.price} USD`);
item.ele('g:availability').txt(product.inStock ? 'in_stock' : 'out_of_stock');
item.ele('g:link').txt(`https://yourstore.com/products/${product.handle}`);
item.ele('g:image_link').txt(product.images[0]);
}
return root.end({ prettyPrint: true });
}---
Step 4: Validate before committing
All platforms support a preview/dry-run step — always use it before committing a large import:
- Shopify built-in: Review the "X products to create / X to update" summary before clicking Import
- Matrixify: The import preview shows which rows have errors with the specific column and issue
- WP All Import: Use "Dry Run" mode to preview what will be created/updated
- BigCommerce: The upload validation step shows errors before processing
Fix all errors listed in the validation step before proceeding. A partial import — where some rows succeed and others fail — is harder to clean up than re-running a fully corrected file.
---
Step 5: Monitor and verify
After importing:
1. Spot-check 5–10 random products in the admin to verify titles, prices, images, and variants loaded correctly 2. Check the error log — Matrixify and WP All Import generate downloadable error reports with row numbers 3. Verify inventory counts — if inventory was included in the import, confirm it matches expectations 4. Test on the storefront — search for and open 2–3 imported products to verify they display correctly
Best Practices
- Always use the platform's sample CSV as your starting template — hand-crafting a CSV from scratch almost always produces header mismatches
- Import in batches of 500–1000 products for large catalogs — easier to debug errors and the platform UI stays responsive
- Keep a backup of the current catalog before a large import — export the existing catalog first so you can restore if something goes wrong
- Don't update inventory via a product import if you have a separate inventory sync running — two systems updating the same field will conflict
- Use handles/slugs as the stable unique key, not titles — product titles change, handles should not
- Schedule recurring imports at off-peak hours — large syncs can slow the admin interface during peak business hours
Common Pitfalls
| Problem | Solution |
|---|---|
| CSV import fails with encoding errors | Ensure the file is saved as UTF-8 (in Excel: File → Save As → CSV UTF-8); special characters in product names are the most common cause |
| Images don't appear after import | Images must be hosted at publicly accessible HTTPS URLs at import time; Shopify and WooCommerce download them during import |
| Variants not created correctly | Ensure your CSV has one row per variant (not one row per product); Shopify's CSV format uses repeated handle rows for multiple variants |
| Import creates duplicate products on re-run | Use a tool that supports upsert by handle/SKU (Matrixify, WP All Import); avoid tools that only support insert |
| Inventory reset to 0 after product import | Keep inventory out of the product import CSV if you manage it separately; most platforms let you skip inventory columns |
| Google feed rejected by Merchant Center | Verify required fields: id, title, description, link, image_link, availability, price, brand, gtin or mpn |
Related Skills
- @variant-matrix
- @product-data-modeling
- @multi-warehouse
- @product-content-enrichment
{
"context": "Tests whether the agent implements the correct async job pattern for catalog imports: accepting the file upload with MIME validation, creating a job and returning 202 immediately without awaiting the job, tracking progress every 100 rows, using upsert for idempotency, and returning capped error lists via the status endpoint.",
"type": "weighted_checklist",
"checklist": [
{
"name": "multer for upload",
"max_score": 7,
"description": "Uses multer middleware for handling the file upload (import from 'multer'), not a manual body parser or busboy"
},
{
"name": "Upload destination path",
"max_score": 6,
"description": "multer is configured with dest: '/tmp/catalog-uploads/' (or equivalent tmp path)"
},
{
"name": "MIME type validation",
"max_score": 9,
"description": "Checks req.file.mimetype against an allowed list containing 'text/csv' and 'application/json', returning 400 if rejected"
},
{
"name": "Returns HTTP 202",
"max_score": 8,
"description": "The import endpoint responds with HTTP status 202 (Accepted), not 200 or 201"
},
{
"name": "Immediate response with jobId",
"max_score": 8,
"description": "Response body includes jobId and status: 'queued' (job is not awaited before responding)"
},
{
"name": "Fire-and-forget job",
"max_score": 9,
"description": "The import job function is called without await (non-blocking), allowing the response to be sent immediately"
},
{
"name": "Job status transitions",
"max_score": 8,
"description": "Job moves through at least: 'queued' → 'processing' → 'completed' (or 'completed_with_errors' or 'failed')"
},
{
"name": "completed_with_errors status",
"max_score": 7,
"description": "Uses the status 'completed_with_errors' (distinct from 'completed') when the import finishes but some rows had errors"
},
{
"name": "Progress update every 100 rows",
"max_score": 9,
"description": "The import loop updates processedRows in the job record every 100 processed rows (not every row, not once at the end)"
},
{
"name": "Upsert for products",
"max_score": 9,
"description": "Uses an upsert operation (not insert) for creating/updating products, keyed on handle + merchantId (or equivalent unique compound key)"
},
{
"name": "Upsert for variants",
"max_score": 8,
"description": "Uses an upsert operation (not insert) for creating/updating product variants, keyed on sku + merchantId"
},
{
"name": "Status endpoint error cap",
"max_score": 9,
"description": "The GET status endpoint returns at most 50 errors (e.g., errorLog.slice(0, 50)), not the full unbounded error list"
},
{
"name": "Status endpoint fields",
"max_score": 3,
"description": "The status response includes jobId, status, processedRows, skippedRows, errors, and completedAt"
}
]
}
Bulk Product Upload Service
Problem Description
SupplyChain Direct, a B2B wholesale supplier, needs to give their merchant partners a way to bulk-upload product catalogs. Their operations team discovered that when merchants try to import thousands of products, the HTTP request times out before the import finishes — leaving merchants with no idea whether their data was accepted. The previous approach blocked on the entire import before responding, causing frustration and support tickets.
The engineering team wants to redesign the import to be non-blocking: the endpoint should accept the file, hand off processing to a background worker, and immediately tell the merchant that the job is queued. Merchants should be able to check back later using a job ID to see how many rows have been processed and whether there were any errors. The system also needs to protect against merchants accidentally uploading the same catalog twice (which has caused duplicate product entries in the past).
Your task is to implement the upload endpoint and the background import job runner as Express route handlers and a job function. You do not need a real database — use an in-memory store (a plain JavaScript object or Map) to simulate the database operations.
Output Specification
Produce a Node.js application with:
1. src/importEndpoints.js — Express route handlers for:
POST /api/catalog/import— accepts a file upload and starts the import jobGET /api/catalog/import/:jobId— returns the current status of a job
2. src/importJob.js — the background job function that processes rows and updates job state
3. demo.js — a runnable script that creates an Express app with the above routes, submits a test import using the provided CSV data (programmatically, without needing an actual HTTP server running), and prints the initial queued response and a final status after processing completes
Run demo.js and capture its output to demo-output.txt.
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/wholesale-catalog.csv =============== handle,title,vendor,product_type,tags,price,compare_at_price,sku,inventory_quantity,option1_name,option1_value,published industrial-shelf-unit,Industrial Shelf Unit,SCD Furniture,Shelving,"industrial,metal,storage",349.00,420.00,SCD-SHF-001,200,Size,Large,true mobile-workbench,Mobile Workbench with Drawers,SCD Furniture,Workbenches,"mobile,workshop,drawers",549.00,650.00,SCD-WRK-002,85,Size,Standard,true heavy-duty-pallet,Heavy Duty Wooden Pallet,SCD Logistics,Pallets,"wooden,heavy-duty,logistics",24.99,,SCD-PLT-003,5000,,,,true warehouse-label-printer,Wireless Label Printer,SCD Tech,Printers,"wireless,labels,barcode",189.00,230.00,SCD-PRT-004,150,,,,true forklift-attachment-hook,Forklift Hook Attachment,SCD Equipment,Attachments,"forklift,hook,lifting",299.00,,SCD-FRK-005,45,Load Capacity,1000kg,true safety-vest-orange,Orange High-Vis Safety Vest,SCD Safety,PPE,"safety,hi-vis,orange",12.99,16.00,SCD-VES-006,1200,Size,Large,true cable-tie-assortment,Cable Tie Assortment Pack,SCD Hardware,Fasteners,"cable-ties,assorted,pack",8.50,,SCD-CBL-007,3000,,,,1 stretch-wrap-roll,Industrial Stretch Wrap Roll,SCD Packaging,Packaging,"stretch-wrap,industrial,rolls",35.00,42.00,SCD-WRP-008,800,,,,true dock-bumper-rubber,Rubber Dock Bumper,SCD Logistics,Dock Equipment,"rubber,dock,bumper",89.00,,SCD-DCK-009,120,,,,true led-work-light,LED Portable Work Light,SCD Lighting,Lighting,"led,portable,work-light",79.99,95.00,SCD-LGT-010,340,,,,true industrial-shelf-unit,Industrial Shelf Unit RESTOCK,SCD Furniture,Shelving,"industrial,metal,storage",329.00,420.00,SCD-SHF-001,250,Size,Large,true safety-vest-orange,Orange High-Vis Safety Vest RESTOCK,SCD Safety,PPE,"safety,hi-vis,orange",11.99,16.00,SCD-VES-006,1500,Size,Large,true
{
"context": "Tests whether the agent implements catalog export correctly: streaming products in batches with cursor pagination, using csv-stringify with the canonical column list and correct HTTP headers, and generating a Google Merchant Center XML feed using xmlbuilder2 with the correct RSS 2.0 structure and g: namespace fields.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Batch size 500",
"max_score": 8,
"description": "Exports products in batches of 500 (not fetching all at once), with a loop that continues until no more products remain"
},
{
"name": "Cursor-based pagination",
"max_score": 8,
"description": "Uses cursor-based pagination (tracking the last product ID) rather than offset-based pagination for the batch fetching loop"
},
{
"name": "csv-stringify used",
"max_score": 8,
"description": "Uses the csv-stringify library (import from 'csv-stringify') to generate CSV output, not manual string concatenation"
},
{
"name": "csv-stringify header:true",
"max_score": 7,
"description": "Passes header: true to csv-stringify so column names are written as the first row"
},
{
"name": "Canonical CSV columns",
"max_score": 8,
"description": "The exported CSV contains all of: handle, title, vendor, product_type, tags, price, compare_at_price, sku, inventory_quantity, option1_name, option1_value, option2_name, option2_value, image_url, published"
},
{
"name": "Content-Type header",
"max_score": 7,
"description": "Sets Content-Type: text/csv on the export HTTP response"
},
{
"name": "Content-Disposition header",
"max_score": 7,
"description": "Sets Content-Disposition: attachment; filename=\"catalog-{timestamp}.csv\" (with a dynamic timestamp, not a static filename)"
},
{
"name": "xmlbuilder2 used",
"max_score": 8,
"description": "Uses the xmlbuilder2 library (import { create } from 'xmlbuilder2') for generating the Google Merchant feed XML"
},
{
"name": "RSS 2.0 root structure",
"max_score": 8,
"description": "The XML feed has an <rss version='2.0'> root element with xmlns:g='http://base.google.com/ns/1.0' namespace attribute"
},
{
"name": "Google feed g: fields",
"max_score": 9,
"description": "Each product item includes g:id, g:title, g:price (with currency suffix e.g. '99.99 USD'), g:availability, g:link, and g:image_link elements"
},
{
"name": "g:availability values",
"max_score": 8,
"description": "g:availability uses 'in_stock' or 'out_of_stock' string values (not boolean or numeric)"
},
{
"name": "Streamed response",
"max_score": 7,
"description": "The CSV export pipes the stringifier directly to the response (res) rather than buffering all output before sending"
},
{
"name": "Tags as comma-separated string",
"max_score": 7,
"description": "When exporting, tags array is joined with commas into a single string value in the CSV"
}
]
}
Product Feed Export System
Problem Description
Trendsetter Marketplace wants to distribute their product catalog to external channels — specifically Google Shopping and a partner retailer that needs a regular CSV export. The catalog contains over 50,000 products and past attempts to export the whole catalog in a single query brought the server to its knees. The team tried using a simple JSON stringify approach first, but generated files were tens of megabytes and caused browser timeouts before the download started.
For the Google Shopping integration, the feed must follow Google's RSS-based product feed specification so it can be directly submitted to Google Merchant Center without conversion. For the partner retailer, a standard CSV export is needed with a specific set of columns they have agreed upon. Both exports need to work efficiently regardless of catalog size.
Your task is to implement the export functionality as a standalone Node.js module (no database required — use the provided in-memory product data). The implementation should demonstrate how it would work in a real HTTP server context.
Output Specification
Build a Node.js module src/exportCatalog.js that exports:
1. exportCatalogCsv(products, res) — streams a CSV export to an HTTP response object 2. exportGoogleFeed(products) — returns a Google Merchant Center XML string
Also produce:
demo.js— demonstrates both export functions using the provided product data. For the CSV export, pipe output to a fileoutputs/catalog-export.csv. For the Google feed, write the XML tooutputs/google-feed.xml.
Run demo.js to produce the output files.
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/products.json =============== [ { "id": "prod_001", "handle": "womens-running-shoe-v2", "title": "Women's CloudStep Running Shoe V2", "vendor": "Trendsetter", "productType": "Footwear", "tags": ["running", "women", "lightweight"], "published": true, "inStock": true, "images": ["https://cdn.trendsetter.io/shoes/cloudstep-v2-main.jpg"], "variants": [ { "sku": "TRD-SHO-001-6", "price": 129.99, "compareAtPrice": 159.99, "inventoryQuantity": 45, "option1Name": "Size", "option1Value": "6", "option2Name": null, "option2Value": null } ] }, { "id": "prod_002", "handle": "mens-casual-chino", "title": "Men's Slim Fit Casual Chino", "vendor": "Trendsetter", "productType": "Bottoms", "tags": ["casual", "chino", "men"], "published": true, "inStock": true, "images": ["https://cdn.trendsetter.io/bottoms/chino-slim-main.jpg"], "variants": [ { "sku": "TRD-CHN-002-32", "price": 79.99, "compareAtPrice": null, "inventoryQuantity": 120, "option1Name": "Size", "option1Value": "32", "option2Name": null, "option2Value": null } ] }, { "id": "prod_003", "handle": "oversized-knit-sweater", "title": "Oversized Cable Knit Sweater", "vendor": "Trendsetter", "productType": "Knitwear", "tags": ["knitwear", "oversized", "winter"], "published": true, "inStock": false, "images": ["https://cdn.trendsetter.io/knitwear/cable-knit-main.jpg"], "variants": [ { "sku": "TRD-KNT-003-M", "price": 119.00, "compareAtPrice": 149.00, "inventoryQuantity": 0, "option1Name": "Size", "option1Value": "M", "option2Name": null, "option2Value": null } ] }, { "id": "prod_004", "handle": "leather-crossbody-bag", "title": "Genuine Leather Crossbody Bag", "vendor": "Trendsetter", "productType": "Accessories", "tags": ["leather", "crossbody", "bag"], "published": true, "inStock": true, "images": ["https://cdn.trendsetter.io/bags/leather-crossbody-main.jpg"], "variants": [ { "sku": "TRD-BAG-004-TAN", "price": 189.00, "compareAtPrice": 220.00, "inventoryQuantity": 30, "option1Name": "Color", "option1Value": "Tan", "option2Name": null, "option2Value": null } ] }, { "id": "prod_005", "handle": "performance-yoga-mat", "title": "Non-Slip Performance Yoga Mat", "vendor": "Trendsetter Active", "productType": "Fitness", "tags": ["yoga", "fitness", "non-slip"], "published": false, "inStock": true, "images": ["https://cdn.trendsetter.io/fitness/yoga-mat-main.jpg"], "variants": [ { "sku": "TRD-YGA-005-STD", "price": 64.99, "compareAtPrice": null, "inventoryQuantity": 75, "option1Name": null, "option1Value": null, "option2Name": null, "option2Value": null } ] } ]
{
"context": "Tests whether the agent implements streaming CSV parsing with zod schema validation, correct field types and constraints, row-level error reporting, and a dry-run preview mode for a catalog import pipeline.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Streaming parser",
"max_score": 10,
"description": "Uses createReadStream piped to csv-parse (or equivalent streaming API) with async iteration rather than loading the whole file into memory (no fs.readFileSync or fs.readFile on the uploaded CSV)"
},
{
"name": "csv-parse options: columns",
"max_score": 8,
"description": "Passes columns: true to csv-parse so the first row is treated as headers"
},
{
"name": "csv-parse options: skip_empty_lines and trim",
"max_score": 7,
"description": "Passes both skip_empty_lines: true and trim: true to csv-parse"
},
{
"name": "Zod schema used",
"max_score": 8,
"description": "Uses the zod library (import from 'zod') to define the product row schema rather than manual validation"
},
{
"name": "Handle regex constraint",
"max_score": 8,
"description": "The handle field is validated with a regex that enforces lowercase alphanumeric and hyphens only (e.g., /^[a-z0-9-]+/)"
},
{
"name": "Published field as enum+transform",
"max_score": 7,
"description": "The published field accepts 'true', 'false', '1', '0' and is transformed to a boolean"
},
{
"name": "Row numbering starts at 2",
"max_score": 7,
"description": "Row error reporting starts at index 2 (not 0 or 1), since row 1 is the header"
},
{
"name": "Structured error output",
"max_score": 8,
"description": "Validation errors include both the field name and a message (not just a generic error string)"
},
{
"name": "Generator/async iterable parser",
"max_score": 7,
"description": "The CSV parser is implemented as an async generator (async function*) that yields results row by row"
},
{
"name": "Dry-run mode exists",
"max_score": 8,
"description": "A dry-run or preview function is implemented that validates without writing to any database or store"
},
{
"name": "Dry-run return shape",
"max_score": 7,
"description": "Dry-run returns validRows count, errorRows count, and an errors array (with at most 100 errors)"
},
{
"name": "inventory_quantity default",
"max_score": 7,
"description": "The inventory_quantity field defaults to 0 when not present in the CSV row"
},
{
"name": "Price coercion",
"max_score": 8,
"description": "The price field is coerced to a number (not kept as string) and validated as positive"
}
]
}
Merchant Catalog Onboarding Tool
Problem Description
A boutique fashion retailer, Marigold & Co., is migrating from a legacy ERP system to a new e-commerce platform. Their product team exports their catalog as a CSV spreadsheet every morning and needs a reliable tool to ingest those files into the platform's product database.
The catalog team has run into issues before: a previous import tool crashed on files larger than a few megabytes, silently skipped rows with bad data, and occasionally created duplicate products when the import was accidentally run twice. The new tool needs to handle real-world messy data — including missing prices, malformed product handles, and inconsistent casing — by clearly identifying which rows have problems and why, rather than failing silently.
The platform's product model uses a handle (URL slug), sku, price, inventory_quantity, published status, and optional variant options. Before merchants commit a large batch, they want to preview exactly how many rows will succeed and what errors exist in the file, without actually writing anything to the database.
Output Specification
Build a Node.js module at src/catalogParser.js (or equivalent path) that:
1. Exports a streaming async generator function parseCatalogCsv(filePath) that processes a CSV file row by row 2. Each yielded value should carry the row number, parsed data (if valid), and any validation errors (if invalid) 3. Exports a dryRunImport(filePath) function that runs validation only and returns a summary
Also produce sample-output.json — run the dry-run function against the provided test CSV and write the result to this file.
Input Files
The following files are provided as inputs. Extract them before beginning.
=============== FILE: inputs/products.csv =============== handle,title,description,vendor,product_type,tags,price,compare_at_price,sku,inventory_quantity,weight_kg,option1_name,option1_value,option2_name,option2_value,image_url,published marigold-wrap-dress,Marigold Wrap Dress,A flowing summer wrap dress,Marigold & Co,Dresses,"summer,wrap,floral",89.99,120.00,MGD-WRP-001,45,0.3,Size,Small,,,,true silk-blouse-cream,Cream Silk Blouse,Lightweight silk blouse for office wear,Marigold & Co,Tops,"silk,office,cream",149.00,,MGD-SLK-002,12,0.2,Size,Medium,,,,1 INVALID HANDLE,Striped Linen Trousers,,Marigold & Co,Trousers,,95.00,,MGD-LIN-003,20,0.5,Size,10,,,,true cargo-pants-khaki,Cargo Pants Khaki,Durable outdoor cargo pants,Marigold & Co,Trousers,"outdoor,cargo",75.00,95.00,MGD-CRG-004,-5,0.7,Size,32,,,,false evening-gown-black,Black Evening Gown,Elegant floor-length gown,Marigold & Co,Dresses,"evening,formal,black",299.00,450.00,MGD-EVN-005,8,1.1,Size,Small,Color,Black,https://cdn.marigold.com/gown-black.jpg,true ,Missing Handle Product,No handle provided,,Tops,,45.00,,MGD-MSS-006,30,,,,,,,true floral-scarf-silk,Floral Silk Scarf,Hand-painted silk scarf,Marigold & Co,Accessories,"floral,silk,scarf",55.00,,MGD-SCF-007,100,0.05,,,,,,0 denim-jacket-indigo,Indigo Denim Jacket,Classic indigo denim jacket,Marigold & Co,Jackets,"denim,indigo,classic",185.00,220.00,MGD-DNM-008,15,0.9,Size,Large,,,,true velvet-blazer,Velvet Blazer,Luxurious velvet evening blazer,Marigold & Co,Blazers,,not-a-number,,MGD-VLV-009,7,0.6,Size,38,,,,true cashmere-turtleneck,Cashmere Turtleneck,Premium cashmere turtleneck sweater,Marigold & Co,Knitwear,"cashmere,winter,luxury",225.00,280.00,MGD-CSH-010,22,0.4,Size,Medium,,,,true
{
"name": "finsi/catalog-import-export",
"version": "0.1.0",
"summary": "Bulk product import/export via CSV, JSON, XML with validation and error handling",
"skills": {
"catalog-import-export": {
"path": "SKILL.md"
}
}
}