SMS QR Code Generator
One scan opens the messaging app with your number in the "to" field — and, on a phone that
honours the pre-filled body, your keyword already typed. SMS reaches every phone with a SIM
card, no apps required on either side. The code is built in your browser from the standard
sms: URI, and every example on this page is computed by the same engine.
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.
The sms: format — and an honest caveat
The code carries an sms: URI: the scheme, your number in international E.164 form,
and — only when you type a message — a ?body= parameter holding the pre-filled text.
That is the syntax standardized in RFC 5724, and it is the form the engine's builder was checked
against when its spec sources (the ZXing Barcode Contents reference and the RFC for each URI scheme)
were verified on 2026-07-20. The number part is universally supported. The pre-filled body is not
quite: Pre-filled message support varies by platform: some phones open the composer with the text, others ignore it. The number always works.
Design for that reality: the code should still make sense if the visitor has to type the keyword themselves. Print the keyword next to the code ("Text DEMO to +1 555 013 2000") and the code becomes a convenience, not a dependency.
Companion guide: URI schemes: mailto, tel, sms and WhatsApp
sets the four address-like schemes this site encodes side by side, with the same engine building
every example. This page stays with sms: alone; the other
guides cover the symbol itself and the remaining payload families.
Worked example
A "text DEMO for a callback" campaign code encodes as:
sms:+15550132000?body=DEMO
On a phone that honors ?body=, the composer opens with "DEMO" typed and the
number set; on one that does not, the composer still opens to the right number.
What the payload contains, piece by piece
Read the worked example left to right and you have the whole convention. The engine emits three parts in a fixed order, and nothing else:
- The scheme,
sms:. Lower-case, followed immediately by the number — no slashes, no space. The scheme is what tells whatever decodes the code that the text after it is a messaging address rather than a web link or a number to dial. (A phone call code carries the same number behind a different scheme,tel:.) - The number, in E.164 form. A plus sign followed by seven to fifteen digits whose
first digit is not zero: the country code, then the subscriber number, with no punctuation. The
generator accepts the way people actually write numbers — spaces, dots, dashes, parentheses, and
the
00international prefix as an alias for+— strips the formatting, and emits+followed by digits. It strips punctuation only: a national trunk zero typed after the country code is not removed, so drop that zero yourself. What it will not accept is a number without a country code: the builder refuses rather than guesses one, because it cannot know where the phone that scans the code will be. - The body, only if you typed one. A non-empty message is appended as
?body=followed by the text, percent-encoded as the next section shows. An empty message field adds nothing — the payload ends at the last digit — and no other parameter is ever emitted.
The number rule in practice: three spellings of one fictional number, each handed to the builder at build time, and one payload out. (The build checks that they agree; if the normalizer ever changed, this page would fail to build rather than show a stale table.)
| Typed into the number field | Payload the builder emits |
|---|---|
001 (555) 013-2000 | sms:+15550132000 |
+1-555-013-2000 | sms:+15550132000 |
+1.555.013.2000 | sms:+15550132000 |
That is also the shortest form an SMS payload takes — scheme plus number, nothing pre-filled:
sms:+15550132000
Rendered by the engine, this is version 2, 25×25 modules, error correction Q. The example was requested at level M, the generator's default, and the engine reports the level it actually encoded: never lower than requested, but sometimes higher, because the reference encoder raises the level whenever doing so does not increase the version. A short payload can get more recovery than it asked for at no cost in size.
Percent-encoding, shown on a fictional service request
The message is where SMS payloads go wrong, because a URI cannot carry a space, a hash or an
ampersand as-is. The builder runs the body through strict RFC 3986 percent-encoding:
every character outside the unreserved set — letters, digits, -,
., _ and ~ — is replaced by a percent sign and its byte value
in hexadecimal. Take a code for a maintenance sticker that pre-fills an asset reference and a fault
description, so the text arrives self-identifying:
Message typed: Asset #A-7: lift & door (urgent)!
sms:+15550132000?body=Asset%20%23A-7%3A%20lift%20%26%20door%20%28urgent%29%21
Line the two up character by character and the rule is visible:
| Character | Typed | In the payload |
|---|---|---|
| space | | %20 |
| hash | # | %23 |
| colon | : | %3A |
| ampersand | & | %26 |
| parentheses | () | %28%29 |
| exclamation mark | ! | %21 |
| hyphen | - | - |
The hyphen in A-7 survives because it is unreserved; everything else that is not a
letter or a digit is escaped. The escapes are the URI's spelling of your text, the way a space in
a web address is written as %20 — they are not characters added to your message,
and they do not mean the generator mangled it. The encoded body runs to
55 characters against the 33 you typed, which is why punctuation-heavy
messages grow the symbol faster than plain words do.
The word strict matters. A browser's built-in encoder leaves the sub-delimiters
!, ', (, ) and * bare, but the
RFC 5724 grammar does not allow them bare inside the body value. The engine therefore encodes
those too: the fragment (urgent)! becomes
%28urgent%29%21 here, where the browser's encoder alone would have left it as
(urgent)!. The engine's tests check that output against independently
written reference strings, so the behaviour cannot drift without a test failing.
Symbol facts for the service-request code: version 5, 37×37 modules, error correction M.
Reading the symbol facts: version, modules, error correction
A QR code has two independent layers. The payload is the text above; the symbol is the module matrix produced to ISO/IEC 18004 by the vendored reference encoder. Three facts describe the symbol, and this page prints all three for every example below.
- Version is the symbol's size class. The encoder chooses it from the payload length and the error-correction level; a longer payload needs a higher version.
- Modules are the squares per side, not counting the quiet zone. Between the keyword example and the sentence example below, each version step adds 4 modules per side.
- Error correction (L, M, Q or H) is how much damage the symbol can absorb and still decode. More recovery costs modules at the same payload, so a higher level often needs a higher version — the sentence body below steps from version 5 at L to version 10 at H — but when the extra recovery fits in the version already chosen, the encoder grants it for free, as the number-only example above shows.
Here is the same fictional number with bodies of increasing length — none, the keyword
JOIN, the service request above, and the sentence
JOIN the opening-day list. Reply STOP at any time to leave it. — all requested at the default level M:
| Payload | Characters | Version | Modules | EC level encoded |
|---|---|---|---|---|
| Number only | 16 | 2 | 25×25 | Q |
| Keyword body (JOIN) | 26 | 2 | 25×25 | M |
| Punctuated body | 77 | 5 | 37×37 | M |
| Sentence body | 106 | 6 | 41×41 | M |
And one payload — the sentence body — requested at each of the four levels:
| Requested | Encoded | Version | Modules |
|---|---|---|---|
| L | L | 5 | 37×37 |
| M | M | 6 | 41×41 |
| Q | Q | 8 | 49×49 |
| H | H | 10 | 57×57 |
The practical reading: a keyword-only code stays small, a sentence-long body does not, and asking for more recovery is not free. If you add a logo in the style panel the engine forces level H regardless of the level you chose, and caps the logo at 16% of the symbol area; the error correction and logos guide explains why that cap is decode-tested rather than derived, and how a QR code encodes data covers the version and module facts in more depth.
Print size: what the readout means for an SMS code
The PNG download comes in 3 preset sizes, and the style panel shows what each one measures when printed at 300 DPI. The readouts, computed here by the same function that drives the panel:
| PNG preset | Printed size |
|---|---|
| 512 px | 1.71 in × 1.71 in (43.3 mm × 43.3 mm) at 300 DPI |
| 1024 px | 3.41 in × 3.41 in (86.7 mm × 86.7 mm) at 300 DPI |
| 2048 px | 6.83 in × 6.83 in (173.4 mm × 173.4 mm) at 300 DPI |
Those are the dimensions of the whole image, and the whole image includes the quiet zone: the 4-module white border on every side that the standard requires and that the renderer draws into every download. It cannot be switched off, and it should not be cropped. The export spreads the preset's pixel width across the symbol plus that border, snapping each module edge to a whole pixel, so on average the worked example at the top of the page — 25 modules plus 4 on each side, 33 in all — comes out with these module sizes:
| PNG preset | Pixels per module (average) | Millimetres per module at 300 DPI (average) |
|---|---|---|
| 512 px | 15.5 | 1.31 mm |
| 1024 px | 31.0 | 2.63 mm |
| 2048 px | 62.1 | 5.25 mm |
That arithmetic is the honest extent of what this page can say about print size. A longer body adds modules, and at the same preset each module gets smaller; the SVG download carries the same modules as a vector drawing for any size the presets do not cover. Error correction helps a damaged code scan, but print size and contrast still matter, and the only test that counts is a real device scanning the actual print. The quiet zone and print size guide works the readouts through for every preset.
What the generator cannot verify
The engine validates format, not truth. It checks that the number carries a country code and is all digits once the punctuation is stripped, and it encodes the body so the URI is well-formed. That is the whole of its knowledge. It cannot know:
- whether the number is reachable, or whether anything at that number can receive a text at all. A mistyped digit encodes perfectly: the symbol is just as valid, and the scan just as clean, as for the right number.
- whether your keyword means anything. If the service behind the number expects
JOINand you encodedJOINS, the code is correct and the campaign is not. - whether the body will be honoured on the phone that scans it. Pre-filled message support varies by platform: some phones open the composer with the text, others ignore it. The number always works.
- who will scan it, or where. That is why the number must be in international form — the one form that does not depend on the scanner's country.
Two more consequences follow from every code on this site being static. The number and the message live in the image itself, with no redirect through our servers, so the code never expires and cannot be held hostage — but it also cannot be edited after printing, and scans cannot be counted. If the number changes, the printed code is simply wrong, and the only remedy is a new print. How the builders and the renderer are tested — reference strings, hostile inputs, and a round-trip suite that renders each payload type, rasterizes it and decodes it with an independent decoder — is set out on the methodology page.
Common misreadings
Most "the code doesn't work" reports are payload problems, which the sections above cover. The rest are symbol problems, and the same few recur:
- Cropping the quiet zone. The white border is part of the code. The renderer draws 4 modules of it on every side of every download; placing the code flush against a frame, a colour block or another code takes it away again in the layout.
- Inverting the colours. Light modules on a dark background are rejected by many scanners. The style panel warns separately about inverted pairs, over and above the contrast check.
- Thinning the contrast. The panel computes the contrast of your colour pair with the WCAG relative-luminance formula and warns below 4.5:1. The ratio is computed from the colours you chose, not guessed, and it is the number to watch when a brand palette goes on the code.
- Reading the escapes as damage.
%20and its relatives in the payload are correct — see the encoding section. Do not "fix" them by pasting the payload into a plain-text generator and editing it; the result would be a malformed URI. - Trusting the body. The number always works; the pre-filled text may not. A code whose printed instructions only make sense if the message appears is a code that fails quietly — print the keyword.
- Testing on the screen and not on the print. Treat a freshly generated code as untested until a real device has scanned it — the print, not the preview.
Where SMS codes fit
- Keyword opt-ins: "Text JOIN for opening-day updates" — with the legally required consent and opt-out language printed alongside, not hidden in the code.
- Service requests without an app: a code on the machine that pre-fills its asset number, so the maintenance text arrives self-identifying — the service-request example above is exactly this.
- Reaching non-smartphone workflows: SMS lands on every handset with a SIM — useful where email goes unread and messaging apps are not guaranteed.
SMS QR code FAQ
Does scanning send the message automatically?
No — on every platform, the visitor sees the draft and presses send themselves. A QR code cannot send an SMS on its own, which also means it cannot silently sign anyone up to anything.
Who pays for the message?
The sender, at their normal SMS rate. If your audience may be charged, standard practice (and in many jurisdictions, a requirement) is to say so next to the code.
SMS or WhatsApp code — which should I print?
Know your audience. SMS works on every phone with a SIM and needs no app; WhatsApp needs the app but is free over data, better for media, and dominant in many countries. When in doubt, print both — see the WhatsApp generator.
Will the message be pre-filled when someone scans?
Pre-filled message support varies by platform: some phones open the composer with the text, others ignore it. The number always works. The generator encodes the body exactly as RFC 5724 specifies; whether a given phone acts on it is outside the code’s control, which is why the keyword belongs in print next to the code as well as inside it.
Why does the generator reject my local-format number?
The payload carries the number in international E.164 form — a plus sign, the country code, then the subscriber number — because the builder refuses to guess a country code. Type the number any way you like with the country code included (spaces, dots, dashes, parentheses and the 00 prefix are all accepted) and the generator emits the normalized form, as the spellings table above shows. It strips punctuation only: a national trunk zero typed after the country code is not removed, so drop that zero yourself.
The payload shows %20 and similar codes in my message — is it broken?
No. Those are percent-escapes, the URI spelling of characters a URI cannot carry bare: %20 is a space and %21 an exclamation mark. The engine applies strict RFC 3986 encoding to the body so the sms: URI is well-formed; the encoding section above shows a full message character by character.
Can I change the number or the message after printing?
No. Every code from this site is static: the number and the body are encoded in the image itself, with no redirect through our servers. That is what lets the code work without us and never expire, and it is also why nothing in it can be edited once printed — a new number means a new code. Scans cannot be counted for the same reason.
Does a longer message make the code bigger?
Yes. A longer body means a longer payload, and the encoder chooses the version that holds it: the keyword example is version 2 (25×25 modules) while the sentence example is version 6 (41×41). At the same PNG preset more modules means each module is smaller — the print-size section shows the arithmetic. Print size and contrast still matter, so treat a fresh code as untested until a real device has scanned it.
Related: the phone call code for voice, and the email code when a longer written message fits better.