WhatsApp QR Code Generator
One scan opens a WhatsApp chat with your number — no contact-saving, no number-typing. Generated entirely in your browser.
Print checklist
- Keep the white margin around the code — it is the quiet zone scanners need, not empty space to crop.
- Size for the scan distance: at least 2 × 2 cm for arm's-length scanning, and roughly one tenth of the farthest scan distance for posters.
- Scan a test print with a real phone before making copies.
How wa.me click-to-chat works
WhatsApp publishes an official "click to chat" link format:
https://wa.me/<number>, where the number is the full international number
with brackets, dashes and the plus sign removed and, per the click-to-chat rule, any leading
zeroes omitted — digits only. Scanning the code opens that link, and WhatsApp (app or web)
opens a chat with the number. An optional
?text= parameter pre-fills the visitor's first message; they still have to press send
themselves, which is exactly the consent step you want.
Because the payload is an ordinary https link, the symbol is a URL code in all but the preset that built it — which is also why, like a URL code, it can be shared as text rather than as an image.
It helps to be precise about what that means for the code itself. The symbol carries a URL and
nothing else, and the QR layer does not know or care that the URL belongs to WhatsApp. The
payload has exactly three parts: the fixed prefix
https://wa.me/, the digits of the number, and — only when you type a message — a
?text= parameter holding that message in url-encoded form. The convention comes from
the WhatsApp Help Center's click-to-chat page, which the engine cites and which was verified against
the live source on 2026-07-20: omit any zeroes, brackets or dashes when adding the number in international
format, and pass pre-filled text as
?text=urlencodedtext. Everything this generator does to your input is in service
of those two sentences.
Companion guide: URI schemes: mailto, tel, sms and WhatsApp walks through the
mailto:, tel: and sms: payloads the same engine builds, what
the four link types share, and where their encoding rules differ. This page stays with the WhatsApp
form; the other guides cover the symbol side.
The number in the example below is a fictional sample, as are all numbers and messages on this page.
Worked example
Number +44 7911 123456 with the pre-filled greeting "Hi! I scanned your code at the
shop." becomes:
https://wa.me/447911123456?text=Hi!%20I%20scanned%20your%20code%20at%20the%20shop.
Note the number appears as bare digits (447911123456) — that is the wa.me rule,
and the generator strips the formatting for you.
What the generator does to the number
The number field accepts the way people actually write numbers, then reduces the input to the
digits wa.me wants. The steps are mechanical and always the same: leading and trailing
whitespace is trimmed; spaces, dots, dashes and parentheses are removed; a leading
+ is accepted, and so is the 00 international dialling prefix as an alias
for it; whatever remains must be digits only, within the length range the engine's own message quotes
below, and must not begin with a zero. The result is the E.164 form of the number — the international
number format the engine's tel: builder carries per RFC 3966, as the builder header
cites, and the same routine normalizes the number for every generator that takes one. For the tel: and sms: generators that form is used as-is; for wa.me the engine then drops the
plus sign, because the click-to-chat rule takes digits only.
The fictional number +1 (555) 013-2000 typed with brackets and a dash becomes:
Number only, no message:
https://wa.me/15550132000
Typing the same number as 001 555 013 2000 builds exactly the same link —
https://wa.me/15550132000 — and this page's build checks that the two agree. Version 2, 25×25 modules, error correction M.
Why the number must be in international format
A local number like 07700 900123 is ambiguous — it carries no country code — and the
click-to-chat rule asks for the international format with any zeroes, brackets or dashes omitted.
The international (E.164, per RFC 3966) form +44 7700 900123 — country code
first, no leading zero — is the shape the normalizer requires. The generator validates this and
tells you exactly what to fix.
The messages below are the engine's own, produced at build time by handing it the inputs shown; if the engine ever stopped rejecting one of them, this page would fail to build rather than describe a check that no longer exists.
-
07700 900123(no country code): Include the country code in international format, e.g. +1 555 123 4567. -
+0 7700 900123(a zero directly after the plus): An international number has 7–15 digits and cannot start with 0 after the country code prefix. -
+1 555 CALL NOW(letters in the number): Use only digits, spaces, dashes, and parentheses.
One mistake passes every check, and it is worth knowing about. The validation looks at the
shape of the number — digits, length, first digit — not at the numbering plan of any country,
so it cannot tell a trunk zero from a real digit once a country code is in front of it. If the
domestic form of a fictional number is 07700 900123 and its international form is
+44 7700 900123, typing +44 07700 900123 is accepted and builds
https://wa.me/4407700900123, while the intended link is https://wa.me/447700900123. Both are valid wa.me links; only one of them is your number. The click-to-chat rule says to
omit the zeroes, and dropping the domestic trunk zero is your job, because the generator has
no way to know it is one.
How the pre-filled message is encoded
The message is carried as the value of ?text=, url-encoded exactly as the
click-to-chat rule requires. Url-encoding (percent-encoding) replaces each character outside a
small safe set — letters, digits and a handful of punctuation marks — with a percent sign and
two hexadecimal digits per byte. A space becomes %20. Characters that
have a structural meaning in a URL are encoded so they cannot be mistaken for structure: a
bare
# would start a fragment and cut the message short, a bare & would
start a second parameter, and a bare ? is the query delimiter, so they become
%23, %26 and %3F.
Not everything is encoded. This builder uses the browser's standard
encodeURIComponent, which leaves !, ', (,
) and * bare. The sms: builder in the same engine uses a
stricter encoder that percent-encodes those five as well, because the RFC 5724 grammar does not
allow bare sub-delimiters in the body value; the mailto: builder uses the same strict
encoder so that every character RFC 3986 reserves is percent-encoded. The WhatsApp rule asks only
for url-encoded text, so the standard form is what you get here. You can see the difference in the
exclamation mark below, which survives untouched while the #, & and
? around it do not:
Message Hi! Is table #7 free & bookable? to the same fictional number:
https://wa.me/15550132000?text=Hi!%20Is%20table%20%237%20free%20%26%20bookable%3F
Version 5, 37×37 modules, error correction M.
Letters outside ASCII are encoded byte by byte in UTF-8, so one accented character turns into
more than one percent-escape — the á below becomes 2. A short
Portuguese greeting shows it:
Message Olá! Reservar mesa?:
https://wa.me/15550132000?text=Ol%C3%A1!%20Reservar%20mesa%3F
The á has become 2 escapes, %C3%A1, and
the symbol itself contains only plain ASCII: version 4, 33×33 modules, error correction M.
The encoding is lossless — every character you type is recoverable from the link — so the question is never whether punctuation or accents will "break" the code. They will not. The cost of a longer or richer message is paid in a different currency: payload length, and therefore symbol density, which the next section makes concrete.
What the pre-filled message is
The pre-filled text is not a message you send. It is text placed in the visitor's
compose box on their behalf, carried as the value of the ?text= parameter, and everything
about it follows from its being part of the link rather than something the generator does:
- It is the visitor's to send, edit or discard. The link only fills the box; pressing send is still the visitor's action.
- It is as public as the number. The link is the data in the image, so anyone who decodes the symbol reads the pre-filled text exactly as they can read the number.
- It is encoded faithfully, typos and all. The builder url-encodes the text and does nothing else to it — no correction, no shortening, no check — so the payload shown in the generator is the one source of truth for what a visitor will see pre-filled.
- It is paid for in payload length. Every character — and every percent-escape a space, an accent or a punctuation mark expands into — lengthens the link, and a longer link can raise the version. On this page the number alone is a 25-character payload and the long message makes it 177; the symbol-facts section below works out what that costs in modules.
- It is fixed once printed. The code is static, so a different pre-fill is a different code — for the same reason a changed number needs a new code.
Reading the symbol facts
Every worked example on this page reports three facts about its symbol, and they are worth understanding before you print. The version is the size class the encoder chose, the smallest that fits the payload at the requested error-correction level. The module count follows from it directly — the engine's model documents the side length as 17 + 4 × version, and this page's build asserts that every example obeys it. The error-correction level is the one actually encoded: the worked examples request level M, the engine's default, but the reference encoder may raise the level when doing so does not increase the version, so the reported letter can be higher than M and is never lower.
| Payload | Characters | Version | Modules | EC level |
|---|---|---|---|---|
| Number only | 25 | 2 | 25×25 | M |
| Short greeting (worked example above) | 82 | 5 | 37×37 | M |
| Punctuation in the message | 81 | 5 | 37×37 | M |
| Accented message | 61 | 4 | 33×33 | M |
| Long message | 177 | 9 | 53×53 | M |
The pattern to take from the table: the number alone is a 25-character payload and produces a version 2 symbol, while the long message is 177 characters and needs version 9 — 53×53 modules against 25×25. Every module in the larger symbol is physically smaller at the same print size. The engine does not say which module size is big enough — print size and contrast still matter, and the only test is a real scan of a real print. A pre-filled message is free in the sense that it always encodes; it is not free in symbol density.
The long message, for comparison with the number-only symbol above:
https://wa.me/15550132000?text=Hello%2C%20I%20would%20like%20to%20book%20a%20table%20for%20two%20on%20Friday%20evening.%20Is%20there%20space%2C%20and%20do%20you%20take%20card%3F
Version 9, 53×53 modules, error correction M.
What the print-size readout means
The PNG download offers 3 pixel sizes — 512, 1024, 2048 — and shows a print-size readout for each. The readout is arithmetic, not advice: the engine divides the pixel count by 300 to get inches and converts the inches to millimetres. The smallest preset, 512 px, reads 1.71 in × 1.71 in (43.3 mm × 43.3 mm) at 300 DPI; the largest, 2048 px, reads 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI. The same readout is computed for whatever pixel size you pick.
What the readout does not tell you is how much of that width each module gets, and that is the number that matters for a dense symbol. The rendered file is the symbol plus the quiet zone — 4 modules of margin on every side — so the first worked example is 45 modules across in total. At 512 px that is about 11.4 px per module, or 0.96 mm when printed at 300 DPI. The long-message example is 61 modules across, which brings each module down to about 0.71 mm at the same preset. The engine does not say which of those is "big enough" — print size and contrast still matter, and the only honest test is a real scan of a real print.
What the generator cannot verify
The engine validates format, not truth. For a WhatsApp code that boundary falls in a specific place, and it is worth stating plainly:
- Whether the number is on WhatsApp. The generator checks the shape of the number and nothing else. A number that was never registered, a number you mistyped, or a number with a stray trunk zero all produce a correct, scannable wa.me link to the wrong place.
- Whether the number is yours. Nothing in the link proves ownership. The code is a pointer to a chat with that number, and the engine has no way to check who answers it.
- What the message says. The text is encoded faithfully, typos and all. The payload shown in the generator is the one source of truth for what a visitor will see pre-filled.
- Anything after the scan. The code is static: the link is the data, stored in the image itself with no redirect through our servers. That is why it never expires and cannot be held hostage, and also why it cannot be edited after printing and why scans cannot be counted.
One delivery detail follows from the payload being a link. The generator's "Share to WhatsApp" button sends text, so for this code type it shares the wa.me link itself — which is equivalent to scanning the code. For payloads that are not links, such as a WiFi string or a contact card, the button shows a note instead: WhatsApp sharing sends text, not images — and this code’s content is not a link, so pasting it into a chat would be meaningless. Download the PNG and attach it in WhatsApp instead.
How the engine is tested — reference strings including hostile inputs, plus a round-trip suite that renders, rasterizes and decodes each payload type with an independent decoder — is set out on the methodology page. The short version: treat a freshly generated code as untested until a real device has scanned it.
Common misreadings
- Cropping the quiet zone. The 4-module white border in every download is part of the symbol, not padding. The renderer always includes it and offers no way to turn it off; trimming it in an editor afterwards is the most common self-inflicted scan failure.
- Inverting the colours. Light modules on a dark background look like a design choice and read like a defect: many scanners reject inverted codes. The style panel warns separately for that case, and warns when the computed WCAG contrast ratio of your colour pair falls below 4.5:1.
- Assuming a scan proves the number. A code that opens a chat has proved only that the link was well-formed. Scan it with a real device and send one message through it before the print run.
- Blaming the symbol for a payload mistake. Most "the code doesn't work" reports are payload problems — a trunk zero, a wrong country code, a message pasted into the number field. The symbol is produced by a vendored reference encoder kept byte-comparable with upstream; when something is wrong, look at the payload text first.
- Adding a logo without re-testing. A logo forces error-correction level H and is capped at 16% of the symbol area, a limit verified by decoding rather than taken from theory — but it was measured on clean, undegraded rasters. A printed symbol adds its own degradation, and a long message adds density, so scan the printed result.
WhatsApp QR code FAQ
Does this work with WhatsApp Business?
Yes — wa.me links open chats with any WhatsApp account, personal or Business, as long as the number is registered on WhatsApp. WhatsApp Business also offers in-app QR codes; the advantage of a wa.me code from this page is that you control the styling, the file, and the pre-filled text, and nothing depends on an app menu.
What happens if someone scans it without WhatsApp installed?
The link opens in the browser, where WhatsApp shows a page explaining that the number uses WhatsApp and offering the app. The scan never dead-ends.
Can people see my number before messaging?
Yes. The number is inside the code (it is part of the link), so treat the code like printing the number itself. If you don't want your personal number public, use a business number.
Group invites?
Group invite links (chat.whatsapp.com/…) are ordinary URLs — encode one with the URL generator. Be aware anyone with the code can join and that WhatsApp lets you revoke the invite link, which kills the printed code.
Why does my pre-filled message look different inside the payload?
The message travels inside the link as the ?text= parameter, url-encoded as the click-to-chat rule asks. Spaces become %20, characters with a structural meaning in a URL such as #, & and ? become %23, %26 and %3F, and accented letters are encoded byte by byte in UTF-8 — the á in the accented example above becomes %C3%A1. The worked examples on this page show the exact string the symbol carries for each kind of input.
Can I put a logo on a WhatsApp code?
Yes, through the style panel. A centre logo destroys modules that error correction must rebuild, so the engine forces error correction level H whenever a logo is present and caps the logo at 16% of the symbol area — a limit verified by decoding in the round-trip test suite, not derived from the theoretical recovery ceiling. A long pre-filled message makes a denser symbol, so treat a logo code as untested until a real device has scanned the printed version.
Does the generator check that the number is actually on WhatsApp?
No. The engine validates format, not truth. It normalizes the number, checks that it has the international shape and strips the plus sign for wa.me, but it cannot know whether the number is registered on WhatsApp, reachable, or yours — a wrong number with a valid shape encodes perfectly. Scan the finished code with a real device before printing it in volume.
Can I change the number or the message after printing?
No. The code is static: the link is the data, encoded in the image itself with no redirect through our servers. That is why it never expires and cannot be held hostage — and also why a changed number or message needs a new code. Scans cannot be counted either. The methodology page sets out the trade-off in full.
Prefer plain calling or texting? See the phone call code and the SMS code. For a full contact card, use the vCard generator.