In the dynamic world of WordPress development, efficiency and automation are key to staying ahead. Deploying your theme to the WordPress.org repository can be a meticulous process, but with GitHub Actions, you can automate and streamline this workflow. This guide will walk you through setting up GitHub Actions to deploy your WordPress.org theme, addressing common questions, and providing valuable insights to enhance your development process.
Introduction to GitHub Actions for WordPress Theme Deployment
GitHub Actions is a powerful tool that enables developers to automate, customize, and enhance their software development workflows directly within GitHub. For WordPress theme developers, this means you can automate the deployment of your themes to the WordPress.org repository, reducing manual effort and minimizing errors.
By leveraging GitHub Actions, you can set up a continuous integration and deployment (CI/CD) pipeline that automatically pushes your theme updates to the WordPress.org repository whenever you create a new release. This not only saves time but also ensures that your users always have access to the latest features and improvements.
Setting Up GitHub Actions for Theme Deployment
To deploy your WordPress theme using GitHub Actions, follow these steps:
1. Prepare Your Theme Repository
Ensure your theme’s code is hosted on GitHub and that your repository follows the standard structure. This includes having a style.css file with the necessary theme headers and a functions.php file.
2. Create the .distignore File
The .distignore file specifies which files and directories should be excluded from the deployment. Common exclusions include development files, tests, and configuration files not required for the theme’s functionality. For example:
node_modules/
tests/
.gitignore
package.json
3. Set Up Secrets in GitHub
To authenticate with the WordPress.org Subversion (SVN) repository, you’ll need to set up the following secrets in your GitHub repository:
SVN_USERNAME: Your WordPress.org username.SVN_PASSWORD: Your WordPress.org password.
To add these secrets, navigate to your repository’s settings, select “Secrets and variables,” then “Actions,” and click on “New repository secret.”
4. Create the GitHub Actions Workflow
In your repository, create a .github/workflows/deploy.yml file with the following content:
name: Deploy Theme to WordPress.org
on:
push:
tags:
- '*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: WordPress.org Theme Deploy
uses: actions/[email protected]
env:
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SLUG: your-theme-slug
This workflow triggers on every push to a tag (e.g., when you create a new release). It checks out your repository and uses the wordpress-theme-deploy action to deploy your theme to WordPress.org. Ensure you replace your-theme-slug with your actual theme slug.
Frequently Asked Questions
How do I handle build processes before deployment?
If your theme requires a build process (e.g., compiling Sass or JavaScript), you can add steps before the deployment step in your workflow. For example:
- name: Install Dependencies
run: npm install
- name: Build Assets
run: npm run build
These steps ensure that your assets are built before the theme is deployed.
Can I deploy only specific branches?
Yes, you can configure the workflow to trigger on specific branches by modifying the on section:
on:
push:
branches:
- main
This configuration triggers the workflow only when changes are pushed to the main branch.
How do I exclude files from deployment?
The .distignore file is used to exclude files and directories from deployment. Ensure this file is in your repository’s root and lists all items to be excluded.
Is it secure to store SVN credentials in GitHub?
Storing credentials as GitHub Secrets is a secure practice. GitHub encrypts and stores these secrets securely, making them accessible only to workflows in your repository.
What if the deployment fails?
If the deployment fails, GitHub Actions provides logs that can help you diagnose the issue. Common issues include incorrect SVN credentials, network problems, or issues with the theme’s code.
Best Practices for Theme Deployment
- Version Control: Use Git tags to manage versions of your theme. This practice helps in tracking changes and rolling back if necessary.
- Automated Testing: Incorporate automated tests into your workflow to catch potential issues before deployment.
- Documentation: Maintain clear documentation for your theme, including setup instructions and changelogs.
- Regular Updates: Keep your theme updated with the latest WordPress standards and practices to ensure compatibility and security.
- Community Engagement: Engage with your theme’s users and the broader WordPress community to gather feedback and improve your theme.
Conclusion
Automating your WordPress theme deployment using GitHub Actions can significantly enhance your development workflow, ensuring efficiency, accuracy, and consistency. By following the steps outlined in this guide, you can set up a robust deployment pipeline that saves time and reduces the potential for errors.
In a WordPress settings page 3rd-party developers can inject their own settings fields / HTML with action hooks, but you cannot do the same if you decide to build your plugin’s settings page in React.
This article explores the options to make our React Components extendible such that 3rd-party developers can easily inject their own components in them.
We are going to use SlotFillProvider, Slot, Fill and PluginArea Gutenberg React Components.
Part 1: Building a simple settings page
We’re going to build a very simple settings page. For the sake of simplicity of this article, we won’t complicate it by writing any server-side logic to save the fields, nor will cover managing the state of our application because our focus is just rendering our components and allowing other developers to render their own components.
So, we will create an AdminSettings Component that renders First Name, Last Name text fields and a Save button.

import { __ } from '@wordpress/i18n';
import {
BaseControl,
TextControl,
Button,
} from '@wordpress/components';
export const AdminSettings = () => {
return (
<>
<h1>{ __( 'Settings using Gutenberg components' ) }h1>
<BaseControl label={ __( 'First Name' ) }>
<TextControl/>
BaseControl>
<BaseControl label={ __( 'Last Name' ) }>
<TextControl/>
BaseControl>
<br />
<Button variant='primary'>
{ __( 'Save' ) }
Button>
>
);
};
Understanding Slot and Fill Components
If we were to compare the behavior with the PHP hooks, the I’d say Slot is similar to what do_action() does and Fill is similar to add_action().
As a plugin developer, you usually provide both the Slot and Fill. 3rd-party developers will inject their Components in this Slot using Fills. But we will do it differently. Instead of relying on other developers to implement Fills, we will expose a component on the global window object that implement Fills.
A Slot and a Fill is connected through the name attribute. For example:
<Slot name="additional-fields-area"/>
<Fill name="additional-fields-area">
<TextControl label="Injected field"/>
Fill>They are only connected if both share the same value for the name attribute.
Understanding SlotFillProvider
SlotFillProvider is a Context provider for Slot and Fill. When you’re using Slot-Fill, you must ensure that both Slot and Fill are inside the SlotFillProvider, else it won’t work.
When you’re building an extendible Gutenberg block, you don’t need to use SlotFillProvider because the entire Gutenberg editor Component is already inside it. But if you’re building an extendible component which will be used outside of the Gutenberg editor, then you must use it.
Understanding how Fill works
As long as Fill is inside the SlotFillProvider, you can render it anywhere, it doesn’t matter. Whatever is between the Fill open and close tags will always be rendered where the connected Slot is.
The SlotFill mechanism is built using React Portals.
Part 2: Adding a Slot to our Settings Component
import { __ } from '@wordpress/i18n';
import { PluginArea } from '@wordpress/plugins';
import {
BaseControl,
TextControl,
Button,
SlotFillProvider,
Slot
} from '@wordpress/components';
export const AdminSettings = () => {
return (
<SlotFillProvider>
<h1>{ __( 'Settings using Gutenberg components' ) }h1>
<BaseControl label={ __( 'First Name' ) }>
<TextControl/>
BaseControl>
<BaseControl label={ __( 'Last Name' ) }>
<TextControl/>
BaseControl>
<Slot name="gutenberg-settings-additional-fields" />
<br />
<Button variant='primary'>
{ __( 'Save' ) }
Button>
SlotFillProvider>
);
};Great! We’re already halfway into building an extendible component.
Now remember I said in the previous point – Fill has to be inside the SlotFillProvider for this to work. You might wonder, how can 3rd-party developers add a Fill here? This will be explained next.
Revising registerPlugin
Gutenberg editor provides a number of SlotFills to allow developers to inject their Components in specific areas of the editor. You may have already used some of them already. If you remember, you may have done something like:
import { registerPlugin } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import { PluginPostPublishPanel } from '@wordpress/editor';
registerPlugin( 'third-party-plugin', {
render: InjectSomeText,
} );
function InjectSomeText() {
return (
<PluginPostPublishPanel>
<p>Post Publish Panelp>
PluginPostPublishPanel>
);
}
Gutenberg provides the following SlotFills:
- MainDashboardButton
- PluginBlockSettingsMenuItem
- PluginDocumentSettingPanel
- PluginMoreMenuItem
- PluginPostPublishPanel
- PluginPostStatusInfo
- PluginPrePublishPanel
- PluginSidebar
- PluginSidebarMoreMenuItem
If InjectSomeText does not return at least one of these, then whatever the component returns won’t be rendered anywhere. So it is clear – The Components rendered by registerPlugin must return at least one of the above.
But why is that? Why does registerPlugin refuse to render a Component that does not return one of the core SlotFills?
This is because, the core SlotFills return Fill components that are connected to the pre-defined Slot areas. And these Fill components are rendered inside PluginArea components.
Understanding PluginArea
The registerPlugin function is very closely related to the PluginArea component and they work together. Let’s try to understand it with a practical example.
import { __ } from '@wordpress/i18n';
import {
BaseControl,
TextControl,
Button,
SlotFillProvider,
Slot,
Fill
} from '@wordpress/components';
export const AdminSettings = () => {
return (
<SlotFillProvider>
<h1>{ __( 'Settings using Gutenberg components' ) }h1>
<BaseControl label={ __( 'First Name' ) }>
<TextControl/>
BaseControl>
<BaseControl label={ __( 'Last Name' ) }>
<TextControl/>
BaseControl>
<Slot name="gutenberg-settings-additional-fields" />
<br />
<Button variant='primary'>
{ __( 'Save' ) }
Button>
SlotFillProvider>
);
};
window.PluginGutenbergSettingsFields = ( { children } ) => {
return (
<>
<Fill name="gutenberg-settings-additional-fields">
{ children }
Fill>
>
);
};Similar to how core provides us with a list of SlotFills, we created a custom SlotFill Component called PluginGutenbergSettingsFields and exposed it on the global window object, so that other developers can easily use it with registerPlugin.
But you might wonder, wait! Didn’t I mention previously that Fill should be inside the same SlotFillProvider ? If other 3rd-party developers use PluginGutenbergSettingsFields to inject code, where would it be rendered? – That’s a good question!
This is where, PluginArea is important!
import { __ } from '@wordpress/i18n';
import { PluginArea } from '@wordpress/plugins';
import {
BaseControl,
TextControl,
Button,
SlotFillProvider,
Slot,
Fill
} from '@wordpress/components';
export const AdminSettings = () => {
return (
<SlotFillProvider>
<h1>{ __( 'Settings using Gutenberg components' ) }h1>
<BaseControl label={ __( 'First Name' ) }>
<TextControl/>
BaseControl>
<BaseControl label={ __( 'Last Name' ) }>
<TextControl/>
BaseControl>
<Slot name="gutenberg-settings-additional-fields" />
<PluginArea/>
<br />
<Button variant='primary'>
{ __( 'Save' ) }
Button>
SlotFillProvider>
);
};
window.PluginGutenbergSettingsFields = ( { children } ) => {
return (
<>
<Fill name="gutenberg-settings-additional-fields">
{ children }
Fill>
>
);
};I will explain the entire flow in the next section.
Part 3: Extending as a 3rd-party developer
As a 3rd-party developer, I would like to add a ToggleControl Component to the settings page.

mport { registerPlugin } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import {
BaseControl,
ToggleControl
} from '@wordpress/components';
const { PluginGutenbergSettingsFields } = window;
registerPlugin( 'third-party-plugin', {
render: InjectAdditionalFields,
scope: PluginGutenbergSettingsFields.scope // Ignore this for now.
} );
function InjectAdditionalFields() {
return (
<PluginGutenbergSettingsFields>
<BaseControl label={ __( 'Activate Account?' ) }>
<ToggleControl/>
BaseControl>
PluginGutenbergSettingsFields>
);
}Summary:
– registerPlugin renders InjectAdditionalFields where the PluginArea component is.
– PluginArea renders a hidden div which renders the contents of InjectAdditionalFields.
– InjectAdditionalFields returns our custom SlotFill PluginGutenbergSettingsFields.
– PluginGutenbergSettingsFields renders Fill next to the Slot and also inside the same SlotFillProvider
– Slot with the help of SlotFillProvider renders content that is between the Fill tags.
Extra: Exposing application data to 3rd party components
Obviously, the Toggle Field will require access to the AdminSettings state data. For example, if you want to make the Toggle Field conditional depending on the value of some other field?
Slot component accepts a prop called as fillProps. This is how you do it:
But to access this data, AdminSettings, PluginGutenbergSettingsFields and InjectAdditionalFields needs to be updated like the following:
import { useState } from '@wordpress/element';
export const AdminSettings = () => {
const [ appData, setAppData ] = useState( { fname: 'Siddharth', lname: 'Thevaril' } );
return (
<SlotFillProvider>
<h1>{ __( 'Settings using Gutenberg components' ) }h1>
<BaseControl label={ __( 'First Name' ) }>
<TextControl value={ appData.fname }/>
BaseControl>
<BaseControl label={ __( 'Last Name' ) }>
<TextControl value={ appData.lname }/>
BaseControl>
<Slot name="gutenberg-settings-additional-fields" fillProps={ { appData, setAppData } } />
<PluginArea />
<br />
<Button variant='primary'>
{ __( 'Save' ) }
Button>
SlotFillProvider>
);
};
window.PluginGutenbergSettingsFields = ( { children } ) => {
return (
<>
<Fill name="gutenberg-settings-additional-fields">
{ ( fillProps ) => children( fillProps ) }
Fill>
>
);
};And for the plugin:
function InjectAdditionalFields() {
return (
<PluginGutenbergSettingsFields>
{
( fillProps ) => {
return (
<BaseControl label={ __( 'Activate Account?' ) }>
<ToggleControl />
BaseControl>
)
}
}
PluginGutenbergSettingsFields>
);
}If your settings page is large and the app data is complex, I would suggest to implement a separate data store instead of passing app state via fillProps.
Automating the deployment of your WordPress plugin to the WordPress.org repository can save you time and ensure a smooth release process. With GitHub Actions and the 10up/action-wordpress-plugin-asset-update action, you can streamline this process. Here’s how to set it up:
Step 1: Prepare Your Plugin
Ensure your plugin follows WordPress.org guidelines and includes the necessary files (readme.txt, plugin header, etc.).
Step 2: Create GitHub Repository
Host your plugin code in a GitHub repository. Make sure your repository is public.
Step 3: Set Up GitHub Actions Workflow
In your GitHub repository, create a .github/workflows/deploy.yml file with the following configuration:
name: Deploy to WordPress.org
on:
push:
tags:
- '*'
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Run build script
run: |
# Add your build scripts here, e.g., npm install && npm run build
- name: WordPress Plugin Asset Update
uses: 10up/[email protected]
with:
# Your WordPress.org plugin repository name
plugin-slug: your-plugin-slug
# WordPress.org username and password (use GitHub Secrets)
svn-username: ${{ secrets.WP_ORG_USERNAME }}
svn-password: ${{ secrets.WP_ORG_PASSWORD }}Step 4: Configure Secrets
In your GitHub repository, go to Settings > Secrets and variables > Actions > New repository secret and add the following secrets:
WP_ORG_USERNAME: Your WordPress.org username.WP_ORG_PASSWORD: Your WordPress.org password.
Step 5: Push Tags to Trigger Deployment
Tag your release in GitHub to trigger the workflow:
git tag v1.0.0
git push origin v1.0.0This will initiate the GitHub Actions workflow, running the build script and deploying your plugin to the WordPress.org repository.
Conclusion
Using GitHub Actions to deploy your WordPress plugin simplifies the release process, making it more efficient and reliable. By following the steps above, you can automate your deployments and focus more on developing great plugins.
For more detailed information, check out the 10up/action-wordpress-plugin-asset-update documentation.
Creating a custom admin page using React in WordPress can enhance the functionality and user experience of your site. In this guide, we’ll use @wordpress/element for React, and @wordpress/components for UI elements. We’ll also set up a minimal workable Webpack configuration.
Step 1: Install Necessary Packages
First, ensure you have Node.js and npm installed. Then, install the required packages:
npm install @wordpress/element @wordpress/components @babel/preset-react @babel/preset-env webpack webpack-cli webpack-dev-server babel-loader --save-devStep 2: Set Up Your Project Structure
Create the following directory structure:
my-plugin/
├── admin
│ ├── build
│ │
│ └── src
│ └── index.js
├── package.json
├── webpack.config.js
└── wp-react.phpStep 3: Configure Webpack
Create webpack.config.js with the following configuration:
const path = require('path');
module.exports = {
entry: './admin/src/index.js',
output: {
path: path.resolve(__dirname, 'admin/build'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
},
devServer: {
contentBase: path.join(__dirname, 'admin/build'),
compress: true,
port: 9000
}
};Step 4: Create React Component
In admin/src/index.js, create a simple React component:
import { render } from '@wordpress/element';
import { Button } from '@wordpress/components';
const App = () => (
My Admin Page
<Button variant="primary" className="button default">Click MeButton>
);
render(<App />, document.getElementById('admin-page'));Step 5: Set Up package.json
In your package.json, add scripts for building and watching your files:
{
"name": "wp-react",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"build": "webpack --mode production",
"watch": "webpack --mode development --watch"
},
"keywords": [],
"author": "Kamal Hosen",
"license": "GPL-v2-or-later",
"devDependencies": {
"@babel/preset-env": "^7.24.7",
"@babel/preset-react": "^7.24.7",
"@wordpress/components": "^28.2.0",
"@wordpress/element": "^6.2.0",
"babel-loader": "^8.3.0",
"webpack": "^5.92.1",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^3.11.3"
}
}
Step 6: Enqueue Your Scripts in WordPress
In my-plugin.php, enqueue the compiled JavaScript file:
php
/**
* Plugin Name: WP React
* Description: A simple plugin to demonstrate how to use React in WordPress.
* Version: 1.0.0
* Author: Kamal Hosen
* Author URI: https://kamalhosen.com
* License: GPL2
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: wp-react
* Domain Path: /languages
* Requires at least: 5.2
* Requires PHP: 7.2
*
*/
function wp_react_enqueue_scripts() {
wp_enqueue_script(
'wp-react-admin-page',
plugins_url('admin/build/bundle.js', __FILE__),
['wp-element', 'wp-components'],
filemtime(plugin_dir_path(__FILE__) . 'admin/build/bundle.js'),
true
);
}
add_action('admin_enqueue_scripts', 'wp_react_enqueue_scripts');
function wp_react_admin_page() {
add_menu_page(
'WP React Admin Page',
'WP React',
'manage_options',
'wp-react',
'wp_react_render_admin_page',
'',
6
);
}
add_action('admin_menu', 'wp_react_admin_page');
function wp_react_render_admin_page() {
echo '';
}
?>
Step 7: Build and Watch Your Project
Run the following commands to build and watch your project:
npm run build
npm run watchConclusion
By following these steps, you can set up a React-based admin page in WordPress, leveraging modern JavaScript development tools like Webpack and Babel. This setup ensures your code is modular, maintainable, and easy to extend. Happy coding!
