# Excluding files and folders in All-in-One WP Migration plugin

> All-in-One WP Migration has four exclude filter hooks, not one. Here's which hook to use for uploads, themes, plugins, or the wp-content root, and why the common snippet you find online silently does nothing.

Published: 2021-08-26T19:05:32.000Z
Updated: 2026-04-23T00:00:00.000Z
Author: Shameem Reza
Category: WordPress
Canonical: https://shameemreza.com/excluding-files-and-folders-in-all-in-one-wp-migration-plugin/

---

import Tldr from '../../components/Tldr.astro';

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.

<Tldr>
  All-in-One WP Migration splits `wp-content` into four separate iterators during export, each with its own exclude filter:

  ```
  ai1wm_exclude_content_from_export   // everything in wp-content except themes/plugins/uploads
  ai1wm_exclude_themes_from_export    // walked starting at wp-content/themes
  ai1wm_exclude_plugins_from_export   // walked starting at wp-content/plugins
  ai1wm_exclude_media_from_export     // walked starting at wp-content/uploads
  ```

  If you want to skip a theme's cache folder, you need the themes hook. The content hook won't touch it, because the themes directory is already excluded from the content walk and iterated on its own.
</Tldr>

## 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](/increase-upload-limit-for-all-in-one-wp-migration-plugin/) 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 because `getSubPathname` returns 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 as `astra/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](https://wordpress.org/plugins/insert-headers-and-footers/) 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](https://github.com/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.
