Version 5.1.0
October 5, 2026
This release fixes the checkout gate. Until now an order Forklift should have refused — an account on hold, an approval outstanding, over a spend limit, a missing PO number — was charged and then left uncompleted: the refusal ran after the payment and failed with a PHP error rather than stopping it. If your store has seen "Completing order failed" or 500s at checkout, look for carts with a successful payment that never became orders. Orders are now refused before any payment is taken.
Fixed
- The checkout gate charged orders it should have refused. It was hooked to
Order::EVENT_BEFORE_COMPLETE_ORDER, which Commerce does not let a plugin cancel — it is triggered with a plain event, so settingisValidthrewUnknownPropertyException— and order completion runs after the payment. The gate now vetoesPayments::EVENT_BEFORE_PROCESS_PAYMENT, before anything is charged, with the reasons on the order as notices. An order completed without a payment is still refused at completion, now with the reasons rather than a PHP error. - Buyers couldn't switch account. The front-end account switcher (
forklift/companies/switch, used by the bundled portal) sat behind the control panel's "View companies" permission, so every buyer who belonged to more than one company got a 403. It now needs only a signed-in member of the company being switched to.
Security
- The quick-order pad, SKU lookup and autocomplete exposed products a visitor shouldn't see. They are open to visitors who have not signed in, on purpose, but read
commerce_purchasablesdirectly — so anyone could enumerate disabled, unreleased, expired and trashed SKUs with their descriptions and prices, andsuggest?q=%%returned every SKU in the store. They now find only what the storefront would show on the current site — an enabled variant of a live product, no drafts — and anything else is "not found", worded exactly like a SKU that does not exist.lookupno longer quotes a price for something that is not for sale, andsuggestleaves it out. A%,_or\in the search is matched literally.
Added
- Events, so a module can react to or veto what Forklift does with money:
Approvals::EVENT_BEFORE_REQUEST/AFTER_REQUESTandEVENT_BEFORE_DECIDE/AFTER_DECIDE;Quotes::EVENT_BEFORE_SEND/AFTER_SEND,EVENT_AFTER_ACCEPTandEVENT_BEFORE_DECLINE/AFTER_DECLINE;Credit::EVENT_BEFORE_SAVE_ENTRY/AFTER_SAVE_ENTRYandEVENT_BEFORE_DELETE_ENTRY/AFTER_DELETE_ENTRY; andPricing::EVENT_DEFINE_PRICEto change any price Forklift resolves. Everybefore…event is cancelable. See the new Events page.
Changed
- PHPStan and ECS configuration, and the findings they raised tidied.
Version 5.0.0
August 27, 2026
Initial release.
Added
- Company accounts as a Craft element, with a field layout of your own, an element index with status and credit sources, account codes, and an on-hold state that stops new orders without touching anything else.
- Buyers, roles and spend limits. Four roles — administrator, approver, buyer, viewer — held per membership rather than per person, with per-order spend limits and an always-needs-approval flag. A company can never be left with no administrator.
- Contract price lists with fixed, percentage-off and amount-off entries; four target levels (variant, product, product type, everything); quantity breaks; date ranges; per-company or trade-wide assignment; priority with a stable tiebreak. CSV import and export, both in the control panel and on the command line.
- A price preview in the control panel that runs the real resolver and explains which list, which entry and which break produced the number — and why a contract price was ignored when it was.
- Spend approvals: company thresholds, per-buyer limits, an approval queue with a control-panel badge, decisions by tokenised email link with no sign-in, expiry, and re-approval when an approved basket grows.
- Net terms as named payment terms including early-settlement discounts (
2/10 Net 30), a Purchase Order gateway that authorises rather than purchases, credit limits enforced at checkout, an append-only signed credit ledger, and aged statements in the control panel, the buyer portal and CSV. - Request a quote as a Quote element: request from a basket, price it in the control panel, send it, and give the buyer a signed pay-by-link that loads the quoted basket into their own session for an ordinary Commerce checkout. Quoted prices survive every recalculation.
- Quick order pad, CSV upload and reorder, all producing the same result shape, with per-row errors and line numbers, a dry-run preview, and reordering that re-prices at today's rates and says what changed.
- Tax exemption certificates per company with jurisdiction and expiry, human approval, and a tax adjuster that subclasses Commerce's own so the arithmetic stays Commerce's.
- A buyer portal — account, orders, quotes, statement, buyers — where every screen can be replaced by a template of your own.
- Console commands for companies, price lists, credit and maintenance, including
forklift/maintenance/doctor, which names the configuration problems that produce no error anywhere. - 143 integration checks and 37 HTTP checks, both idempotent and self-cleaning.
Notes
- Requires Craft CMS 5.3+, Craft Commerce 5.3+ and PHP 8.2+.
- Five default payment terms are created on install.
- Forklift touches Commerce through five documented extension points and replaces the tax engine only when Commerce's own is in place.