What Ships With Your Code
Published
Most things that end up in a front-end bundle by accident are embarrassing rather than expensive. Pricing is the exception. Discount tiers, eligibility rules, promotion stacking, shipping thresholds, surge and yield multipliers — when those decisions run in the browser they fail in two separate directions at the same time, and only one of the two is usually noticed.
Two failures, not one
The first failure is disclosure. Your pricing rules describe how your business decides what to charge, which segments it treats differently and where its thresholds sit. That is commercially sensitive in a way that almost nothing else in a bundle is. A competitor reading a promotion engine learns your stacking policy and your floor in an afternoon.
The second failure is integrity, and it is the one that costs money directly. A price computed in the browser and then submitted onward is a price the customer selects. It does not require expertise: a network request can be replayed with a different number in it, and every browser ships the tools to do it. Whether this matters to you depends entirely on whether your server recomputes, which is a question worth answering today rather than after a reconciliation exercise.
Nearly every other piece of client-side logic has only the first problem. Pricing has both.
Obfuscation does not solve the first one, and here is why
This site is in the business of protecting JavaScript, and the honest position is that transformation is a weak answer to the disclosure half. The reason is not that the transforms are weak. It is that the rules are observable from the outside.
Anyone can open your site and vary the inputs. Add a second item and watch the unit price move. Change the region, the quantity, the delivery date, the account tier. Come back on a Tuesday. Each of those is a measurement, and enough measurements reconstruct the behaviour of your rules without anyone opening the bundle. That is exactly what a competitor's analyst does, and it is the same argument this site makes about scrapers applied to a rule set rather than to a data set: your page will answer any question a visitor asks it, politely, all day.
So if the goal is to keep your pricing strategy from a competitor, the lever is what your interface is willing to compute for an anonymous visitor, not how the code that computes it was written.
What protection is genuinely worth here
Two narrow things, both real.
It raises the cost of lifting the implementation wholesale. If your pricing engine is genuinely substantial work — a real rule evaluator, years of accumulated edge cases — then making it unpleasant to extract and reuse has value, in the same way it does for any other body of proprietary work. Behavioural probing tells a competitor what your rules do; it does not hand them your implementation.
And it raises the cost of casual tampering. The opportunistic customer who opens a console, finds a total and edits it is stopped by inconvenience more often than by cryptography. That is a legitimate use of a hardening measure, provided nobody mistakes it for the control.
The control is on your server, and it has two parts
The server decides, the browser displays. A pricing request returns the amount, whatever explanation you want the customer to see, and an opaque reference to the quote. The client renders it and nothing more.
At checkout the server recomputes from that reference and its own data. If the recomputation disagrees with what arrived, the server's number is the price. The second part is the one teams forget: log the disagreement. A silently corrected mismatch is a discarded signal, and a pattern of mismatches from one account is the clearest fraud indicator you are ever handed. This is the same principle as keeping authority off the client, and it is worth stating that a correct implementation makes the tampering question uninteresting rather than merely difficult.
The rule table in your network tab
Here is the version of the problem that protection cannot touch at all, and it is common.
To make an interface feel instant, a team ships the whole promotion matrix to the client as data: every tier, every threshold, every stacking rule, as JSON. The bundle may be beautifully protected, and it makes no difference whatsoever, because the table is sitting in a response that any visitor can read in the network panel. The code was never the exposure.
The fix is scoping rather than concealment. Send this user, for this view, the tier that applies to them, the prices already computed, and the thresholds relevant to what is actually in their cart. That is usually a small fraction of the table, it is frequently faster, and it removes the disclosure entirely rather than obscuring it.
The scheduled price change that leaks on deploy
This one is specific enough to be worth its own section, because it is a genuine surprise the first time it happens.
A price change is agreed for the start of next quarter. Somebody implements it as a client-side branch: the new rates go into the bundle now, behind a date check, so the launch requires no deployment. From the moment that build ships, the future prices are public. Anyone reading the bundle — or anyone who simply changes their computer's clock — sees exactly what you intend to charge and when. It is the same mechanism that makes an unreleased feature flag leak your roadmap, with a number attached.
The date guard this engine provides does not rescue that design, and it is worth being precise about why. The guard compares the device's local calendar date against a value you set, with no network call anywhere in it. It is a build-hygiene tool for expiring an evaluation copy, not an enforcement mechanism, because the clock belongs to the person you are trying to constrain. A scheduled commercial change belongs in server-side configuration, where it stays private until you switch it on and where switching it on takes no deployment either.
Experiments and personalised offers
The same category, leaking more than most teams expect. Shipping the variant matrix so the client can pick a branch exposes the experiment design, the segments and often the margin difference you were testing. It also lets a visitor assign themselves to whichever arm they prefer, which corrupts the result and quietly gives the best offer to whoever looked.
Assign on the server, return the assigned variant only, and keep the definition of the other arms somewhere the customer is not. The interface still gets a single value to render and loses nothing in responsiveness.
How to audit your own bundle this week
Search the client source for the vocabulary rather than for a module name, because this logic is almost never in a file called pricing. Discount, tier, margin, threshold, surge, multiplier, eligibility, promo, coupon, bundle, waiver. For each hit, ask one question: does this decide something, or does it format a number the server already decided? The first kind is a migration candidate. The second kind is fine.
Then repeat the exercise against your network responses, which is where the larger exposure usually turns out to be. In most audits the result is two or three genuine decisions that moved into the client years ago for interface responsiveness and were never moved back — which is how this happens, rather than through anybody choosing it.
The short version
Compute prices where you control the inputs, return the answer, and recompute at checkout while logging every disagreement. Send the subset of the rule table this user needs rather than all of it. Keep scheduled changes and experiment definitions on the server, because anything you ship early is public early and a client-side date check runs on somebody else's clock. Then, on top of a design that is already correct, protect the bundle — because raising the cost of reading and editing your work is worth doing once it is no longer load bearing.
Frequently asked questions
Why is client-side pricing logic worse than other client-side logic?
Because it fails in two directions at once, and most teams only think about one of them. The disclosure problem is that your discount tiers, eligibility rules and thresholds describe how your business decides what to charge, which is commercially sensitive in a way that a date picker is not. The integrity problem is that a number computed in the browser and sent onward is a number the customer can choose. Almost every other piece of front-end logic has only the first problem. Pricing has both, and the second one costs money directly rather than competitively.
Does obfuscation stop a competitor learning our pricing rules?
Not meaningfully, because the rules are observable from the outside. Anyone can open your site, vary the inputs and record the outputs: change the quantity, add a second item, switch the region, wait for a different day of the week. That measurement reconstructs the behaviour of the rules without opening the bundle at all, and it is exactly what a competitor's analyst does. Protection raises the cost of reading the code path; it does nothing about the fact that your page will answer any question a visitor asks it. This is the same argument as the one about scrapers, applied to a rule set instead of a data set.
So what does protection genuinely help with here?
Two narrow things worth having. It raises the cost of lifting your implementation wholesale, which matters if the rule engine itself represents real work rather than three if statements. And it makes casual tampering more effortful, so the opportunistic customer who opens a console and edits a total finds it more trouble than it is worth. Neither of those is the control. The control is that your server recomputes the price and refuses a mismatch, and protection is a hardening measure layered on top of that, not a substitute for it.
What does a correct architecture look like?
The server decides and the browser displays. A pricing request returns the amount, the applied rules in whatever detail you want the customer to see, and an opaque reference to the quote. The client renders that. At checkout the server recomputes from the reference and its own data, and if the recomputation disagrees with what the client submitted, the server's number wins and the discrepancy is logged. That last part matters: a silently corrected mismatch is a lost signal, and repeated mismatches from one account are the clearest fraud indicator you will get.
Our pricing endpoint returns the whole rule table for a fast interface. Is that a problem?
It is the same exposure with a different delivery mechanism, and one that protection cannot touch at all. If the table travels in a response, it sits in the network panel in readable form regardless of how the code that requested it was built. Teams sometimes protect a bundle carefully and then ship the entire promotion matrix as JSON to make the interface feel instant. The fix is to send only what this user needs for this view: their applicable tier, their computed prices, the thresholds relevant to their cart. That is usually a small fraction of the table and often faster as well.
Can we ship a future price change and gate it on a date?
You can, and it is a leak with a specific failure mode. Any rule that ships before it takes effect is readable from the moment it deploys, so a price rise scheduled for next quarter is public the day the build goes out. The date guard in this engine will not save it either: the check compares the local calendar date against your value using the device's own clock, with no network call at all, so a visitor who changes their system date sees the future state. Treat a scheduled change as server-side configuration, not as a client-side branch, and it stays private until you activate it.
What about experiment assignment and personalised offers?
Same category, and it leaks more than people expect. Shipping the variant matrix to the client so the interface can pick a branch exposes the experiment design, the segments and often the intended margin differences between them. It also lets anyone assign themselves to whichever variant they prefer, which corrupts the experiment and hands out the best offer to whoever bothers to look. Assign server-side, return the assigned variant only, and keep the definition of the other arms where the customer is not.
How would we find out whether our own bundle has this problem?
Search your client source for the vocabulary rather than for a module name, because the logic is rarely in a file called pricing. Look for discount, tier, margin, threshold, surge, multiplier, eligibility, promo and coupon, then follow each one to see whether it decides anything or merely formats a number the server sent. Then do the same for your network responses. The audit usually turns up two or three genuine decisions that migrated into the client for interface responsiveness and were never migrated back, which is the ordinary way this happens rather than anything anyone chose.
Related reading