# WordPress Plugins That Assume Your Directory Structure

WordPress has supported [customizing your directory structure](https://developer.wordpress.org/apis/wp-config-php/#moving-wp-content-folder) for nearly twenty years. You can rename your `wp-content` directory. You can [install WordPress in a subdirectory](https://developer.wordpress.org/advanced-administration/server/wordpress-in-directory/). These are documented and supported features.

There's still lots of plugins that completely ignore this and hardcode paths that only work on a default install.

If you've ever used [Bedrock](https://roots.io/bedrock/), managed WordPress with Composer, or moved WordPress into its own directory for a cleaner project root, you've probably run into this. A plugin that works fine on a vanilla install will break on yours.

## What does a non-standard install look like?

A "standard" WordPress install puts everything in one directory with `wp-content` sitting alongside `wp-admin` and `wp-includes`. WordPress gives you the tools to change this:

- **`WP_CONTENT_DIR`** and **`WP_CONTENT_URL`** let you rename or relocate your content directory entirely.
- **Installing WordPress in a subdirectory** (like `/wp/`) keeps core files separate from your project root.

People do this for good reasons: cleaner project structure, Composer-based dependency management, improved security, or simply better organization. Bedrock installs WordPress in a `wp/` subdirectory and moves the content directory to `app/` ([Radicle](https://roots.io/radicle/) uses `content/`, which Bedrock v2 will be adopting).

## The three most common offenders

### Hardcoded `wp-content` paths

This is the most widespread problem. Plugins reference `/wp-content/` as a literal string — in PHP includes, in JavaScript, in CSS, in generated URLs — instead of using the constants and functions WordPress provides.

```
// Wrong
$path = ABSPATH . 'wp-content/plugins/my-plugin/lib/foo.php';
$url = site_url('/wp-content/plugins/my-plugin/assets/style.css');

// Right
$path = plugin_dir_path(__FILE__) . 'lib/foo.php';
$url = plugins_url('assets/style.css', __FILE__);

```

WordPress gives you `WP_CONTENT_DIR`, `WP_CONTENT_URL`, `plugin_dir_path()`, `plugins_url()`, `content_url()`, and more. There's no reason to hardcode the path.

### Directly including `wp-load.php`

Some plugins try to bootstrap the WordPress environment by directly including `wp-load.php` — usually in standalone PHP files that handle AJAX requests, cron jobs, or file processing outside the normal WordPress lifecycle.

```
// Wrong
require_once('../../../wp-load.php');

// Also wrong
require_once(dirname(__FILE__) . '/../../../wp-load.php');

```

This is fragile in any install, but it's guaranteed to break when WordPress lives in a subdirectory. The relative path to `wp-load.php` changes, and the plugin has no way to find it.

The fix is to stop doing it entirely. WordPress has proper APIs for everything these standalone files are trying to do, including using the [WordPress REST API](https://roots.io/wordpress-rest-api-vs-admin-ajax-php-the-modern-choice/).

### Assuming WordPress lives in the root directory

Some plugins hardcode paths like `/wp-admin/` or `/wp-includes/` relative to the site root, assuming WordPress core files live at the top level. When WordPress is installed in a subdirectory (like `/wp/`), these paths point to nothing.

```
// Wrong — assumes WordPress is in the root directory
home_url('/wp-admin/admin-ajax.php');
home_url('/wp-includes/js/some-script.js');

// Better — but still assumes wp-admin and wp-includes paths
site_url('/wp-admin/admin-ajax.php');
site_url('/wp-includes/js/some-script.js');

// Best — uses the dedicated functions
admin_url('admin-ajax.php');
includes_url('js/some-script.js');

```

WordPress provides `admin_url()`, `includes_url()`, and `site_url()` specifically for this. Any time you're building a path to a core file, use these functions instead of assuming the directory structure.

## How big is the problem?

Thanks to [Veloria](https://veloria.dev/), you can search the entire WordPress plugin directory with regex and see for yourself.

![](https://roots.io/app/uploads/veloria-search-hardcoded-wp-content-includes-1024x839.png)**Note: not every match for these searches are a real problem and some results will be false positives**. Such as references to `wp-content` in readme files, hardcoded URLs pointing to external domains, or sample data. But dig into the results and you'll find plenty of legitimate offenders.

Veloria also offers an [MCP server](https://veloria.dev/docs#mcp) that you can use with AI tools like Claude Code to programmatically search plugin code, inspect matches in context, and filter out false positives.

### Notable offenders

| Plugin | Active Installs | Issue |
|---|---|---|
| Really Simple Security | 3,000,000 | Includes `wp-load.php` in `download.php` and `system-status.php` |
| Starter Templates | 2,000,000 | `require_once ABSPATH . 'wp-content/themes/astra/functions.php'` |
| Complianz GDPR | 1,000,000 | Bootstraps WordPress via `wp-load.php` in `system-status.php` |
| Google Sitemap Generator | 1,000,000 | `require_once '../../../wp-load.php'` and hardcoded `wp-content` includes |
| W3 Total Cache | 900,000 | `require_once __DIR__ . '/../../../../wp-load.php'` in `pub/sns.php` |
| NextGEN Gallery | 400,000 | Entire bootstrap file dedicated to finding and loading `wp-load.php` |
| LoginPress | 200,000 | `require ABSPATH . '/wp-load.php'` in login template |
| Burst Statistics | 200,000 | Standalone `endpoint.php` that hunts for `wp-load.php` |
| Matomo Analytics | 100,000 | Hardcoded `wp-content` paths throughout — views, asset manager, system report |

### Hardcoded `wp-content` in includes

Search for plugins that `include` or `require` a path containing `wp-content`:

```
\b(?:include|include_once|require|require_once)\b[^\n;]*['"][^'"]*wp-content[^'"]*['"]
```

### Hardcoded `wp-content` in URLs and paths

The problem is even bigger when you look beyond just `include`/`require` statements. Search for `wp-content` being concatenated or embedded in strings more broadly:

```
['"/]wp-content/
```

### Direct `wp-load.php` includes

Search for plugins that try to bootstrap WordPress by including `wp-load.php`:

```
\b(?:require|require_once|include|include_once)\b\s*(?:\(\s*)?[^\n;]*['"][^'"]*wp-load\.php['"]\s*\)?
```

This surfaces another pile of plugins that will break on any non-standard directory structure.

You can run all of these searches yourself on [Veloria](https://veloria.dev/) — it's a full regex search across the WordPress plugin directory.

## What plugin authors should do

If you maintain a WordPress plugin, this is straightforward to fix:

1. Search your own codebase for literal `wp-content` strings. Replace them with the appropriate WordPress constant or function (`WP_CONTENT_DIR`, `plugins_url()`, `content_url()`, etc.).
2. Remove any direct includes of `wp-load.php`.
3. Replace hardcoded paths to `/wp-admin/` and `/wp-includes/` with `admin_url()`, `includes_url()`, and similar functions.
4. Use `site_url()` instead of `home_url()` when referencing WordPress core files. `home_url()` points to your front-end URL, not where WordPress is installed.
5. Test on a non-default install. Set up [Bedrock](https://roots.io/bedrock/) and run your plugin against it. If it works on Bedrock, it'll work anywhere.

## References

- [Bedrock: Compatibility docs](https://roots.io/bedrock/docs/compatibility/)
- [Otto: Don’t include wp-load, please.](https://ottopress.com/2010/dont-include-wp-load-please/)