← Skills
<?php
/**
* Plugin Name: My Plugin
* Plugin URI: https://github.com/user/my-plugin
* Description: Does one thing well.
* Version: 1.0.0
* Requires PHP: 8.1
* Author: Your Name
* License: MIT
* Text Domain: my-plugin
*/
declare(strict_types=1);
my-plugin/
├── my-plugin.php # Plugin header + bootstrap
├── includes/ # PHP classes
│ ├── class-admin.php
│ └── class-frontend.php
├── assets/ # Built assets (commit dist, not src)
│ ├── css/
│ ├── js/
│ └── img/
├── languages/ # .pot / .po / .mo files
└── readme.txt # WordPress.org readme
myplugin_do_thing(), hooks myplugin_saved_dataMYPLUGIN_VERSIONmyplugin_settingsslug or settings - prefix themadd_action() / add_filter() in a dedicated bootstrapper method, not globally@param and @return:/**
* Fires after a save sync completes.
*
* @param int $save_id The save file ID.
* @param string $status Result status: success | error
*/
do_action("myplugin_after_sync", $save_id, $status);
esc_attr(), esc_html(), esc_url() for outputsanitize_text_field(), sanitize_email(), etc. for inputwp_kses() for HTML input with an allowlistcurrent_user_can() before any admin operationwp_nonce_field() + check_admin_referer() for form submissions$wpdb->prepare() for all database queries$wpdb->prefix for table names, never hardcodedbDelta()uninstall.php)get_option() for upgrade routines__("Save file", "my-plugin")wp i18n make-pot . languages/plugins_loaded:add_action("plugins_loaded", function () {
load_plugin_textdomain("my-plugin", false, dirname(plugin_basename(__FILE__)) . "/languages");
});
wp_register_*(), enqueue only where neededwp_enqueue_script() with array() dependencies and filemtime() versioning<head>delete_transient() on relevant savesassets/ (built), ignore node_modules/