The Firefox add-on for ccgenerator.org — Luhn-valid test card data for developers and QA, one click from any checkout form.
Open source under the MIT licence. This repository is the complete
source of the published add-on: scripts/package.py builds the uploaded ZIP
from it and nothing else, so every claim the listing makes — no network
requests, no remote code, no host permissions — can be read off the files here.
It is deliberately a small surface. The popup covers the three things people do dozens of times a day while building a payment flow; everything that needs room to explain itself stays on the site and is one link away.
The Chrome build lives in a separate repository, ccgenerator-chrome. The two share the card engine and the UI; what differs is listed under Differences from the Chrome build.
| Generate | 1–25 dummy cards on any of the nine supported networks (Visa, Mastercard, Amex, Troy, Discover, JCB, Diners Club, Maestro, UnionPay), or a random mix. Copy one number, copy the whole card, copy all, copy JSON, export CSV. |
| Validate | Paste a number and get the Luhn check digit, the detected network, the accepted lengths for that network, the MII and the digit anatomy. |
| Test cards | The published sandbox numbers for Stripe, Braintree, Adyen, Square, PayPal, Authorize.Net and iyzico, including the common decline triggers. Click the row to copy, or Fill to put it straight into the form — the fields the provider does not pin are generated around it. |
| Autofill | Fills the card number, expiry, CVC and cardholder name on the page you are looking at — from either tab of the popup, the right-click menu, or Alt+Shift+F. |
A provider publishes a number; a checkout form wants four or five fields. The
missing ones are generated, but never on top of a value the provider pins —
src/testcards.js carries defaults per gateway and expiry/cvv per card,
and CCG.cardFromNumber() keeps whatever it is given and invents only the rest,
at the right CVV length for the detected network.
That distinction is the whole point. Adyen's cards expect 03/2030 and CVC 737 (7373 on Amex); Square's expect CVV 111. Substituting a random expiry there produces a card the provider no longer recognises, and a test that proves nothing.
The popup links out to these rather than reimplementing them:
- BIN lookup and the card image generator
- IBAN generator and the name and address generator
- The full gateway test-card tables, with 3-D Secure and decline codes — the popup carries the common subset
- The guides
The tables in src/cards.js are not folklore; each one has a page explaining
where it comes from. Worth reading before changing a range or a length:
| The Luhn algorithm | What CCG.luhnValid() and the check-digit report implement, and in code in a dozen languages |
| Card number structure | MII, IIN, account identifier, check digit — the anatomy the Validate tab draws |
| BIN/IIN explained | Why detection reads the first six to eight digits |
| Length by network | The lengths array per network, including the 19-digit Maestro and UnionPay ranges |
| Brand detection regex | The prefix rules CCG.detect() is a port of |
| Why generated cards have no balance | The answer to the question every issue about this repo will eventually ask |
npx web-ext run --source-dir .Launches a clean Firefox profile with the add-on already loaded, and reloads it
when a file changes. To use your own profile instead: about:debugging → This
Firefox → Load Temporary Add-on → pick manifest.json. Either way the
add-on is gone at the next restart; permanent installation needs a signed build
from AMO.
The toolbar icon opens the popup. Alt+Shift+C opens it from the keyboard;
Alt+Shift+F fills the form on the current page. Both are remappable in
about:addons → the gear menu → Manage Extension Shortcuts.
manifest.json MV3 manifest, add-on id, permissions and commands
background.js event page: context menus, shortcuts, injection
popup.html/.css/.js the three-tab popup
options.html/.css/.js defaults (network, quantity, expiry window, theme)
src/cards.js network table, Luhn, generation, detection, exports
src/testcards.js published gateway sandbox numbers
src/i18n.js applies _locales strings to the markup
content/autofill.js injected on demand: field detection, filling, toast
_locales/en UI and listing strings
icons/ 16/32/48/96/128 toolbar, about:addons and AMO icons
scripts/make-assets.py regenerates icons/
scripts/package.py builds the AMO upload ZIP
store/ AMO listing copy and submission notes
Same features, same engine. What Gecko needs done differently:
| Event page, not a service worker | background.scripts lists src/cards.js and background.js; there is no importScripts(). Listeners are registered synchronously at the top level so the page can be suspended and revived. |
browser.*, not chrome.* |
Promises throughout instead of callbacks, browser.runtime.lastError never appears, and the onMessage listener answers by returning a promise rather than the sendResponse / return true dance. |
| An add-on id | browser_specific_settings.gecko.id. Without it storage.sync has nowhere to sync to and AMO cannot version the listing. |
menus, not contextMenus |
Gecko's own name for the API, and the permission that goes with it. |
| The right-clicked element is known | menus.getTargetElement(info.targetElementId) returns the exact node the click landed on, so "Insert a test card number here" no longer has to assume the right-click moved focus — which on Gecko it does not reliably do. document.activeElement stays as the fallback. |
| Data-collection disclosure | data_collection_permissions: { required: ["none"] }, which AMO requires of new submissions. It is what raises strict_min_version to 140. |
| Downloads need an anchor in the document | The CSV export attaches its link before clicking it and removes it after; a detached anchor is a no-op on Gecko. |
| Android is a real target | menus and commands do not exist on Firefox for Android, so both are feature-guarded. Everything else — the popup, Fill, the options page — works there. |
src/cards.js, src/testcards.js, popup.css, options.css, the icons and
the two locales are the same files in both repositories. Changing a card table
or a style in one means copying it to the other.
python3 scripts/package.pyWrites ../ccgenerator-firefox-<version>.zip, which is what you upload to AMO;
what users install is the signed .xpi AMO hands back. It builds from an
explicit allowlist with Python's zipfile rather than shelling out to zip -r:
several files here carry a com.apple.provenance extended attribute, and both
macOS zip and Finder's "Compress" turn that into a __MACOSX/ directory of
._ AppleDouble members inside the archive. The script cannot produce those,
checks for them anyway, and verifies that every path the manifest references is
in the package and that the add-on id is present.
Validate the result before uploading — AMO runs the same linter:
npx addons-linter ../ccgenerator-firefox-1.0.0.zip| Permission | Why |
|---|---|
activeTab |
Reach the page the user pointed at — and only on a click, a menu choice or a shortcut. There is no host permission and no standing content script, so the add-on cannot read pages on its own. |
scripting |
Inject content/autofill.js into that tab at that moment. |
storage |
Settings (sync) and the cards currently shown in the popup (session, cleared when the browser closes). |
menus |
The four right-click entries. |
clipboardRead |
Optional. Requested only when the visitor presses "Paste from clipboard" in the Validate tab, and declinable. |
Nothing generated or typed is sent anywhere: there is no fetch, no XHR and no
analytics in the add-on. Card numbers exist in the popup, in the clipboard if
the user copies one, and in the page if the user fills a form.
MV3's default content security policy (script-src 'self') is left in place
rather than restated in the manifest, so remote script cannot run here even by
mistake.
src/cards.js is a port of the tables and algorithms in the site repo
(layouts/partials/card-generator.html, card-validator.html and
packages/test-cards/src/). When a network's IIN ranges or accepted lengths
change, change them in both places or the add-on and the site will disagree
about the same number.
One difference exists on purpose and is marked in the source: the add-on
detects UnionPay's 81 range as well as 62. The
site's validator only tests
62, which reports Adyen's own UnionPay test card 8171 9999 2766 0000 as an
unrecognised prefix. The site is worth fixing to match.
There is no test runner; the engine is small enough to check from a shell.
node -e "global.crypto=require('crypto').webcrypto;const C=require('./src/cards.js').CCG;let bad=0;for(const k of C.NETWORK_KEYS){const r=C.NETWORKS[k];for(let i=0;i<5000;i++){const c=C.generateCard(k);const d=C.detect(c.number);if(!C.luhnValid(c.number)||!r.lengths.includes(c.number.length)||c.cvv.length!==r.cvvLength||!d||d.key!==k){console.log('FAIL',k,c.number);bad++;break}}}console.log(bad?'FAILURES '+bad:'ALL PASS')"Autofill is checked against store/field-detection-cases.md, which lists the
seven form shapes the detector is expected to handle.
python3 scripts/make-assets.pyWrites icons/icon{16,32,48,96,128}.png. Requires Pillow.
See store/SUBMISSION.md for the listing copy, the reviewer notes, and what AMO asks for that the Chrome dashboard does not.
MIT. LICENSE is shipped inside the packaged add-on as well as kept
here.
The licence covers the code. It does not make the numbers this code produces anything other than test data: they satisfy the Luhn checksum and the networks' published prefix and length rules, no bank issued them, and no payment will ever clear on one. Anything built from this is subject to the same fact.