Compare for Craft CMS

Configuration

Compare sets

A set decides three things: what may be compared, how many at once, and which rows the table draws. Sets live in project config, so a set and its table version and deploy with the rest of the project rather than being rebuilt by hand in production.

One set holds one element type. A site that compares products and case studies has two sets. An element belongs to at most one set — that is what lets a compare button work out, on its own, which list it is adding to.

Which elements are in the set

Tick the sections, category groups or product types you want. Tick nothing and the set takes all of them.

When two sets could both match an element, the one that names its sources wins over a catch-all. That way adding a "everything" set later cannot silently steal every button on the site.

Row types

Every row is a resolver: it is handed an element and works out what to say about it. That is what lets one table definition serve a product, an entry and a category.

RowWhat it readsSettings
TitleThe element's titleLink to the element
ImageAn asset fieldWhich field (blank = first one found), transform handle
Custom fieldAny custom fieldWhich field
Element attributeA native propertyPost date, expiry, created, updated, slug, ID, status, author
LinkA "View" buttonLink text, open in a new tab
Price (Commerce)The purchasable's priceCheapest variant / default variant / range; show original when discounted
SKU (Commerce)The purchasable's SKU
Stock (Commerce)AvailabilityIn stock / out of stock, or the number in stock
Add to cart (Commerce)A real Commerce formButton text
Twig template (Pro)Whatever you renderTemplate path

Leave a row's Label blank and it uses the row type's own name, or the field's name for a custom field row.

A field that isn't on every element

That is the normal case, not a mistake. A set spanning two entry types where only one has screenSize gives the other a blank cell — and "one of these has no warranty and the others do" is exactly the difference a shopper is looking for, so a blank counts as a difference.

The Twig template row

The escape hatch, because no fixed list of row types survives contact with a real catalogue. Somebody will always need to compare "delivery band", which is three fields and a lookup table.

The template is rendered in site mode and given element and row:

{# templates/_compare-rows/delivery-band.twig #}
{% set band = element.weightKg > 30 ? 'Two-person delivery' : 'Standard courier' %}
<strong>{{ band }}</strong>

Its comparable value is the rendered text, tags stripped and whitespace collapsed — otherwise markup differing only by an ID attribute would report as a difference on every row and make the "differences only" view useless exactly where it is most needed.

Display settings, per set

SettingWhat it does
Highlight differences (Pro)Marks rows whose values disagree; greys out the ones that agree
Start with differences only (Pro)Opens the table already filtered. Visitors can toggle it back
Freeze the label columnRow labels stay put while the table scrolls sideways
Freeze the header rowProduct names stay put while it scrolls down
Custom table templateRenders instead of the bundled table

Difference highlighting compares normalized values, not rendered markup — so $10.00 and $10 are one price, Yes and yes are one answer, and two relation fields holding the same three entries in a different order are the same answer.

Plugin settings

Settings → Plugins → Compare.

SettingDefaultNotes
Comparison page URIcompareMust not clash with an entry URI on any site
Comparison bar positionBottomOr top, or none
Open when fullOffA modal nobody asked for is an interruption
Load the bundled front endOnTurn off to drive the JSON API from your own build
Require loginOffOnly signed-in users may build a comparison
Guest list lifetime30 daysSigned-in users' lists never expire
Cookie nameCraftCompareTokenHolds a token, never the list
Record insightsOn (Pro)Element IDs only — no personal data
Keep insights for90 daysSwept by Craft's garbage collection
Share link lifetimeForever (Pro)The point of a link is that it still works later

Why the cookie holds only a token

Putting the list itself in a cookie is the obvious implementation and fails three ways: it cannot be shared by URL, cannot be reported on, and blows the 4 KB header budget at around forty products — silently, on the visitor's machine. So the cookie carries a random token and the list lives in the database.

That is also what makes the login merge work: sign in and the guest list folds into your account, so three products added on a phone are still there on a laptop.

Permissions

Two, under Compare in the user-group permission list:

  • View visitors' comparisons — the CP comparison browser
  • View comparison insights — the Pro reports

Editing sets is admin-only, because a set is project config.

Console commands

php craft compare            # what Compare currently holds
php craft compare/prune      # drop abandoned guest lists and expired events

Both sweeps also run from Craft's own garbage collection; the commands exist for when waiting is not an answer.