Troubleshooting Super Speedy Imports

July 5, 2026

Every Super Speedy Imports failure is one of three things: a single stage errored out, the whole import lost its database connection, or your saved config vanished after a database wipe. Find your case below.


Table of Contents

A stage failed: stage-by-stage failure recipes

When an SSI import fails, the failure happens INSIDE a specific stage. Each stage has its own failure modes — and recognising them quickly turns “the import broke and I don’t know why” into a 5-minute fix.

This is a per-stage troubleshooting reference. Find your failing stage in the table of contents, jump to that section.

Stages

load-csv

This stage reads the CSV into the per-import batch table. It’s the FIRST stage, so most CSV-format issues surface here.

“Import aborted — N row(s) have a blank unique identifier”

The CSV has rows with blank values in the configured unique-identifier column (typically SKU). The import deliberately aborts here rather than creating un-re-importable rows.

Fix: open the CSV, fill in the blank cells (or delete the empty rows), re-run. See Choosing your unique identifier for the design rationale.

“Term slug collision: ‘X’ and ‘Y’ both map to slug ‘Z'”

Two CSV taxonomy values sanitise to the same WordPress slug. Either fix the CSV to use one canonical spelling, or change Additional Options → Duplicate term-slug behaviour to “Merge”. See Handling duplicate term slugs.

“Could not find column ‘X’ in CSV header”

A meta_key in your post_meta config points at a CSV column that doesn’t exist in the header row.

Fix: open the CSV in a spreadsheet, check the header row. Either fix the spelling in the config, or add the missing column to the CSV. Common cause: the CSV got re-exported with different headers and the config wasn’t updated.

Memory exhausted partway through

Symptom: Allowed memory size of NNN bytes exhausted after thousands of rows processed.

Cause: WP’s $wpdb accumulates query logs / cache that don’t get freed inside the row loop. SSI mitigates this by switching off $wpdb for the bulk of load-csv (via ssi_set_use_wpdb(false)) — but a custom template’s mapData override that uses $wpdb directly can leak.

Fix:

  1. Confirm with wp ssi <id> load-csv --verbose — if the per-row memory keeps climbing linearly, something is leaking.
  2. If you have a custom template with a mapData override that calls WP functions, switch to mysqli direct calls (see the developer Creating custom templates guide).
  3. As a temporary workaround, reduce parallel workers (--workers=2) so each worker has more PHP heap headroom.

CSV file not found

Error: CSV file not found. — either the path in wp_ssi_imports.csv_file_name is stale (you moved the file) or the import was created via CLI and the file lives outside wp-content/uploads/super-speedy-imports/.

Fix: re-upload the CSV via the admin UI, OR use wp ssi <id> --file=/absolute/path/to/csv to point at the file directly for this run.

import-taxonomies

Creates new WordPress terms for taxonomy values in the CSV.

Hangs with no progress

At very large scale (10k+ new terms across multiple taxonomies), wp_insert_term can be slow. Each call fires the full WP filter chain.

Worker count can’t help here. As of 2.55.8 import-taxonomies is forced to a single worker (see “Choosing parallel workers per stage”), so raising its Workers setting in Performance Options does nothing. The only levers that affect this stage are the Hierarchy batch flush size (hierarchy_batch_size) knob and DB session tuning (Additional Options → Optimise DB Session settings for slow stages, enable_db_tuning).

Fix:

  1. Check progress with wp db query "SELECT taxonomy, COUNT(*) FROM wp_terms t INNER JOIN wp_term_taxonomy tt ON t.term_id = tt.term_id GROUP BY tt.taxonomy". If counts are climbing, it’s working, just slow.
  2. If the same terms keep appearing run-after-run (the import isn’t matching the existing terms), check Handling duplicate term slugs — a slug-collision policy mismatch can cause terms to be re-created on every run.

“WordPress database error: Duplicate entry”

Two processes tried to create the same term. SSI’s own import-taxonomies stage recovers from this within the stage (since 2.55.8): on a duplicate-key error it re-resolves the term via term_exists() rather than aborting, so this no longer kills an SSI import. If you’re still seeing it surface as a fatal, it’s almost certainly a custom add-on doing taxonomy writes inside a parallel stage of its own — SSI forces import-taxonomies itself to a single worker, so the race can’t originate there. Check add_action('ssi_register_stages', ...) callbacks for any taxonomy-mutating code added at parallel-stage priority.

match-existing

Finds existing WordPress posts that match CSV rows by unique identifier.

Zero matches when you expected many

Posts Matched: 0 despite re-importing data that should match existing products.

Causes in order of likelihood:

  1. You changed the unique identifier between runs. First import used SKU → every post got _ssi_unique_item_id = <sku>. Second import is configured with a different identifier → no match. See Choosing your unique identifier.
  2. The unique identifier values in the CSV have trailing whitespace that didn’t get cleaned. "ABC-123 ""ABC-123". Open the CSV in a text editor and check.
  3. The _ssi_unique_item_id postmeta got deleted by a plugin or manual SQL. Check with wp db query "SELECT COUNT(*) FROM wp_postmeta WHERE meta_key = '_ssi_unique_item_id'".
  4. The matching column is empty for most rows. match-existing only matches when both sides have non-empty UIDs.

Diagnostic SQL:

-- Sample 10 CSV rows that should have matched
SELECT id, _ssi_unique_item_id, post_id
FROM wp_super_speedy_imports_batch_<id>
WHERE post_id IS NULL
LIMIT 10;

Cross-reference the unique IDs against wp_postmeta to see if the corresponding posts genuinely don’t have that key:

SELECT post_id FROM wp_postmeta
WHERE meta_key = '_ssi_unique_item_id' AND meta_value = '<one of the IDs above>';

Way more matches than expected

Post Names Reconciled: 555,000 on an import you expected to mostly INSERT.

Usually means the unique identifier is too loose — e.g. you mapped a Category column as the UID and many products share the same category. Pick a column whose values are unique across all rows.

update-posts / insert-posts

Writes to wp_posts.

“Duplicate Post Names Fixed: “

Not an error — informational. Indicates many CSV rows had the same post_name (the URL slug). SSI dedups by appending -2, -3. If you’re seeing this every run, your post-title generation is unstable across runs and you may want to map post_name explicitly in the config so it’s predictable.

Some posts ended up with post_status = 'draft'

By default SSI sets post_status = 'publish'. If you’re seeing drafts, either:

  1. Your CSV has a Status or post_status column being mapped — check the mapping and the column values.
  2. A plugin filter is intercepting wp_insert_post and overriding the status. Check wp_filter['wp_insert_post_data'] callbacks.

“Could not get WooCommerce product with ID:” during save-posts

Fixed in 2.52 — save-posts now skips rows where post_id is NULL/0 (which happens when an import-mode preset skips insert-posts). If you see this on pre-2.52, upgrade or run save-posts manually only after re-establishing post_ids for every batch row.

update-postmeta / insert-postmeta

Bulk writes to wp_postmeta.

“Got a packet bigger than ‘max_allowed_packet’ bytes”

A single bulk INSERT/UPDATE statement exceeded MySQL’s max packet size. Default is 16MB; SSI builds large multi-row statements that can blow past this.

Fix:

SET GLOBAL max_allowed_packet = 268435456;   -- 256MB

And persist in my.cnf. See MySQL stopped mid-import for the full fix.

“Duplicate entry for key ‘PRIMARY'” in wp_postmeta

Shouldn’t happen — wp_postmeta doesn’t have a UNIQUE constraint on (post_id, meta_key). If you see this, it’s likely a custom add-on that ADDED such a constraint. Check SHOW CREATE TABLE wp_postmeta.

Postmeta written but stage reports “Postmeta Updated: 0”

Known bug in older SSI versions where the ssi_query wrapper’s return value (affected rows) didn’t propagate. The data DID get written; the count reporting is wrong. Affects benchmark output only, not correctness. Cosmetic only.

upsert-relationships

Writes term relationships (post-to-term assignments).

Stage takes hours on what should be minutes

Three causes, in order of frequency:

  1. Missing covering indexes on the hierarchies aux table. Should be added automatically by 2.47+. Verify: SHOW INDEX FROM wp_super_speedy_imports_hierarchies_<id>; You should see hier_source_idx listed. If not, your load-csv finalisation didn’t add them — check the version and the load-csv stage’s logs.
  2. Temp tables spilling to disk. The INSERT ... SELECT DISTINCT materialises a temp table; at default tmp_table_size=16M, this spills to disk for any taxonomy with 10k+ relationships. Fix: turn on Additional Options → Optimise DB Session settings for slow stages, which bumps tmp_table_size to 512M for this single connection during this single stage. See Database tuning for the per-host-size recipes.
  3. Per-taxonomy issue. Since 2.49, the stage prints per-taxonomy progress with timing: product_brand (hierarchical) ... deleted: 0, created: 850,000 [45.23s] / product_status (flat) ... deleted: 0, created: 850,000 [3.45s]. If one taxonomy is taking 90% of total time, that’s the one to optimise — usually an index gap on the source column.

“Cannot add or update a child row: a foreign key constraint fails”

wp_term_relationships has no FK by default, but a plugin or custom build might have added one to enforce referential integrity. Check SHOW CREATE TABLE wp_term_relationships. If a FK exists, the issue is usually a missing parent row in wp_term_taxonomy for a term that should have been created in import-taxonomies.

attach-existing-images

For featured images whose URL is already in the media library, sets _thumbnail_id to the existing attachment.

Images Attached: 0 when you expected many

The match is by URL via wp_ssi_image_lookup (SSI’s dedicated URL → attachment-ID lookup table). If your existing attachments aren’t in the lookup table (e.g. they were uploaded outside SSI, or you’ve manually edited the media library and want to refresh), this stage finds nothing. That’s expected — upload-remote-images would then have to re-download those URLs.

Workaround: Run the resync tool to (re)populate wp_ssi_image_lookup from your current media library.

wp ssi resync-image-lookup

The tool TRUNCATEs the lookup table and rebuilds it from wp_posts.guid for every attachment, mapping each guid URL → attachment ID. It prints per-row timings so you can sanity-check the speed on large media libraries.

Migration note (2.55): the lookup mechanism switched from wp_postmeta.original_image_url to the dedicated wp_ssi_image_lookup table for performance. The first import after upgrading to 2.55 automatically runs the resync once (it sets a wp_options flag and only does it once). If you’ve manually edited the media library since, run wp ssi resync-image-lookup to refresh.

Now attach-existing-images will find them on the next run.

upload-remote-images

Downloads images that aren’t in the media library and attaches them.

Many failed downloads

Symptom: Remote Failed Images: <large count>.

Common causes:

  1. Remote server returns 403 or 404. The URLs in your CSV are wrong, expired, or behind authentication. Test a few: curl -I "<some-image-url-from-the-csv>"
  2. Remote server rate-limited you. Workers were spawning faster than the upstream allowed. Reduce workers for this stage (Performance Options → per-stage override → upload-remote-images = 2 or 1).
  3. SSL certificate validation failure. Some upstream servers have broken certs. Check the SSI error log for SSL certificate problem.
  4. SSRF protection rejecting the URL. Since 2.40, SSI rejects URLs that resolve to private IP ranges (RFC1918). If you’re hosting images on a local network internally, you’ll need to disable the SSRF check (a security trade-off).

Image uploaded but wrong filename

Symptom: WP assigns a generic name like image.jpg instead of the actual filename from the URL.

Cause: the URL doesn’t have a filename in its path (e.g. https://example.com/image.php?id=12345). WordPress falls back to the Content-Disposition header — if that’s not set, it generates a random name.

Workaround: if you control the upstream, set the Content-Disposition header. If not, post-process the attachments via a custom hook.

“Got an HTTP error while attempting to download an image: HTTP/1.1 503 Service Unavailable”

Upstream overwhelmed. Reduce workers, add retry logic via a custom add-on, or schedule the image stage for a different window.

Same patterns as upload-remote-images. Additional gotcha:

Same image appears in featured + gallery → uploaded twice

Fixed in 2.44 — load-csv now normalises batch.featured_image to keep only the first URL when a multi-URL list is present, so featured + gallery references to the same image dedup properly. If you see double-uploads, you’re on a pre-2.44 SSI; upgrade.

Featured image also showing in the gallery

As of 2.55.7 attach-gallery-images no longer strips the featured image out of the gallery — if your CSV lists the same URL in both the featured and gallery columns, it now stays in the gallery (the old “Images Removed from Gallery” metric was retired). This is intentional: SSI honours exactly what the CSV says. If you don’t want the featured image to appear in the gallery, remove its URL from the gallery column in the CSV.

fix-attributes

Rewrites WooCommerce attribute postmeta and lookup tables.

Stage takes very long with many “(setint)” or similar errors

Means a programming-level bug (typo’d cast, etc.). Check error_log for PHP warnings. The 2.47 release had a (setint) typo (should have been (int)) — long-since fixed; mentioning here for context if you’re on an old version.

Attributes appear correct in the admin but the storefront doesn’t filter by them

WC has TWO data sources for attribute filtering: postmeta (per-product) and the attribute lookup table (wp_wc_product_attributes_lookup). fix-attributes rewrites both, but a stale lookup table can persist if the stage was skipped. Run the WC tool manually:

wp wc tool run regenerate_product_attributes_lookup_table --user=<admin-user>

process-deletes

Deletes posts that were imported by a previous run but are missing from the current CSV. Only runs if Additional Options → Delete missing items is on, OR if --delete-all is passed.

Deleted too much

If you expected to delete 10 missing items but it deleted 10,000, the most likely cause is your CSV being truncated or filtered before the import. SSI deletes anything tagged with this import’s id that ISN’T in the current CSV — if the CSV is incomplete, lots of valid products get classified as “missing”.

Pre-flight check: count CSV rows before running:

wc -l data.csv

Compare to the expected count. If too low, FIX THE CSV before re-running.

“Force delete (skip trash)” — can I undo?

No. --delete-all and force_delete = true permanently delete posts via wp_delete_post($id, true). No recovery without a database backup. If you ran this by mistake and have a recent WP backup or WP Reset snapshot, restore from there.


The whole import crashed: MySQL stopped mid-import

You’re in the middle of an import. Suddenly:

--- Stage: update-postmeta ---
Error: Error establishing a database connection.
PHP Fatal error: Uncaught TypeError: mysqli_get_server_info(): Argument #1 ($mysql) must be of type mysqli, null given

The second error is downstream noise. The first one is the real problem: your import lost its database connection. This section covers the four most likely causes and how to identify each.

Step 1 — was the connection drop one-off or did MySQL itself die?

sudo systemctl status mariadb       # or mysql, depending on your distro

If MySQL is “active (running)” but with a recent restart timestamp, MySQL was deliberately stopped and restarted mid-import. Most likely causes:

  • Hosting watchdog killed it (GridPane, cPanel, Plesk, RunCloud, etc. — see below).
  • Unattended security upgrade restarted the package mid-run.
  • OOM killer stopped it because of memory pressure (rare but happens).

If MySQL is “failed”, it crashed. Most likely causes:

  • Out-of-memory crash (less common than OOM-killer-stops).
  • Disk full (the import generated more data than the disk could hold; common with bulk image uploads).
  • MySQL bug / assertion failure (rare).

Step 2 — check the MySQL error log

This is where the definitive answer lives. Location varies by distro:

# Common locations
sudo tail -200 /var/log/mysql/error.log
sudo tail -200 /var/lib/mysql/$(hostname).err
sudo journalctl -u mariadb --since "1 hour ago" | tail -100

Look for the timestamp matching your import failure. Common patterns:

Log message Cause
Normal shutdown (initiated by: unknown) Someone or something sent SIGTERM. Could be systemctl stop, hosting watchdog, or apt upgrading the mariadb package. Check journalctl for Stopping MariaDB.
Aborted connection ... (Got an error reading communication packets) max_allowed_packet was exceeded. A single INSERT statement was bigger than the configured limit. Default 16M; bump to 128M-256M.
Got error 28 from storage engine Disk full. No room left for InnoDB to write. df -h will confirm.
[ERROR] InnoDB: Cannot allocate memory for the buffer pool Tried to start with a innodb_buffer_pool_size bigger than available RAM. Check your my.cnf.
Assertion failure in ... Real MySQL bug. Check the version (mariadbd --version) and search the changelog for the assertion message; upgrade if there’s a fix.

If you see NO MySQL log entries for your import’s timeframe, MySQL was killed externally (kill -9, OOM-killer, hosting platform). Check dmesg:

sudo dmesg -T | grep -iE 'oom|killed process' | tail -10

The OOM killer logs the process it shot. If you see Killed process <PID> (mariadbd), that’s your answer.

Cause 1 — hosting watchdog (GridPane and friends)

GridPane ships a MySQL memory monitor at /usr/local/bin/gpworker that runs every 15 minutes. Hardcoded thresholds:

  • MEM_HIGH warning: MySQL using ≥ 1757MB for 20 minutes
  • MEM_RESTART: MySQL using ≥ 1953MB for 40 minutes → systemctl restart mariadb

These thresholds are tuned for vanilla WordPress hosts where MySQL shouldn’t grow large. They’re catastrophic for import servers where you’ve configured a 5GB buffer pool. The journal entry looks like:

May 18 13:11:19 ... root[2853929]: ***** GridPane Monitoring - SYS_MEM_USAGE 99% RAM Warning *****
May 18 13:11:20 ... systemd[1]: Stopping MariaDB 10.11.16 database server...
May 18 13:11:28 ... root[2854251]: ***** GridPane Monitoring - MYSQL MEM_RESTART Error *****
                                   MYSQL has been using at least 1953MB RAM for at least the last 40 minutes... Restarting MYSQL!

Three options:

  1. Disable the monitor via GridPane’s UI / CLI (preferred).
  2. Raise the threshold in /usr/local/bin/gpworker (gets overwritten by GridPane’s nightly update — fragile).
  3. Pause the cron entry that runs gpworker for the duration of your import: sudo crontab -l | sed 's|^.*gpworker.*$|# &|' | sudo crontab - then run the import, then sudo crontab -e to uncomment the gpworker lines when done.

cPanel, Plesk, RunCloud have analogous “MySQL high memory” toggles in their control panels. Check before launching a big import.

Cause 2 — max_allowed_packet exceeded

Look for Got an error reading communication packets in the MySQL error log. The default max_allowed_packet = 16MB is too small for bulk INSERT statements during big imports — a single statement carrying 100k rows of postmeta easily exceeds 16MB.

wp db query "SHOW GLOBAL VARIABLES LIKE 'max_allowed_packet';"

Bump it:

SET GLOBAL max_allowed_packet = 268435456;   -- 256MB, applies to new connections immediately

And persist in my.cnf:

[mysqld]
max_allowed_packet = 256M

256M is generous. 128M is fine for most cases.

Cause 3 — Unattended upgrades

On Ubuntu/Debian, unattended-upgrades can auto-install MySQL/MariaDB package updates and restart the service mid-import.

sudo grep -iE "mariadb|mysql" /var/log/apt/history.log* 2>/dev/null | tail -10
sudo grep -iE "Allowed origins" /var/log/unattended-upgrades/unattended-upgrades.log 2>/dev/null | tail -3

If MariaDB or MySQL appears in the “Allowed origins” line, the service is fair game for auto-restart. Mitigations:

1. Pause unattended-upgrades around the import:

sudo systemctl mask unattended-upgrades.service apt-daily-upgrade.timer
# ... run import ...
sudo systemctl unmask unattended-upgrades.service apt-daily-upgrade.timer

2. Permanently blacklist DB packages from unattended upgrade. Edit /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Package-Blacklist {
    "mariadb-server";
    "mariadb-server-core";
    "mariadb-client";
    "libmariadb3";
    "libmysqlclient21";
};

You’ll still get security update notifications via apt list --upgradable; you just install them manually in a maintenance window.

3. Disable needrestart auto-restart. needrestart triggers service restarts after library upgrades. Edit /etc/needrestart/needrestart.conf:

$nrconf{restart} = 'l';   # list only, never auto-restart

Cause 4 — Out of memory

If MySQL itself crashed (status “failed” + nothing useful in the error log), check the kernel log:

sudo dmesg -T | grep -iE 'oom|killed process'

If you see Out of memory: Kill process ... (mariadbd), the kernel killed MariaDB to keep the system alive. Three things contribute:

  • innodb_buffer_pool_size too big for the box. If you set it to 5G on a 4GB-RAM host, MySQL tries to allocate 5G — fails — gets killed. Confirm with free -h BEFORE the import: total RAM minus running daemons should comfortably exceed the buffer pool.
  • enable_db_tuning adds 600MB during upsert-relationships on top of the buffer pool. If you’re already close to the RAM limit, the session bump pushes you over.
  • Too many SSI parallel workers — each worker is a ~250MB PHP process. 8 workers + a 5GB buffer pool + the OS + nginx + PHP-FPM easily blows past 8GB.

Solutions in order of impact:

  1. Reduce innodb_buffer_pool_size to ~50% of RAM (less on a shared box where PHP also lives).
  2. Reduce SSI parallel workers (Performance Options → 2-4 on smaller boxes).
  3. Turn off enable_db_tuning if RAM is tight.

See Database tuning for the full sized recipes (4GB / 8GB / 16GB host configs).

What to do AFTER the failure

Don’t re-run the whole import. SSI’s stage system lets you resume from where it died:

-- Find the most recent import history row
SELECT id, stage_name, start_time, end_time, duration
FROM wp_ssi_import_history_stages
WHERE history_id = (SELECT MAX(id) FROM wp_ssi_import_history WHERE import_id = <YOUR-IMPORT-ID>)
ORDER BY stage_number;

The last row with a start_time but no end_time is the stage that died. Resume from there:

wp ssi <your-import-id> update-postmeta,insert-postmeta,upsert-relationships,...

The per-import staging tables (wp_super_speedy_imports_batch_<id> etc.) survive the failure — load-csv already filled them. The remaining stages can pick up against the existing batch table.


Config reset after WP Reset or a database wipe

If you’ve run WP Reset (or wp db reset, or any similar wipe tool) on a site that has SSI imports configured, you may see weird behaviour next time you open the SSI admin page — empty Import Types dropdown, partially-populated imports with missing values, or fields that have reset to default.

This section explains what happens, why, and how to recover.

What survives a WP Reset and what doesn’t

SSI stores import configuration across two custom tables:

Table Holds Survives WP Reset?
wp_ssi_imports Import name, CSV filename, base template, post mappings JSON, taxonomy mappings Yes — protected by a foreign-key constraint from the meta table
wp_ssi_imports_meta additional_options JSON blob (unique_item_id, term_slug_collision_policy, enable_db_tuning, import_mode, etc.), functions (custom PHP) No — has no incoming foreign keys, so it’s dropped first by WP Reset’s loop

The history tables (wp_ssi_import_history, wp_ssi_import_history_stages) and the per-import staging tables (wp_super_speedy_imports_batch_<id> etc.) also get dropped.

Why the asymmetry

WP Reset’s main “Reset Site” action drops every table starting with the WP prefix, alphabetically. The loop doesn’t disable foreign-key checks (the explicit “Drop custom tables” Tool does, but the main reset doesn’t). Tables with incoming FKs survive; the rest don’t.

wp_ssi_imports_meta has a FK pointing AT wp_ssi_imports — that protects the PARENT table from being dropped, but doesn’t protect the meta table itself. So:

  • wp_ssi_imports survives (FK from meta blocks the DROP)
  • wp_ssi_imports_meta gets dropped (no incoming FK)

Result: your saved imports still appear in the dropdown, but loading one shows empty additional_options fields because the meta data is gone.

What you’ll see

Two symptom clusters depending on whether you’re on SSI 2.52+ or earlier:

Symptom — “Import Types” dropdown is empty

Open the SSI admin page, click Create New Import, and the Import Type dropdown is empty (no Posts, no WooCommerce Products, no custom templates).

This was a code bug from the 2.5x template-class rename, fixed in 2.52. If you’re on 2.52+ you shouldn’t see it any more. If you’re on an earlier 2.5x release, upgrade.

Symptom — loaded import has empty additional-options fields

You select an existing import from the dropdown. The mappings look correct (categories mapped, postmeta mapped, etc.). But the Additional Options section shows:

  • Unique Item Identifier: empty
  • Defer Image Downloads: unchecked
  • Duplicate term-slug behaviour: reset to default (“Alert”)
  • Optimise DB Session settings for slow stages: unchecked
  • Import mode: reset to “Full import”

This is the meta-table-wipe in action. The values you saved are gone from the DB.

Recovery — two paths

Path 1 — let SSI self-heal, then reload from JSON (recommended)

Since 2.52, SSI’s maybe_upgrade_db() hook (which runs on every admin_init for one page load after a version bump) calls the activation routine again, which recreates wp_ssi_imports_meta via dbDelta. The table comes back empty but the structure is restored.

If you have a saved JSON config for the import (Settings → Export Config in the SSI admin, or you saved one when you set the import up), reload it:

  1. In the SSI admin, select the import.
  2. Click Load Config.
  3. Paste or upload the JSON.
  4. Click Save Import.

All your additional_options values get rewritten to the meta table from the JSON. Done.

If you didn’t save the JSON before the wipe, set the fields manually based on what they were. The mappings (template_mappings JSON in wp_ssi_imports) survived, so you only need to fix the Additional Options.

Path 2 — verify the table state via SQL

If you’re on a pre-2.52 SSI version OR the self-heal didn’t run yet, the table may genuinely not exist. Check:

wp db query "SHOW TABLES LIKE 'wp_ssi_imports%'"

Expected output:

wp_ssi_imports
wp_ssi_imports_meta
wp_ssi_import_history
wp_ssi_import_history_stages

If wp_ssi_imports_meta is missing from that list, force a re-activation to recreate it:

wp plugin deactivate super-speedy-imports
wp plugin activate super-speedy-imports

This fires the register_activation_hook callback which runs dbDelta for all the SSI tables. Then come back to the admin UI and reload your saved JSON config.

Verifying the saved import is fully restored

After reloading the JSON:

-- Confirm wp_ssi_imports row exists:
wp db query "SELECT id, import_name, base_template, LENGTH(template_mappings) AS mappings_size FROM wp_ssi_imports WHERE id = <YOUR-IMPORT-ID>"

-- Confirm wp_ssi_imports_meta has the additional_options row:
wp db query "SELECT meta_key, LENGTH(meta_value) AS value_size FROM wp_ssi_imports_meta WHERE import_id = <YOUR-IMPORT-ID>"

You should see both queries returning rows. The value_size for additional_options should be a few hundred bytes to a few KB depending on how many options you set.

Preventing this — what you can and can’t do

Can’t easily

Add a FK that protects wp_ssi_imports_meta itself. We considered this (would require another table with a FK pointing INTO the meta table) and decided against it — the self-heal in 2.52 is a cleaner fix that doesn’t add schema complexity, and WP Reset’s destructive Tools (SET foreign_key_checks=0) bypass FK protection anyway.

Can do

  • Save your import config as JSON after every meaningful change. SSI admin → Settings → Export Config. Store the JSON file somewhere outside wp-content/uploads/ (which WP Reset also wipes by default).
  • Use WP Reset’s Snapshot feature before running Reset. A snapshot is a full DB backup taken inside WP Reset; restoring a snapshot brings BOTH tables back. See the existing Wiping WordPress for a fresh import article for the snapshot workflow.
  • Upgrade to SSI 2.52+ — the self-heal handles the recreate-empty-table half of the recovery automatically.

Counts, filters and search are empty after an import

Super Speedy Imports writes with direct SQL and does not fire WordPress save hooks during the import, so anything normally rebuilt on save is left stale: WooCommerce category and attribute counts show 0, the standard “Filter by attribute” widget and plugins like JetSmartFilters return nothing, a category page shows fewer products than the backend count, and search or filter indexes are out of date. Opening one product and clicking Update fixes that single product, which confirms this is the cause.

Rebuild after the import, in order of preference:

  1. Term counts: wp taxonomy list --field=name | xargs wp term recount (or WooCommerce > Status > Tools > Recount Terms, or wp wc tool run recount_terms).
  2. Per-product save hooks (rebuilds other plugins’ indexes, slower): wp ssi <id> save-posts. This fires save_post / $product->save() for every item, the equivalent of editing and saving each product.
  3. WooCommerce attribute lookup table: wp wc palt regenerate (or WooCommerce > Status > Tools). This one can take an hour on a large catalogue.
  4. Super Speedy Search / Filters: wp sss rebuild and wp ssf rebuild.
  5. Flush the object cache afterwards (Redis, LiteSpeed, Breeze and similar serve stale admin and front-end data).

Chain the recount onto a scheduled import: wp ssi 1 && wp taxonomy list --field=name | xargs wp term recount. If a cron environment blocks the xargs pipe, use a loop with your host’s explicit binary paths: for tax in $(wp taxonomy list --field=name); do wp term recount $tax; done.


Setup and environment issues

Collation mismatch: “Illegal mix of collations”

The import aborts with a message like Illegal mix of collations (utf8mb4_unicode_ci,IMPLICIT) and (utf8mb4_unicode_520_ci,IMPLICIT) for operation '='. Your database tables do not all share one collation (mixed collations also make joins much slower). Force SSI’s staging tables to a compatible collation by adding to wp-config.php:

define('SSI_COLLATION', 'DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci');

Some MySQL 8 builds reject utf8mb4_unicode_520_ci and require utf8mb4_unicode_ci even though the two are compatible. The long-term fix is to make all your tables share one collation.

The CSV is not found, or fields do not appear

Two common causes. First, the filename: use plain, hyphenated names with no leading underscore, no spaces and no special characters (soccer-uni.csv, not _soccer or Soccer @ Uni.csv). A leading underscore in particular means the file will not be found. Second, the file must be in wp-content/uploads/super-speedy-imports/. If you placed it there by FTP, refresh the Imports page for it to appear in the dropdown.

“Failed to insert or find term” (delimiter spacing)

In taxonomy, category and attribute fields the hierarchy separator > and the multiple-value separator | must have a space on each side: Cat A > Sub | Cat B > Sub. A single bad delimiter aborts the whole import, because SSI stops rather than import a broken taxonomy. Image URL lists separated by | do not need the surrounding spaces.

The plugin will not update

This is deliberate. Super Speedy Imports ships with an early-access guard so a live site cannot be updated by accident, and it blocks manual updates from wp-admin as well as automatic ones. To allow updates (do this on staging first), add to wp-config.php:

define('SSI_ALLOW_AUTO_UPDATE', true);

Keep automatic updates off on live sites: WordPress and WooCommerce point releases have shipped changes that break imports.

An update did not take effect

If you updated but the old behaviour persists, you may still be running the old code. Every import prints its version at the top, for example “Welcome to Super Speedy Imports version 2.56”. If that number is behind the version you installed, your host is serving cached PHP: flush opcache, and the object cache / Redis, on the server.

Cron import is killed on shared hosting

An import that runs fine from the terminal can be killed under cron with Aborted connection ... Got an error reading communication packets, usually a 300 second max_execution_time or a database resource limit. Run the stages sequentially from a single cron script rather than overlapping several imports and a recount at once, and if there is a hard 300 second cap, run each stage as its own cron step. A 300 second limit on a legitimate low-CPU job is misconfigured hosting; it is worth asking the host to lift it.

Stock: set _stock, and include _backorders

From 1.85 you only need to set _stock; SSI calculates _manage_stock, _stock_status, _backorders and _low_stock_amount for you (you can still override any of them by mapping that postmeta). One gotcha: WooCommerce has no global default for _backorders, so if you set stock levels you must also include _backorders in the import (notify, yes or no) or it resets to the default on every update.

A custom slug or permalink is ignored

Map your slug column to post_name, which is the real slug field. Do not use post_slug (a legacy field). SSI sanitises and de-duplicates the slug for you. For variable products, child slugs are generated internally.

“Undefined array key post_title” (minimum fields)

SSI uses _sku as the unique identifier, so map it for every import. The minimum to run the post-insert stages is _sku plus post_title. If you are only updating postmeta and have no title, do not map post_title, and run only the stages you need (for example load-csv, match-existing, update-postmeta) rather than the full pipeline.

A stage is slow because of other plugins (save-posts / fix-attributes)

The save-posts and fix-attributes stages call update_post_meta and $product->save(), which fire every other active plugin’s save_post hooks, so their speed is set by what those plugins do on save. Disable suspect plugins one at a time to find the culprit, or use the --perf option (since 1.59) to see which function is taking the time: wp ssi <id> save-posts --perf.

Custom fields (ACF / SCF / Houzez) are blank though the data imported

SSI writes raw postmeta correctly, but ACF, SCF and similar store their values under an internal field key and sometimes inside a serialised array, so a plain postmeta write does not always show in their fields. For Houzez there is a dedicated Houzez import add-on that maps the fields. For other builders, map to the exact internal keys the plugin expects.

Image add-ons keep downloading, or do not refresh (Delayed / External Images)

For Delayed Image Import, do not map the image to the Featured Image field (that downloads immediately). Leave Featured empty and map the URLs to the custom fields delayed_image_url and delayed_gallery_image_urls. For External Images, if a re-import does not show new images, set the ei_converted postmeta to 0 and flush the object cache; the conversion runs the next time the product is viewed. Since 1.81, changing external_image_url resets ei_converted automatically.


Related guides

  • Diagnosing slow imports — when stages aren’t failing, just slow.
  • Database tuning — server-side sizing (4GB / 8GB / 16GB host configs) that prevents most crashes.
  • Choosing parallel workers per stage — worker counts sized to your box.
  • Frequently asked questions — cross-cutting one-paragraph answers.
×
1/1