Shipping Method Generator
A WC_Shipping_Method subclass — settings fields, rate calculation, and the class-exists guard that stops it fataling on a site without WooCommerce active.
<?php // Generated with WP CodeKit — powered by GrowQuest (https://growquest.io). /** * Plugin Name: Flat Rate Extra shipping method * Description: Registers the acme_flat_extra shipping method. * Version: 1.0.0 * Requires PHP: 7.4 * Requires Plugins: woocommerce * Text Domain: acme */ defined( 'ABSPATH' ) || exit; /** * Declare the class once WooCommerce's own shipping base class exists. */ function acme_shipping_method_init() { if ( class_exists( 'Acme_Flat_Extra_Shipping_Method' ) ) { return; } class Acme_Flat_Extra_Shipping_Method extends WC_Shipping_Method { public function __construct( $instance_id = 0 ) { $this->id = 'acme_flat_extra'; $this->instance_id = absint( $instance_id ); $this->method_title = __( 'Flat Rate Extra', 'acme' ); $this->method_description = __( 'A flat additional cost added on top of the customer\'s chosen shipping option.', 'acme' ); $this->supports = array( 'shipping-zones', 'instance-settings' ); $this->init(); } /** * Load the settings API — fields, saved values, and the save hook. */ public function init() { $this->init_form_fields(); $this->init_settings(); $this->title = $this->get_option( 'title' ); $this->cost = $this->get_option( 'cost' ); add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) ); } /** * The settings fields, keyed by option name. */ public function init_form_fields() { $this->instance_form_fields = array( 'title' => array( 'title' => __( 'Method title', 'acme' ), 'type' => 'text', 'default' => 'Flat Rate Extra', 'description' => __( 'Shown to the customer at checkout.', 'acme' ), 'desc_tip' => true, ), 'cost' => array( 'title' => __( 'Cost', 'acme' ), 'type' => 'number', 'default' => '5.00', 'description' => __( 'Enter a flat cost, or 0 to disable.', 'acme' ), 'desc_tip' => true, ), ); } /** * Add a rate for this method to the package. * * @param array $package The cart contents and destination. */ public function calculate_shipping( $package = array() ) { $rate = array( 'id' => $this->get_rate_id(), 'label' => $this->title, 'cost' => $this->cost, 'package' => $package, 'taxes' => '', // Let WooCommerce calculate tax on this rate. ); $this->add_rate( $rate ); } } } add_action( 'woocommerce_shipping_init', 'acme_shipping_method_init' ); /** * Register it so it can be added to a shipping zone. * * @param array $methods Existing methods. * @return array */ function acme_add_shipping_method( $methods ) { $methods['acme_flat_extra'] = 'Acme_Flat_Extra_Shipping_Method'; return $methods; } add_filter( 'woocommerce_shipping_methods', 'acme_add_shipping_method' );
About this generator
Shipping Method Generator Online
Generate a WooCommerce custom shipping method as a complete WC_Shipping_Method subclass — constructor, init(), init_form_fields() and calculate_shipping() — plus the two registrations that make it selectable inside a shipping zone. The class is declared on woocommerce_shipping_init, so it is never parsed before WooCommerce’s own base class exists.
The Reference tab explains the parts of the API that are easy to get wrong: the difference between id and instance_id, why init_settings() has to run before any get_option() call, and when to use instance_form_fields rather than form_fields. Free to use, no account, nothing uploaded.
Reviewed Output tested on WordPress 6.8 · PHP 8.3
Why the shipping method generator beats copying a WC_Shipping_Method skeleton
The skeletons floating around the web are usually a decade old: they define the class at file scope so it fatals on any site where WooCommerce is inactive, they put settings in form_fields where a zone-based method needs instance_form_fields, and they call get_option() in a constructor that has not run init_settings() yet — which returns the default, silently, forever. This generator writes the modern shape and explains the ordering rules in the Reference tab.
Declared inside woocommerce_shipping_init
The class definition sits in a function hooked to woocommerce_shipping_init and guarded with class_exists(). WooCommerce fires that hook only once its own WC_Shipping_Method base class is loaded, so the file can never fatal on a site with WooCommerce deactivated.
init() before get_option(), always
The constructor calls init(), which runs init_form_fields() and then init_settings() before reading a single get_option(). That order is what makes saved settings actually appear on $this->title and $this->cost — read them before init_settings() and you silently get the defaults.
Zone-aware settings
The method declares array( 'shipping-zones', 'instance-settings' ) in $this->supports and puts its fields in instance_form_fields, so each zone that adds the method keeps its own cost and title rather than sharing one global value.
Extra settings fields you define
Add as many text, number, checkbox or select fields as the method needs. Each becomes a real entry in instance_form_fields with a title, type, default and optional desc_tip description, and is read back into a property inside init().
A tax status you choose deliberately
Taxable emits 'taxes' => '' so WooCommerce calculates tax on the rate; non-taxable emits 'taxes' => false. The choice is visible in the generated calculate_shipping() rather than left to a default nobody remembers.
Checks for the mistakes that cost real time
A missing method id or title is an error, because the id becomes part of the rate id stored on every order. Duplicate extra-field keys are an error, a select with no choices is an error with a one-click fix, and a non-numeric default cost is a warning.
How does the Shipping Method Generator work?
Four steps. Identify the method, set its default cost and tax treatment, add any extra settings, then export.
- 01
Identify the method
Set the method id, the title shown when adding it to a zone, and the description under it. Set the function prefix and text domain; the class name is derived from these and shown as you type.
- 02
Set the cost and tax status
Give the method a default cost — the store owner can change it per zone — and choose whether the rate is taxable or not. This is what
calculate_shipping()passes toadd_rate(). - 03
Add extra settings fields
Optional. Add any further per-zone settings the method needs: a handling fee, a free-over threshold, a courier picker. Each gets a key, label, type, default and description.
- 04
Clear the checks, then export
Resolve anything flagged, then copy the snippet or download it as a
functions.phpblock or a plugin file. Remember the method still has to be added to a zone under WooCommerce → Settings → Shipping.
Worked example — a flat extra cost, taxable, per zone
The class body as the tool writes it. init_form_fields() is omitted here for length; everything else is verbatim.
class Acme_Flat_Extra_Shipping_Method extends WC_Shipping_Method {
public function __construct( $instance_id = 0 ) {
$this->id = 'acme_flat_extra';
$this->instance_id = absint( $instance_id );
$this->method_title = __( 'Flat Rate Extra', 'acme' );
$this->method_description = __( 'A flat additional cost added at checkout.', 'acme' );
$this->supports = array( 'shipping-zones', 'instance-settings' );
$this->init();
}
public function init() {
$this->init_form_fields();
$this->init_settings();
$this->title = $this->get_option( 'title' );
$this->cost = $this->get_option( 'cost' );
add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
}
public function calculate_shipping( $package = array() ) {
$rate = array(
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => $this->cost,
'package' => $package,
'taxes' => '', // Let WooCommerce calculate tax on this rate.
);
$this->add_rate( $rate );
}
}The generator also emits a woocommerce_shipping_methods filter that maps acme_flat_extra to the class name. Registering the method does not place it anywhere — it becomes available to add to a zone, and until someone adds it, no shopper will ever see the rate.
WooCommerce custom shipping methods — frequently asked questions
The questions that come up when building a shipping method rather than configuring one.
Why does my shipping method not appear at checkout?
Registering the class only makes the method available; it does not place it. Go to WooCommerce → Settings → Shipping, open the relevant zone and add the method there. If it is added and still not shown, check that calculate_shipping() actually calls add_rate() on that package — a method that adds no rate is simply not offered.
Why does get_option() return the default instead of my saved setting?
Because it ran before init_settings(). init_settings() is what loads the saved values out of the database into the object, so any $this->get_option() call has to come after it. The generated init() runs init_form_fields() then init_settings() then the reads, in that order, for exactly this reason.
What is the difference between id and instance_id?
id identifies the method type and is shared by every zone that uses it — it is what you register in the woocommerce_shipping_methods filter. instance_id identifies one specific placement of that method in one specific zone. Settings are stored per instance, so get_option() returns the values for that zone, and get_rate_id() combines both into the rate id stored on the order.
Should I use form_fields or instance_form_fields?
Use instance_form_fields for anything zone-based, which is the normal case and what this generator emits. form_fields is for settings that apply globally to the method regardless of zone; a method that declares shipping-zones in $this->supports and puts its cost in form_fields will appear to ignore per-zone configuration.
Can one shipping method offer several rates?
Yes. calculate_shipping() may call $this->add_rate() more than once, and each call becomes a separate option in the shopper’s list. Give each rate a distinct id — usually $this->get_rate_id() with a suffix — otherwise later rates overwrite earlier ones.
How do I make a shipping rate free above a certain cart total?
Read the package inside calculate_shipping(): $package['contents_cost'] is the cart subtotal for that package. Compare it with your threshold and either skip the add_rate() call or add the rate with a cost of zero. Because it is your own class, the condition can be as specific as you need — destination, weight, shipping class or a per-zone setting you added.
Where do I paste the code from the Shipping Method generator?
You have three good options and one bad one. Best is a small site-specific plugin — the generator can output one for you, and it keeps working when you change theme. A code-snippets plugin is fine too. A child theme's functions.php works but ties the code to that theme. Never edit the parent theme: the next update overwrites it and your code is gone.
Can I use the generated code in client work or a commercial plugin?
Yes. The output is ordinary WordPress API calls that you configured — there is no licence attached to it and no attribution required. Use it in client sites, commercial plugins and themes, or products you sell. The generated file carries a short credit comment you are free to delete.
The other WooCommerce generators in the library, plus the WordPress tools most often used alongside this one.