Version 5.0.1
October 5, 2026
Security
- Comparisons could show elements a visitor shouldn't see. The table endpoint and the comparison page loaded
?ids=withstatus(null), adding resolved an ID withgetElementById(), and stored lists did the same — and all of them are anonymous. Guessing IDs read the rows (prices, specs, titles) of disabled, scheduled and expired products and entries, and drafts; adding one returned its title and URL; and a share link kept showing a product after it was taken offline. Every path now loads elements through the element type's normal front-end query — live, no drafts — so anything else is left out of the table, refused when added, and drops out of the visitor's list, their session payload and any share link once it goes offline. Removing it still works. Staff viewing a visitor's comparison in the control panel see the same table the visitor does.
Fixed
- An unknown
sethandle answered 500 instead of 400: the error message interpolated a variable calledhandle”. - A row type registered by another plugin that implements
RowInterfacedirectly, rather than extendingBaseRow, took the whole table down — the table builder calledBaseRow::resolveSafely()on every row. Its failures now leave an empty cell, as built-in rows' do. - The README and docs listed the JSON endpoints as
compare/list/…; since the rebrand they arecomparer/list/….
Changed
- PHPStan and ECS configuration, and the findings they raised tidied.
Version 5.0.0
August 23, 2026
Initial release.
Added
- Comparison sets, stored in project config: one element type each, any sections / category groups / product types, an item ceiling, and the rows of the comparison table.
- Row types that resolve themselves off any element: title, image, custom field, element
attribute, link, and — with Craft Commerce — price, SKU, stock and add-to-cart. Plugins can
register their own through
Rows::EVENT_REGISTER_ROW_TYPES. - Difference highlighting on normalized values rather than rendered markup, plus a "differences only" view (Pro).
- Visitor compare lists held in the database: a guest's under a random cookie token, a signed-in user's under their account, with the guest list merged into the account on login.
- A zero-dependency front-end runtime — compare buttons, a comparison bar with drag reordering, a modal, and a print stylesheet. No build step, no third-party requests.
- A standalone comparison page at
/compare, and shareable comparison links (Pro). - Insights (Pro): what visitors added, and which items they put next to each other.
- A control-panel comparison browser, so support can see what a customer is looking at.
- A documented JSON API, a Twig API, and
compare/compare/pruneconsole commands. - Lite and Pro editions. A lapsed Pro licence narrows the table rather than breaking the page.