Thank you for your interest in contributing to ThemePlus. Contributions of all kinds are welcome — bug reports, feature requests, documentation improvements, and pull requests.
Please take a moment to read this guide before opening an issue or submitting a PR.
- Code of Conduct
- Ways to Contribute
- Development Setup
- Project Structure
- Adding a New Field Type
- Coding Standards
- Commit Messages
- Pull Request Process
- Reporting Bugs
- Requesting Features
This project follows the WordPress Community Code of Conduct. By participating, you agree to uphold it. Please be respectful and constructive in all interactions.
- Report a bug — Open an issue with clear steps to reproduce
- Request a feature — Open an issue describing the use case and proposed solution
- Fix a bug — Fork, fix, and open a pull request
- Add a field type — See Adding a New Field Type below
- Improve documentation — Typos, clarity, missing examples — all appreciated
- Translate — Contribute translations via the
.potfile in/languages/
- Node.js 18+
- npm
- Local WordPress install (LocalWP recommended)
- PHP 8.0+
cd wp-content/plugins
git clone https://github.com/fronttheme/themeplus.git
cd themeplus
npm installAdd the following to your wp-config.php:
define( 'WP_DEBUG', true );
define( 'THEMEPLUS_DEV', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_ENVIRONMENT_TYPE', 'local' );
THEMEPLUS_DEVmust be the booleantrue— not the string"true".
ThemePlus uses a hybrid build system — Vite for SCSS and webpack/@wordpress/scripts for React/JSX.
# Start Vite dev server (SCSS with HMR — requires THEMEPLUS_DEV)
npm run dev
# Start webpack watch (JS/React)
npm run blocks:start
# Production build — SCSS via Vite
npm run build
# Production build — JS/React via wp-scripts
npm run blocks:build
# Generate translation .pot file
npm run pot
# Build and package release ZIP
npm run packageRun
npm run devandnpm run blocks:startin two separate terminals during development.
themeplus/
├── assets/ # Compiled assets (do not edit directly)
│ ├── css/admin.css # Built by Vite from src/scss/
│ ├── js/admin.js # Built by wp-scripts from src/js/
│ ├── js/admin.asset.php # wp-scripts dependency manifest
│ └── fonts/fontawesome/ # Bundled FontAwesome 6
├── includes/
│ ├── classes/
│ │ ├── core/ # Framework core classes
│ │ │ ├── class-themeplus-framework-config.php
│ │ │ ├── class-themeplus-config.php
│ │ │ ├── class-themeplus-settings.php
│ │ │ ├── class-themeplus-sanitizer.php
│ │ │ ├── class-themeplus-rest-api.php
│ │ │ ├── class-themeplus-admin.php
│ │ │ ├── class-themeplus-frontend.php
│ │ │ └── class-themeplus-dev-panel.php
│ │ └── custom-fonts/ # Custom Fonts module
│ │ ├── class-custom-fonts-manager.php
│ │ ├── class-custom-fonts-api.php
│ │ ├── class-custom-fonts-frontend.php
│ │ └── class-custom-fonts-mime-type.php
│ ├── config/
│ │ ├── default-config.php # Built-in sections (Custom Fonts, Import/Export, Dev Panel)
│ │ └── sample-config.php # Copy-and-customize template for theme developers
│ └── functions/ # Public helper and config functions
├── src/
│ ├── js/admin/
│ │ ├── components/
│ │ │ ├── Fields/ # One file per field type (30 components)
│ │ │ ├── Common/ # Shared UI — FieldRenderer, Dialog, Select, SearchResults
│ │ │ ├── Layout/ # Sidebar, Header, Body, Footer, MainWrapper
│ │ │ ├── Sections/ # Import/Export, Custom Font Uploader
│ │ │ └── DevPanel/ # Developer Panel components
│ │ ├── context/ # React Context — Settings, Theme
│ │ ├── hooks/ # Custom React hooks
│ │ ├── services/ # Google Fonts and Custom Fonts API services
│ │ ├── utils/ # fieldHelpers.js — conditional logic evaluation
│ │ └── App.jsx # Root application component
│ └── scss/ # SCSS source — 7-1 modular architecture
├── languages/ # themeplus.pot + translations
├── themeplus.php # Plugin entry point — constants and bootstrap
├── uninstall.php # Clean removal of all plugin data
└── includes/class-themeplus.php # Main singleton class and loader
ThemePlus is designed to make adding field types straightforward. Every new field type requires changes in six places — follow all six steps to keep the framework consistent.
Add a new file in src/js/admin/components/Fields/:
// src/js/admin/components/Fields/MyNewField.jsx
import { useState } from '@wordpress/element';
function MyNewField({ id, label, value = '', onChange, help = '' }) {
return (
<div className="tpo-field tpo-field--my-new-field">
<div className="tpo-field__header">
<label className="tpo-field__label" htmlFor={id}>{label}</label>
{help && <p className="tpo-field__help">{help}</p>}
</div>
<div className="tpo-field__control">
{/* Your field UI here */}
<input
id={id}
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</div>
</div>
);
}
export default MyNewField;Important: Do not copy the field's value prop into useState — derive directly from value and call onChange with the new value. Copying into state causes stale values after Reset and Import.
Add your export to src/js/admin/components/Fields/index.js:
export { default as MyNewField } from './MyNewField';Import and register in src/js/admin/components/Common/FieldRenderer.jsx:
import { MyNewField } from '../Fields';
const fields = {
// ...existing fields
my_new_field: MyNewField,
};Create a partial in src/scss/components/:
// src/scss/components/_my-new-field.scss
.tpo-field--my-new-field {
// Your styles here — use existing SCSS variables and mixins
}Import it in src/scss/admin.scss:
@use 'components/my-new-field';Add a case to includes/classes/core/class-themeplus-sanitizer.php inside sanitize_field():
case 'my_new_field':
return sanitize_text_field( (string) $value );For structured values (arrays), follow the pattern of existing types like sanitize_border() or sanitize_link(). Without this step, the field falls back to sanitize_text_field() — safe for strings but incorrect for arrays.
Every field type must have a documented, consistent return value. In your PR description, include:
- What the field returns (scalar or array shape)
- The default value when nothing is saved
- A usage example in a theme template
- Follow WordPress PHP Coding Standards
- PHP 8.0+ features are encouraged — type hints, union types, match expressions, named arguments
- All new classes use the singleton pattern consistent with existing core classes
- Use
str_contains(),str_starts_with(),str_ends_with()overstrpos()for PHP 8.0+ - Sanitize all input; escape all output —
esc_html(),esc_attr(),esc_url(),wp_kses_post() - Never use
extract()or short PHP tags - All user-facing strings use
__()oresc_html__()with thethemeplustext domain
i18n convention: ThemePlus translates its own strings with the themeplus domain. Any string passed in by a theme (via themeplus_framework_config() or themeplus_add_section()) is the theme's responsibility to translate in its own domain — the plugin treats these as opaque, already-translated strings.
- Functional components only — no class components
- Use
@wordpress/elementfor React imports (import { useState } from '@wordpress/element'), neverreactdirectly - Derive field display from the
valueprop — never copyvalueintouseStateat mount - Follow BEM naming:
.tpo-block__element--modifier - No jQuery — ever
- No
console.log()in production code — guard dev logging withisDev
- Follow the 7-1 modular architecture in
src/scss/ - One partial per component, prefixed with
_ - Use existing variables and mixins from
src/scss/abstracts/ - No hardcoded colour values — use CSS custom properties or SCSS variables
Use Conventional Commits format:
feat(fields): add DateRange field type
fix(conditions): correct empty operator for boolean false
refactor(sanitizer): extract sanitize_row() helper for repeater and group
docs(readme): update field value shapes table
chore(build): update vite to v7
Types: feat, fix, refactor, docs, chore, test, style
Scopes (optional but helpful): fields, conditions, admin, rest, sanitizer, fonts, build, readme
Shell quoting note: In zsh, use single quotes for commit messages containing ! (e.g. !empty, !contains) — the ! character triggers history expansion in double-quoted strings.
-
Fork the repository and create your branch from
develop:git checkout -b feat/your-feature-name develop
-
Follow the coding standards above
-
Test thoroughly:
- Test in both dev mode (
THEMEPLUS_DEV=true) and a production build (npm run build && npm run blocks:build) - Test Reset Section and Reset All — field values must update immediately without a page reload
- Test Import — importing a JSON snapshot must restore all field values immediately
- Test in both light and dark WordPress admin color schemes
- Test in both dev mode (
-
For new field types: run the ThemePlus Demo theme to verify your field appears in the All Values panel with the correct shape
-
Run Plugin Check against a built ZIP before submitting — target zero errors and zero warnings
-
Open a pull request against
develop(notmain) with:- A clear title
- Description of the change and why it is needed
- For new fields: the documented return value shape
- Screenshots or recordings for UI changes
- Notes on any breaking changes
-
Be responsive — PRs inactive for 30 days may be closed
Open an issue and include:
- ThemePlus version
- WordPress version
- PHP version
- Active theme name
- Steps to reproduce
- Expected vs actual behavior
- Browser console errors or PHP error log output (if any)
Open an issue describing:
- The use case — what problem does this solve?
- Your proposed solution
- Any alternatives you considered
Feature requests with a clear use case are far more likely to be implemented.
Thank you for helping make ThemePlus better for the WordPress community.
Made with ❤️ by Faruk Ahmed · fronttheme.com