Skip to content

Everything for WordPress, web development — and beyond

🗑 Bulk deleting WooCommerce products and attributes: SQL, WP-CLI and plugins

🗑 Bulk deleting WooCommerce products and attributes: SQL, WP-CLI and plugins

You open the WooCommerce admin panel and see three thousand products, half of them duplicates from a botched import, with attributes like "Color 1," "Color 2," "Size_copy_2023." The "Products → select all → Delete" interface times out after the first couple hundred. Sound familiar? Bulk catalog cleanup is a task everyone faces when migrating a store, merging a staging database with production, or relaunching a storefront after a rebrand.

The problem comes down to how WooCommerce is structured: products are spread across four tables (wp_posts, wp_postmeta, wp_term_relationships, and wp_term_taxonomy), and attributes are stored in three more. You can't just click "Delete all"; the engine will crash before it reaches the end of the list. You need a tool that bypasses the interface and hits the database directly, or works through the CLI.

Below are three working methods, from radical SQL to safe plugins. With a backup, with prefix checks, and with an understanding of what exactly happens in each table.

💡 Quick overview:

  • Make a full database dump; DELETE is irreversible, there is no trash
  • Check the table prefix in wp-config.php and substitute it for wp_
  • Run the SQL commands in cascade order: attributes → products → orphaned postmeta
  • If you have SSH, WP-CLI handles it in a single command and fires the necessary hooks
  • For a production store with no SQL experience, plugins with a "Delete" button are safer

Precautions: backup and prefix

Any SQL command that modifies the contents of WordPress tables is irreversible. DELETE does not ask for confirmation, does not send the record to the trash; the row disappears instantly and permanently. Rule number one: make a full database backup before running any of the queries below.

The most reliable approach is to export a dump via phpMyAdmin: the "Export" tab → SQL format → compress with gzip. Or through your hosting panel (cPanel → Backup → Database). For those who work from the command line:

1mysqldump -u username -p database_name > backup_$(date +%Y%m%d).sql

Second point: all queries below use the standard wp_ prefix. If you changed the prefix during WordPress installation to wpx_, store_, or anything else, replace wp_ in every command with yours. The actual prefix is in wp-config.php, the $table_prefix line. Checked? Now let's get to it.

Method 1: SQL commands in phpMyAdmin, full control

The fastest and most radical method. Best suited for when you need to wipe hundreds or thousands of records in a single pass and the standard WooCommerce interface times out. All queries are executed in phpMyAdmin on the "SQL" tab, one at a time, in strict order.

Deleting WooCommerce attributes

Attributes live in three tables at once: wp_terms, wp_term_taxonomy, and wp_term_relationships. They differ from regular categories and tags by the pa_ prefix in the taxonomy field, short for "product attribute." You need to delete them in cascade, starting with terms and ending with relationships:

1DELETE FROM wp_terms WHERE term_id IN
2(SELECT term_id FROM wp_term_taxonomy WHERE taxonomy LIKE 'pa_%');
3
4DELETE FROM wp_term_taxonomy WHERE taxonomy LIKE 'pa_%';
5
6DELETE FROM wp_term_relationships WHERE term_taxonomy_id NOT IN
7(SELECT term_taxonomy_id FROM wp_term_taxonomy);

The first query deletes attribute names from wp_terms. The second removes their taxonomy records from wp_term_taxonomy. The third cleans up orphaned "term-object" relationships from wp_term_relationships that were left without a parent taxonomy. The order matters: if you delete the taxonomy before the terms, the third query will catch too much.

Deleting WooCommerce products

Products and their variations are records with the type product and product_variation in the wp_posts table. But simply erasing rows from wp_posts is not enough: metadata will remain in wp_postmeta (price, SKU, shipping settings) and term relationships will remain in wp_term_relationships (categories, tags). Three queries, in cascade:

1DELETE FROM wp_term_relationships WHERE object_id IN
2(SELECT ID FROM wp_posts WHERE post_type IN ('product','product_variation'));
3
4DELETE FROM wp_postmeta WHERE post_id IN
5(SELECT ID FROM wp_posts WHERE post_type IN ('product','product_variation'));
6
7DELETE FROM wp_posts WHERE post_type IN ('product','product_variation');

First we break the product's relationships with taxonomies, then delete the metadata, and only then the product record itself. If you reverse the order and delete wp_posts first, the subqueries SELECT ID FROM wp_posts in the second and third steps will return an empty set, and the metadata and relationships will remain as dead weight in the database.

Cleaning up orphaned postmeta

After any delete operations via SQL, it is worth checking whether wp_postmeta contains rows referencing nonexistent posts. This happens with interrupted transactions, broken imports, or when posts were deleted without cascading:

1DELETE pm
2FROM wp_postmeta pm
3LEFT JOIN wp_posts wp ON wp.ID = pm.post_id
4WHERE wp.ID IS NULL;

The query finds all wp_postmeta rows that have no parent record in wp_posts and deletes them. It is safe: it does not touch live data, only garbage.

Method 2: WP-CLI, fast and without phpMyAdmin

If you have SSH access to the server, WP-CLI handles bulk deletion more elegantly than any SQL queries. One command, and WooCommerce walks through the related tables on its own, leaving no orphaned data behind:

1wp wc product delete $(wp wc product list --field=ID --per_page=-1) --force

The --per_page=-1 flag exports the IDs of all products without pagination. --force skips the trash and deletes permanently. If you have more than 10,000 products, it is better to split into batches of 500 to avoid hitting memory limits:

1wp wc product list --field=ID --per_page=500 --page=1 | xargs wp wc product delete --force

To delete attributes via WP-CLI, use:

1wp wc product_attribute list --field=id --per_page=-1 | xargs -I{} wp wc product_attribute delete {} --force

The main advantage of WP-CLI over raw SQL is that it fires WooCommerce's internal hooks, before_delete_post and after_delete_post. This gives caching and search plugins (Elasticsearch, Redis, Relevanssi) a chance to clean up their indexes. SQL queries do not do this; after them, search may continue returning already-deleted products for some time.

Method 3: plugins, when you don't want to touch the database

For those who find the command line and phpMyAdmin too risky, the market offers specialized plugins. They work on top of the same SQL queries but hide them behind a button.

Delete All Products for WooCommerce is a free plugin from the official WordPress.org repository. It adds a single button to the admin panel. Click → choose "move to trash" or "permanently" → confirm. Minimal steps, zero risk of a SQL typo. The downside: it only works with products, it does not touch attributes.

WooCommerce Store Toolkit (also known as Store Toolkit for WooCommerce) is a more serious option. It cleans not only products and attributes but also orders, coupons, sessions, and transients, with filters by date and status. Suitable for a full deep clean of the store before a relaunch.

Whichever plugin you choose, the backup rule still applies. A plugin runs the same DELETE queries; you just don't see them.

Comparing methods: what to choose and when

Method

Speed

Safety

Flexibility

Intended for

SQL in phpMyAdmin

Instant

Low, no protection from mistakes

Full control over tables

Developers, server admins

WP-CLI

Fast, seconds

High, hooks and cascading

Convenient flags and pagination

Developers, DevOps

Plugins

Slow, hundreds per minute

Maximum, UI-based

Limited to plugin functionality

Store owners

If you have one product or a dozen, the WooCommerce interface "Products → select → Delete" will do the job. Hundreds or thousands call for SQL or WP-CLI. A live production store where there is no room for error calls for a plugin or WP-CLI.

Important limitations: what is not deleted

Bulk deletion of products and attributes via SQL does not touch media files. Product images uploaded to the WordPress media library (records with post_type = 'attachment') stay in place, both in the file system and in the database. If you are rebuilding the catalog from scratch and want to free up space on your hosting, media files must be cleaned separately: via "Media → select → Delete Permanently" or WP-CLI:

1wp post delete $(wp post list --post_type=attachment --field=ID --per_page=-1) --force

SQL commands also do not update WooCommerce counters (product counts by category) that are cached in wp_termmeta and wp_options as transients with the _wc_term_counts_ prefix. After a bulk deletion, the admin panel may temporarily show incorrect product counts per category. This is fixed by recounting:

1wp wc tool run recount_terms

Or via the Recount Terms plugin from the repository.

The video provides a step-by-step walkthrough of SQL commands for deleting WooCommerce attributes in phpMyAdmin: navigating the tables and verifying results after each query.

⁉️🤔 Frequently asked questions

Is it safe to delete products via SQL on a live store?

On a production store, using raw SQL for bulk deletion is a risky practice. A single typo in a table name or WHERE clause can affect orders, users, or settings. If the store is live and generating revenue, use WP-CLI or plugins that won't let you shoot yourself in the foot. Save SQL for dev environments, staging, and situations where the admin interface is already failing to load.

Why are products still visible in site search after SQL deletion?

Search plugins (Relevanssi, Elasticsearch, SearchWP) maintain their own index, which is not updated when wp_posts is manipulated directly, bypassing the WordPress API. After cleaning via SQL, you need to rebuild the search index in the plugin settings or via WP-CLI: for example, wp relevanssi index --reindex.

How do I delete products from a specific category only, not all of them?

Add a filter by the term_taxonomy_id of the desired category. The approach: get the category's term_id → find the term_taxonomy_id in wp_term_taxonomy → filter object_id in wp_term_relationships before deleting from wp_posts. In practice, it is easier to use WP-CLI: wp wc product list --category=slug-kategorii --field=ID | xargs wp wc product delete --force.

Can products be restored after SQL deletion?

Only from a backup. Unlike deletion via the WordPress trash (Move to Trash), the SQL DELETE command erases rows physically and irreversibly. This is exactly why the "backup first" rule is repeated in every section of this article. Two minutes spent on a dump will save hours on recovery.

What is the difference between deleting attributes and deleting variations?

Variations are a subtype of products (product_variation). They are deleted by the same SQL commands as simple products: the condition post_type IN ('product','product_variation') in the second block of "Method 1" already includes variations. Attributes (pa_color, pa_size) are taxonomies; they are deleted separately, by the first block of SQL commands. The correct order is: products first (including variations), then attributes. If done in reverse, variations will lose their attribute bindings, but the variation records themselves will remain in the database.

What to use in 2026: the bottom line

SQL commands, WP-CLI, and plugins are three tools of varying risk levels for one task. The choice comes down to a simple matrix:

  • You have SSH and command-line experience → WP-CLI (wp wc product delete). Safe, fast, with hooks.
  • No SSH but you have phpMyAdmin and understand the table schema → SQL. Full control, instant results. But backup first.
  • You want no risk → Delete All Products or Store Toolkit. Slower, but a button instead of a SQL query.

In any scenario, a database backup is action number one. Don't start without it.

🔗 Delete All Products for WooCommerce, free on WordPress.org🔗 WooCommerce Store Toolkit, advanced store cleanup