
Woocommerce Plugin Development
- 71 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Creates custom WooCommerce plugins using action/filter hooks, the Settings API, and REST API extensions without modifying core.
About
Builds upgrade-safe WooCommerce plugins with WordPress hooks, admin settings pages, custom REST endpoints, and HPOS compatibility. A developer uses it to add custom shipping, payment, or order-lifecycle logic.
- Hooks, Settings API, and custom REST endpoints
- HPOS (High-Performance Order Storage) compatibility
Woocommerce Plugin Development by the numbers
- 71 all-time installs (skills.sh)
- Ranked #46 of 65 PHP & Laravel skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill woocommerce-plugin-developmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 71 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Creates custom WooCommerce plugins using action/filter hooks, the Settings API, and REST API extensions without modifying core.
Files
WooCommerce Plugin Development
Overview
Build custom WooCommerce plugins that extend store functionality using WordPress hooks (actions and filters), the WooCommerce Settings API, custom post types and meta boxes, REST API extensions, and HPOS (High-Performance Order Storage) compatibility. This skill covers plugin architecture, the WooCommerce lifecycle hooks for orders and products, and patterns for building maintainable, upgrade-safe extensions.
When to Use This Skill
- When building a custom feature that extends WooCommerce (custom shipping, payment, or discount logic)
- When creating a plugin that adds admin settings and configuration pages
- When hooking into the WooCommerce checkout, order, or product lifecycle
- When extending the WooCommerce REST API with custom endpoints
- When migrating a plugin to support HPOS (High-Performance Order Storage)
Core Instructions
1. Set up the plugin boilerplate
<?php
/**
* Plugin Name: My Custom WooCommerce Extension
* Description: Adds custom functionality to WooCommerce
* Version: 1.0.0
* Author: Your Name
* Requires Plugins: woocommerce
* WC requires at least: 8.0
* WC tested up to: 9.0
*
* @package MyWooExtension
*/
defined('ABSPATH') || exit;
// Check if WooCommerce is active
if (!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
return;
}
define('MWE_VERSION', '1.0.0');
define('MWE_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MWE_PLUGIN_URL', plugin_dir_url(__FILE__));
// Declare HPOS compatibility
add_action('before_woocommerce_init', function () {
if (class_exists(\Automattic\WooCommerce\Utilities\FeaturesUtil::class)) {
\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
'custom_order_tables',
__FILE__,
true
);
}
});
// Initialize the plugin after WooCommerce loads
add_action('woocommerce_loaded', function () {
require_once MWE_PLUGIN_DIR . 'includes/class-mwe-main.php';
MWE_Main::instance();
});2. Use WooCommerce hooks for order lifecycle events
class MWE_Order_Handler {
public function __construct() {
// Order status transitions
add_action('woocommerce_order_status_completed', [$this, 'on_order_completed'], 10, 2);
add_action('woocommerce_order_status_changed', [$this, 'on_status_change'], 10, 4);
// New order created
add_action('woocommerce_checkout_order_created', [$this, 'on_order_created']);
// Payment complete
add_action('woocommerce_payment_complete', [$this, 'on_payment_complete']);
// Before/after order item saved (HPOS compatible)
add_action('woocommerce_new_order_item', [$this, 'on_new_order_item'], 10, 3);
}
public function on_order_completed(int $order_id, \WC_Order $order): void {
// Example: trigger fulfillment via external API
$items = $order->get_items();
$shipping_address = $order->get_address('shipping');
foreach ($items as $item) {
$product = $item->get_product();
$sku = $product->get_sku();
$quantity = $item->get_quantity();
// Call your external fulfillment service
$this->send_to_fulfillment($sku, $quantity, $shipping_address);
}
$order->add_order_note('Order sent to fulfillment service.');
}
public function on_status_change(int $order_id, string $from, string $to, \WC_Order $order): void {
// Log status transitions
error_log(sprintf(
'Order #%d transitioned from %s to %s',
$order_id, $from, $to
));
}
}3. Add settings using the WooCommerce Settings API
class MWE_Settings {
public function __construct() {
add_filter('woocommerce_settings_tabs_array', [$this, 'add_settings_tab'], 50);
add_action('woocommerce_settings_tabs_mwe_settings', [$this, 'render_settings']);
add_action('woocommerce_update_options_mwe_settings', [$this, 'save_settings']);
}
public function add_settings_tab(array $tabs): array {
$tabs['mwe_settings'] = __('My Extension', 'my-woo-extension');
return $tabs;
}
public function render_settings(): void {
woocommerce_admin_fields($this->get_settings());
}
public function save_settings(): void {
woocommerce_update_options($this->get_settings());
}
private function get_settings(): array {
return [
[
'title' => __('General Settings', 'my-woo-extension'),
'type' => 'title',
'id' => 'mwe_general_settings',
],
[
'title' => __('Enable Feature', 'my-woo-extension'),
'desc' => __('Enable the custom feature', 'my-woo-extension'),
'id' => 'mwe_enable_feature',
'type' => 'checkbox',
'default' => 'yes',
],
[
'title' => __('API Key', 'my-woo-extension'),
'desc' => __('Enter your external service API key', 'my-woo-extension'),
'id' => 'mwe_api_key',
'type' => 'text',
'css' => 'min-width: 300px;',
],
[
'title' => __('Processing Mode', 'my-woo-extension'),
'id' => 'mwe_processing_mode',
'type' => 'select',
'options' => [
'sync' => __('Synchronous', 'my-woo-extension'),
'async' => __('Asynchronous (Queue)', 'my-woo-extension'),
],
'default' => 'sync',
],
[
'type' => 'sectionend',
'id' => 'mwe_general_settings',
],
];
}
}4. Modify product data with filters
class MWE_Product_Customizer {
public function __construct() {
// Add a custom product tab in admin
add_filter('woocommerce_product_data_tabs', [$this, 'add_product_tab']);
add_action('woocommerce_product_data_panels', [$this, 'render_product_panel']);
add_action('woocommerce_process_product_meta', [$this, 'save_product_meta']);
// Modify price display on frontend
add_filter('woocommerce_get_price_html', [$this, 'modify_price_display'], 10, 2);
// Add custom data to cart item
add_filter('woocommerce_add_cart_item_data', [$this, 'add_custom_cart_data'], 10, 3);
// Display custom data in cart
add_filter('woocommerce_get_item_data', [$this, 'display_cart_item_data'], 10, 2);
// Save custom data to order item
add_action('woocommerce_checkout_create_order_line_item', [$this, 'save_order_item_meta'], 10, 4);
}
public function add_product_tab(array $tabs): array {
$tabs['mwe_custom'] = [
'label' => __('Custom Fields', 'my-woo-extension'),
'target' => 'mwe_custom_product_data',
'class' => [],
'priority' => 80,
];
return $tabs;
}
public function render_product_panel(): void {
global $post;
echo '<div id="mwe_custom_product_data" class="panel woocommerce_options_panel">';
wp_nonce_field('mwe_save_product_meta', 'mwe_product_meta_nonce');
woocommerce_wp_text_input([
'id' => '_mwe_custom_field',
'label' => __('Custom Field', 'my-woo-extension'),
'description' => __('Enter a custom value for this product', 'my-woo-extension'),
'desc_tip' => true,
]);
woocommerce_wp_select([
'id' => '_mwe_product_badge',
'label' => __('Product Badge', 'my-woo-extension'),
'options' => [
'' => __('None', 'my-woo-extension'),
'new' => __('New', 'my-woo-extension'),
'sale' => __('Sale', 'my-woo-extension'),
'limited' => __('Limited Edition', 'my-woo-extension'),
],
]);
echo '</div>';
}
public function save_product_meta(int $post_id): void {
// Verify nonce before processing $_POST data
if (!isset($_POST['mwe_product_meta_nonce']) ||
!wp_verify_nonce($_POST['mwe_product_meta_nonce'], 'mwe_save_product_meta')) {
return;
}
$custom_field = sanitize_text_field($_POST['_mwe_custom_field'] ?? '');
update_post_meta($post_id, '_mwe_custom_field', $custom_field);
$badge = sanitize_text_field($_POST['_mwe_product_badge'] ?? '');
update_post_meta($post_id, '_mwe_product_badge', $badge);
}
}5. Extend the WooCommerce REST API
class MWE_REST_Controller {
public function __construct() {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
register_rest_route('mwe/v1', '/analytics/summary', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'get_analytics_summary'],
'permission_callback' => [$this, 'check_admin_permission'],
]);
register_rest_route('mwe/v1', '/products/(?P<id>\d+)/custom-data', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [$this, 'get_product_custom_data'],
'permission_callback' => '__return_true',
'args' => [
'id' => [
'validate_callback' => function ($param) {
return is_numeric($param);
},
],
],
]);
}
public function check_admin_permission(\WP_REST_Request $request): bool {
return current_user_can('manage_woocommerce');
}
public function get_analytics_summary(\WP_REST_Request $request): \WP_REST_Response {
$days = absint($request->get_param('days') ?? 30);
$date_from = date('Y-m-d', strtotime("-{$days} days"));
// HPOS-compatible order query
$orders = wc_get_orders([
'date_created' => ">={$date_from}",
'status' => ['wc-completed', 'wc-processing'],
'limit' => -1,
'return' => 'ids',
]);
$total_revenue = 0;
foreach ($orders as $order_id) {
$order = wc_get_order($order_id);
$total_revenue += (float) $order->get_total();
}
return new \WP_REST_Response([
'period' => "{$days}_days",
'total_orders' => count($orders),
'total_revenue' => $total_revenue,
'avg_order' => count($orders) > 0 ? $total_revenue / count($orders) : 0,
]);
}
}6. Create a custom shipping method
// Register the shipping method
add_filter('woocommerce_shipping_methods', function (array $methods): array {
$methods['mwe_custom_shipping'] = 'MWE_Custom_Shipping_Method';
return $methods;
});
class MWE_Custom_Shipping_Method extends \WC_Shipping_Method {
public function __construct(int $instance_id = 0) {
$this->id = 'mwe_custom_shipping';
$this->instance_id = absint($instance_id);
$this->method_title = __('Custom Shipping', 'my-woo-extension');
$this->method_description = __('Custom shipping rate calculation', 'my-woo-extension');
$this->supports = ['shipping-zones', 'instance-settings'];
$this->init();
}
public function init(): void {
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option('title', 'Custom Shipping');
$this->enabled = $this->get_option('enabled', 'yes');
}
public function init_form_fields(): void {
$this->instance_form_fields = [
'title' => [
'title' => __('Method Title', 'my-woo-extension'),
'type' => 'text',
'default' => 'Custom Shipping',
],
'base_cost' => [
'title' => __('Base Cost', 'my-woo-extension'),
'type' => 'price',
'default' => '5.00',
],
'per_item_cost' => [
'title' => __('Per Item Cost', 'my-woo-extension'),
'type' => 'price',
'default' => '1.00',
],
];
}
public function calculate_shipping($package = []): void {
$base_cost = (float) $this->get_option('base_cost', 5);
$per_item = (float) $this->get_option('per_item_cost', 1);
$item_count = 0;
foreach ($package['contents'] as $item) {
$item_count += $item['quantity'];
}
$cost = $base_cost + ($per_item * $item_count);
$this->add_rate([
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => $cost,
]);
}
}Examples
Custom WooCommerce email notification
class MWE_Custom_Email extends \WC_Email {
public function __construct() {
$this->id = 'mwe_order_shipped';
$this->title = __('Order Shipped', 'my-woo-extension');
$this->description = __('Sent when an order is marked as shipped', 'my-woo-extension');
$this->heading = __('Your order has shipped!', 'my-woo-extension');
$this->subject = __('Your {site_title} order #{order_number} has shipped', 'my-woo-extension');
$this->template_base = MWE_PLUGIN_DIR . 'templates/';
$this->template_html = 'emails/order-shipped.php';
$this->template_plain = 'emails/plain/order-shipped.php';
$this->customer_email = true;
add_action('mwe_order_shipped_notification', [$this, 'trigger'], 10, 2);
parent::__construct();
}
public function trigger(int $order_id, string $tracking_number): void {
$this->object = wc_get_order($order_id);
if (!$this->object) return;
$this->recipient = $this->object->get_billing_email();
$this->placeholders['{tracking_number}'] = $tracking_number;
if ($this->is_enabled() && $this->get_recipient()) {
$this->send(
$this->get_recipient(),
$this->get_subject(),
$this->get_content(),
$this->get_headers(),
$this->get_attachments()
);
}
}
}
// Register the email class
add_filter('woocommerce_email_classes', function (array $emails): array {
$emails['MWE_Order_Shipped'] = new MWE_Custom_Email();
return $emails;
});HPOS-compatible order meta access
// Old way (post meta) — DEPRECATED
// $value = get_post_meta($order_id, '_custom_field', true);
// New way (HPOS compatible)
$order = wc_get_order($order_id);
// Read custom meta
$custom_value = $order->get_meta('_mwe_custom_field', true);
// Write custom meta
$order->update_meta_data('_mwe_custom_field', 'new_value');
$order->save();
// Query orders with custom meta (HPOS compatible)
$orders = wc_get_orders([
'meta_query' => [
[
'key' => '_mwe_custom_field',
'value' => 'specific_value',
],
],
'status' => 'completed',
'limit' => 50,
]);Best Practices
- Always declare HPOS compatibility — WooCommerce is migrating to custom order tables; use
FeaturesUtil::declare_compatibilityand avoid directwp_postsqueries for orders - Use WooCommerce CRUD methods — access order/product data via
$order->get_total(), notget_post_meta(); CRUD methods work with both legacy and HPOS storage - Hook at the right priority — default priority is 10; use lower numbers (5) to run before WooCommerce's own handlers, higher (20+) to run after
- Sanitize and escape everything — use
sanitize_text_field()on input,esc_html()/esc_attr()on output; never trust$_POSTor$_GETdata - Use WooCommerce's built-in functions —
wc_get_orders(),wc_get_products(),wc_price()handle edge cases and are forward-compatible - Namespace your meta keys — prefix all custom meta with your plugin slug (e.g.,
_mwe_) to avoid conflicts with other plugins - Support multisite — use
is_plugin_active_for_network()checks if your plugin needs to work on WordPress multisite - Test with WooCommerce's test suite — use
WC_Unit_Test_Caseas a base class for PHPUnit tests
Common Pitfalls
| Problem | Solution |
|---|---|
| Plugin breaks after WooCommerce update | Hook into woocommerce_loaded instead of plugins_loaded to ensure WooCommerce classes are available |
| Order meta not saving with HPOS enabled | Use $order->update_meta_data() and $order->save() instead of update_post_meta() |
| Hooks fire multiple times (duplicate emails, double stock reduction) | Check if the hook has already been processed using a static flag or transient; use did_action() to check |
| Custom shipping method not appearing | Ensure the method is registered via woocommerce_shipping_methods filter and the class extends WC_Shipping_Method |
| REST API endpoint returns 403 | Check permission_callback — use current_user_can('manage_woocommerce') for admin endpoints, or '__return_true' for public |
Related Skills
- @woocommerce-performance
- @product-data-modeling
- @discount-engine
- @ecommerce-seo
- @erp-integration
{
"context": "Tests whether the agent correctly extends WC_Shipping_Method to create a custom shipping class, registers it via the appropriate filter, supports shipping zones and instance settings, and builds a REST API endpoint with proper permission handling and HPOS-compatible order queries.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Extends WC_Shipping_Method",
"max_score": 12,
"description": "Custom shipping class extends \\WC_Shipping_Method (not implementing from scratch or extending a different base class)"
},
{
"name": "Registered via filter",
"max_score": 10,
"description": "Custom shipping method class is registered using the 'woocommerce_shipping_methods' filter that returns the modified $methods array with the class name as the value"
},
{
"name": "Supports shipping zones",
"max_score": 8,
"description": "$this->supports includes 'shipping-zones' in the array"
},
{
"name": "Supports instance settings",
"max_score": 5,
"description": "$this->supports includes 'instance-settings' in the array"
},
{
"name": "instance_form_fields used",
"max_score": 8,
"description": "Shipping configuration fields are defined in $this->instance_form_fields inside init_form_fields()"
},
{
"name": "add_rate() in calculate_shipping",
"max_score": 10,
"description": "The calculate_shipping() method calls $this->add_rate() to add a rate with at least 'id', 'label', and 'cost' keys"
},
{
"name": "REST endpoint via rest_api_init",
"max_score": 10,
"description": "REST routes are registered inside a callback hooked to the 'rest_api_init' action"
},
{
"name": "register_rest_route used",
"max_score": 8,
"description": "REST routes are registered using register_rest_route() with a versioned namespace (e.g., 'myplugin/v1')"
},
{
"name": "Admin permission callback",
"max_score": 12,
"description": "Protected REST endpoint uses current_user_can('manage_woocommerce') in its permission_callback (not hardcoded true or '__return_true' for admin endpoints)"
},
{
"name": "HPOS-compatible order query",
"max_score": 10,
"description": "Order queries use wc_get_orders() rather than new WP_Query() or $wpdb direct queries"
},
{
"name": "validate_callback on args",
"max_score": 7,
"description": "REST route args include at least one parameter with a 'validate_callback' key"
}
]
}
Weight-Tiered Shipping Plugin with Analytics Endpoint
Problem/Feature Description
A health-and-wellness store ships products across several shipping zones. Their current flat-rate shipping is losing them money on heavy orders and overcharging customers on light ones. The operations manager wants a custom shipping method that calculates cost based on the total weight of the cart — with a base handling fee plus a per-kilogram rate — and that can be configured per shipping zone from the WooCommerce admin.
In addition, the store's BI team wants a protected REST endpoint they can call from their internal dashboard to retrieve a summary of completed orders over a configurable number of days. This endpoint should be locked down so only WooCommerce administrators can call it.
Build a WooCommerce plugin that provides both pieces of functionality. The shipping method should be configurable without editing code. The REST endpoint should return the count of orders and total revenue for the requested time window.
Output Specification
Deliver the plugin as PHP source files inside a folder named wc-weight-shipping/. Include:
- The main plugin file with the required plugin header
- The custom shipping method class
- The REST controller class (or equivalent)
- A
README.mddescribing how to configure the shipping method in a shipping zone and how to call the REST endpoint (include the example endpoint URL pattern and what parameters it accepts)
No external libraries or build tools are needed — pure PHP/WordPress/WooCommerce only.
{
"context": "Tests whether the agent correctly bootstraps a WooCommerce plugin with the proper header, safety guards, and WooCommerce dependency check, declares HPOS compatibility, uses the correct initialization hook, hooks into the order lifecycle, and accesses order data using HPOS-safe CRUD methods.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Plugin header Requires Plugins",
"max_score": 8,
"description": "Plugin header comment includes 'Requires Plugins: woocommerce' declaration"
},
{
"name": "ABSPATH guard",
"max_score": 7,
"description": "PHP file contains 'defined(\"ABSPATH\") || exit' (or equivalent) to prevent direct access"
},
{
"name": "WooCommerce active check",
"max_score": 8,
"description": "Plugin checks whether WooCommerce is active using in_array with apply_filters('active_plugins', get_option('active_plugins')) before executing plugin logic"
},
{
"name": "HPOS compatibility declaration",
"max_score": 13,
"description": "Plugin declares HPOS compatibility using FeaturesUtil::declare_compatibility('custom_order_tables', __FILE__, true) inside a 'before_woocommerce_init' action"
},
{
"name": "woocommerce_loaded init hook",
"max_score": 10,
"description": "Plugin initializes its main class or includes files using the 'woocommerce_loaded' action (NOT 'plugins_loaded')"
},
{
"name": "Order lifecycle hook used",
"max_score": 10,
"description": "Plugin hooks into at least one of: woocommerce_order_status_completed, woocommerce_order_status_changed, woocommerce_payment_complete, or woocommerce_checkout_order_created"
},
{
"name": "HPOS-compatible meta write",
"max_score": 12,
"description": "Plugin writes order metadata using $order->update_meta_data() followed by $order->save(), NOT update_post_meta()"
},
{
"name": "HPOS-compatible meta read",
"max_score": 12,
"description": "Plugin reads order metadata using $order->get_meta(), NOT get_post_meta()"
},
{
"name": "CRUD method for order data",
"max_score": 8,
"description": "Order totals or fields are read via WooCommerce CRUD methods such as $order->get_total(), $order->get_status(), or $order->get_items() rather than raw database access"
},
{
"name": "Namespaced meta key",
"max_score": 6,
"description": "All custom meta keys stored on orders are prefixed with a plugin-specific slug (e.g., '_myplugin_' or '_myprefix_'), not generic names like '_status' or '_value'"
},
{
"name": "Input sanitization",
"max_score": 6,
"description": "Any user-supplied input (e.g., POST data, request parameters) is sanitized with sanitize_text_field() or equivalent before use"
}
]
}
Order Fulfillment Tracker Plugin
Problem/Feature Description
A mid-sized e-commerce store running WooCommerce has recently integrated with a third-party warehouse management system (WMS). When an order is paid and moves through the fulfillment pipeline, the warehouse system needs to be notified, and a tracking token returned from the WMS must be recorded against the order so that the support team can look it up later.
The store's tech lead has asked you to build a WordPress plugin that hooks into the order lifecycle to detect when relevant transitions happen, records a simulated fulfillment token on the order, and can later retrieve that token. The plugin must be safe to install on a modern WooCommerce store that has the high-performance order storage feature turned on.
Output Specification
Produce a working WordPress plugin as PHP source code. The plugin does not need to make real HTTP requests to an external WMS — simulate the fulfillment API call with a simple function that returns a fake token string. The plugin should:
- Be structured as a standalone plugin (single file or with an includes/ subfolder)
- Detect when an order reaches a relevant status (such as payment received or order completed)
- Record a fulfillment token on the order (use a made-up token value like
'FULFILL-' . $order_id . '-' . time()) - Expose a way to read back the stored token from an order object
Deliver all plugin PHP files under a folder named wc-fulfillment-tracker/. Also produce a README.md inside that folder briefly describing what the plugin does and how the token is stored and retrieved.
{
"context": "Tests whether the agent uses the WooCommerce Settings API correctly to add a configuration page, adds a custom product data tab using the proper WooCommerce hooks, uses WooCommerce helper functions to render form fields, and correctly namespaces and sanitizes meta data.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Settings tab filter",
"max_score": 10,
"description": "Plugin uses the 'woocommerce_settings_tabs_array' filter to register a new settings tab"
},
{
"name": "Settings render action",
"max_score": 8,
"description": "Plugin uses a 'woocommerce_settings_tabs_{id}' action to render the settings page"
},
{
"name": "Settings save action",
"max_score": 8,
"description": "Plugin uses a 'woocommerce_update_options_{id}' action to save the settings"
},
{
"name": "woocommerce_admin_fields render",
"max_score": 8,
"description": "Settings page is rendered by calling woocommerce_admin_fields() with an array of field definitions"
},
{
"name": "woocommerce_update_options save",
"max_score": 8,
"description": "Settings are saved by calling woocommerce_update_options() with the same field definitions array"
},
{
"name": "Product data tab filter",
"max_score": 10,
"description": "Plugin registers a custom product data tab using the 'woocommerce_product_data_tabs' filter"
},
{
"name": "Product panel action",
"max_score": 8,
"description": "Plugin renders the product custom panel using the 'woocommerce_product_data_panels' action"
},
{
"name": "Product meta save action",
"max_score": 8,
"description": "Plugin saves custom product meta using the 'woocommerce_process_product_meta' action"
},
{
"name": "WooCommerce field helper",
"max_score": 10,
"description": "Product panel uses woocommerce_wp_text_input() or woocommerce_wp_select() to render input fields (not raw HTML inputs)"
},
{
"name": "Namespaced meta key",
"max_score": 8,
"description": "Custom product meta keys are prefixed with a plugin-specific slug (e.g., '_myplugin_field', not '_field')"
},
{
"name": "Input sanitization",
"max_score": 7,
"description": "POST data for saving product meta is passed through sanitize_text_field() or equivalent before storage"
},
{
"name": "Output escaping",
"max_score": 7,
"description": "Data displayed in HTML output uses esc_html() or esc_attr() to escape before rendering"
}
]
}
Product Promotion Badge Plugin
Problem/Feature Description
A boutique online retailer wants to highlight certain products with labels such as "New Arrival", "Staff Pick", or "Limited Stock" to draw shoppers' attention. Store managers need to assign these badges per product from the WooCommerce product editor, and the badge should be visible in the cart so customers know why they added the item. The marketing team also wants a global on/off toggle and a configurable default badge text accessible from the WooCommerce admin, so they don't have to touch code to change the store-wide defaults.
Your task is to build a WordPress plugin that makes this possible. Product editors in WooCommerce admin should be able to pick a badge for each product. Shoppers should see the badge label alongside the product in their cart. The plugin should also provide an admin configuration area within WooCommerce where a store manager can toggle the feature on or off and set a default label for products that have no individual badge assigned.
Output Specification
Deliver the plugin as PHP source files inside a folder named wc-product-badges/. The plugin should be structured so a developer can read and understand it. Also include a README.md inside that folder explaining how to install the plugin and how a store manager would use it.
No external libraries or composer packages are required — the plugin should use only WordPress and WooCommerce built-in APIs.
{
"name": "finsi/woocommerce-plugin-development",
"version": "0.1.0",
"summary": "Custom WooCommerce plugins with hooks, filters, and settings API",
"skills": {
"woocommerce-plugin-development": {
"path": "SKILL.md"
}
}
}