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.
| Row | What it reads | Settings |
|---|---|---|
| Title | The element's title | Link to the element |
| Image | An asset field | Which field (blank = first one found), transform handle |
| Custom field | Any custom field | Which field |
| Element attribute | A native property | Post date, expiry, created, updated, slug, ID, status, author |
| Link | A "View" button | Link text, open in a new tab |
| Price (Commerce) | The purchasable's price | Cheapest variant / default variant / range; show original when discounted |
| SKU (Commerce) | The purchasable's SKU | — |
| Stock (Commerce) | Availability | In stock / out of stock, or the number in stock |
| Add to cart (Commerce) | A real Commerce form | Button text |
| Twig template (Pro) | Whatever you render | Template 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
| Setting | What 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 column | Row labels stay put while the table scrolls sideways |
| Freeze the header row | Product names stay put while it scrolls down |
| Custom table template | Renders 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.
| Setting | Default | Notes |
|---|---|---|
| Comparison page URI | compare | Must not clash with an entry URI on any site |
| Comparison bar position | Bottom | Or top, or none |
| Open when full | Off | A modal nobody asked for is an interruption |
| Load the bundled front end | On | Turn off to drive the JSON API from your own build |
| Require login | Off | Only signed-in users may build a comparison |
| Guest list lifetime | 30 days | Signed-in users' lists never expire |
| Cookie name | CraftCompareToken | Holds a token, never the list |
| Record insights | On (Pro) | Element IDs only — no personal data |
| Keep insights for | 90 days | Swept by Craft's garbage collection |
| Share link lifetime | Forever (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.