Skip to content

WP Packages is our new WPackagist replacement that's 17x faster and updates every 5 minutes

  1. Blog

WordPress Plugins That Assume Your Directory Structure

Ben Word Ben Word

WordPress has supported customizing your directory structure for nearly twenty years. You can rename your wp-content directory. You can install WordPress in a subdirectory. 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, 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 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.

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, you can search the entire WordPress plugin directory with regex and see for yourself.

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 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

PluginActive InstallsIssue
Really Simple Security3,000,000Includes wp-load.php in download.php and system-status.php
Starter Templates2,000,000require_once ABSPATH . 'wp-content/themes/astra/functions.php'
Complianz GDPR1,000,000Bootstraps WordPress via wp-load.php in system-status.php
Google Sitemap Generator1,000,000require_once '../../../wp-load.php' and hardcoded wp-content includes
W3 Total Cache900,000require_once __DIR__ . '/../../../../wp-load.php' in pub/sns.php
NextGEN Gallery400,000Entire bootstrap file dedicated to finding and loading wp-load.php
LoginPress200,000require ABSPATH . '/wp-load.php' in login template
Burst Statistics200,000Standalone endpoint.php that hunts for wp-load.php
Matomo Analytics100,000Hardcoded 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 — 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 and run your plugin against it. If it works on Bedrock, it’ll work anywhere.

References

Discuss this post on Roots Discourse

About the author

Ben Word

Ben Word has been creating WordPress sites since 2004. He loves dogs, climbing, and yoga, and is passionate about helping people build awesome things on the web.

Subscribe for updates

Join over 8,000 subscribers for the latest Roots updates, WordPress plugin recommendations, modern WordPress projects, and web development tips.

One last step! Check your email for a verification link.