Architecture Overview
Developer/LLM-oriented map of how SSI is put together: the major components, what each does, the notable design decisions, and where the extension points are. For how to build add-ons and templates, see creating-addons and creating-custom-templates; for the stage system in depth see stage-system-reference and how-imports-work-stage-pipeline.
Summary — components at a glance
| Component | File(s) | Purpose |
|---|---|---|
| Main plugin | super-speedy-imports.php (class SuperSpeedyImports) | Admin UI, AJAX/REST handlers, enqueue, DB-version migration, auth toggle. |
| CLI orchestrator | run-import.php | The wp ssi <id> entry. Runs sequential stages in-process and fans parallel stages out across worker subprocesses. |
| Browser runner | super-speedy-imports.php (REST routes under superspeedyimports/v1) | The admin Run button: import/setup, import/run, import/stage-setup and import/finalize (parallel stages), import/complete, with import/stage-status as the recovery probe. |
| Scheduled runner | functions/scheduled-imports.php | Scheduled imports through Action Scheduler (WP-Cron fallback): short re-entrant ticks in one process at a time. |
| Template hierarchy | import-template-abstract.php + templates/ | SuperSpeedyImports_ImportTemplate (abstract) → SuperSpeedyImports_PostTemplate → SuperSpeedyImports_WooCommerceProductTemplate; plus SuperSpeedyImports_CustomPostTypeTemplate. Defines field mapping, stage registration, batch-table schema. |
| Stage pipeline | stages/{core,post,woocommerce,utils}/ | The unit of work. Each stage is a set-based SQL operation over the per-import batch table. |
| DB layer | functions/db.php | superspeedyimports_query / superspeedyimports_get_results / superspeedyimports_get_var / superspeedyimports_insert over a wpdb-or-direct-mysqli switch; SUPERSPEEDYIMPORTS_FORCE_WPDB in wp-config.php forces wpdb. |
| Image lookup | functions/image-lookup.php + wp_ssi_image_lookup table | URL→attachment dedup by url_hash = UNHEX(MD5(url)); replaced original_image_url postmeta in 2.55. |
| Import history / metrics | functions/import-history.php (ImportLogger) | Per-stage timings + counters, aggregated across workers, into wp_ssi_import_history(_stages). |
| Perf options | functions/perf-options.php | Global + per-stage worker counts and batch-size knobs (superspeedyimports_perf_*). |
| Stage plan | functions/class-ssi-auth-manager.php + functions/class-ssi-auth-shim.php | Builds each import’s stage plan (core stage names and priorities) locally from the stages bundled with the plugin; every runner and CLI worker reads it through SuperSpeedyImports_AuthManager. |
| Tools | tools/class-tools.php + tools/* | Standalone utilities. Some are id-prefixed (wp ssi <id> save-posts / sanitize-terms / list-files); others take NO import id (wp ssi export …, wp ssi resync-image-lookup). |
| ACF/SCF | functions/acf-*.php | Custom-field group/repeater expansion into postmeta. |
Unusual / interesting aspects (expanded at the end):
- Set-based, not per-row. Stages are bulk SQL over a staging batch table, not a per-product loop, which is the core reason SSI is fast at scale.
- Per-import disposable batch tables, intentionally left on disk after a run for post-mortem.
- Parallel stages via
proc_openworker subprocesses, each owning a segment range; coordination is purely through the shared batch table (no IPC). - wpdb-or-direct-mysqli switch to dodge per-connection memory overhead and WP filter chains when needed.
- Image dedup table (
wp_ssi_image_lookup) with aBINARY(16)URL hash: constant-time lookups regardless of URL length, andINSERT IGNOREmakes concurrent workers race-safe. - Recursive-CTE hierarchy resolution done once in a stage finalizer, not per row.
- One stage plan for every runner: core stage names and priorities come from a locally built stage plan, so CLI, browser and scheduled runs and CLI workers register the same pipeline.
Request lifecycle
wp ssi <id> ──► superspeedyimports_run_import($args,$assoc) [run-import.php]
├─ stages → superspeedyimports_run_parallel() (fan-out orchestrator)
└─ <tool> → $template->runUtility()/tools
Admin Run button ──► REST routes under superspeedyimports/v1 [super-speedy-imports.php]
import/setup → import/run per sequential stage;
per parallel stage: import/stage-setup, import/run per segment, import/finalize
→ import/complete
import/stage-status: recovery probe after a timed-out request
Schedule ──► Action Scheduler, WP-Cron fallback [functions/scheduled-imports.php]
re-entrant ticks, each running a bounded slice of the stage list
superspeedyimports_run_import builds the template instance (resolving base_template via superspeedyimports_resolve_template_class()), parses flags (--workers, --file, --show-sql, --delete-all, --verbose) and dispatches. All three runners take their stage list, with the import-mode preset applied, from getFilteredStagesWithParallelInfo().
Notable components
Orchestrator (run-import.php)
superspeedyimports_run_parallel($template,$id,$workers)iterates the stage list fromgetFilteredStagesWithParallelInfo(). For each stage:setupStageAuxTables()(DROP+CREATE any stage-specific aux tables) runs first — in all execution modes.- If the stage is not parallel → run in-process.
- If parallel and
effective_workers === 1→ single-worker bypass: run in-process (noproc_open) but still callfinalizeStage(). - If parallel and
> 1→ build one worker command per segment (superspeedyimports_build_worker_command) and spawn viasuperspeedyimports_spawn_workers(proc_open); thenfinalizeStage(). - Segment sources:
csv(byte-offset row ranges, fromcountCsvRows),batch/terms/hierarchies(ID ranges from captured max-IDs). finalizeStage($name)is the reduce step — runs once on the parent after workers finish (e.g.load-csv: dedupe UIDs, build post-load indexes, capture max-IDs;import-taxonomies: recursive-CTE term-taxonomy propagation). Called in every mode, so single- and multi-worker runs produce identical results.- Parallel stages (
$parallel_stagesinimport-template-abstract.php):load-csv(csv),attach-existing-images(batch),upload-remote-images(batch),attach-gallery-images(batch),fix-attributes(batch). Everything else is sequential.
Template hierarchy (import-template-abstract.php, templates/)
- Abstract
SuperSpeedyImports_ImportTemplate; concreteSuperSpeedyImports_PostTemplate,SuperSpeedyImports_WooCommerceProductTemplate(extends PostTemplate),SuperSpeedyImports_CustomPostTypeTemplate. (The classes were renamed twice, from unprefixed names toSSI_*and then toSuperSpeedyImports_*;functions/template-resolver.php::superspeedyimports_resolve_template_class()maps both legacy spellings of a savedbase_templateto the current class, and only instantiates classes fromtemplates/or registered throughsuperspeedyimports_register_templates.) - Templates register stages (
registerStage($name,$priority,$callback,$desc)), define the batch-table schema (setupBatchTables), the field-mapping UI shape, andgetStagePresets()(import modes:full,only_insert,only_update,prices_only,taxonomies_only,images_only). - Asset auto-discovery: a template’s JS and CSS are auto-enqueued when they share the basename of the file that defines the template class (
MyTemplate.php→MyTemplate.js/MyTemplate.css), found next to that file or in itsjs/orcss/subfolder.
Stage pipeline
Core (stages/core/): load-csv → import-taxonomies → match-existing → handle-slug-duplicates → update-posts → insert-posts → update-postmeta → insert-postmeta. Post (stages/post/): attach-existing-images, upload-remote-images, then upsert-relationships. WooCommerce (stages/woocommerce/, product imports only): process-variable-products, assign-product-type, attach-gallery-images, fix-attributes, and sync-variable-prices after process-deletes; variable-product parents are synthesised inside load-csv‘s finalize through superspeedyimports_after_load_csv_synthesise, not as a stage of their own. process-deletes runs last of the import stages and post-import-jobs (After Import) after everything. An import with a fetched remote source gets source-fetch, and an XML or JSON source gets xml-to-csv, just before load-csv. Utils (stages/utils/): save-posts, sanitize-terms, list-files, run only when named. Each stage is set-based SQL over the batch table; full per-stage detail in stage-system-reference.
Database layer
- Per-import tables
wp_super_speedy_imports_<type>_<id>andwp_ssi_<type>_<id>:batch(the staging table; UNIQUE on_ssi_unique_item_id),terms,hierarchies(UNIQUEpath_keypreserves the full source-cell and level identity;parent_path_keyresolves nullable parent IDs in one load-csv finalisation UPDATE),gallery_images,deletes,ambiguous_terms/_hierarchies,duplicate_unique_ids,validation_errors,acf_expanded. Left on disk after a run for diagnostics; recreated next run. functions/db.phpwraps writes/reads with asuperspeedyimports_should_use_wpdb()switch: wpdb by default, direct mysqli only inside load-csv’s row loop, where wpdb’s per-query overhead builds up memory. Stage SQL goes throughsuperspeedyimports_query/superspeedyimports_get_results/superspeedyimports_get_var, which stamp each statement with the stage fingerprint/*ssi:<import_id>:<stage>*/and refresh the run heartbeat; that is how the stage-status probe tells a running stage from a dead one.superspeedyimports_insertis not fingerprinted. The WordPress.org edition ships no direct-mysqli code, so it runs load-csv through wpdb as well;define('SUPERSPEEDYIMPORTS_FORCE_WPDB', true);inwp-config.phpputs any edition on that same wpdb-only path: the direct-mysqli helpers are not loaded, so load-csv runs through$wpdb, and WooCommerce attributes go through the prepared$wpdbattribute engine. Use it to run the WordPress.org edition’s database path from a paid build, or where a direct connection is not wanted.- History:
wp_ssi_import_history+_stagesviaImportLogger; metrics are numeric and summed across workers (outputAggregatedStageSummary).
Image pipeline
SuperSpeedyImports_PostTemplate::uploadImage()downloads, validates MIME, andmedia_handle_sideload()s insidewithImageSizesFilter()(which can suppress intermediate-size generation), then registers the result viasuperspeedyimports_image_lookup_insert().wp_ssi_image_lookup(global) mapsurl_hash(BINARY(16) MD5) →attachment_id. Lookups are O(1)-ish regardless of URL length;INSERT IGNOREon the uniqueurl_hashmakes parallel workers race-safe.superspeedyimports_image_lookup_cleanup_stale()(anti-join) prunes rows for attachments deleted in wp-admin, run at the top of image stages. This replaced the pre-2.55original_image_urlpostmeta contract — never read/write that postmeta; usefunctions/image-lookup.php.
Taxonomy handling
load-csv parses each taxonomy column into hierarchy levels (the configured separator, e.g. >) and multiple terms (the | multi-term separator). Flat terms resolve to term_taxonomy_id during import-taxonomies; hierarchical paths are staged and resolved by a recursive-CTE bulk UPDATE in finalizeStage('import-taxonomies'). Slug collisions (two source values sanitising to the same slug) follow term_slug_collision_policy (alert vs merge); genuinely ambiguous paths surface in the ambiguous_* tables.
Hook points (brief)
Add-ons/templates extend SSI through actions/filters — full how-to in creating-addons. The notable ones:
| Hook | Type | When / use |
|---|---|---|
superspeedyimports_register_templates | action | Register external template classes (SuperSpeedyImports_ImportTemplate::addTemplate([...])). |
superspeedyimports_register_stages / registerStage() | action / method | Add custom stages to a template instance. |
superspeedyimports_before_import_stages / superspeedyimports_after_import_stages | action | Bracket the stages once per run on every runner (from 3.49.0), after superspeedyimports_before_import_setup. |
superspeedyimports_filter_stages_to_run | filter | Add/remove stages (import-mode preset runs here at priority 5; CLI explicit-stage at 10). |
superspeedyimports_stage_presets | filter | Add your own stages to the import-mode presets, or a preset drops them. |
superspeedyimports_recoverable_stages | filter | Stages that may be re-run whole after a browser timeout or a stalled scheduled run. |
superspeedyimports_pre_auto_config | filter | Return your own auto-mapping for an uploaded file before the built-in mapper runs. |
superspeedyimports_stage_starting / superspeedyimports_stage_ending | action | Per-stage brackets (ACF postmeta expansion hangs off superspeedyimports_stage_ending). |
superspeedyimports_after_load_csv_synthesise | action | Post-load row synthesis (variable-product parents). |
superspeedyimports_apply_additional_options | action | Mutate template.config from additional_options (e.g. the Delayed-Image-Import add-on). |
superspeedyimports_session_tuning_for_stage | filter | Return SET SESSION tuning for a stage (used by upsert-relationships). |
superspeedyimports_template_js_files / superspeedyimports_template_css_files | filter | Register external-template assets. |
superspeedyimports_allow_private_image_hosts / superspeedyimports_allowed_downloaded_image_mimes / https_local_ssl_verify | filter | Image-download SSRF gate, MIME allow-list, SSL verify. |
superspeedyimports_filter_deletes | filter | Add an AND ... condition to the delete-missing-items query (e.g. spare private posts). |
superspeedyimports_standalone_tools | filter | Register custom wp ssi <id> <tool> utilities. |
Add-ons detect SSI by checking class_exists('SuperSpeedyImports_ImportTemplate').
The
ssi_prefix is gone. Version 2.79.0 renamed every hook, class and function fromssi_*tosuperspeedyimports_*for the WordPress.org directory’s prefix rules, and there are no back-compatibility aliases: an add-on still hookingssi_apply_additional_optionsor checkingclass_exists('SSI_ImportTemplate')silently does nothing. Option keys, capabilities (ssi_manage_imports,ssi_run_imports), meta keys (_ssi_unique_item_id,ssi_updated), table names (wp_ssi_*) andwp-config.phpconstants were deliberately NOT renamed and still use the short prefix.
Why it’s built this way (interesting aspects)
- Bulk SQL over a batch table is the central idea: load the CSV into a staging table once, then every stage is a small number of set-based statements (
INSERT … SELECT,UPDATE … JOIN) instead of N per-row WordPress calls. This is what makes it scale (no-op updates short-circuit; unchanged rows cost ~nothing). - Parallelism is opt-in per stage and segment-based: the orchestrator splits the work into ID/byte ranges and runs
wp ssiworker subprocesses; they never talk to each other, only to the shared batch table. The single-worker bypass keeps the same code path correct without spawning. - Disposable per-import tables trade a little disk for debuggability — after a run you can inspect exactly what each stage saw.
- The image-lookup table is a deliberate move away from
wp_postmetalookups: a dedicated, well-indexed, collation-controlled table the plugin owns, so URL→attachment resolution stays fast and side-effect-free at scale. - Hierarchy resolution as one recursive CTE in a finalizer avoids per-row term queries and is computed once per import regardless of worker count.
- One stage plan, built locally, gives the orchestrator, its workers and the browser and scheduled runners the same set and ordering of core stages.
Maintenance note: avoid citing line numbers in this doc — they drift. Names of classes, functions, hooks, tables and stages are the stable references.
Written for Super Speedy Imports 3.63.0.

Comments