How to structure folders for modern WordPress plugins

My opinion on how to best structure a WordPress plugin folder, considering modern tools and best practices from other developers.

If you’re starting a new WordPress plugin project or trying to refactor, you might have found yourself asking this question:

What does God need with a starship?

Captain James Tiberius Kirk

The second most important question might be: How should I organize my plugin folders?

The “classic” way of structuring folders

If you check out the official Plugin Developer Handbook, it provides this outline:

/plugin-name
     plugin-name.php
     uninstall.php
     /languages
     /includes
     /admin
          /js
          /css
          /images
     /public
          /js
          /css
          /images

Well there’s the answer. That was easy, right? Thanks for reading.

There’s a problem though with this structure: If you use any of the command line interface (CLI) tools WordPress provides, they don’t structure plugins this way. Instead they use a more modern approach, one using create-block in Node and the other using scaffold using the WP CLI.

Two more modern approaches

I haven’t found any documentation that shows what folder structure these two commands create, so I’ll just show you here.

@wordpress/create-block

To use create-block, you’ll need to have Node and NPM installed, and have your terminal path in the plugins folder of whatever WordPress site you’re working on. Then enter:

npx @wordpress/create-block plugin-name

The result should be a folder structure like this:

/plugin-name
     plugin-name.php
     /build
     /src

Whoa, why the huge difference from what’s described in the handbook? It’s because the create-block script is designed to create plugins for blocks, which uses a special configuration of Webpack to compile the JavaScript (or TypeScript if you prefer).

wp scaffold plugin

The WP CLI also provides a script for generating a plugin template. If you are using Local WP, you can do this easily by running a site, and then selecting Open site shell. Then enter:

wp scaffold plugin plugin-name

The result should be this folder structure:

/plugin-name
     plugin-name.php
     /bin
     /tests

Unlike the create-block command, the scaffold plugin command barely structures anything for you.

And it has Gruntfile.js, which immediately tells me the age of this command. There’s nothing wrong with Grunt, but I was using it almost a decade ago. Considering that create-block uses the more modern Webpack, I was surprised to find that the WP CLI was still using this.

I honestly wouldn’t use the scaffold plugin anymore. Instead, you can do what I’ve done: use create-block and then make a few modifications.

A modern folder structure

Right now I’m working on a simple plugin that will show developers the different UI styles and components that ship with WordPress. The project mostly uses React and TypeScript, with minimal PHP.

To get started, I used create-block to build the initial structure, then made some additions based on Web Dev Simplified’s recent post on structuring a React app.

/plugin-name
     plugin-name.php
     /build
     /src
          /data
          /features
          /pages

Here’s what goes in what:

  • Data: JSON files containing static data, such as for color swatches or dummy form options.
  • Features: This is where I put components together that are used in the app. Currently, I have a forms folder containing a number of grouped form components that are imported from @wordpress/components.
  • Pages: These are the pages for the routes within the app.

Since this plugin is going to be visible only in the

Now if this plugin wasn’t admin-only, I would create subfolders under src for admin and public, and then extend the Webpack configuration. I’m actually doing that for another project. But for this plugin, since everything is on the admin side of WordPress, I don’t need the extra folder structure.

Next time I’ll share what’s different in the plugin PHP file that makes it function like an actual plugin instead of a block.