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.
Update Filter: superspeedyimports_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: superspeedyimports_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('superspeedyimports_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: superspeedyimports_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: superspeedyimports_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('superspeedyimports_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: superspeedyimports_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: superspeedyimports_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('superspeedyimports_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 missing items 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.
Combining the delete filter with Keep sold items
On a WooCommerce product import, Keep sold items (the keep_sold_items option, under Delete missing items) adds its own exclusion to the same query: any product or variation that is a line item of a completed, processing or on-hold order is never selected for deletion. It reads the orders from whichever storage WooCommerce is using, the High-Performance Order Storage wc_orders table or the posts table, so it works on stores that never enabled HPOS.
Keep sold items is not implemented through this filter. It is part of SSI’s own query, and your condition is appended after it, as the last part of the WHERE clause. The two combine: a post is deleted only when it is missing from the file, is not in one of those orders, and passes your condition. The sold-products exclusion is a derived table joined as ssi_sold, so do not use ssi_sold as an alias in your own condition.
add_filter('superspeedyimports_filter_deletes', function($where_condition, $import_id) {
global $wpdb;
// With Keep sold items on as well: also spare anything published in the last 7 days.
$where_condition .= $wpdb->prepare(
" AND pm.post_id NOT IN (SELECT ID FROM {$wpdb->posts} WHERE post_date > %s)",
gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS)
);
return $where_condition;
}, 10, 2);
Best Practices
Always append to $where_condition. Other add-ons and snippets may 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 whole WHERE clause. Your fragment is glued onto an existing clause, so it must begin with AND.
Use the correct table aliases. Reference only the aliases that exist for that specific filter – p and import for updates, pm and import for 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.
Not in the WordPress.org edition. The WordPress.org edition of SSI does not apply these three filters; every other edition does.
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.
Written for Super Speedy Imports 3.40.0.

Comments