When you work in WordPress development for a while, you get used to seeing path handling like
ABSPATH . 'wp-content/plugins/plugin/plugin-name.php';
Such code is… let’s go with fragile. It relies on a common WordPress directory structure, the one it can easily be configured out of.
Whenever you work with paths you need to be aware of both directory structure and how it can be configured. You need that to write reliable code to handle paths.
Out of the box it is common for WordPress directory structure in an installation to look like this:
Content directory is inside the core directory and contains nearly everything, other than core code files.
The nuance is that WordPress merely defaults to such structure. It is a product of default distribution and default constants, defined during load by:
If we annotate the common structure with them it would look like this:
ABSPATH . 'wp-admin'
ABSPATH . '/wp-content')
WP_CONTENT_DIR . '/mu-plugins')
WP_CONTENT_DIR . '/plugins')
WP_CONTENT_DIR . '/themes'
WP_CONTENT_DIR . '/uploads'
ABSPATH . WPINC
ABSPATH . 'wp-config.php'
While we are talking about local paths (in server’s file system), there are also respective URL constants to some of these, such as
Some of these constants can be customized in configuration. In the wild it can be very different from default case.
It is typical for modern WordPress site stack to be structured closer to this:
- wp-content (no constant! just happens to be here)
- themes (
- themes (
- content (
- wp-config.php (
dirname( ABSPATH ) . '/wp-config.php')
The changes in this structure are:
- WordPress core has its own directory;
- content directory is configured to be on same level as core, not nested inside of it;
- core themes directory is configured as additional theme directory (thought there can only be one? ha…);
- configuration file is level above core directory (natively supported case).
Note that we only moved the whole content directory in this case. We could just as easily have moved plugins/themes/uploads to different locations.
Separating core and content directories is basic technique. It is beneficial for:
- cleaner structure;
- simpler workflows (such as backups);
- applying modern development principles (such as dependency management).
Path vs URL
It is often tempting (or even necessary) to make a jump from a file path context to an URL context. Or the other way around. While it seems reasonable with simple structures, URL structure can be flexibly configured too.
The knowledge that directory B is inside directory A in file system doesn’t always translate to URL for directory B being inside URL to directory A. Sometimes they can be as far as different domains altogether.
Some extensions have a need (or at least temptation) to write files into a WordPress installation. This is unreliable in the wild. It is common for writes to be locked down due to hosting limitations, security, and intentionally restrictive configurations.
The only folder that has reasonable expectation of being writable in WordPress is uploads. But even it might be not.
Attempts to write to any other folder should go through respective Filesystem API and will require user to provide server access credentials, if direct write is impossible.
It is common for plugin basename (combination of folder and file with header, such as
plugin/plugin.php) to be hardcoded.
It is important to know that folder part is mutable. It can get arbitrarily changed, even if you release it with specific name.
The good practice is to store actual path from
__FILE__ magic constant during plugin’s load routine and make use of it.
So how to write reliable code, given such flexibility of folder structure?
- Use constants to configure paths.
- Use API functions to access paths.
- Use the most specific function available.
To get you started the top of functions to know and use would roughly be:
WordPress folder structure can be configured with constants. For working with paths API functions, most precise to the context, must be used.