Screenshots
About MiniShop3
MiniShop3 is a modern, full-featured e-commerce component for MODX 3 — the successor to MiniShop2 (MODX 2). It provides product catalog with options and galleries, cart, checkout and orders, customer accounts with email/SMS verification, deliveries and payments, model fields and grids — all through a Vue 3 + PrimeVue admin UI and a clean REST API for storefront and integrations.
Information
Released
October 23, 2026
Supported Database
MySQL
License
GPLv2
Instructions
New in 1.14.3-beta2
[1.14.3-beta2]
Fixed
- Inline editing of numeric columns in the category products grid now saves. The typed value snapped back, no request was sent and no error was shown: PrimeVue
InputNumberwrites the typed value into the model only on blur and on Enter, and it reports the blur before doing so, so the save handler read the value the cell had opened with and treated the edit as unchanged. Capturing Enter added a second failure — the cell was torn down before the field finished its own handler, which raised a console error. This affected every numeric column, not just custom product fields: built-in price and weight behaved the same way when in-table editing was enabled. Text columns and checkboxes were never affected (#943).
[1.14.3-beta1]
Security
Product/GetListno longer passessortanddirintoORDER BYunchecked. xPDO only blocks;,union,sleep()andbenchmark(), so a comma and a subquery went through and the row order leaked arbitrary tables, including manager password hashes. The processor declares no permission, so any manager session was enough. Direction is nowASCorDESConly, the column comes from a fixed list and is quoted, everything else falls back tomenuindex(#919).- The category products grid had the same hole: an unknown
sortreached SQL asmsProduct.{sort}, andview_documentwas enough to use it. Sorting is now built from grid option and relation columns,msProductDataextra columns with a valid identifier, and a fixed map of built-in columns. This also fixes sorting bynew,popularandfavorite: they live in product data but were addressed with the wrong alias and unquoted, sonew(a reserved MySQL word) broke the query and the grid returned an empty page (#918). - Product fields are escaped before output. The category grid substituted values into a column HTML template rendered with
v-html, the gallery utility printed the media source name the same way, and the storefront row chunk emitted title, description, vendor and article unfiltered — a product title carrying markup executed in the browser, which in the manager targets a higher-privileged user. Grid values are escaped while the column template markup is kept, the gallery no longer stores HTML in a lexicon entry, and the chunk pipes fields through|e, attributes included (#920). - Telegram and SMS notifications no longer execute MODX tags coming from customer data. The guard added for e-mail in 1.14.2-beta1 did not cover these channels, so an order comment with
[[++setting]]revealed system settings — and in a customer-facing channel the attacker read them. No account is needed: placing an order is enough. Tag masking moved to a sharedTemplateTagGuard; Telegram values are additionally escaped forparse_mode=HTML, so an unclosed tag no longer breaks delivery (#929). - Customer and user processors no longer return credential columns.
Customer\GetListComboboxandSystem\User\GetListnarrowed the payload only inside thecombobranch;Customer\GetList— a grid page — could not filter at all, leaking hashes and tokens for every customer at once, andCustomer\Updatereturned the full row through core, so an empty update read any customer's password hash. All five now use an allowlist: a column added later stays out until it is listed. Extra fields registered by the site still travel. Credential columns are excluded from the query as well, so they are never read (#908).
Fixed
- Delivery cost by weight was ignored on submit.
Delivery::getCost()read the weight behindempty($this->ms3->cart), butMiniShop3exposescartthrough__get()without__isset(), so the check always passed and the weight stayed zero — the customer paid a cost without weight while the manager recalculation produced a different total. Weight now comes from the order's own products, matching the manager path: for "390 + 60 per kg" and a 0.4 kg cart the cost is 414 instead of 390 (#810). - Extra
msProductDatafields andstockwork in the category products grid. A column bound to a custom field stayed empty and inline edits were lost while the API reported success. Reading and writing kept independent column lists, neither aware of extra fields; the same mismatch left the built-instockcolumn empty even though edits reached the database. Both paths now share one policy built from the model map (#914). - Editing order lines in the manager reconciles the stock ledger. Adding, changing quantity and removing a line left the reservation untouched, so payment and cancellation settled the pre-edit quantity. The ledger is now matched to the resulting quantity per product, stock moves by the delta, and a line is not saved when stock is short (#829).
- The cabinet "Log out" link works with friendly URLs disabled. It was built as
[[~[[*id]]]]?action=logout, which producedindex.php?id=47?action=logoutand a 404. The parameter now goes through the link tag so MODX picks the right separator (#850). - Upgrades no longer flood the install log. The snippet properties resolver read them through a method that touches the property description without a check, emitting one warning per property — about seventy lines that look like install errors (#782).
Changed
- New permission
msorder_inventory_overrideallows editing order lines past a stock shortage. It is created on upgrade but not granted in the default policy: grant it explicitly if your managers relied on going below zero.sudousers have it implicitly. - If
ms3_inventoryis replaced by a custom service that does not implement the newAdjustableInventoryInterface, editing an order that already holds stock is rejected. - Sorting the legacy product combo list by product data columns (article, for example) falls back to the default order. The combo returns only id, title and context, so the UI is unaffected.
[1.14.2-beta1]
Security
- Storefront order endpoints no longer accept internal draft columns.
POST /api/v1/order/addtook anykeyand wrote it to the draft, so a visitor holding their own cart token could set a foreigncustomer_id— it is not overwritten on submit, and the order landed in someone else's account. The same route exposedcost,status_id,num,tokenandproperties. Form fields and shop-defined extra columns stay writable (#860). - Customer-supplied values are no longer parsed as tags when an order e-mail is built. With pdoTools installed
$modx->getParser()is Fenom rather than MODX tag parsing, so an order comment could run[[++setting]]and{$_modx->runSnippet(...)}. The subject was affected too: data was substituted into the subject string before compilation. Custom e-mail chunks keep working — Fenom, MODX settings, lexicon entries and snippets are still available in the template itself (#888). - E-mail placeholders no longer carry secrets: the order
tokenandidempotency_key, the customerpasswordandtoken, and the delivery methodpropertiesholding integration keys. Customer data now goes throughCustomerPublicDto(active extra fields included), delivery through the newDeliveryPublicFields(#889). - Cabinet pages no longer expose the bcrypt password hash, the API token, the privacy-consent IP or login counters to Fenom chunks, and the order array no longer carries
tokenandidempotency_key(#840, #848). - The order page in the cabinet no longer hands the full delivery row to chunks —
properties,classandvalidation_rulesare gone, matching what payment methods already did (#889). - Customer and user combo processors no longer concatenate the request
idintoORDER BY FIELD(id, …). The value is normalised to a list of positive integers (#882).
Fixed
- Install and upgrade now grant the shop policy to user groups that already have access to the
mgrcontext. Previously the component permissions lived only inminiShopManagerPolicy, which was assigned to nobody, so any administrator withoutsudohit "Access denied. Required permission: msproduct_save". A group created after the install still needs the policy granted manually (#893).
Changed
- The category product grid in the manager is paginated again. Document-level ACL added in #473 replaced
LIMIT/OFFSETwith scanning, so every page request read the whole category — 18 queries and 953 ms on a 2114-product category, 33 queries and 1350 ms on later pages. When no resource-group access records exist (or the context setting is off, or the user issudo) the row-level check cannot drop anything, and a page is served by oneSELECTwithLIMITplus oneCOUNT: 1 query and 24–31 ms, no longer growing with the page number. With resource groups configured the previous behaviour stays (#473).
Notes for upgrades
ms3_customer_token_ttlfalls back to 86400 instead of 604800 when the setting is missing from the database, matching the packaged value. On sites without that setting customers will sign in again after a day rather than a week (#848).- Templates printing
{$customer.password},{$order.token}or{$delivery.properties}now get empty values. Delivery methods exposeid,name,description,priceandlogoin e-mails and the cabinet. Utils::sendEmail()no longer parses tags in the body it receives. The method is unused inside the component; add-ons that passed a body with MODX tags must parse it themselves, before any customer text is in the string (#888).
[1.14.1-beta2]
Changed
- Package files are shipped as their own vehicles and no longer written to a
.preserved.zipon every install. Upgrades get faster — 3.1 s to 2.2 s on a warm cache, more on slow disks — at the cost of the Restore uninstall mode for MiniShop3 files: with no archive there is nothing to roll back, so that mode now restores category objects while removing the files. Preserve (the default) and Remove behave exactly as before, and the rollback was already partial because Phinx migrations never roll back (#783).
[1.14.1-beta1]
Fixed
- Upgrading over an installed MiniShop3 no longer dies with
Class "ComposerAutoloaderInit…" not found.bootstrap.phploadsvendor/autoload.phpon every MODX request, so the previous version'scomposer/autoload_real.phpis already in memory when the transport unpacks a newvendor/over it; the migrations resolver then re-readsautoload.php, skips itsrequire_onceon the unchangedautoload_real.phppath and calls a class nobody declared.ms3PhinxEnsureAutoloaderInit()now reads the expected class name out of the newautoload.phpand requirescomposer/autoload_real.phpdirectly when it is missing (#779). - The generated autoloader class name is pinned through
config.autoloader-suffix, so it no longer moves with the package set and cannot desynchronise across releases (#779). - The transport build refuses to package a
vendor/that still holdsrequire-devpackages. 1.14.0-beta1 shipped PHPUnit, PHPStan and the testbench — 45 MB instead of roughly seven (#779).
[1.14.0-beta1]
Added
- Opt-in inventory: stock is reserved on the New status, committed on Paid and released on Canceled, with an atomic ledger in
ms3_inventory_reservationsand sixmsOnInventory*events. Off by default (ms3_inventory_enabled); an external warehouse replaces thems3_inventoryservice (#589, #603, #763). - Payment attempt lifecycle:
ms3_payment_attempts+ms3_payment_attempt_events, a publicPOST /api/v1/payment/webhook/{payment_method_id}endpoint and idempotent webhook replay guarded by a uniqueprovider_event_id. Gateways plug in throughPaymentWebhookHandlerInterface(#590, #604, #626). - Shipment lifecycle:
ms3_shipments+ms3_shipment_events, delivery webhook, tracking numbers and order-status sync, plusGET|PUT /orders/{id}/shipmentin the Manager API and{$shipments}inmsGetOrder. Off by default (ms3_shipment_enabled) (#591, #605, #606, #607). - Order status lifecycle gate: validated transitions with optional
ms3_order_status_transitionsallow-list, in-transaction domain ports and an idempotentensure(). A plugin error after commit no longer reverts the status (#592, #596). - Domain events and an outbound webhook contract:
order.status_changedwith an id/amount-only payload, HMAC-SHA256 signature over{timestamp}.{rawBody}(X-MS3-Signature,X-MS3-Timestamp, 300 s skew), aNullWebhookDispatcherby default and in-process listeners viaDomainEventBridge::addListener()(#593, #764, #772, #774). - Resource-group ACL across the storefront: the public catalog, Fenom snippets and cart honour MODX resource groups; a signed-in customer whose customer group is linked to a MODX user group sees the member catalog (
ms3_web_catalog_respect_resource_groups). NewmsCustomerGroupmodel and Manager CRUD (#659, #666, #677, #681, #757). - Public Category API (
list/get/tree), extendedproduct/listfilters,product/filtersfacets, publicdelivery/listandpayment/list, product gallery inproduct/{id}/images, catalog resolve byalias|uri(#578, #580, #586, #597, #598, #644). - SEO block for the public catalog:
title,description,canonical,robotsand Open Graph fields, TV overlay throughms3_public_seo_tv_map,robotsderived fromsearchable, and themsOnGetPublicSeoevent (#599, #715). - Customer auth contract for SSR front ends:
me,refreshand Bearer binding, plus order-draft prefill from the customer profile after login (#588, #639). - Text watermarks for Media Source thumbnails alongside the existing image overlay, with the font path jailed to the site root (#731, #769).
- Per-category
menuindex: products keep their own order inside additional categories, both in the manager grid and inmsProducts(#625, #627). - Live MODX test suite on
modxkit/testbenchacross MODX 3.1.2-pl, 3.2.3-pl and 3.2.4-pl, with the schema built from Phinx migrations and processor ACL assertions (#687, #698, #699, #712).
Changed
- Vue manager follows the MODX 3 look: the theme comes from VueTools
getActiveTheme()(Aura stays the default,vuetools.theme=modxswitches), control heights in toolbars match MODX, and button sizes are unified across screens (#621, #702, #738, #760, #761, #765, #768). msProductsvalidates stringsortbyagainst an allow-list before it reaches pdoTools: resource and product columns are qualified, declared TVs, vendor fields andsortbyOptionskeys pass through,RAND(),FIELD(),IFNULL(),COALESCE()andCAST()are allowed with checked arguments, and anything else is dropped and logged (#741, #742, #757).- Failed payments no longer cancel the order by default —
ms3_payment_on_failed_statusnow defaults to0, so a customer can retry without a 409. With inventory enabled the reservation stays with the order until it is canceled (#754, #756). - Notifications are sent even when a plugin on
msOnChangeOrderStatusreturns an error: the status is already committed, so the customer is no longer left uninformed (#754, #756). - Package upgrades clean up obsolete files from
coreandassets, skipping anything still listed inms3_frontend_assets, and migrations always run on MiniShop3's own Phinx instead of another extra's copy (#690, #709, #726, #728). - CORS preflight is answered before the middleware stack, without touching the MODX session or poisoning the cache (#637, #708, #725, #727).
Fixed
- Customer lockout: a temporary lockout no longer blocks password reset, permanent blocks revoke tokens, and the failure counter resets once the lockout expires (#734, #747, #748).
PdoPaymentAttemptStoreno longer swallows write errors on the MODX connection (ERRMODE_SILENT): duplicate keys are reported honestly and failed statements raise instead of passing silently (#752, #753).- Repeated delivery webhooks heal an order status that drifted from the shipment, and a duplicate claim no longer re-emits
msOn*events (#754, #756). - Option suggestions in the manager return distinct values again (#745, #746).
- Manager connector never emits invalid UTF-8 or an empty JSON body on broken input (#662, #676, #691, #618).
- Gallery and product sorting honour
msproductfile_save/msproduct_savepermissions, and order field ACL followsminiShopManagerPolicy(#614, #661, #675). - Facet and page caches are invalidated when resource-group ACL, group membership or a customer-group link changes (#735).
- Cart accepts JSON strings for
optionsincart/addandcart/change-option(#633, #638). - A poisoned English lexicon cache under a non-English key is healed once after install or upgrade, restoring translated product tabs (#758, #759, #767, #771).
Notes
- Documentation for inventory, shipment and payment attempts is not in
docs.modx.proyet — it follows in a separate update.