MiniShop3

Full-featured e-commerce component for MODX 3 with Vue admin

Creator: Nikolay Savin (biz87)

Need help installing this extra?

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

Supported Versions
3.0 - Current

Downloads
238

Documentation
MiniShop3 Documentation

Instructions

  • Install via MODX Package Manager: System → Package Management → Download Extras → search "MiniShop3" → Download → Install.
  • After installation MiniShop3 appears as a top-level menu item in the manager.
  • Configure currency, payments and deliveries in MiniShop3 → Settings.
  • Set up product categories from the standard MODX resource tree (use the msCategory class key) and add products inside them.
  • Full documentation, snippets reference and integration guides: docs.modx.pro/components/minishop3/.
  • Support and bug reports: GitHub Issues.
  • 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 InputNumber writes 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/GetList no longer passes sort and dir into ORDER BY unchecked. xPDO only blocks ;, union, sleep() and benchmark(), 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 now ASC or DESC only, the column comes from a fixed list and is quoted, everything else falls back to menuindex (#919).
    • The category products grid had the same hole: an unknown sort reached SQL as msProduct.{sort}, and view_document was enough to use it. Sorting is now built from grid option and relation columns, msProductData extra columns with a valid identifier, and a fixed map of built-in columns. This also fixes sorting by new, popular and favorite: they live in product data but were addressed with the wrong alias and unquoted, so new (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 shared TemplateTagGuard; Telegram values are additionally escaped for parse_mode=HTML, so an unclosed tag no longer breaks delivery (#929).
    • Customer and user processors no longer return credential columns. Customer\GetListCombobox and System\User\GetList narrowed the payload only inside the combo branch; Customer\GetList — a grid page — could not filter at all, leaking hashes and tokens for every customer at once, and Customer\Update returned 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 behind empty($this->ms3->cart), but MiniShop3 exposes cart through __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 msProductData fields and stock work 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-in stock column 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 produced index.php?id=47?action=logout and 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_override allows 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. sudo users have it implicitly.
    • If ms3_inventory is replaced by a custom service that does not implement the new AdjustableInventoryInterface, 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/add took any key and wrote it to the draft, so a visitor holding their own cart token could set a foreign customer_id — it is not overwritten on submit, and the order landed in someone else's account. The same route exposed cost, status_id, num, token and properties. 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 token and idempotency_key, the customer password and token, and the delivery method properties holding integration keys. Customer data now goes through CustomerPublicDto (active extra fields included), delivery through the new DeliveryPublicFields (#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 token and idempotency_key (#840, #848).
    • The order page in the cabinet no longer hands the full delivery row to chunks — properties, class and validation_rules are gone, matching what payment methods already did (#889).
    • Customer and user combo processors no longer concatenate the request id into ORDER 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 mgr context. Previously the component permissions lived only in miniShopManagerPolicy, which was assigned to nobody, so any administrator without sudo hit "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/OFFSET with 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 is sudo) the row-level check cannot drop anything, and a page is served by one SELECT with LIMIT plus one COUNT: 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_ttl falls 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 expose id, name, description, price and logo in 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.zip on 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.php loads vendor/autoload.php on every MODX request, so the previous version's composer/autoload_real.php is already in memory when the transport unpacks a new vendor/ over it; the migrations resolver then re-reads autoload.php, skips its require_once on the unchanged autoload_real.php path and calls a class nobody declared. ms3PhinxEnsureAutoloaderInit() now reads the expected class name out of the new autoload.php and requires composer/autoload_real.php directly 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 holds require-dev packages. 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_reservations and six msOnInventory* events. Off by default (ms3_inventory_enabled); an external warehouse replaces the ms3_inventory service (#589, #603, #763).
    • Payment attempt lifecycle: ms3_payment_attempts + ms3_payment_attempt_events, a public POST /api/v1/payment/webhook/{payment_method_id} endpoint and idempotent webhook replay guarded by a unique provider_event_id. Gateways plug in through PaymentWebhookHandlerInterface (#590, #604, #626).
    • Shipment lifecycle: ms3_shipments + ms3_shipment_events, delivery webhook, tracking numbers and order-status sync, plus GET|PUT /orders/{id}/shipment in the Manager API and {$shipments} in msGetOrder. Off by default (ms3_shipment_enabled) (#591, #605, #606, #607).
    • Order status lifecycle gate: validated transitions with optional ms3_order_status_transitions allow-list, in-transaction domain ports and an idempotent ensure(). A plugin error after commit no longer reverts the status (#592, #596).
    • Domain events and an outbound webhook contract: order.status_changed with an id/amount-only payload, HMAC-SHA256 signature over {timestamp}.{rawBody} (X-MS3-Signature, X-MS3-Timestamp, 300 s skew), a NullWebhookDispatcher by default and in-process listeners via DomainEventBridge::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). New msCustomerGroup model and Manager CRUD (#659, #666, #677, #681, #757).
    • Public Category API (list / get / tree), extended product/list filters, product/filters facets, public delivery/list and payment/list, product gallery in product/{id}/images, catalog resolve by alias|uri (#578, #580, #586, #597, #598, #644).
    • SEO block for the public catalog: title, description, canonical, robots and Open Graph fields, TV overlay through ms3_public_seo_tv_map, robots derived from searchable, and the msOnGetPublicSeo event (#599, #715).
    • Customer auth contract for SSR front ends: me, refresh and 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 in msProducts (#625, #627).
    • Live MODX test suite on modxkit/testbench across 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=modx switches), control heights in toolbars match MODX, and button sizes are unified across screens (#621, #702, #738, #760, #761, #765, #768).
    • msProducts validates string sortby against an allow-list before it reaches pdoTools: resource and product columns are qualified, declared TVs, vendor fields and sortbyOptions keys pass through, RAND(), FIELD(), IFNULL(), COALESCE() and CAST() 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_status now defaults to 0, 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 msOnChangeOrderStatus returns an error: the status is already committed, so the customer is no longer left uninformed (#754, #756).
    • Package upgrades clean up obsolete files from core and assets, skipping anything still listed in ms3_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).
    • PdoPaymentAttemptStore no 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_save permissions, and order field ACL follows miniShopManagerPolicy (#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 options in cart/add and cart/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.pro yet — it follows in a separate update.

    Current releases

    1.14.3-beta2
    View | Download
    October 23, 2026
    3.0 - Current