wc-shipping-method
Registers a custom WooCommerce shipping method with explicit control over which fields appear in the per-zone settings modal — extend WC_Shipping_Method, declare your fields in init_form_fields (and ONLY those fields, no unset / DOM hacks / CSS hides), set the $supports array to control whether the modal opens (omit 'instance-settings' to suppress it entirely), register via the woocommerce_shipping_methods filter, load the class on woocommerce_shipping_init. Corrects the "this is React, removing fields is hard" misconception — the zone-method modal is Backbone with a PHP-rendered settings_html string; the field list is wholly PHP-controlled. Use when scaffolding a shipping method or when you want a feature-flag-only modal without WC defaults. Triggers on WC_Shipping_Method, woocommerce_shipping_methods, woocommerce_shipping_init, init_form_fields with shipping context, $supports shipping-zones, calculate_shipping, add_rate, or "remove default fields from shipping method".
Best use case
wc-shipping-method is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Registers a custom WooCommerce shipping method with explicit control over which fields appear in the per-zone settings modal — extend WC_Shipping_Method, declare your fields in init_form_fields (and ONLY those fields, no unset / DOM hacks / CSS hides), set the $supports array to control whether the modal opens (omit 'instance-settings' to suppress it entirely), register via the woocommerce_shipping_methods filter, load the class on woocommerce_shipping_init. Corrects the "this is React, removing fields is hard" misconception — the zone-method modal is Backbone with a PHP-rendered settings_html string; the field list is wholly PHP-controlled. Use when scaffolding a shipping method or when you want a feature-flag-only modal without WC defaults. Triggers on WC_Shipping_Method, woocommerce_shipping_methods, woocommerce_shipping_init, init_form_fields with shipping context, $supports shipping-zones, calculate_shipping, add_rate, or "remove default fields from shipping method".
Teams using wc-shipping-method should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/wc-shipping-method/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How wc-shipping-method Compares
| Feature / Agent | wc-shipping-method | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Registers a custom WooCommerce shipping method with explicit control over which fields appear in the per-zone settings modal — extend WC_Shipping_Method, declare your fields in init_form_fields (and ONLY those fields, no unset / DOM hacks / CSS hides), set the $supports array to control whether the modal opens (omit 'instance-settings' to suppress it entirely), register via the woocommerce_shipping_methods filter, load the class on woocommerce_shipping_init. Corrects the "this is React, removing fields is hard" misconception — the zone-method modal is Backbone with a PHP-rendered settings_html string; the field list is wholly PHP-controlled. Use when scaffolding a shipping method or when you want a feature-flag-only modal without WC defaults. Triggers on WC_Shipping_Method, woocommerce_shipping_methods, woocommerce_shipping_init, init_form_fields with shipping context, $supports shipping-zones, calculate_shipping, add_rate, or "remove default fields from shipping method".
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# WooCommerce: register a custom shipping method
For plugins that add their own shipping logic to a WC store. The skill covers the registration flow plus — and this is the part AI assistants consistently get wrong — **how to control exactly which fields appear in the per-zone settings modal**, including the case where you want NO settings UI at all.
## Misconception this skill corrects
> "The WooCommerce shipping zones admin is React, so removing the default fields from a shipping method modal is hard."
It is not. The shipping-zone method settings modal is **Backbone**, not React. It opens a `WCBackboneModal` whose body is the `settings_html` string returned by `WC_Shipping_Method::get_admin_options_html()` — which calls `generate_settings_html()` on the array returned by `get_instance_form_fields()` ([wp-content/plugins/woocommerce/includes/abstracts/abstract-wc-shipping-method.php:490](abstract-wc-shipping-method.php), [wp-content/plugins/woocommerce/assets/js/admin/wc-shipping-zone-methods.js:171-270](wc-shipping-zone-methods.js)).
This means the **field list is wholly PHP-controlled**. There is nothing on the JS side adding default fields. If your `instance_form_fields` array contains one field, the modal renders one field — no `unset()`, no DOM hack, no CSS-hide, no React-prop monkey-patching needed.
The newer React surfaces inside WC Admin (Analytics, Settings Editor, etc.) are unrelated to the shipping zones method modal — don't conflate them.
## When to use this skill
Trigger when ANY of the following is true:
- Scaffolding a new WC shipping method (carrier integration, custom-logic method, internal shipping rule).
- You want the per-zone settings modal to show ONLY a feature flag (or a subset of fields), not the WC defaults.
- You want a shipping method that registers in zones but exposes NO settings UI at all (the plugin owns config elsewhere — own admin page, external API, hardcoded rules).
- Reviewing a plugin where you see custom field-removal hacks (`unset( $form_fields['title'] )`, CSS `display: none`, JS DOM mutation) — those are antipatterns; this skill explains why.
- Debugging "my custom shipping method shows fields I never declared".
## Architecture in one paragraph
A WC shipping method is a PHP class extending `WC_Shipping_Method`, registered via the `woocommerce_shipping_methods` filter. The abstract initializes `$instance_form_fields = array()` — empty. **Nothing is auto-injected.** Your `init_form_fields()` populates exactly the fields you want; the modal renders exactly that list. Whether the modal opens at all is governed by `has_settings()` ([abstract-wc-shipping-method.php](abstract-wc-shipping-method.php)), which returns true if the method's `$supports` array includes `'instance-settings'`. Calculation runs through `calculate_shipping( $package )`, which calls `$this->add_rate()` for each rate.
## Minimal scaffold
### Bootstrap (main plugin file)
```php
/**
* Plugin Name: My Shipping
* Requires Plugins: woocommerce
*/
add_action( 'woocommerce_shipping_init', 'myplugin_load_shipping_method' );
add_filter( 'woocommerce_shipping_methods', 'myplugin_register_shipping_method' );
function myplugin_load_shipping_method(): void {
require_once __DIR__ . '/includes/MyShippingMethod.php';
}
function myplugin_register_shipping_method( array $methods ): array {
$methods['myplugin_shipping'] = 'MyShippingMethod';
return $methods;
}
```
`woocommerce_shipping_init` is the canonical hook for loading shipping method classes — it fires after `WC_Shipping_Method` is loaded, avoiding the "class not found" race that happens if you load on `plugins_loaded`.
### Method class (variant A: feature-flag only modal)
```php
class MyShippingMethod extends WC_Shipping_Method {
public function __construct( $instance_id = 0 ) {
$this->id = 'myplugin_shipping';
$this->instance_id = absint( $instance_id );
$this->method_title = __( 'My Shipping', 'myplugin' );
$this->method_description = __( 'Custom shipping logic for X.', 'myplugin' );
// 'shipping-zones' = can be added to a zone (instance-based).
// 'instance-settings' = the per-zone settings modal opens.
// 'instance-settings-modal' = use the JS-driven modal (modern zones admin).
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->init_form_fields();
$this->init_settings();
// Hardcoded customer-facing label. To make it admin-editable, add a
// 'title' entry to instance_form_fields and read $this->settings['title'].
$this->title = __( 'My Shipping', 'myplugin' );
add_action(
'woocommerce_update_options_shipping_' . $this->id,
array( $this, 'process_admin_options' )
);
}
public function init_form_fields(): void {
// ONLY the feature flag. WC adds nothing else.
$this->instance_form_fields = array(
'use_premium_logic' => array(
'title' => __( 'Use premium logic', 'myplugin' ),
'label' => __( 'Enable premium routing for this zone', 'myplugin' ),
'type' => 'checkbox',
'description' => __( 'Routes through the plugin\'s premium engine.', 'myplugin' ),
'default' => 'no',
'desc_tip' => true,
),
);
}
public function calculate_shipping( $package = array() ) {
$use_premium = ( $this->get_option( 'use_premium_logic', 'no' ) === 'yes' );
$cost = $use_premium ? 25.0 : 10.0;
$this->add_rate(
array(
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => $cost,
'package' => $package,
)
);
}
}
```
### Method class (variant B: NO settings UI at all)
When the plugin owns its config elsewhere (own admin page, external API, hardcoded rules), suppress the modal entirely:
```php
class MyExternalShippingMethod extends WC_Shipping_Method {
public function __construct( $instance_id = 0 ) {
$this->id = 'myplugin_external_shipping';
$this->instance_id = absint( $instance_id );
$this->method_title = __( 'My External Shipping', 'myplugin' );
// Only 'shipping-zones'. NO 'instance-settings' = no modal opens,
// no settings cog renders. Verified in has_settings() at
// wp-content/plugins/woocommerce/includes/abstracts/abstract-wc-shipping-method.php
$this->supports = array( 'shipping-zones' );
$this->title = __( 'Configured externally', 'myplugin' );
// Still call init_settings() so $this->settings is an empty array
// rather than null — avoids notices in any code reading $this->settings[*].
$this->init_settings();
}
public function calculate_shipping( $package = array() ) {
$cost = myplugin_compute_external_rate( $package );
$this->add_rate(
array(
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => $cost,
'package' => $package,
)
);
}
}
```
## Critical rules
### 1. Field list is fully under your control
`WC_Shipping_Method` initializes `$instance_form_fields = array()` ([abstract-wc-shipping-method.php:112](abstract-wc-shipping-method.php)). Built-in methods (Free Shipping, Flat Rate, Local Pickup) populate the array entirely from their own `init_form_fields()` ([free-shipping/class-wc-shipping-free-shipping.php:101-143](class-wc-shipping-free-shipping.php) — a complete `title` / `requires` / `min_amount` / `ignore_discounts` declaration with nothing else added underneath).
Implication: to expose ONLY the fields you want, declare ONLY those fields. Do not:
- `unset( $this->instance_form_fields['title'] )` after the fact — fragile, breaks on future WC versions if internal structure shifts.
- Filter `woocommerce_shipping_instance_form_fields_{id}` to delete entries you yourself just declared — circular nonsense.
- Hide fields with CSS — they still post and save.
- Mutate the DOM with JS — the rendered HTML is server-generated, your script runs after; the fields are real.
### 2. `$supports` array controls modal availability
| `$supports` entry | Effect |
|---|---|
| `'shipping-zones'` | Method can be added to a zone (instance-based). |
| `'instance-settings'` | The per-zone settings modal exists for this method. |
| `'instance-settings-modal'` | Render via the JS-driven Backbone modal (modern zones admin). |
| `'settings'` | Legacy non-zone settings page. Most modern plugins don't need this. |
For a feature-flag-only modal: include all three (`shipping-zones`, `instance-settings`, `instance-settings-modal`).
For no settings UI at all: include only `shipping-zones`.
`has_settings()` ([abstract-wc-shipping-method.php](abstract-wc-shipping-method.php)) returns `$this->supports( 'instance-settings' )` for instance-based methods. Without that key, the cog icon doesn't render and the modal never opens.
### 3. `title` field is conventional but optional
Built-in WC methods include a `title` field so admins can rename "Flat rate" to "Standard delivery". You don't have to. If your method's customer-facing label is fixed (e.g. branded carrier name), hardcode `$this->title = __( 'Premium shipping', 'myplugin' )` in the constructor and skip the field.
If you DO want it admin-editable: add it to `instance_form_fields`, then `$this->title = $this->get_option( 'title' )` in the constructor.
### 4. Load the class on `woocommerce_shipping_init`
```php
add_action( 'woocommerce_shipping_init', 'myplugin_load_shipping_method' );
```
This action fires AFTER WC has loaded `WC_Shipping_Method`. Loading on `plugins_loaded` can race the WC bootstrap on some setups and produce "class WC_Shipping_Method not found".
### 5. Register via `woocommerce_shipping_methods` filter
```php
add_filter( 'woocommerce_shipping_methods', function ( $methods ) {
$methods['myplugin_shipping'] = 'MyShippingMethod';
return $methods;
} );
```
Array key = method ID (must match `$this->id`). Value = fully qualified class name. WC instantiates the class as needed (one instance per zone-method combination).
### 6. Always call `init_settings()` even with no fields
`init_settings()` populates `$this->settings` from saved DB values. Skipping it leaves `$this->settings` as `null`, causing notices in any read. With zero fields it's still cheap — call it.
### 7. `calculate_shipping()` minimum: one `add_rate` call
```php
public function calculate_shipping( $package = array() ) {
$this->add_rate( array(
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => 10.0,
'package' => $package,
) );
}
```
`get_rate_id()` returns `<method_id>:<instance_id>` — guaranteed unique per zone-method combination. Don't roll your own ID.
`add_rate` accepts `'cost'` as a single value (per-order cost) or an array (per-item costs). See [abstract-wc-shipping-method.php `add_rate()`](abstract-wc-shipping-method.php) for the full args list (`taxes`, `calc_tax`, `meta_data`, `price_decimals`).
### 8. Save handler wiring
```php
add_action(
'woocommerce_update_options_shipping_' . $this->id,
array( $this, 'process_admin_options' )
);
```
Required for the legacy non-instance settings tab. For instance settings (per-zone), WC handles save through its own AJAX flow; the action above is harmless but not strictly necessary in modern installs. Include it for backward compatibility unless the method is `'shipping-zones'`-only AND you've confirmed your target WC version doesn't need it.
## Common mistakes
```php
// WRONG — field-removal via unset (fragile, breaks across WC versions)
public function init_form_fields(): void {
parent::init_form_fields();
unset( $this->instance_form_fields['title'] );
}
// RIGHT — just declare what you want
public function init_form_fields(): void {
$this->instance_form_fields = array(
'use_premium_logic' => array( /* ... */ ),
);
}
// WRONG — DOM hack to hide fields
add_action( 'admin_print_footer_scripts', function () {
echo '<script>jQuery("[name*=\"title\"]").hide();</script>';
} );
// WRONG — CSS hide
add_action( 'admin_head', function () {
echo '<style>tr.title-field { display: none; }</style>';
} );
// WRONG — registering on plugins_loaded (class may not exist yet)
add_action( 'plugins_loaded', function () {
require_once __DIR__ . '/MyShippingMethod.php';
} );
// RIGHT
add_action( 'woocommerce_shipping_init', function () {
require_once __DIR__ . '/MyShippingMethod.php';
} );
// WRONG — leaving 'instance-settings' in $supports when there are no fields
$this->supports = array( 'shipping-zones', 'instance-settings' );
$this->instance_form_fields = array(); // empty modal opens, useless cog icon
// RIGHT — drop instance-settings to suppress the modal entirely
$this->supports = array( 'shipping-zones' );
```
## Testing the smoke result
After registering the method, verify in:
1. `/wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=N` — your method appears in the "Add shipping method" picker.
2. Click the method to open settings — modal contains EXACTLY the fields you declared (or no modal opens at all if `'instance-settings'` is omitted).
3. At checkout for an address in the zone, the rate appears with the cost from `calculate_shipping()`.
A simple smoke plugin demonstrating both variants (feature-flag-only and no-settings) is at `wp-content/plugins/test-shipping-method/` in this repo (if present) — useful as a reference scaffold.
## Cross-references
- Run **`wp-plugin-bootstrap`** for the surrounding plugin file (header, autoload, `Requires Plugins: woocommerce` declaration).
- Run **`wp-plugin-architecture`** for the broader includes/ folder structure if the plugin has more than this one method.
- Run **`wp-security-audit`** on `process_admin_options()` and the saved settings flow — admin-facing input.
## What this skill does NOT cover
- Shipping zone management itself (creating, editing, deleting zones) — that's WC core admin, not the plugin author's concern.
- Carrier API integration (rate fetching, label printing, tracking) — adjacent topic, depends on the carrier.
- Cart-level shipping logic beyond the per-method `calculate_shipping` (e.g. cross-method rules, shipping-class-aware pricing) — niche, out of scope.
- The WC Blocks checkout / Cart blocks integration. Methods registered via `WC_Shipping_Method` work with both classic and Blocks checkout for rate display, but Blocks-specific custom UI is a separate topic.
- Distance / weight / class-based rate matrices — flat-rate / table-rate plugins implement these inside their `calculate_shipping`; the structure is a regular PHP loop, no WC-specific scaffolding.
## References
- [`WC_Shipping_Method` abstract](wp-content/plugins/woocommerce/includes/abstracts/abstract-wc-shipping-method.php) — `instance_form_fields`, `has_settings`, `get_admin_options_html`, `add_rate`.
- [Free Shipping reference implementation](wp-content/plugins/woocommerce/includes/shipping/free-shipping/class-wc-shipping-free-shipping.php) — example of a method declaring its full field list explicitly.
- [`wc-shipping-zone-methods.js`](wp-content/plugins/woocommerce/assets/js/admin/wc-shipping-zone-methods.js) — Backbone modal source. Confirms PHP-rendered `settings_html`, no React.
- [WooCommerce Shipping Method API docs](https://woocommerce.com/document/shipping-method-api/) — official guide; cross-check against current source for new `$supports` keys or hook additions.Related Skills
wc-stripe-add-payment-method
WooCommerce Stripe Gateway skill for the fragile My Account payment-methods and add-payment-method flows. Use when editing or replacing Woo account templates, payment-methods.php, form-add-payment-method.php, Stripe card/token UI, saved cards, SetupIntent, Payment Element/UPE, billing details, WC payment tokens, customer account payment-method endpoints, Subscriptions change-payment-method compatibility, or code contains add_payment_method, add-payment-method, form#add_payment_method, payment_method_stripe, wc-stripe-upe-element, wc-stripe-setup-intent, stripe_source, wc_stripe_create_setup_intent, wc_stripe_upe_params, wc-stripe-payment-method, or woocommerce-gateway-stripe.
wc-shipping-providers
Register a custom WooCommerce shipping provider — extend AbstractShippingProvider and add it via the woocommerce_fulfillment_shipping_providers filter (WC 10.1+). Distinct from shipping methods (rate at checkout) — a provider is a carrier identity (UPS, FedEx, DHL, your custom carrier) used by the Fulfillments system for tracking URLs, tracking-number pattern detection, country-pair support, and the orders-list filter. Required methods get_key / get_name / get_icon / get_tracking_url, with optional shipping-from / shipping-to / try_parse_tracking_number. Registration accepts class names (resolved via the DI container) or pre-built instances, keyed by get_key. Use when integrating a private carrier, fulfillment service, or any tracking-aware shipping identity. Triggers on AbstractShippingProvider, woocommerce_fulfillment_shipping_providers, FulfillmentUtils::get_shipping_providers, "shipping provider" in WooCommerce context, or carrier tracking integration.
webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
web-artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.
valyu
Search the live web and 36+ specialised data sources including SEC filings, PubMed, ChEMBL, clinical trials, FRED economic indicators, and patent databases. Use when current, authoritative, or paywalled data is required.
ui-ux-pro-max
AI-powered design intelligence with 67 UI styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 15+ tech stacks. Generates complete design systems for any product type with industry-specific reasoning rules.
theme-factory
Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.
slack-gif-creator
Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."
skill-creator
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
shannon
Autonomous AI security pen testing. Executes real exploits against web applications to find SQL injection, XSS, SSRF, authentication flaws, and IDOR vulnerabilities. Reports only confirmed, reproducible findings — no false positives.
remotion
Create programmatic videos using React and Remotion. Translate natural language descriptions into working Remotion components for product demos, release announcements, explainer videos, and animated content.
planetscale
Design schemas and write queries for PlanetScale serverless MySQL using branch-based workflows. Ensures index coverage, avoids foreign key anti-patterns, and treats every schema change as a reviewable, reversible deploy request.