wp-content Directory

WordPress consists of 3 folders wp-includes, wp-admin, wp-content and several files next to these folders.

All files and folders except wp-content - this is WordPress, the engine. That is, the directories: wp-includes and wp-admin are the core of WordPress, and wp-content is everything else - all user data...

The wp-content directory is where almost all user files are stored, except for the wp-config.php configuration file (which is an integral part of the core). This is where plugins, themes, plugin files, themes and site content are stored. This is also the place to store all the files associated with the expansion of WordPress.

Initially in WordPress, wp-content contains one file index.php and 3 folders: plugins, themes, languages.

File wp-content/index.php

Should always exist and should have this content:

<?php // Silence is golden.

This file prevents you from seeing a list of files in the folder. If index.php doesn't exist and your web server allows you to see files in directories, then clicking on the link http://example.com/wp-content you can see all the files and folders in that directory. This can be used by hackers to gain access to key files, allowing the site to be compromised. For example, if you have a vulnerable plugin installed, the site could easily be checked for that vulnerable plugin, and then the attacker could easily break the site.

When updating WordPress manually, never touch the wp-content folder or anything in it. It has nothing to do with updating WordPress.

A list of what what the wp-content directory may contain:

/mu-plugins - must-use plugins

In WordPress, there are so-called "plugins that must be used", they are located in the wp-content/mu-plugins directory. About them I wrote in mu-plugins post, be sure to check it out!

Briefly about Must-use plugins: Must-use plugins, also known as mu-plugins are plugins that are installed in a special mu-plugins folder in the content wp-content directory and activated automatically (always active) for the site and network sites. These plugins are not visible among the regular plugins. In the admin panel, they appear in the top info line and there is no way to disable them except to delete the plugin file from the wp-content/mu-plugins directory.

/plugins

WordPress plugins are located in the wp-content/plugins directory. A plugin can be a single file or multiple files within a folder. Any files in the /plugins directory are scanned by WordPress to determine if the file is a plugin file. If the file is determined to be a plugin, it appears in the admin panel under "Plugins" and is ready to be activated.

To deactivate a plugin, you can delete the plugin from the /plugins folder. You can also rename the name of the folder, in which case, WordPress simply can not find the plugin file and will deactivate it while trying to connect. But keep in mind that it is better to delete plugins from the admin panel, through the Delete button, because the deletion triggers some functions that clean up the plugin data in the database or in the files.

/themes

WP Themes are stored in the wp-content/themes directory. Each theme must be in its own folder and contain a properly formatted style.css file so that WordPress will recognize it as a usable theme. There should be at least 2 files in the theme directory: index.php and style.css.

WordPress can store as many themes as you want in this directory. You can easily view any available theme or activate it in the Appearance ► Themes tab in the admin panel.

/uploads - media and other files

WordPress stores uploaded files in the wp-content/uploads directory. This directory does not exist in the WordPress distribution by default. It is created when the file is first uploaded to WordPress. A separate creation is necessary because this folder can be moved to another location (see below)

By default, WordPress stores uploads in folders by year and month:

/wp-content/uploads/2012/06/image.png

Before any images or files can be uploaded to WordPress, the server must be allowed to create folders in the /wp-content directory. When the first image is uploaded, WordPress automatically creates the /uploads directory and the necessary sub-directories in it. After the first file is uploaded, you need set the permissions for /wp-content back, usually 755. Some servers allow the php script to create folders and files right away.

The uploads directory should have all permissions to allow PHP freely create and delete files in it, usually it's 777 permissions.

WordPress does NOT know how to recognize and import images to admin uploaded to uploads directly (not through the admin). And such files don't show up in the WordPress file library - WordPress doesn't know anything about them.

uploads to Multisite

In Multisite installation for the main site files are uploaded as usual. And for all additional sites, creates a folder /wp-content/uploads/sites/2, where 2 is the network site ID.

So for each site is created a folder with its ID in the /wp-content/uploads/sites directory. Then the files are also arranged in folders by year and month.

This approach allows to separate uploads for each site and simplifies their maintenance.

Before WP 3.5, the files of additional sites were not located in /wp-content/uploads/sites, but in /wp-content/blogs.dir.

So, for example, the directory for a site with ID 3 looks like this:

  • WP 3.5 and above: /wp-content/uploads/sites/3.
  • WP 3.4 and below: /wp-content/blogs.dir/3
Moving the uploads folder

To move the uploads folder, you need to define the UPLOADS constant in wp-config.php as follows:

// this means that the uploads folder should be in the root of the site
define( 'UPLOADS', 'uploads' );

Or you can change the options: upload_path and upload_url_path in the options table, see update_option().

Moving the uploads folder is not recommended, I wrote about it in the article: Bug with moving the uploads folder.

/upgrade - auto updates

The wp-content/upgrade directory is created automatically by WordPress when you update WordPress. This folder is used to store the new version of WordPress downloaded from WordPress.org. Before updating, WordPress downloads the archive and extracts its contents into this folder. For the automatic update process to be successful, it is recommended not to touch this folder. If this directory is deleted, WordPress will create it the next update.

/languages - translations

wp-content/languages directory is present only if you install a non-English version of WordPress. This contains all the localization (translation) files of WordPress. Such files have extensions:

  • .mo - a compressed version of a similar .po file, which is used by PHP for translation.
  • .po - the original translation file. This file can be used to edit the translation. After editing it must be compiled into a compressed version with the .mo extension.

There may also be special sub-directories in languages dir:

  • /pliugns - contains translations of plugins. Plugin translation file must have the format: plugin_name-local.mo, for example: akismet-ru_RU.mo. Before loading its translation file, the plugin checks if there is a translation file in this folder, and if it is there, then this translation file is used instead of the plugin's native translation.

  • /themes - contains translations of themes. Translation file must have the format: theme_name-local.mo, for example: twentyfifteen-ru_RU.mo. As well as with plugins - these files have a higher priority than native theme translation files.

The arbitrary directories

In /wp-content you can create any directories. Some plugins, create such folders for storing files. Usually an additional folder is created when you need to store a lot of files or when the stored files are somehow different from the default WP files.

For example plugin WP Super Cache creates directory /wp-content/cache to store cached pages. Cached page - this is a generated page of the site, stored as a static HTML file. When such page is accessed, it is not re-generated, but a static file is given. This is the page cache, which reduces the server load dozens of times, because pages are not generated on every crawl, and are created only when the cache is overwritten.

The WP Super Cache plugin also adds two files to the wp-content directory: advanced-cache.php (special) and wp-cache-config.php. They are needed for WP Super Cache to work.

Another example, a popular gallery plugin - NextGen Gallery - creates a directory /wp-content/gallery to store images uploaded to galleries. Each gallery created is a subdirectory of /gallery.

Another example, my plugin Kama Thumbnail, which also creates a folder /wp-content/cache/thumb and writes created thumbnail files into it.

Special drop-in files

You can see all possible drop-in files used in WordPress in the _get_dropins() function. Here they are in the form of a table:

File Description Loaded Type
advanced-cache.php Advanced caching plugin. If WP_CACHE is true Normal
db.php Custom database class. on load Normal
db-error.php Custom database error message. on error Normal
install.php Custom installation script. on install Normal
maintenance.php Custom maintenance message. on maintenance Normal
object-cache.php External object cache. on load Normal
php-error.php Custom PHP error message. on error Normal
fatal-error-handler.php Custom PHP fatal error handler. on error Normal
sunrise.php Executed before Multisite is loaded. If SUNRISE is true Multisite
blog-deleted.php Custom site deleted message. on deleted blog Multisite
blog-inactive.php Custom site inactive message. on inactive blog Multisite
blog-suspended.php Custom site suspended message. on archived or spammed blog Multisite

Notes on drop-in:

  • When do drop-in modules load?
    Most drop-in plugins run before any other regular or MU plugin. In the table above, the Loaded column shows when each file is used.

  • Where can I see active drop-in modules?
    Plugins can be seen in the WordPress admin in the plugins page under Plugins > Drop-in.

  • How to activate, deactivate drop-in modules from the admin area?
    You cannot manage drop-in modules from the WordPress admin area, drop-in modules can only be managed on your server.

advanced-cache.php

Called at the earliest stage of loading WordPress, in the wp-settings.php file, if the WP_CACHE constant is enabled. This is what the call looks like:

/**
 * Filters whether to enable loading of the advanced-cache.php drop-in.
 *
 * This filter runs before it can be used by plugins. It is designed for non-web
 * run-times. If false is returned, advanced-cache.php will never be loaded.
 *
 * @since 4.6.0
 *
 * @param bool $enable_advanced_cache Whether to enable loading advanced-cache.php (if present).
 *                                    Default true.
 */
if ( WP_CACHE && apply_filters( 'enable_loading_advanced_cache_dropin', true ) && file_exists( WP_CONTENT_DIR . '/advanced-cache.php' ) ) {
	// For an advanced caching plugin to use. Uses a static drop-in because you would only want one.
	include WP_CONTENT_DIR . '/advanced-cache.php';

	// Re-initialize any hooks added manually by advanced-cache.php.
	if ( $wp_filter ) {
		$wp_filter = WP_Hook::build_preinitialized_hooks( $wp_filter );
	}
}

This file is used by page caching plugins. It usually checks for a suitable cache file, and if there is one, it is displayed and the script is terminated. This allows you not to load 90% of WordPress files and give static HTML files.

object-cache.php

Called from function wp_start_object_cache(), which in turn is called from the wp-settings.php file, a little later advanced-cache.php. Unlike advanced-cache.php, object-cache.php is always triggered if it exists. It is needed to override the operation of basic WordPress object caching.

The object caches based on this file are Memcache, Redis, Memcached, APC, XCache.

The call looks like this:

// Starts WordPress object caching or external object caching if a special file exists.
wp_start_object_cache();

Since WP 5.8 the enable_loading_object_cache_dropin hook has appeared which allows you to disable object caching plugin. The hook is invoked before the plugins are loaded and is needed when the code is launched not from the web, for example during tests.

maintenance.php

wp-content/maintenance.php is responsible for displaying a stub page that is shown when WoordPress auto-updates. Such a page is defined by default and the wp_maintenance() function is responsible for it. But if you create a file maintenance.php in wp-content, then the contents of that file will be responsible for the page's output.

In maintenance.php you need to describe the maintenance-page according to all HTML rules.

See function wp_maintenance() for details.

db-error.php

Allows you to display an arbitrary database error page template.

If the wp-content/db-error.php file exists in the wp-content, then this file will be loaded instead of the default WordPress database connection error message. In the file you need to create HTML code for the error page!

The connection error page should set the 500 response status, so that search engines do not process content.

File db-error.php is called by function dead_db(), and the function in turn is called when the database connection error occurs.

An example of such a page:

<?php
  // custom WordPress database error page
  // Edit by Kolshix

  // error variations
  // 500 software works, but there are serious internal problems that prevent queries from being processed correctly.
  // 503 indicates a large queue of requests on the server - when the server is overloaded there can be problems with the connection to the database

  header('HTTP/1.1 503 Service Temporarily Unavailable');
  header('Status: 503 Service Temporarily Unavailable');
  header('Retry-After: 600'); // 1 hour = 3600 seconds // inform the bot that it should try again after the set time

  // admin email notification
  // mail( '[email protected]', 'Database Error', 'There is a problem with the database!', 'From: Db Error Watching');
?>

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<title>Database Error</title>
	</head>
	<body>
		<center>
			<script>
				setTimeout( "window.location.reload()", 9000 );
			</script>

			<h1>Error Connection</h1>
			<style>
				a.button {
					color: white;
					text-decoration: none;
					user-select: none;
					background: rgb(76, 144, 232);
					padding: 9px;
					outline: none;
					border: 3px solid #3d6ea0;
					border-radius: 10px;
				}
				a.button:hover  { background: rgb(86, 218, 72); }
				a.button:active { background: rgb(76, 144, 232); }
			</style>

			<script type="text/javascript">
				function timer(){
				var obj=document.getElementById('timer_inp');
				obj.innerHTML--;
					if (obj.innerHTML==0){
						setTimeout(function(){},1000);
					} else {
						setTimeout(timer,1000);
					}
				}
				setTimeout(timer,1000);
			</script>

			<h1><div>Wait: <span id="timer_inp">9</span> Or <a href='<?php echo "http://".$_SERVER['HTTP_HOST'].$_SERVER["REQUEST_URI"]; ?>' class="button">Refresh</a></div></h1>
			<img itemprop="url" src="/err_c.gif" width="300" height="300" >
		</center>
	</body>
</html>

db.php

Allows you to rewrite the database engine. If the file exists in the wp-content folder, it will be called before creating a connection to the database. Further, if in this file variable $wpdb is defined, then it will be used as a global variable for work with the database.

With this logic, you can, for example, extend the base class wpdb{} or replace it completely.

An example of an extension for the base wpdb{} class.

<?php

// this is code for the db.php file, which is in the wp-content folder

defined( 'ABSPATH' ) || die();

if ( defined( 'DOING_CRON' ) && DOING_CRON )
	return;

class MY_DB extends wpdb {

	public function __construct( $dbuser, $dbpassword, $dbname, $dbhost ) {

		// additional code...

		parent::__construct( $dbuser, $dbpassword, $dbname, $dbhost );

	}

	// redefining the wpdb::query() method
	public function query( $query ) {

		// our modified code
	}

}

$wpdb = new QM_DB( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );

sunrise.php

Only loaded for multisite builds, i.e. when is_multisite() is triggered and SUNRISE constant is defined (it should be defined in wp-config.php).

File wp-content/sunrise.php allows you to change the logic of the site at an early stage in the network multisite. For example, here you can set global variables $current_site, $current_blog defining the current network site. Or you can change the prefix of the database tables - the variable $table_prefix.

Also in the unrise.php file you can change the constants that are responsible for where the directories of MU plugins or ordinary plugins are located. See wp_plugin_directory_constants().

sunrise.php connects even before SHORTINIT constant.

sunrise.php is connected in the wp-includes/ms-settings.php file, which in turn is connected in the main loading wp-settings.php file.

blog-deleted.php, blog-inactive.php, blog-suspended.php

It works only for multisite setup.

At the end of WP loading, after init hook but before wp_loaded hook there is a check: whether current blog status is deleted, archived or spam.

If it is in one of the status:

  • and if there is a corresponding file for the current blog status, it will be connected and the site will be terminated via die(). The corresponding files (see [ms_site_check( ms_site_check():

    • wp-content/blog-deleted.php - for status deleted = 1.
    • wp-content/blog-inactive.php - for status deleted = 2.
    • wp-content/blog-suspended.php - for status archived or spam.
  • and there is no special file for the current status of the blog, the GP also interrupt php through wp_die() with default message corresponding to the status of the site.

php-error.php

Responsible for displaying (template) of fatal error. Triggers when a fatal error occurs on the site and WP displays it.

Example of displaying a fatal error on the screen

The code for this file should completely replace the method: WP_Fatal_Error_Handler::display_default_error_template( $error, $handled ). The variables $error, $handled will be available in it as well as in the method.

WP since version 5.2 can handle fatal errors: it sends an email to the administrator and displays an error message on the screen. However, this is done only if the WP_SANDBOX_SCRAPING constant is NOT true. WP_SANDBOX_SCRAPING sets to true under certain conditions: e.g. activation of a plugin, editing php files in the admin area, etc.

fatal-error-handler.php

Allows to override the Fatal Error Handler class. By default the class WP_Fatal_Error_Handler{} is used.

The code for this file should return an object of the class with a handle() method in it.

Sample code for this file:

<?php
/**
 * Substitute for the class: WP_Fatal_Error_Handler()
 */

class My_Fatal_Error_Handler {

	public function handle() {
		// your code here
	}
}

return new My_Fatal_Error_Handler();

As a basis for creating such a class we should take the class WP_Fatal_Error_Handler{}.

Renaming or moving the wp-content folder

In some cases, for example, to unique many URLs of your site, or to combine site structure with another script, or for some other reason, it is necessary that the directory wp-content have a different name or that it is located in a different directory.

Moving or renaming wp-content is very simple. To do this, open the wp-config.php configuration file, which is in the root of your site, and define in it two constants:

  • WP_CONTENT_DIR - the path to the content directory.
  • WP_CONTENT_URL - URL to the content directory.
define( 'WP_CONTENT_DIR', $_SERVER['DOCUMENT_ROOT'] .'/data' );
define( 'WP_CONTENT_URL', 'http://'. $_SERVER['HTTP_HOST'] .'/data' );

This code renames wp-content to data.