Import Stages
Every Super Speedy Imports run is a pipeline of independent stages. Each stage does one well-defined thing and writes its results to per-import staging tables that the next stage reads. The architecture matters because nearly every advanced feature — running just one stage, parallel workers, the import-mode presets, re-running after a failure — works at the stage boundary.
This page explains what each stage does. Once you know the vocabulary, the rest of the KB makes sense.
Table of Contents
- The big picture
- The stages
- load-csv (priority 10)
- import-taxonomies (priority 20)
- match-existing (priority 30)
- update-posts (priority 40)
- insert-posts (priority 50)
- update-postmeta (priority 60)
- insert-postmeta (priority 70)
- upsert-relationships (priority 80)
- attach-existing-images (priority 90+ on post / product templates)
- upload-remote-images (priority 90+ on post / product templates)
- attach-gallery-images (priority 90+ on product templates)
- fix-attributes (priority 90+, WooCommerce only)
- process-deletes (priority 90+)
- Why the stage system matters
- Per-import staging tables
- What’s next
The big picture
A typical import flows like this:
load-csv → import-taxonomies → match-existing → update-posts / insert-posts
→ update-postmeta / insert-postmeta
→ upsert-relationships
→ image stages
→ fix-attributes (WC only)
→ process-deletes (if enabled)
Each stage runs once per import. The orchestrator (wp ssi <id> or the admin Run button) figures out the order from each stage’s priority (lower = earlier), spawns parallel workers where allowed, and writes per-stage timing + counts to the import history panel.
The stages
load-csv (priority 10)
Reads your CSV file into a per-import batch table (wp_super_speedy_imports_batch_<id>). Each CSV row becomes one row in the batch table with one column per mapped field. Hierarchical taxonomy values get parsed into a separate hierarchies aux table; flat taxonomy values go into a terms aux table.
This is the only stage that reads the CSV. Every later stage works against the staging tables.
Runs in parallel — by default 8 workers each chew through a portion of the CSV.
import-taxonomies (priority 20)
Creates any new WordPress terms your CSV references (categories, tags, attributes, custom taxonomies). Calls wp_insert_term for terms that don’t exist yet; resolves their term_taxonomy_id for everything in the batch.
Runs as a single worker — as of 2.55.8 this stage is hard-forced to 1 worker regardless of the configured count. Parallelising it measured only ~7% faster and risked a duplicate-key race in wp_insert_term; the stage now also recovers from that race via term_exists() instead of aborting, so it stays single-threaded by policy rather than out of necessity.
match-existing (priority 30)
For every row in the batch, looks for an existing WordPress post that matches by your configured unique identifier (typically _sku postmeta). When it finds a match, writes that post’s ID back into batch.post_id so downstream stages know whether to UPDATE or INSERT.
The single most important stage for re-imports: without it, every row would be inserted as a new post on every run.
update-posts (priority 40)
For batch rows where match-existing found an existing post (post_id is populated), this stage UPDATEs wp_posts.post_title, post_content, post_excerpt, etc. with the new CSV values.
Skipped by the Only insert new items import mode preset.
insert-posts (priority 50)
For batch rows where match-existing found nothing (post_id is NULL), this stage INSERTs new rows into wp_posts and writes the new IDs back into batch.post_id.
Skipped by the Only update existing items and Update prices / postmeta only import mode presets.
update-postmeta (priority 60)
For matched rows, UPDATEs wp_postmeta with the values from your post_meta config block (_sku, _regular_price, custom meta keys, etc.).
This is where price/stock changes actually land. Cheap to run in isolation — the Update prices / postmeta only preset uses just this plus load-csv and match-existing.
insert-postmeta (priority 70)
Same idea as update-postmeta but for newly inserted posts. Writes the initial postmeta rows for items that came into existence in insert-posts.
upsert-relationships (priority 80)
For every imported post, writes the right rows into wp_term_relationships so the post is associated with the categories, tags, brands, and attributes the CSV said it should be. Deletes any pre-existing relationships in the same taxonomies first (so a re-import correctly REPLACES the term set rather than appending).
This is usually the longest stage on bigger imports because the JOIN spans the whole batch table × the hierarchies/terms tables. The 2.47 covering indexes and 2.51 dedup fix specifically targeted this stage.
Since 2.55.9 the assign_all_hierarchy_levels option (“Assign every category level”) controls how many relationship rows get written per hierarchical post: when ON, a product is assigned EVERY level of a category path (parent + leaf), not just the leaf term; when OFF it gets only the leaf. New imports default this ON (pre-2.55.9 imports stay leaf-only), so expect the per-post relationship count — and the “Term Relationships” total — to be higher than the old leaf-only behaviour.
attach-existing-images (priority 90+ on post / product templates)
For featured images whose URL is already present in SSI’s image-lookup table (wp_ssi_image_lookup), sets the post’s _thumbnail_id to that attachment. No re-upload.
Runs in parallel — each worker handles a slice of the batch.
Changed in 2.55: the lookup used to read from a
wp_postmetarow withmeta_key = 'original_image_url'. As of 2.55 the canonical store is the dedicatedwp_ssi_image_lookuptable (indexed byurl_hashfor near-constant lookups regardless of URL length). The migration is automatic on first import after upgrade.
upload-remote-images (priority 90+ on post / product templates)
For featured images whose URL is NOT in the lookup table, downloads the remote file, runs it through wp_handle_upload to put it in wp-content/uploads/, creates the attachment post, and sets it as the post’s thumbnail. Also inserts a row into wp_ssi_image_lookup so future imports take the existing-attachment path.
Runs in parallel; each unique URL is uploaded by exactly one worker (the CTE-based owner assignment guarantees no duplicate uploads).
attach-gallery-images (priority 90+ on product templates)
Same as upload-remote-images but for the product’s gallery (the _product_image_gallery postmeta on WooCommerce products).
fix-attributes (priority 90+, WooCommerce only)
Rewrites the _product_attributes postmeta blob and rebuilds WooCommerce’s attribute lookup table for every imported product. Skipped on non-WooCommerce post types.
process-deletes (priority 90+)
If you’ve ticked “Delete missing items” in Additional Options, this stage deletes any post that was imported by a previous run of this same import but is missing from the current CSV. Force-delete or move-to-trash based on the linked option.
Also the ONLY stage that runs when you pass --delete-all on the CLI.
Why the stage system matters
You can rerun a single stage
If upload-remote-images failed halfway through but everything else succeeded:
wp ssi 5 upload-remote-images
Picks up where the previous run left off. The batch table is still populated; the stage just identifies rows that don’t yet have an attached thumbnail and processes those. Saves hours.
You can restrict the pipeline to a subset
The Import mode dropdown in Additional Options (since 2.53) lets you save a preset that only runs the stages you need. See the dedicated KB article for the full list of presets and their stage sets.
Per-stage timing is visible
The import-history panel (and the CLI summary at the end of every run) shows each stage’s wall-clock + CPU time. When an import gets slower, you can see WHICH stage got slower. See “Diagnosing slow imports” in the Server & Performance section.
Parallel vs sequential is explicit
Only four stages run in parallel: load-csv, attach-existing-images, upload-remote-images, fix-attributes. Everything else runs in a single process. This is by design — stages that write to global state (terms, auto-increment IDs, etc.) need coordination, while stages that operate per-row scale linearly with worker count.
Per-import staging tables
Each import has its own set of tables suffixed with the import’s ID — e.g. wp_super_speedy_imports_batch_5, wp_super_speedy_imports_terms_5, wp_super_speedy_imports_hierarchies_5. These persist between runs on purpose: when an import dies mid-stage, the staging tables let you re-run just the failing stage against the same data without re-loading the CSV. They also make post-mortem diagnostics possible (e.g. SELECT COUNT(*) FROM wp_super_speedy_imports_batch_5 WHERE post_id IS NULL tells you how many rows match-existing didn’t find).
load-csv drops and recreates these tables at the start of every full run, so re-importing the same CSV starts from a clean slate automatically.
What’s next
- Quick start — importing products: the practical day-1 walkthrough.
- Stage selection presets: how to control which stages actually run.
- Diagnosing slow imports: how to read the per-stage timing output.
- Stage system reference (developer KB): hooks and filters for extending the pipeline.