WP Manifestindependent plugin directory
manifest / developer / wp-starter-plugin

My Plugin

A modern WordPress plugin boilerplate with tabbed settings UI

by ThachPN165 · github.com/thachpn165/wp-starter-plugin · website

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/thachpn165/wp-starter-plugin/archive/refs/heads/main.zip

WordPress Plugin Starter

A production-ready WordPress plugin boilerplate that provides a complete development environment out of the box. Built with modern standards (PSR-4 autoloading, WordPress Coding Standards, Vite for assets), this boilerplate helps you skip the setup and focus on building features. Easily extensible architecture with clean separation of concerns.

Requirements

  • WordPress 6.0+
  • PHP 7.4+
  • Composer
  • Node.js 18+
  • Docker (optional)

Quick Start

# Clone the repository
git clone https://github.com/thachpn165/wp-starter-plugin.git your-plugin-name
cd your-plugin-name

# Initialize your plugin
./scripts/init.sh

The init script will:

  1. Prompt for plugin name, slug, and namespace
  2. Replace all boilerplate strings
  3. Generate unique Docker ports (random, avoids conflicts)
  4. Create .env file with configuration
  5. Install Composer and NPM dependencies
  6. Build assets
  7. Optionally initialize a new git repository

After initialization:

# Start Docker
docker-compose up -d

# Check your unique ports in terminal output or .env file

Development

Docker Environment

Docker mounts dist/my-plugin/ (not source) to prevent WordPress from overwriting your files.

Development workflow:

# Terminal 1: Build + watch for changes
npm run dev:docker

# Terminal 2: Start Docker
docker-compose up -d

Or build once and start:

./scripts/build.sh build
docker-compose up -d

Docker commands:

docker-compose up -d      # Start
docker-compose down       # Stop
docker-compose logs -f    # View logs

Ports are defined in .env (generated by init.sh).

Build Commands

./scripts/build.sh build       # Build to dist/
./scripts/build.sh dev         # Build + watch for changes
./scripts/build.sh zip         # Create ZIP archive
./scripts/build.sh deploy-svn  # Deploy to SVN structure
./scripts/build.sh version X.X # Bump version
./scripts/build.sh clean       # Clean outputs

Code Quality

composer phpcs     # Run PHPCS
composer phpcbf    # Auto-fix errors
composer test      # Run tests

Assets

npm run dev        # Watch Vite assets only
npm run dev:docker # Build + sync watch for Docker dev
npm run dev:sync   # Sync watch only (no initial build)
npm run build      # Production build

Directory Structure

your-plugin/
├── src/
│   ├── Admin/
│   │   ├── AdminMenu.php       # Menu registration, AJAX handler
│   │   ├── SettingsPage.php    # Main settings page wrapper
│   │   └── Tabs/               # Modular tab components
│   │       ├── DashboardTab.php
│   │       ├── GeneralTab.php
│   │       ├── AdvancedTab.php
│   │       └── IntegrationsTab.php
│   ├── Core/           # Core classes
│   ├── PublicSide/     # Public functionality
│   ├── Interfaces/
│   └── Traits/
├── assets/
│   ├── src/            # Source (scss, js)
│   ├── css/            # Compiled CSS
│   └── js/             # Compiled JS
├── scripts/            # Build scripts
├── docker/             # Docker configs
├── dist/               # Build output (Docker mounts this)
└── languages/          # Translations

Features

  • Tabbed Settings UI: Dashboard, General, Advanced, Integrations tabs
  • Modular Tab Architecture: Each tab is a separate class for easy customization
  • AJAX Save: No page reload, toast notifications
  • Dashboard Tab: Plugin info, usage guides with accordion
  • Build Scripts: ZIP, SVN deploy, version bump
  • Docker Ready: Random ports, no conflicts with other projects

Examples

Adding a New Setting Field

In src/Admin/Tabs/GeneralTab.php, add to render() method inside the <table>:

<tr>
    <th scope="row">
        <label for="my_new_option"><?php esc_html_e( 'My New Option', 'your-plugin' ); ?></label>
    </th>
    <td>
        <input type="text" id="my_new_option" name="my_new_option"
            value="<?php echo esc_attr( $settings['my_new_option'] ?? '' ); ?>"
            class="regular-text" />
    </td>
</tr>

In src/Admin/AdminMenu.php, update ajax_save_settings() and sanitize_settings():

// In ajax_save_settings()
'my_new_option' => sanitize_text_field( wp_unslash( $_POST['my_new_option'] ?? '' ) ),

// In sanitize_settings()
$sanitized['my_new_option'] = sanitize_text_field( $input['my_new_option'] ?? '' );

// In get_default_settings()
'my_new_option' => '',

Adding a New Tab

  1. Create src/Admin/Tabs/CustomTab.php:
<?php
namespace ThachPN165\YourPlugin\Admin\Tabs;

defined( 'ABSPATH' ) || exit;

class CustomTab {
    public static function render( array $settings ): void {
        ?>
        <div class="my-plugin-tab-content" id="tab-custom">
            <h2><?php esc_html_e( 'Custom Settings', 'your-plugin' ); ?></h2>
            <table class="form-table">
                <!-- Your fields here -->
            </table>
        </div>
        <?php
    }
}
  1. Register in src/Admin/SettingsPage.php:
// Add import at top
use ThachPN165\YourPlugin\Admin\Tabs\CustomTab;

// Add to get_tabs()
'custom' => array(
    'label' => __( 'Custom', 'your-plugin' ),
    'icon'  => 'dashicons-star-filled',
),

// Add render call in render_content()
<?php CustomTab::render( $settings ); ?>

Adding Custom Admin Page

Create src/Admin/CustomPage.php:

<?php
namespace ThachPN165\YourPlugin\Admin;

class CustomPage {
    public static function render(): void {
        echo '<div class="wrap"><h1>Custom Page</h1></div>';
    }
}

Register in src/Admin/AdminMenu.php:

add_submenu_page(
    'your-plugin',
    __( 'Custom Page', 'your-plugin' ),
    __( 'Custom', 'your-plugin' ),
    'manage_options',
    'your-plugin-custom',
    array( CustomPage::class, 'render' )
);

License

GPL-2.0+