A shipping method
A shipping method is a class extending WC_Shipping_Method. Its main method, calculate_shipping(), receives the cart contents with the address and reports a price. Methods are bound to shipping zones: a zone is a set of countries or regions with its own list of methods and prices.
Zones in the admin panel
Open WooCommerce → Settings → Shipping. Create a Germany zone with the Germany region — we will add our method to it. The customer is shown the methods of the zone their address matches.
The method class
Create the file includes/class-acme-shipping.php:
// wp-content/plugins/acme-shop/includes/class-acme-shipping.php
class Acme_Shipping extends WC_Shipping_Method
{
public function __construct($instanceId = 0)
{
$this->id = 'acme_courier';
$this->instance_id = absint($instanceId);
$this->method_title = 'Acme courier';
$this->method_description = 'Delivery by our own courier.';
$this->supports = ['shipping-zones', 'instance-settings'];
$this->instance_form_fields = [
'title' => ['title' => 'Title', 'type' => 'text', 'default' => 'Courier'],
'cost' => ['title' => 'Cost', 'type' => 'price', 'default' => '5'],
];
$this->title = $this->get_option('title');
}
public function calculate_shipping($package = []): void
{
$this->add_rate([
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => (float) $this->get_option('cost'),
]);
}
}An instance is a method added to a particular zone: each has its own number and its own settings from instance_form_fields. add_rate() offers the customer an option with a name and a price.
Registration
// wp-content/plugins/acme-shop/acme-shop.php
add_action('woocommerce_shipping_init', function () {
require_once __DIR__ . '/includes/class-acme-shipping.php';
});
add_filter('woocommerce_shipping_methods', function (array $methods): array {
$methods['acme_courier'] = 'Acme_Shipping';
return $methods;
});The class is included on the woocommerce_shipping_init hook — the base class does not exist earlier. The array key matches the method id. Now open the Germany zone, press Add shipping method and choose Acme courier. On the cart page with a German address the option Courier — €5.00 appears.
Calculating by contents
The $package argument holds the products and the address. Let us compute the price by weight:
// wp-content/plugins/acme-shop/includes/class-acme-shipping.php
public function calculate_shipping($package = []): void
{
$weight = 0.0;
foreach ($package['contents'] as $item) {
$weight += (float) $item['data']->get_weight() * $item['quantity'];
}
$cost = (float) $this->get_option('cost') + max(0, $weight - 2) * 1.5;
$this->add_rate([
'id' => $this->get_rate_id(),
'label' => $this->title,
'cost' => $cost,
]);
}Up to two kilograms the base price applies; every further kilogram adds €1.50. A product's weight is set in the editor on the Shipping tab.
$package key | What it holds |
|---|---|
contents | Cart items, as in WC()->cart->get_cart() |
contents_cost | The products total |
destination | The address: country, state, postcode, city |
If the method is unavailable — for example, for a parcel that is too heavy — simply do not call add_rate().
Changing other methods' prices
The cost of any method is changed with a filter, without a class of your own. Let us make shipping free from €50:
// wp-content/plugins/acme-shop/includes/cart.php
add_filter('woocommerce_package_rates', function (array $rates, array $package): array {
if ($package['contents_cost'] >= 50) {
foreach ($rates as $rate) {
$rate->set_cost(0);
}
}
return $rates;
}, 10, 2);The filter receives all options found for the package. Fill the cart up to €50 — all shipping methods become free. The filter works in the block cart too.
Common mistake
The price was changed in the code, but the cart still shows the old one. WooCommerce remembers shipping options in the customer's session and recalculates them only when the cart or the address changes. While debugging, change a product quantity in the cart or turn on Debug mode in WooCommerce → Settings → Shipping → Shipping options — it switches this remembering off.