File Structure

To keep things nice and consistent across all the different projects we’re working on, it’s helpful to follow the same general structure across projects.

General Structure General Structure

Assuming you’re working on a full site build, your project will need to include all of the configuration, bootstrap files, plugins, themes, and WordPress itself.

We typically use a WordPress Skeleton-derived structure, with the following top-level files and directories:

  • content/ – WordPress content directory, renamed from wp-content
    • hm-platform/ – Submodule containing the platform library
    • mu-plugins/ – Must-use plugins for the site.
    • plugins/ – Plugins for the site.
    • themes/ – Themes for the site.
  • wordpress/ – Submodule containing WordPress
  • wp-config.php – Main configuration file. Must correctly load server-wide configuration, see below.
  • – Project readme. Use the readme creator to get a nice-looking readme.

hm-base can be used as a starting point for new projects. When using this, don’t forget to update the submodules, including hm-platform and wordpress.

Optionally, you may also wish to have the following:

  • .config/ – If your wp-config.php is starting to get complex, consider breaking it into multiple files inside a .config directory.
  • .tests/ – Test setup, bootstrap files, configuration, and other tools can live in a separate directory to clean up the root project directory.
  • .build-script – Bash build script

Code Location Code Location

When working on a new piece of code, you need to decide where in the project it should live. Code typically fits into one of three buckets: must-use plugins, plugins, or themes. (Note: “plugins” can refer to must-use plugins or regular plugins.)

Typically, code in themes should contain only code related to the frontend. This includes obvious things like HTML output, enqueueing static assets, etc, but can also include significant backend code where it is used only for the frontend. For example, code that transforms internal menu data into a more-easily usable structure for JS would live here.

In most projects, the bulk of the backend code should live inside mu-plugins as separate modules. Using mu-plugins means the code can assume the whole codebase is active, and ensures that code can be separated as desired by engineers.

Each mu-plugin directory should contain a main plugin file with a plugin comment header, just like regular plugins. These directories should typically be named {project}-{module name}, as you may also want to add common libraries (such as hm-rewrite). The mu-plugin main file needs to be loaded manually by adding the file to mu-plugins/loader.php‘s array:

$hm_mu_plugins = [

(This loader file also ensures that mu-plugins are displayed in the UI on the Plugins page.)

On most projects, the plugins directory only contains third-party plugins. For multisite projects, it should also contain per-site functionality that may need to be enabled only for certain sites.

Plugins should be generally focussed on single, modular pieces of functionality. They should be focussed on a single feature rather than by technology; for example, rather than a plugin containing all your rewrite rules, you should have rewrite functionality in each feature’s plugin. You might also want to have helper functionality in a central place (for example, rewrite registration tools), but this should be structured as a library and do nothing independently.

Plugin Structure Plugin Structure

Plugins should follow a consistent file structure:

  • plugin.php – The main plugin file.
  • inc/ – The plugin’s backend/PHP code.
  • assets/ – Directory containing any static assets:
    • css/ – CSS to be served to the frontend.
    • scss/ – SCSS source files, if in use.
    • js/ – JavaScript files.
    • images/ – Images.
  • tests/ – PHP test files.
  • – Plugin readme, if desired.

Plugins with complex JavaScript frontends (such as React single-page apps) may wish to use a slightly different structure for frontend assets. This structure better matches the ecosystem tooling:

  • src/ – Source JavaScript and CSS files.
  • build/ – Built assets, typically generated by Webpack and Babel
  • tests/ – Test files.
    • js/ – JavaScript test files.
    • php/ – PHP test files.

Instead of duplicating your src/ folder structure in a top-level test directory, in many React projects JavaScript unit tests are colocated alongside their associated module. Because this differs from our PHP testing conventions, electing this scheme is left to teams’ preference.

plugin.php <code>plugin.php</code>

Your main plugin.php file should include the plugin header comment and any plugin constants (such as const FILE = __FILE__ for easiest use of plugins_url() and related functions). It should load in any namespace files, register any autoloaders, and handle the initial hooking into WordPress.

This file should be the only file to contain side-effects (function calls outside a function).

For complex plugins, you may want to move the actual add_action/add_filter calls into a main function in the namespace, and simply call that function from plugin.php. (This function is typically named bootstrap() or setup() to indicate it is responsible for setting up the plugin’s behaviour.)

For super-simple plugins with only a handful of functions, you can declare functions in this file as well. This should be done sparingly, as you’ll need to ignore the coding standards checks on the file; if in doubt, split the functions out.

inc <code>inc</code>

Your backend PHP code should live inside the inc directory. You’ll typically have a main namespace file with any common functions or bootstrapping code, then files for classes and sub-namespaces as needed.

Files should be organised hierarchically based on namespaces. If a namespace only contains functions its filename may be {namespace}.php, as in the Baz or Foo namespace examples below. If the namespace also contains classes, it is best to group those classes together with the namespace file in a {namespace}/ directory: in this case you can title the main namespace file {namespace}/namespace.php, as in the Bar namespace below, while classes should be in a file prefixed with class- with a “slugified” class name.

For example, if your plugin’s namespace is \ProjectName\ExamplePlugin:

  • inc/ – Contains the \ProjectName\ExamplePlugin namespace.
    • namespace.php – Contains \ProjectName\ExamplePlugin functions.
    • baz.php – Contains \ProjectName\ExamplePlugin\Baz functions.
    • class-foo.php – Contains the \ProjectName\ExamplePlugin\Foo class.
    • bar/ – Contains the \ProjectName\ExamplePlugin\Bar namespace.
      • namespace.php – Contains \ProjectName\ExamplePlugin\Bar functions.
      • foo.php – Contains \ProjectName\ExamplePlugin\Bar\Foo functions.
      • class-some-thing.php – Contains the \ProjectName\ExamplePlugin\Bar\Some_Thing class.

Theme Structure Theme Structure

Themes follow a similar structure to plugins, however the main file should be functions.php rather than plugin.php:

  • functions.php – The main theme file.
  • style.css – The main CSS file.
  • inc/ – The theme’s backend/PHP code.
  • assets/ – Directory containing any static assets:
    • css/ – CSS to be served to the frontend.
    • scss/ – SCSS source files, if in use.
    • js/ – JavaScript files.
    • images/ – Images.
  • template-parts/ – Common template parts.
  • tests/ – PHP test files.
  • – Theme readme, if desired.
  • *.php – Other templates for WordPress’ template hierarchy

Similar to plugins, you may wish to use an alternative structure for static assets if using a modern JavaScript framework such as React.

functions.php <code>functions.php</code>

Your main functions.php file should follow the same guidelines as a plugin’s plugin.php. You do not need to include a header comment, as style.css is used for this purpose instead.

style.css <code>style.css</code>

Your main style.css file should contain the theme’s header comment.

It may also contain the CSS for the project, however typically we use assets/css/ for this purpose to better fit with build tools.