Customising Imports using SQL Filters
Super Speedy Imports builds and runs its update and delete SQL as set-based queries — one big UPDATE ... INNER JOIN or INSERT ... SELECT instead of a row-by-row loop. That’s what makes it fast, but it also means you can’t just hook a per-row callback to skip a record. Instead, SSI exposes three WordPress filters that let you append your own SQL WHERE conditions to control exactly which records get updated or deleted during an import.
Each filter passes you the current WHERE condition string and the import ID, and expects you to return a (possibly modified) condition string. The string is concatenated directly onto SSI’s generated query, so you always append to it — never replace it.
If you’re building a reusable extension rather than a one-off snippet, put these add_filter() calls in an add-on plugin — see Creating Add-ons. To control which whole stages run at all (rather than which rows a stage touches), see Stage Selection Presets.
Table of Contents
Update Filter: ssi_filter_updates
Controls which posts get updated during the update-posts stage. The condition is appended to SSI’s UPDATE {$wpdb->posts} p INNER JOIN <batch> import ... query.
Hook: ssi_filter_updates
Parameters:
$where_condition(string) — current WHERE condition, empty by default$import_id(int) — the current import ID
Table aliases available:
p— alias for thewp_poststable ({$wpdb->posts})import— alias for the per-import batch table ({$this->table_names['batch']})
Example:
add_filter('ssi_filter_updates', function($where_condition, $import_id) {
// Only update posts that are already published
$where_condition .= " AND p.post_status = 'publish'";
return $where_condition;
}, 10, 2);
Update Postmeta Filter: ssi_filter_updates_postmeta
Controls which postmeta records get updated during the update-postmeta stage. This filter is applied once and the resulting condition is reused for every meta key SSI updates, so the condition is appended to each UPDATE {$wpdb->postmeta} pm INNER JOIN <batch> import ... query.
Hook: ssi_filter_updates_postmeta
Parameters:
$where_condition(string) — current WHERE condition, empty by default$import_id(int) — the current import ID
Table aliases available:
pm— alias for thewp_postmetatable ({$wpdb->postmeta})import— alias for the per-import batch table ({$this->table_names['batch']})
Example:
add_filter('ssi_filter_updates_postmeta', function($where_condition, $import_id) {
// Skip meta updates for posts that aren't published
$where_condition .= " AND pm.post_id IN (SELECT ID FROM {$GLOBALS['wpdb']->posts} WHERE post_status = 'publish')";
return $where_condition;
}, 10, 2);
Delete Filter: ssi_filter_deletes
Controls which posts get trashed (or force-deleted) when they’re missing from the current import batch — i.e. the “delete items missing from this import” behaviour. The condition is appended to the INSERT INTO <deletes> ... SELECT ... FROM wp_postmeta pm LEFT JOIN <batch> import ... query that selects the posts to remove.
Hook: ssi_filter_deletes
Parameters:
$where_condition(string) — current WHERE condition, empty by default$import_id(int) — the current import ID
Table aliases available:
pm— alias for thewp_postmetatable ({$wpdb->postmeta})import— alias for the per-import batch table ({$this->table_names['batch']})
Example:
add_filter('ssi_filter_deletes', function($where_condition, $import_id) {
// Never delete posts that you've flagged as protected
$where_condition .= " AND pm.post_id NOT IN (SELECT post_id FROM wp_protected_posts)";
return $where_condition;
}, 10, 2);
Two things to know about the delete filter:
- It only fires when delete-on-import is actually enabled — that is, when you’ve ticked Delete items missing from this import in Additional Options (the
delete_itemsoption). If that’s off, the delete stage returns early and the filter never runs. - It is deliberately skipped in
--delete-allmode (the CLI delete-all flag). When you explicitly ask to wipe every item from an import, SSI won’t let a keep-this-one filter preserve records you asked to remove.
The WooCommerce template uses this same hook internally at priority 10000 (for example, keep-sold-products logic), so make sure you append rather than overwrite — see the best practices below.
Best Practices
-
Always append to
$where_condition. SSI’s own templates (and any other add-ons) use these filters too. Concatenate your condition onto the string you’re handed and return it; never return a fresh string that throws theirs away. -
Always start your fragment with
AND. The filters hand you an additional condition, not a replacement for the wholeWHEREclause. Your fragment is glued onto an existing clause, so it must begin withAND(orOR). -
Use the correct table aliases. Reference only the aliases that exist for that specific filter —
pandimportfor updates,pmandimportfor postmeta and deletes. Referencing an alias the query doesn’t define is a SQL error. -
Sanitise dynamic values. Use
$wpdb->prepare()for anything that comes from user input or another query before splicing it into the condition. -
Test on staging first. If your condition produces invalid SQL, SSI logs the offending query so you can see exactly what broke — much easier to fix before it hits production.
-
Mind performance. SSI is fast because its queries are set-based. A correlated subquery against an unindexed column in your filter can undo that. If an import slows down after you add a filter, the filter is the first place to look.
- Creating Add-ons — package these filters into a reusable add-on plugin, plus the full hooks reference.
- Stage Selection Presets — control which whole stages run, rather than which rows each stage touches.