
Reactions
Universal reactions for MODX 3 — likes, GitHub-style sets, tops and trending on any object


In snippets and the API pass the short class msProduct and the product ID (&object= / object_id). The server resolves the product via FQCN (MiniShop3\Model\msProduct) and STI modResource. It does not look up the short name in xPDO, so error.log stays clean.
Aggregates store object_class as msProduct (whatever you passed in class / class_key). Build catalog joins on that same string.
github in picker mode (chips + +):
[[!Reactions?
&class=`msProduct`
&object=`[[*id]]`
&set=`github`
&context=`web`
]]{'!Reactions' | snippet : [
'class' => 'msProduct',
'object' => $_modx->resource.id,
'set' => 'github',
'context' => 'web',
]}👍/👎 only:
[[!Reactions?
&class=`msProduct`
&object=`[[*id]]`
&set=`updown`
]]{'!Reactions' | snippet : [
'class' => 'msProduct',
'object' => $_modx->resource.id,
'set' => 'updown',
]}[[!ReactionsCount?
&class=`msProduct`
&object=`[[+id]]`
&format=`👍 {LIKES}`
]]{'!ReactionsCount' | snippet : [
'class' => 'msProduct',
'object' => $id,
'format' => '👍 {LIKES}',
]}Use msProducts with leftJoin.
[[!msProducts?
&parents=`10`
&limit=`12`
&leftJoin=`{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = msProduct.id AND Aggregate.object_class = 'msProduct'"}}`
&sortby=`Aggregate.likes`
&sortdir=`DESC`
&tpl=`tpl.msProduct.card`
]]{'!msProducts' | snippet : [
'parents' => 10,
'limit' => 12,
'leftJoin' => '{"Aggregate":{"class":"Reactions\\Model\\ReactionAggregate","on":"Aggregate.object_id = msProduct.id AND Aggregate.object_class = \'msProduct\'"}}',
'sortby' => 'Aggregate.likes',
'sortdir' => 'DESC',
'tpl' => 'tpl.msProduct.card',
]}[[!TopLiked?
&class=`msProduct`
&period=`month`
&limit=`6`
&tpl=`tpl.top.product`
]]{'!TopLiked' | snippet : [
'class' => 'msProduct',
'period' => 'month',
'limit' => 6,
'tpl' => 'tpl.top.product',
]}Trending products:
[[!Trending?
&class=`msProduct`
&limit=`8`
]]{'!Trending' | snippet : [
'class' => 'msProduct',
'limit' => 8,
]}In tpl.msProduct.card:
<div class="product-card">
<h3>[[+pagetitle]]</h3>
<p class="product-price">[[+price]] ₽</p>
[[!ReactionsCount?
&class=`msProduct`
&object=`[[+id]]`
&format=`❤️ {TOTAL}`
]]
</div><div class="product-card">
<h3>{$pagetitle}</h3>
<p class="product-price">{$price} ₽</p>
{'!ReactionsCount' | snippet : [
'class' => 'msProduct',
'object' => $id,
'format' => '❤️ {TOTAL}',
]}
</div>Products usually live in the web context. If the catalog uses a separate context, pass context:
[[!Reactions?
&class=`msProduct`
&object=`[[*id]]`
&context=`catalog`
&set=`updown`
]]{'!Reactions' | snippet : [
'class' => 'msProduct',
'object' => $_modx->resource.id,
'context' => 'catalog',
'set' => 'updown',
]}Only when the product has like/dislike votes. In Fenom use {raw …}:
[[!ReactionsSchema?
&class=`msProduct`
&object=`[[*id]]`
&context=`web`
]]{raw ('!ReactionsSchema' | snippet : [
'class' => 'msProduct',
'object' => $_modx->resource.id,
'context' => 'web',
])}After a migration or import:
php core/components/reactions/cli.php recount --class-key=msProduct