| name | 10up-plugin-development |
| description | Create WordPress plugins following 10up architecture patterns. Covers plugin structure, the 10up PHP framework module system, hooks, settings API, and security. Use when building new plugins or refactoring existing ones. |
| license | MIT |
| compatibility | WordPress 6.4+, PHP 8.0+, Composer recommended |
| globs | ["*.php","includes/**/*.php","src/**/*.php","plugin.php","functions.php"] |
| metadata | {"author":"10up","version":"1.0"} |
10up Plugin Development
This skill guides you through creating WordPress plugins following 10up architecture patterns and the 10up PHP framework.
When to Use
- Creating a new WordPress plugin
- Refactoring an existing plugin to 10up standards
- Adding features to a 10up scaffold plugin
- Implementing settings, admin pages, or REST endpoints
- Ensuring security best practices
Key Concepts
10up plugin architecture emphasizes:
- Modular design - Features as separate modules
- Auto-loading - Classes loaded from
src/ directory
- Namespaces - PSR-4 autoloading
- Framework patterns - Standardized initialization
Procedure
Step 1: Plugin File Structure
Standard 10up plugin structure:
plugin-name/
├── plugin-name.php # Main entry point
├── composer.json # PHP dependencies
├── package.json # JS dependencies
├── src/ # PHP source files
│ ├── PluginCore.php # Module initialization
│ ├── Blocks.php # Block registration
│ ├── Assets.php # Asset enqueueing
│ └── Admin/
│ └── Settings.php # Settings page
├── blocks/ # Custom blocks
│ └── block-name/
├── dist/ # Compiled assets
├── languages/ # Translation files
└── tests/ # Test files
Step 2: Main Plugin File
plugin-name.php:
<?php
namespace PluginName;
if (!defined('ABSPATH')) {
exit;
}
define('PLUGIN_NAME_VERSION', '1.0.0');
define('PLUGIN_NAME_PATH', plugin_dir_path(__FILE__));
define('PLUGIN_NAME_URL', plugin_dir_url(__FILE__));
if (file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
}
add_action('plugins_loaded', function () {
PluginCore::get_instance();
});
Step 3: Module Initialization (10up Framework Pattern)
src/PluginCore.php:
<?php
namespace PluginName;
use TenupFramework\ModuleInitialization;
class PluginCore {
private static ?PluginCore $instance = null;
public static function get_instance(): PluginCore {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->init();
}
private function init(): {
::()->(
PLUGIN_NAME_PATH . ,
);
}
}
Step 4: Create a Module
Modules implement ModuleInterface and use the Module trait:
src/Blocks.php:
<?php
namespace PluginName;
use TenupFramework\Module;
use TenupFramework\ModuleInterface;
class Blocks implements ModuleInterface {
use Module;
public function can_register(): bool {
return true;
}
public function register(): void {
add_action('init', [$this, 'register_blocks']);
}
public function register_blocks(): void {
$blocks_dir = PLUGIN_NAME_PATH . 'blocks/';
if (!is_dir($blocks_dir)) {
;
}
= ( . );
( ) {
(());
}
}
}
Step 5: Asset Management
src/Assets.php:
<?php
namespace PluginName;
use TenupFramework\Module;
use TenupFramework\ModuleInterface;
use TenupFramework\Traits\GetAssetInfo;
class Assets implements ModuleInterface {
use Module;
use GetAssetInfo;
public function can_register(): bool {
return true;
}
public function register(): void {
add_action('wp_enqueue_scripts', [$this, 'enqueue_frontend_assets']);
add_action('enqueue_block_editor_assets', [$this, 'enqueue_editor_assets']);
}
public {
= ->();
(
,
PLUGIN_NAME_URL . ,
[],
[]
);
(
,
PLUGIN_NAME_URL . ,
[],
[],
);
}
{
= ->();
(
,
PLUGIN_NAME_URL . ,
[],
[],
);
}
{
= PLUGIN_NAME_PATH . ;
(()) {
;
}
[
=> [],
=> PLUGIN_NAME_VERSION,
];
}
}
Step 6: Settings Page (Without Framework)
For plugins without 10up framework:
src/Admin/Settings.php:
<?php
namespace PluginName\Admin;
class Settings {
private const OPTION_NAME = 'plugin_name_settings';
public function __construct() {
add_action('admin_menu', [$this, 'add_menu_page']);
add_action('admin_init', [$this, 'register_settings']);
}
public function add_menu_page(): void {
add_options_page(
__('Plugin Name Settings', 'plugin-name'),
__('Plugin Name', 'plugin-name'),
'manage_options',
'plugin-name-settings',
[$this, 'render_settings_page']
);
}
{
(
,
::,
[
=> ,
=> [, ],
=> ->(),
]
);
(
,
(, ),
,
);
(
,
(, ),
[, ],
,
,
[
=> ,
=> (, ),
]
);
}
{
(!()) {
;
}
<div {
= (::, ->());
= [[]] ?? ;
<label>
<input
type=
name=
value=
(, );
/>
([]);
</label>
}
{
= [];
[] = !([]);
;
}
{
[
=> ,
];
}
}
Step 7: Security Best Practices
Always follow these security rules:
if (!wp_verify_nonce($_POST['_wpnonce'], 'plugin_name_action')) {
wp_die(__('Security check failed', 'plugin-name'));
}
if (!current_user_can('manage_options')) {
wp_die(__('Unauthorized access', 'plugin-name'));
}
$title = sanitize_text_field($_POST['title']);
$email = sanitize_email($_POST['email']);
$url = esc_url_raw($_POST['url']);
$html = wp_kses_post($_POST['content']);
$int = absint($_POST['count']);
echo esc_html($title);
echo esc_attr($class);
echo esc_url($link);
();
;
= ->(
->(
,
)
);
See references/security.md for complete security checklist.
See references/wordpress-patterns.md for hooks, options, transients, cron, and rewrite rules.
Composer Configuration
composer.json:
{
"name": "10up/plugin-name",
"description": "Plugin description",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=8.0",
"10up/wp-framework": "^1.3"
},
"require-dev": {
"10up/phpcs-composer": "^2.0"
},
"autoload": {
"psr-4": {
"PluginName\\": "src/"
}
}
}
Verification
After creating a plugin:
- Activate plugin without errors
- Check for PHP notices/warnings (enable WP_DEBUG)
- Verify blocks register correctly
- Test settings save and load
- Run PHPCS:
composer run lint
Failure Modes
Plugin doesn't activate:
- PHP syntax error
- Missing dependency
- Namespace mismatch
Modules not loading:
- Missing
ModuleInterface implementation
can_register() returning false
- Autoloader not configured
Settings not saving:
- Missing nonce verification
- Incorrect option name
- Sanitization returning empty
Escalation
Ask the user when:
- Custom database tables needed
- Complex activation/deactivation logic
- Multi-site considerations
- REST API design decisions