The first time I tried to exclude the uploads folder from an All-in-One WP Migration export, the filter I wrote did absolutely nothing. The export came out with every image intact. Nine gigabytes of them.
I’d copied the same snippet everyone on the internet copies. add_filter( 'ai1wm_exclude_content_from_export', ... ) with the path I wanted gone. It didn’t work. Not once. Took me longer than I’d like to admit to figure out why.
Turns out All-in-One WP Migration has four exclude filters, not one. And the one everyone shares online only matches files that sit directly inside wp-content and aren’t already in a themes, plugins, or uploads subfolder.
Why bother excluding anything
A WooCommerce site I migrated last year was 14 GB. Half of that was variation thumbnails, old Elementor cache, a Duplicator backup folder nobody cleaned up, and the uploads/2019 folder from before the store relaunched. The free version of All-in-One WP Migration caps imports at 512 MB on the destination. So exporting the whole 14 GB wasn’t just slow, it was useless, because the destination would refuse the file.
Exclusions solve two problems at once. The export file shrinks. The migration actually finishes.
If you’re hitting the upload size limit on the destination rather than the size of the export itself, that’s a separate problem. I wrote a tiny plugin to fix it, and a post on how to increase the All-in-One WP Migration upload limit that walks through the 512 MB error, the removed constants.php constant, and the snippet.
Why the common snippet fails
If you look inside lib/model/export/class-ai1wm-export-enumerate-content.php, the first thing the export does is pre-populate the exclude list with three things:
$exclude_filters = array_merge(
array( ai1wm_get_uploads_dir(), ai1wm_get_plugins_dir() ),
ai1wm_get_themes_dirs()
);
Then it iterates wp-content. Anything under themes/, plugins/, or uploads/ is skipped at this stage. Those three live branches are walked by their own dedicated enumerators later, each with its own hook.
So when you write something like this:
add_filter( 'ai1wm_exclude_content_from_export', function( $paths ) {
$paths[] = 'themes/astra/cache';
return $paths;
} );
The content iterator never even visits the themes folder. Your entry goes into an array that gets compared against paths the iterator will never produce. The filter runs. It just has nothing to filter.
The four hooks, with examples
Exclude a subfolder inside a theme
Use ai1wm_exclude_themes_from_export. The themes iterator walks starting at wp-content/themes, so paths are relative to that.
add_filter( 'ai1wm_exclude_themes_from_export', function( $paths ) {
$paths[] = 'astra/cache';
$paths[] = 'hello-elementor/node_modules';
return $paths;
} );
Exclude a whole plugin or a plugin subfolder
Use ai1wm_exclude_plugins_from_export. The plugins iterator walks wp-content/plugins.
add_filter( 'ai1wm_exclude_plugins_from_export', function( $paths ) {
$paths[] = 'wordfence';
$paths[] = 'elementor/data';
$paths[] = 'backup-plugin-i-no-longer-use';
return $paths;
} );
Note: you almost never want to exclude the entire plugins directory with this hook. All-in-One WP Migration relies on some of those plugins existing on the destination. Exclude individual plugins or their cache/log folders, not the whole thing.
Exclude a year or a tool’s folder under uploads
Use ai1wm_exclude_media_from_export. The media iterator walks wp-content/uploads.
add_filter( 'ai1wm_exclude_media_from_export', function( $paths ) {
$paths[] = '2019';
$paths[] = '2020';
$paths[] = 'elementor';
$paths[] = 'woocommerce_uploads';
return $paths;
} );
Common targets here are old year folders, page-builder caches, and the woocommerce_uploads folder if it holds unprotected product files you don’t want in the backup.
Exclude something directly in wp-content
Use ai1wm_exclude_content_from_export only for paths that aren’t inside themes, plugins, or uploads. Things like cache, a rogue backups-dup-pro, languages, or custom folders some host dropped in there.
add_filter( 'ai1wm_exclude_content_from_export', function( $paths ) {
$paths[] = 'backups-dup-pro';
$paths[] = 'cache';
$paths[] = 'upgrade';
return $paths;
} );
Quick heads-up: the All-in-One WP Migration export UI already has a “Do not export cache” checkbox that sets no_cache, which adds cache to the exclude list automatically. If you check that box, you don’t need to repeat it in your filter.
How the path matching actually works
Reading lib/vendor/servmask/filter/class-ai1wm-recursive-exclude-filter.php, the filter compares three things against your exclude array on every file:
$this->getInnerIterator()->getSubPathname() // path relative to the iterator root
$this->getInnerIterator()->getPathname() // full absolute path
$this->getInnerIterator()->getPath() // parent absolute path
It’s an exact in_array match on each. That means:
- Relative paths (like
astra/cache) work becausegetSubPathnamereturns paths relative to the iterator’s starting directory. - Absolute paths work too, for the full path of a file or its parent directory.
- Wildcards do not work.
uploads/2*matches nothing. - Trailing slashes break the match.
astra/cache/is not the same entry asastra/cache.
Forward slashes get converted to the platform separator automatically, so you don’t need to worry about Windows vs Linux path differences.
Where to add the snippet
Put it in a child theme’s functions.php, or use a code snippets plugin like WPCode so the exclusion survives theme switches and updates. Don’t drop it in the parent theme’s functions.php. Theme updates will overwrite it.
Another option, and the one I prefer for client sites, is a tiny mu-plugin. Create wp-content/mu-plugins/ai1wm-exclusions.php:
<?php
add_filter( 'ai1wm_exclude_media_from_export', function( $paths ) {
$paths[] = 'elementor';
$paths[] = '2019';
return $paths;
} );
mu-plugins load automatically, can’t be deactivated by accident from the admin, and won’t disappear on a theme switch.
Gotchas I wish I’d known earlier
The filter runs, but nothing gets excluded. Nine times out of ten, you’re using the wrong hook. Content hook for a themes path, or themes hook for a media path. Trace it back to which iterator walks the folder you’re targeting.
The exclusion works on the first export but not the second. The export process caches a content list in wp-content/ai1wm-backups. If you wrote the filter after starting an export, the list was already built. Start a fresh export after the filter is saved.
You tried to exclude the uploads folder entirely and the export still contains images. The media iterator walks the uploads folder itself, so you can exclude subfolders of uploads, but excluding uploads at the content level also has no effect because that folder is skipped from the content walk. The cleanest “no media” option is All-in-One WP Migration’s built-in “Do not export media” checkbox, which sets the no_media flag and skips the whole media iterator.
If you’ve hit the exclusion wall and need to go further, the official docs list these filters under the Developer section. They’re sparse, but the GitHub repo for ServMask mirrors the plugin’s source so you can trace exactly how the iterators work before writing your snippet.
The short version is: find the iterator first, pick the hook that goes with it, then write the path relative to that iterator’s root. That’s the rule that took me an embarrassing afternoon to learn.
Join the Conversation
Have thoughts, questions, or a different take? I'd love to hear from you.
Powered by Giscus · Sign in with GitHub to comment. · Privacy policy