What’s new in StoreMaster — the features, improvements and fixes in every release, newest first.
Every entry here is work that shipped. Nothing on this page is a preview of what is planned.
The setup check now reads the real list
Yesterday's release added a step that goes back and confirms the capabilities you set up really did switch on. It was reading the wrong list — an empty one — so it always told you everything was on.
Always green is not the same as correct
The check asked the setup plan for a field it does not have. An empty answer reads the same as "nothing is wrong", so the reassuring message appeared whether or not anything was switched on, and the warning it exists to show could not appear at all.
It now reads what the plan actually returns, and tells you two different things apart: a capability that is switched off, which you can turn on, and one that is simply not in your build, which you cannot. Calling the second one "off" sends you looking for a switch that is not there.
Fixes & improvements
- The setup check is now exercised against the real server response rather than a hand-written stand-in, which is how this got past its own tests.
Setting up now looks at your store first
Choosing what kind of shop you are building is the second question now. The first is whether the shop is ready to be built on.
It checks before you choose
Open Get started and StoreMaster looks at your store: supported PHP, WordPress and WooCommerce, whether you are served over HTTPS, whether the background job runner is available, your database tables, which checkout you are running, how WooCommerce stores your orders, and whether another plugin overlaps something you are about to switch on.
None of it stops you. If everything passes you get one line saying so, and the detail if you want it. If something needs knowing you are told in plain words and the setup choices are still right there underneath. A staging copy on plain http is a normal thing to be building on, and it is treated as one.
And it checks afterwards
Applying a setup used to end with a list of what was left to you. It still does — but now it also goes back and looks: are those capabilities really switched on, do their own health checks pass, and here are links to open your shop, a product page, the basket and the checkout.
That last part is the point. A screen that shows you its own green ticks has not shown you a working shop.
Fixes & improvements
- Setup ends by offering to export the configuration you just built. The moment it is worth keeping is the moment you made it.
- Diagnostics gained the same three new checks, so the setup screen and the health screen cannot tell you two different things about one store.
Four documented hooks that did nothing now do something
If you have ever written an add_action() against one of this plugin's hooks and wondered why your callback never ran, this release is probably why.
Four names that led nowhere
The plugin documents thirteen PHP hooks as its extension API. Four of them were never fired by any code: storemaster/access_decided, storemaster/cart_configuration_validated, storemaster/order_item_snapshotted and storemaster/search_projected.
That is a worse failure than a missing feature, because nothing complains. You attach a callback to a name that is published, WordPress accepts it without a murmur, and the code you shipped silently never runs. All four now fire at the boundary their name describes.
What they carry, and what they deliberately do not
Every payload is ids, counts and outcomes. The search hook reports how long the term was and how many products survived access filtering, not what the shopper typed. The cart hook reports how many validation errors there were, not their wording. The snapshot hook reports how many options or placements were frozen onto the line, never the uploaded file.
That is not an oversight. Any plugin on the site can listen to these, so none of them hands over something a customer wrote or uploaded.
storemaster/access_decided is announced once per decision per request, not once per time something asks. A shop page re-checks the same category for every product in the loop, and a listener wants the decision, not the number of reads.
Fixes & improvements
- A build gate now reads the hook list out of the specification and fails if any published name is fired by nothing, so this cannot come back quietly.
- The storefront's "install the free edition" link pointed at a web address that will not exist. It now follows the name the plugin actually ships under.
The translation domain now matches the directory's slug
A packaging change, with no effect on how the plugin behaves.
WordPress.org requires a plugin's translation domain to match its directory slug, so that volunteers can translate it through the project's own tools. The WordPress.org Plugins Team assigned this plugin a slug during review, and the domain has been changed to match it in all 2,132 places it appears — the gettext calls in PHP, the ones in the admin JavaScript, the script-translation registrations and the plugin header.
Nothing you have configured is affected, and no string a customer reads has changed.
Fixes & improvements
- The translation domain matches the plugin slug, so community translations can be applied once the listing is live.
- A correction in the plugin's own notes about which checks enforce output escaping on dynamic blocks — the claim there was broader than the tooling actually does.
A hardening release for protected catalogues
StoreMaster 2.12.1 is a maintenance release. It hardens the part of the plugin that decides whether a request to the REST API may be shown a product, and it tightens the checks that keep that decision honest as the code changes.
If you use protected catalogues, update. There is nothing to reconfigure, and no policy, rule or price behaves differently afterwards.
Fixes & improvements
- Several hardening fixes in how protected catalogues check requests to the REST API, and in the product-table and variation-matrix order forms.
- The checks covering those decisions now test many more of the ways a single request can be spelled, and each one is paired with a check that proves it could notice a difference in the first place.
Prices in search and quick view, written for people
Two places in StoreMaster send a price to the browser as plain text rather than as markup: the suggestions that drop down under the search box, and the price beside each variation in quick view. Both were flattening WooCommerce's price HTML with a function that does nothing more than remove the tags.
The currency symbol was showing as code
WooCommerce stores a currency symbol as an HTML entity — £ for a pound, $ for a dollar — and embeds it in the price that way. Removing the tags leaves the entity behind, and because these two fields are written as text, not as markup, nothing ever turned it back into a symbol.
So every suggestion row read £20.00.
A discounted price read out its own footnote
A price that has changed is marked up as two figures: the old one struck through, the new one beside it, and a short sentence that exists only so a screen reader can say which is which. Removing the tags promoted that sentence into the visible text and left the figures touching:
> £20.00 Original price was: £20.00.£10.00Current price is: £10.00.
A suggestion row is one line tall. It now reads:
> £10.00 (was £20.00)
with the tax basis kept where your shop shows one, and the same for a price range — both ends of both ranges, in order.
—
Worth recording, because it shaped the fix: the first version inserted a space wherever a tag had been. That reads as obviously correct and it is wrong — the currency symbol sits in its own element directly against the digits, so £20.00 became £ 20.00. The one place a space is genuinely wanted, before the tax suffix, already had one all along.
A hardening fix for protected catalogues
StoreMaster 2.11.1 is a maintenance release with one hardening fix, in the part of the plugin that decides whether a request to the REST API may see a product.
If you use protected catalogues, update. There is nothing to reconfigure, and no policy, rule or price behaves differently afterwards.
Fixes & improvements
- A hardening fix in how protected catalogues check requests to the REST API.
- The checks that keep that decision honest as the code changes now cover more of the ways a request can be spelled, so a future change cannot quietly reintroduce the same class of problem.
A manual order is now priced for the buyer
If you write up an order in wp-admin for a wholesale customer, StoreMaster now charges that customer's prices. It was charging yours.
The help centre has described the correct behaviour since wholesale pricing shipped: "A shop manager placing an order on behalf of a trade customer gets that customer's price. StoreMaster evaluates a price for the buyer, not for whoever is signed in — which is what makes a manual order match what the customer would have paid themselves."
That is what the design was for, and the plumbing for it was built in September. It was never connected. The price pipeline asked WordPress who was logged in, which on an admin screen is you — and a shop manager holds no trade role, so every line came out at retail while the customer's own trade rules sat unmatched.
What changes for you
New manual orders for customers with a trade role will total less than they did yesterday, by whatever that customer's own pricing rules come to. That is the figure those customers see when they order themselves.
Orders already placed are untouched. StoreMaster records the price on the line when the line is created, and nothing rewrites history.
If you have been correcting those lines by hand, you can stop.
Nothing on your storefront moves. A shopper buying for themselves was always priced correctly; this only ever affected an order created by somebody else on their behalf.
Why it took this long to notice
Everything around it was right, which is the awkward part. The customer's roles were looked up correctly, the rules matched correctly when asked about the right person, and a test confirmed the pipeline could be *told* whose price to compute. What nothing asked was whether the pricing code actually read that answer — and it did not. One method, with one caller: a cache key. So a variable product's price range was filed under the buyer while the price underneath it was worked out for the member of staff.
The test that now covers it compares two evaluations of the same product, one declared as the buyer and one as the session, and requires the trade rule to appear in the first and not the second. With the fix removed, both come back at the same price and it fails.
Seven things your storefront was getting wrong
The storefront sweep of 1 October found 48 places where what a shopper reads differed from what the code does. Sixteen were wording and were corrected the same day. These seven needed the code to change, and each was verified by an independent reviewer whose job was to argue the fix was wrong — which, twice, it was.
A correct password did not open a private shop
With the whole shop behind a password, entering the right one sent the visitor back to the form and told them the password was correct but did not open this shop. It then dropped the page they had been trying to reach.
The question was being asked on the wrong request. Entering a password issues a cookie, and the request that issues a cookie cannot read it back — so the shop asked "does this visitor have access now?" at the one moment the answer is always no. The check has moved to the next request, where the cookie has arrived, and the notice now appears only where it is true: a password that opens part of the catalogue but not the thing you asked for.
A Blocks checkout saved a notification address and sent to nobody
A customer can add a second address to be notified about their order. On the Blocks checkout — the one WooCommerce has shipped by default since 8.3 — the address was saved against the account and then copied to nobody, because the order snapshot was written on the classic checkout's path only and the Blocks checkout places its order by a different route.
Both now write it, through the same writer. Two checkouts with two implementations is how two checkouts come to disagree.
Bulk adds counted what they tried, not what the basket took
Add several products at once — from a product table, a variation grid, or a saved list — and the count came from the lines that passed StoreMaster's own checks. The basket runs further checks of its own that none of those cover: a variable product with no options chosen, stock measured against what is already in the basket, a product limited to one per order that is already there.
So WooCommerce's refusal and "3 products added to your basket." arrived in the same breath. The count now comes from the basket, and the refused lines are named.
A variable product saved to a list without choosing options is the common case, and it is exactly what saving from a listing page does.
A full quote request blamed the product
At its line limit, adding another product said that product could not be added to your quote request. The product was fine; the list was full.
It now says so. The two refusals that *are* about a product — one you may not see, and one that does not exist — remain word-for-word identical to each other, which they must be: anything else would let a visitor sort product numbers into real and not-real by reading the difference. Getting that ordering right was the whole of the second reviewer's objection to the first attempt.
A search that could not answer said nothing matched
When the suggestions endpoint answered a rate limit, a server error, or nothing at all, the box announced "No products found." That is a statement about your catalogue; the truth was a statement about the request.
It now says suggestions are unavailable and points at the full search, and a search genuinely answering zero matches still says so.
"Clear all" did not clear the price range
The filter panel's own From and To boxes write different query keys from the ones the clear links removed, so clearing the filters left the price range applied.
An option showed a price that was not its price
With a price set as a percentage, per character, by formula, or entered by the customer, the figure printed beside each choice came from the choice while the charge came from the field. A choice labelled "+5%" could cost 10%; one labelled "+£5.00" could cost £50. A reduction rendered as "+-£5.00".
A figure is now shown only where that figure really is what gets charged.
—
Two of the seven were sent back. The quote-request fix, as first written, would have reopened the very thing it was meant to close — on a full list, a product you may not see and a product that does not exist would have received different messages again. And the search fix arrived with tests that passed just as well with the fix removed; it now has a test that loads the storefront's own search code and runs it, and that test fails when the fix is taken away.
The command line now agrees with itself
StoreMaster ships nine WP-CLI commands, for the people who script a store rather than click through it. They had never been read against the code underneath them — and unlike a screen, a shell gives you nothing to check a command's word against. Four of them were wrong, one of them completely.
A command that could not be run
wp storemaster rules validate answered Error: Too many positional arguments: validate and stopped. Run it with no action and it printed a usage line telling you to type exactly the thing it had just refused.
WP-CLI decides what a command accepts by reading the documentation block above it, and that block had never mentioned the word validate. So the command was inert for as long as it has existed, in both editions, and the only two things it could do were refuse you or send you round in a circle. It is one line, and nothing in the toolchain could see it: a documentation block is a comment to PHP's syntax checker and prose to the code-standards gate.
It now runs. It also now checks *every* rule rather than the first hundred in each area — a second defect that had been sitting in code nothing could reach — and it refuses to call the result clean if it could not read them all.
Importing a configuration was not possible from the command line
wp storemaster config import refused every import and said this build could only preview one. That was not true. A store with the configuration-import module can apply a file, and does so from the control centre — the command had simply never asked whether the capability was present, so it gave the same answer either way. The one place to use it was the one place it was unavailable, and the person at a shell is usually the person deploying.
It now applies a file with --apply, and --strict refuses the whole import if anything in it cannot be found on your store. Without --apply it previews and changes nothing, as the screen does. --dry-run used to be required, so every script written against the old behaviour passes it — it still forces a preview, and those scripts keep previewing rather than quietly starting to write.
One more thing came out of building it. Applying a rule is checked against your permission for that area, and a shell has no signed-in account unless you name one. So the command would have written your settings, skipped every single rule for "lack of permission", and reported success. It now stops and asks for --user before doing any of it.
A rebuild that said it had started when it had not
wp storemaster index rebuild reported "Rebuilding into version 4" whether it had started a rebuild or found one already running. If a rebuild had stopped partway — a lost background job, which is what the control centre's own screen has a button for — the command reported progress every time you ran it, indefinitely, and offered no way to clear it.
It now says which of the two happened. wp storemaster index status says whether a build has stalled, and --force discards an abandoned one and starts again. Your shoppers keep the current index throughout, either way.
Formats that were accepted and ignored
Three commands offered --format and quietly printed a table whatever you asked for. They now accept only the formats they produce, and say so when you ask for another.
And safe-mode says what it does
Turning safe mode on from the command line has always applied to the whole site — every shopper served a storefront with no StoreMaster module on it. Its own help did not mention that. Now it does.
—
All four are held by assertions that run a real command against a real store, each one a pair: the same command, same file, with a capability present and then absent, required to give different answers. A command that always refuses looks identical to a broken one, and a command that always succeeds tells you nothing about where its answer came from.
Messages your customers read, corrected
Every message StoreMaster shows a shopper — on a product page, in the basket, at checkout, in an order email — has been read against the code behind it. Most were accurate. Sixteen were not, and each one would have left somebody with the wrong idea.
A closure longer than a week named the wrong day
With opening hours set to closed mode, a shut shop says when it opens again. It said "Monday 09:00" whether that Monday was three days away or ten, so a fortnight's holiday closure turned away a shopper and named a day it would not open on. Reopening times a week or more out now carry the date.
A bulk add that refused lines said nothing about how many
Adding several lines at once from a product table listed the products it could not add, up to ten of them, and stopped. If eleven were refused you saw ten names and no indication there were more. It now says how many were refused, and says when the list is cut short.
Fixes & improvements
- A filter facet listed backordered products under "Out of stock". They are now "On backorder", which is what they are.
- Clearing the extra-recipient box in your account said it stopped copying anyone. It stops copying on future orders; emails for orders already placed go to the addresses recorded with them, and the help text now says so.
- A back-in-stock alert said we would email you when the product returned and then delete your address. The wait is not unlimited — an alert lapses after 180 days, and a sent one is deleted a month later. Both are now stated.
- A trade application said it would be reviewed by a person. That is true unless the shop has turned auto-approval on, and the message now appears only when it applies.
- A minimum-order message compared your basket against a figure measured before tax without saying so.
- Two messages refusing a product distinguished "you may not see this" from "this could not be added", which told a shopper something about a catalogue they cannot see. They now read the same.
Discounts now reach a variable product's price range
If you sell variable products and run discount campaigns, the prices your shoppers saw on shop and category pages were wrong. Two separate faults, both fixed.
The range did not carry the discount
WooCommerce builds a variable product's "£10 – £13" from each variation's stored price. That read deliberately skips the filter every other surface goes through, so a campaign never reached it: the listing showed full prices while the basket charged the discounted one. Each variation now goes through the same pricing engine as everything else, so the range is the range a shopper will actually pay.
StoreMaster also tells WooCommerce what that range depends on, so a range worked out for one customer is never shown to another and publishing a campaign takes effect immediately rather than waiting for a cached range to expire.
One variation's price was shown as the product's
With a campaign running, a variable product selling at £10 to £13 was rendered as a struck-through £10, a new price of £9, and "Save £1.00" — the cheapest variation's figures, presented as the product's. That appeared on every shop loop, category page and related-products block.
It now shows both ranges: £10 – £13 struck through, £9 – £11.70 beside it. There is no single "Save" figure, because a saving that differs by variation cannot be written as one number.
Fixes & improvements
- Nothing about how a product is priced in the basket or at checkout has changed. This was always about what was displayed; the amount charged was correct throughout.
A product option that only collects information no longer blocks the sale
A product option with no price attached — a gift message, an engraving, delivery instructions — now goes into the basket as soon as a shopper fills it in. Until this release, filling one in stopped the order.
Fields that collect text, not money
A product option can change the price, or it can simply collect something you need to know. A field set to collect only — which is how every new field starts — refused the basket the moment a shopper typed into it, saying the option could not be priced. An empty field was accepted, so a product looked right while you were setting it up and turned the first shopper away. If you had marked the field as required, the product could not be bought at all.
These fields now add to the basket, change nothing about the price, and carry what the shopper typed onto the basket line and the order, where your team can read it — including on the order itself, so it is still there months later.
There is nothing to change at your end. Fields you have already set up start working when you update.
The control centre now says what it does
Every string in the control centre has been read against the code behind it — 253 of them, from field labels to error messages. Most were accurate. These were not, and each one would have sent somebody down the wrong path.
The Settings screen no longer contradicts what you just did
Following a module's link to its settings could land on "Those settings belong to a module that is switched off. Turn it on under Modules" — immediately after switching it on. The screen's list of forms is built when the page loads and nothing refreshes it, so it cannot know. It now says the form is not on this page and that reloading will bring it in.
Fast search described an index it does not have
The Fast search card said "Reads the catalogue index". The index is a Pro module; the free edition matches product titles and SKUs on its own. It now says that.
Fixes & improvements
- Quick view said "Nothing to set up". Its button wording is a setting, and the card now says where.
- The safe-mode notice linked to "Turn it off", which went to a screen with no such control. It names the command instead.
- Rule check: a rule that matched said "This rule applied" — the check also runs your live rules, so it now says "this rule, or its live copy". The price it shows is what the customer would pay with everything applying, and says so.
- "The evaluator did not consider it at all" is now "StoreMaster did not report why", which is what that case means.
- Setup checklist: a blocked step said "Switch this module on"; what it needs is the capability, which may be more than one module. Finishing a blueprint said your shop was set up, where what it knows is that nothing is left on the list.
- Order desk export: the note said it covers everything the filters match. The search box is not one of them, and the note now says so.
- Product table columns: ticking none shows a default set, which the help text did not mention.
Four controls that did not do what they said
A sweep of every string in the control centre against the code behind it. Most of what it found is wording, and that is being corrected separately. These four were not wording.
An access password with no expiry date was born expired
The expiry box on the access-passwords screen says "leave empty and it works until you revoke it". Leaving it empty created a password that had already expired — stored as expiring in 1970 — so it never let anybody in. There was no error and nothing in the list looked wrong: the password was there, it was active, and every attempt to use it was simply refused.
Empty now means no expiry, as the screen says. A date that cannot be read is refused when you save it, with the date you typed quoted back, rather than being quietly turned into one in the past.
If you created an access password and it did not work, create it again. The old one cannot be repaired — its expiry is stored — and deleting it is safe.
Opening an old checkout field made it compulsory
A checkout field saved before conditional fields arrived has no condition stored. Opening one showed "Require an answer: Always" whatever the field was actually set to, so a field you had made optional became compulsory the moment you opened it and pressed Save.
It now opens with the setting the field has. A field that stores its own condition still keeps it.
Fixes & improvements
- The extra-recipients screen said a customer could add up to ten addresses. One is what the product stores, and what the paragraph above it already said.
- The trade-registration field editor offered a Placeholder box. Nothing ever showed it on the form, so it has been removed rather than left as a box that does nothing.