Skip to content
ExponaBLOG
WORDPRESS DEVELOPMENT & PLUGINS · DAY 003 / 100

WordPress file structure explained: a step-by-step guide

Where everything lives.

Expona Admin6 min read

WordPress file structure explained: a step-by-step guide

When you inherit a WordPress site or start building plugins for production, understanding the directory layout becomes your foundation for everything that follows. Misplacing a file or editing the wrong location can break your deployment pipeline or cause your updates to vanish. Let's walk through the key folders and files you'll actually work with, so you know exactly where everything lives and why it matters.

The root directory: your starting point

When you SSH into a WordPress installation, you'll land in a directory containing several critical files and folders. The root gives you immediate access to wp-config.php, .htaccess, wp-settings.php, and the three main content folders. This is where WordPress boots up, and understanding what sits here helps you configure environments properly and avoid accidentally shipping hardcoded secrets to production.

Most of the root files are either core WordPress files (which you should never touch) or configuration files you'll need to maintain. For example, if you're setting up a staging environment, you'll copy the entire root but then regenerate wp-config.php with different database credentials and debug settings.

wp-content holds themes, plugins and uploads

The wp-content folder is where customization lives. Inside you'll find three main subdirectories that you'll interact with constantly:

  • themes/ — Your active theme and any child themes
  • plugins/ — Every installed plugin, each in its own folder
  • uploads/ — All user-uploaded media files, organized by year and month

This folder is the only part of WordPress you should be actively developing in. When you create a custom plugin, it goes here. When you build a child theme, it goes here. When users upload images through the media library, they end up here. This separation is intentional—it makes upgrading WordPress core safe because you're never modifying anything in the root or wp-admin.

Let's say you're building a custom e-commerce integration plugin. You create the folder structure like this:

php
wp-content/
└── plugins/
    └── custom-ecommerce-sync/
        ├── custom-ecommerce-sync.php
        ├── admin/
        │   ├── class-settings.php
        │   └── settings-page.php
        ├── includes/
        │   ├── class-api-client.php
        │   └── class-sync-worker.php
        ├── assets/
        │   ├── css/
        │   ├── js/
        │   └── images/
        └── README.md

The main file custom-ecommerce-sync.php contains your plugin header and hooks. Everything else is organized by responsibility. WordPress automatically loads this plugin once it's activated, and all your code lives isolated from other plugins and core.

wp-config.php stores configuration

This is the single most important file on your server. It contains your database credentials, authentication keys, debug settings, and environment-specific constants. You'll never commit this file to version control—it should be in your .gitignore immediately.

Here's what a typical wp-config.php looks like for a development environment:

php
// ** Database settings ** //
define( 'DB_NAME', 'wordpress_dev' );
define( 'DB_USER', 'wp_user' );
define( 'DB_PASSWORD', 'your_password_here' );
define( 'DB_HOST', 'localhost' );

// Debugging
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', true );

// Security keys
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );

// Environment
define( 'WP_ENV', 'development' );

In production, you'll use environment variables instead of hardcoding values. Many teams load these from a secrets manager or pass them through container orchestration:

php
define( 'DB_NAME', getenv( 'DB_NAME' ) );
define( 'DB_USER', getenv( 'DB_USER' ) );
define( 'DB_PASSWORD', getenv( 'DB_PASSWORD' ) );
define( 'WP_DEBUG', 'production' !== getenv( 'WP_ENV' ) );

This file is read first when WordPress boots, so any define() statement here can be checked anywhere in your plugin code. Use custom constants here for feature flags, API endpoints, or anything environment-specific.

Never edit core files

The folders wp-admin, wp-includes, and the root PHP files like wp-load.php, index.php, and wp-settings.php are all core WordPress files. Editing them directly is tempting when you're in a hurry, but it breaks your update strategy and creates technical debt that multiplies over time.

Here's why: when you update WordPress from 6.3 to 6.4, the update process replaces all core files. If you've modified anything in wp-admin or wp-includes, your changes get overwritten. You then spend hours reapplying them to the new version, and every future update becomes a manual merge conflict. On a team managing dozens of sites, this becomes unsustainable.

Instead, use hooks and filters to modify WordPress behavior. If you need to change how posts are displayed, use the_content filter. If you need to add admin pages, use admin_menu hook and build your page in wp-content/plugins. If you need custom post types, register them via init hook, again from your plugin file.

The only exception is wp-config.php and .htaccess, which are your responsibility to maintain and configure. Everything else is off-limits.

mu-plugins load automatically

The wp-content/mu-plugins folder (mu stands for "must-use") is where you place plugins that should always load without needing activation. This folder doesn't exist by default—you create it yourself.

WordPress loads files from mu-plugins alphabetically before loading regular plugins, and they're always active. You can't deactivate them through the admin interface. This makes them perfect for:

  • Network-wide functionality on multisite installations
  • Critical security fixes that can't be accidentally disabled
  • Shared functionality used across multiple sites or plugins
  • Environment-specific configuration that bridges wp-config.php and plugins

Here's a practical example: you run multiple WordPress sites and want to ensure all of them send errors to a centralized logging service. You create an mu-plugin:

php
 SENTRY_DSN,
        'environment' => getenv( 'WP_ENV' ),
    ) );

    set_error_handler( function( $errno, $errstr, $errfile, $errline ) {
        Sentry\captureException(
            new Exception( "[$errno] $errstr in $errfile:$errline" )
        );
    } );
}

Drop this in wp-content/mu-plugins/error-logging.php, and it runs on every request across all your sites. No activation needed. You define SENTRY_DSN in wp-config.php or as an environment variable, and centralized error tracking is live.

The catch: since these files load before regular plugins, they can't depend on plugins being active. Keep mu-plugins focused and lean.

Other important directories

Beyond the main three, you should know about:

  • wp-admin/ — The WordPress admin dashboard code. Never modify this.
  • wp-includes/ — Core WordPress functions and classes. Never modify this.
  • wp-content/languages/ — Translation files. WordPress creates this automatically when you install a language pack.

Common mistakes

Editing wp-settings.php or index.php directly. This breaks during updates. Use hooks instead. If you need to customize the homepage, use the homepage template system or filters, not root file edits.

Putting custom code in wp-config.php. This file should contain only define() statements and requires for bootstrapping. Complex logic belongs in mu-plugins or regular plugins.

Uploading files manually to wp-content/uploads/. While it works in a pinch, the media library has a standardized folder structure (year/month). Bypass it and you break WordPress's media queries and organization.

Creating plugin folders without a main plugin file. WordPress finds plugins by looking for the main plugin file with a header comment. A folder without a properly formatted PHP file won't be recognized.

Ignoring wp-config.php in version control. Always add it to .gitignore. Use environment variables and a template file instead, so new team members know what variables to set.

Key takeaways

  • wp-content/ is your sandbox—themes, plugins, and uploads all belong here and nowhere else.
  • wp-config.php is your single source of truth for environment configuration; never hardcode secrets.
  • Core files in wp-admin/, wp-includes/, and the root should never be edited; use hooks and filters instead.
  • mu-plugins/ are automatically loaded plugins perfect for shared functionality and network-wide features.
  • Understanding this structure lets you safely upgrade WordPress, collaborate with teams, and deploy with confidence.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles