justinholtweb / craft-freeride
Free shipping rules for Craft Commerce — offer it, waive it, block it, and tell customers how far away they are from earning it.
Package info
github.com/justinholtweb/craft-freeride
Type:craft-plugin
pkg:composer/justinholtweb/craft-freeride
Requires
- php: ^8.2
- ext-json: *
- craftcms/cms: ^5.3.0
- craftcms/commerce: ^5.0.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-08-29 12:58:26 UTC
README
Free shipping rules for Craft Commerce 5 — offer it, waive it, block it, and tell customers how far away they are from earning it.
Commerce can already price shipping conditionally. Free Ride is about the other half of the job: the free shipping offer itself, in one ordered list, with the conditions stores actually use and a cart that knows how to say "$12.00 more for free shipping".
Requirements
Craft CMS 5.3+, Craft Commerce 5.0+, PHP 8.2+.
Installation
composer require justinholtweb/craft-freeride php craft plugin/install freeride
How it works
A rule is a set of conditions that all have to pass. The ordered list of rules is the "or":
- Blocking rules run first. If one matches, nothing else can make that order's shipping free.
- Then the granting rules, in order. The first one that matches wins.
So "free over $75, but never on the canoes, and free express over $200" is three rules, not one expression, and you can read the whole policy top to bottom on one screen.
The three things a rule can do
| Mode | What happens |
|---|---|
| Offer a free shipping method | A $0 method appears at checkout when the rule matches — its own name, its own description. Optionally hides the paid methods while it applies. |
| Waive the cost of the chosen method | Whatever the customer picked drops to $0, either for every method or for the ones you name. This is how "free express over $200" works without duplicating the express method. |
| Block free shipping | A veto. Oversized items, excluded regions, clearance stock — anything that should disqualify an order however much it spends. |
Conditions
Everything Commerce can ask an order — coupon code, item subtotal, discounted item subtotal, total quantity, total weight, purchasable, shipping zone, gateway, site — plus Free Ride's own:
- Shipping Postal Code —
28105, 282*, 10000-19999, in or not in - Shipping Country and Shipping State / Province — without modelling a zone first
- Cart Product Types — includes any / includes only / includes none
- Cart Contains Something Related To — a category, a collection entry, anything relatable
- Largest Item Dimension — for "nothing oversized"
- Distinct Items — line items, not quantity
- Day of Week and Time of Day — recurring windows, evaluated now, wrapping midnight
Plus customer conditions (user groups and everything else Craft knows about a user), a date window per rule, and per-site targeting. Rules belong to a store, so multi-store installs get their own policy per store.
Templating
{% set progress = craft.freeride.progress() %}
{% if progress.eligible %}
<p class="free-shipping">{{ progress.message }}</p>
{% elseif progress.isAchievable %}
<p>{{ progress.message }}</p>
<progress value="{{ progress.percent }}" max="1"></progress>
{% endif %}
craft.freeride:
| Call | Returns |
|---|---|
progress(order) |
how far the cart is from free shipping |
eligible(order) |
whether free shipping is available right now |
message(order) |
the one line to show the customer |
explain(order) |
the whole decision, rule by rule and condition by condition |
rules(storeId) |
the store's enabled rules, in evaluation order |
Every argument is optional — with none, they read the current cart.
For carts that update without a page load, POST/GET to actions/freeride/cart/progress returns
the same thing as JSON.
The progress API tells the truth. A cart is only "$12 away" when every other condition on that rule already passes. A cart shipping somewhere the rule excludes is not $12 away from anything, and it is told nothing rather than something wrong.
The simulator
Free Ride → Simulator takes an order or cart number and prints the decision: which rule matched, or exactly which condition failed, and what the customer would be charged. It reads the same evaluation checkout uses, so it can't disagree with it.
From the command line:
php craft freeride/simulate/order <number|reference|id>
Console
php craft freeride/rules # list, in evaluation order php craft freeride/rules/export --file=rules.json # rules live in the database, not project config php craft freeride/rules/import --file=rules.json # matching handles are updated, not duplicated
All three take --store=<handle>.
Settings
Default method label, the progress and qualifying messages, whether an estimated shipping address may be used before there is a real one, and a switch that logs every decision while you're setting rules up.
Permissions
Manage free shipping rules and Use the simulator.
License
Proprietary. See LICENSE.md.