Coupon Code Generator
Batches of coupon codes from the browser's own randomness, on an alphabet with no character that can be misread, with the guessability stated in bits.
# 10 codes, 10 random characters from 26
# 47 bits each: strong: guessing is not a practical way in
SPRING-GENERATED-IN-YOUR-BROWSER
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
Two things make a coupon code good, and neither of them is being memorable.
It has to resist guessing. SPRING20 is a code anybody can find on the third try, and there are large forums whose whole purpose is guessing codes. A code needs enough randomness that trying is pointless, and this tool states how much it has in bits rather than telling you it is secure.
It has to survive being read out. A code that goes on a printed card, a podcast or a phone call meets O against 0 and I against 1, so the alphabet here keeps one of each confusable pair and drops the other. That costs a fraction of a bit per character and saves every support ticket that starts “it says invalid code”.
Codes are generated in your browser, fresh on every load. Nothing is sent anywhere.
How to use
- Set a prefix if you want the codes labelled. It is readable, not secret.
- Choose how many random characters and how many codes.
- Leave the alphabet on unambiguous unless the codes will only ever be copied and pasted.
- Copy or download them, then load them into your store before you print anything.
Example
Ten characters, six codes, grouped every five, with a SPRING prefix:
# 6 codes, 10 random characters from 26
# 47 bits each: strong: guessing is not a practical way in
SPRING-639NX-NC3AA
SPRING-RHNHT-4FWV6
SPRING-KJ6DW-CGV8G
SPRING-VF7TJ-Y67CK
SPRING-KPJHT-3A3TP
SPRING-D6398-EXEH8
Yours will be different: they are drawn from crypto.getRandomValues when the page
loads. Forty-seven bits is about 140 trillion possible codes, so with six live ones a
guess has roughly a one in twenty trillion chance of landing.
Pitfalls
A prefix adds nothing to security. It is printed next to the code on the poster. That is why the entropy figure here counts only the random part; a tool that counts the prefix makes a weak code look strong.
Single-use is a store setting, not a property of the code. In WooCommerce the usage limit per coupon and the limit per user are both off by default. A generated code with no limit is a permanent discount that ends up on a deals site.
Always set an expiry. A code with no end date will be found in a report two years from now, still working.
Load the codes before you print them. Codes that exist on a card and not in the database are a support queue rather than a campaign.
Short codes collide. Four or five characters is fine for a dozen codes and guaranteed to produce duplicates across thousands. The tool refuses duplicates inside one batch, but it cannot know about the codes from last month, so keep the length up and check on import.
Don’t email one code to a whole list and call it unique. If a code is in ten thousand inboxes it is public. Unique codes mean one code per recipient, which is what generating a batch this size is for.
Compatibility
Randomness comes from crypto.getRandomValues, available in every browser since
roughly 2013, and the page recomputes on load so what you see is yours. During the
build the tool has no randomness at all, on purpose: baking a set of codes into the
page would hand every visitor the same codes.
Selection uses rejection sampling. A byte that lands in the short final block of 256 is thrown away rather than taken modulo the alphabet size, because the modulo makes the first few characters of the alphabet slightly more likely, and on a 26-character set that bias is real.
The unambiguous alphabet is ACDEFGHJKMNPQRTVWXY2346789: 26 characters, 4.7 bits
each. B is dropped and 8 kept, S and 5 both dropped, I and L and 1 dropped, O and 0
dropped, and U dropped so that codes do not spell words.
Import is a paste into WooCommerce, Shopify, Easy Digital Downloads or whatever else you use. The plain text output has one code per line so it drops straight into a bulk-coupon importer or a spreadsheet column.