Pet Tag QR Code

The pet-tag preset is the one occasion on this site that encodes a contact card rather than a link or a message: a vCard, written field by field by the engine's payload builder and drawn in the heart-module style. This page is that preset's measured rules made legible — what the builder writes into the code, how each field you add grows the symbol and its print floor, why the hearts stay decodable, what the verification badge proves about the card and what it cannot, and how to print and test one. Every number below was computed by the rendering engine when the page was built, and your browser decodes the finished artwork back to the exact card before you can download it.

Contact card details

Scanning opens the phone's add-contact screen — one tap to call. Ideal on a collar tag.

Deep shades only: the engine rejects anything under 4.5:1 contrast on white, because scanners need it.

Fill in the form — your keepsake appears and is verified as you type.

Codes render on light backgrounds for reliable scanning.

What the code contains
 

Delivery unlocks only after this exact render is decoded back to your content — the decode-verify promise.

Keep this keepsake

Saves the verified artwork to this browser only — no account exists on this site. Find it later under My keepsakes.

A worked example, rendered by the real engine

Rendered at build time by the same engine as the tool above — heart modules in slate, corner landmarks classic so it scans. It encodes a standard vCard 3.0:

BEGIN:VCARD
VERSION:3.0
N:Alvarez;Maya;;;
FN:Maya Alvarez
TEL;TYPE=CELL:+15551234567
END:VCARD

Version 10 at 57×57 modules, error correction H — margin for a tag that lives outdoors at dog height.

What the pet-tag preset encodes

Guide: how large a keepsake must be printed, what the PNG sizes mean at 300 DPI and where the badge stops making promises are set out in full in Printing and scanning a keepsake; the heart style itself is covered in Heart-module codes, explained. This page applies those rules to one preset, with the numbers computed from it; all of the site's guides are at /guides/.

In the occasion registry the pet-tag preset starts on the Heart modules style with a contact card as its content (content type vcard) and a default heart colour of #1f2937, a dark slate. Every preset fixes the same three things — the style a page opens on, the payload builder it uses and the example rendered above — and the same registry entry drives the generator, this page and the test suite, so the three cannot disagree. Nothing is locked: the style, the content type and the colour stay editable in the generator. But the preset's choice is the reason this page exists, because a card in a heart code is governed by two sets of rules at once, the builder's and the style's.

The builder writes a vCard 3.0 as RFC 2426 describes it: a BEGIN line, a VERSION line, the structured name (N) and the formatted name (FN), one TEL line when a phone number is given, an optional EMAIL line, and END — every line terminated by CRLF, the last one included, with backslashes, semicolons, commas and line breaks escaped inside text values. That is the list for the fields the generator above exposes — first name, last name, phone and an optional email; the builder can also write ORG, TITLE and URL lines, which this generator does not expose. The builder requires at least a first or last name; every other field is optional, and a field left empty produces no line at all rather than an empty one.

The worked example above is a fictional card written for this page and belonging to no one: 6 lines, 101 characters with their line endings. Its name lines read N:Alvarez;Maya;;; and FN:Maya Alvarez, and its number line reads TEL;TYPE=CELL:+15551234567. The number in the registry is typed as +1 555 123 4567; the builder normalised it to E.164 — a plus sign followed by the digits alone, +15551234567 — before writing the line. Spaces, dots, dashes and brackets are stripped, so the same number typed as +1 (555) 123-4567 produces exactly the same TEL line. What the builder will not do is guess a country. 555 123 4567, typed without its prefix, is refused with the message “Include the country code in international format, e.g. +1 555 123 4567.” and +1 234 is refused with “An international number has 7–15 digits and cannot start with 0 after the country code prefix.” A number may begin with + or with the 00 international prefix, which the builder treats as an alias for the plus sign.

At error-correction level H those 101 characters fit a version 10 symbol: 57×57 modules — 3249 symbol cells, 1630 of them dark — and 65×65 once the 4-module quiet zone is drawn on every side. The engine classifies every dark module before drawing it. 201 fall inside the three finder patterns or one of the 6 alignment patterns (centred at module positions 6, 28, 50) and are drawn as classic crisp squares; the remaining 1429 are drawn as hearts, including the 36 on the two timing tracks, which the anatomy rule allows to be styled. None of those figures is typed into this page: they are read from the same renderOccasionExample call that draws the artwork, so if the engine changed, the prose would change with it or the build would fail.

What each field costs

A QR symbol grows in fixed steps called versions, and the encoder picks the lowest version that holds the payload at level H. The hearts do not shrink to make room for a longer card; the grid gains cells, and at any given print size each cell — and each heart — is smaller. Every module still needs the engine's working minimum of 0.5 mm on paper, so a denser card has a larger print floor. A card is a long payload by this site's standards: the lines that make it a vCard cost characters before a single letter of the name is written. The engine built 4 fictional cards for this page through the same vCard builder and encoded each at level H:

Card Lines Characters Version Modules (with quiet zone) Minimum print width
First name and phone 6 86 9 53×53 (61 across) 30.5 mm
The worked example: first name, last name, phone 6 101 10 57×57 (65 across) 32.5 mm
The worked example plus an email 7 133 11 61×61 (69 across) 34.5 mm — flagged oversized
A longer name and a longer email 7 177 13 69×69 (77 across) 38.5 mm — flagged oversized

Two things in the table are worth reading twice. The fixed lines are present whatever you type: a first name alone — the example's Maya, no number — is already a version 6 symbol, 49 modules across with its quiet zone, and the example's first name with its phone is version 9, 61 across. And the engine's print helper flags any symbol wider than 65 modules, quiet zone included, as oversized for the objects these codes go on — in its own words, gift tags and cards. The worked example comes out at 65 across, exactly at that line; adding an email takes the card to 69 across, past it, and the generator shows its oversized warning for that card. The flag is a limit on what prints usefully, not on what can be encoded: the builder writes the longer card and the verifier verifies it. Whether a code of that width fits the object you have in mind is a measurement only you can make, and the readout under the preview gives you the number to make it with.

The heart-module rules that apply to a card

Maximum error correction, always. Every style that redraws modules — hearts and the photo halftone — encodes at level H, roughly 30% recovery, and the heart renderer refuses any model encoded at a lower level. Only a framed code without heart modules, which leaves the symbol untouched on its panel, may also carry level Q when the generator's opt-in smaller-code setting is chosen, and the engine rejects anything below Q. Hearts reshape every data module, so they hold the line at H, and the worked example is at level H. For a card that means the density in the table above cannot be bought back by lowering the level: the only way to a coarser code is a shorter card.

Landmarks are never decorated. A scanner finds the code by its three large finder squares and orients a dense code with the smaller alignment squares. Alignment patterns multiply with version: the example has 6 of them, where the preset's own link placeholder, https://example.com/biscuit, encodes at the same level as version 4 with 1. Restyling alignment patterns broke decoding outright in this engine's testing, so in a heart code the finder and alignment modules stay square whatever colour the hearts take. Timing tracks may be styled, and are. The 4-module quiet zone around the symbol is always drawn.

Hearts span 0.97 of their cell. The heart glyph is a fixed set of curves scaled about the centre of the cell by the engine's HEART_CELL_COVERAGE constant — 97% of the cell side. The figure is decode-verified: large enough that a scanner's sampling grid still reads each cell as decisively dark, small enough that neighbouring hearts read as separate shapes to the eye. It matters more as the grid gets denser, because a card's cells are small at any given print size and the coverage constant is what keeps each small heart reading as a dark cell. It is not adjustable, and it is pinned by the test suite so a future change cannot quietly move it.

Foreground and background must measure at least 4.5:1. The engine computes the WCAG 2.x contrast ratio of the heart colour against the background and rejects weaker pairs rather than warning, because scanners binarise the image and low contrast measurably hurts decoding. The ratio is a function of relative luminance, not of hue, so the preset's slate is not safe because it is grey; it is safe because it is dark. Measured at build time against the worked example's own symbol:

  • The preset's #1f2937 on white: 14.68:1 — accepted.
  • A mid grey, #6b7280, on white: 4.83:1 — accepted.
  • A pale grey, #9ca3af, on white: 2.54:1 — rejected. The engine's own message reads: “Artistic styles require at least 4.5:1 contrast (got 2.54:1) — decoration already spends decode margin.”

The gate measures the pair, so a tinted background is judged with the hearts, not separately; and the engine refuses inverted codes outright, however high their ratio — dark hearts on a light background only.

What a phone does with a contact card

The content type matters at scan time as much as the style does. A QR code containing bare text carries no scheme, so a phone has nothing to act on. Apple's Camera and Google's ML Kit both recognise links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins — and no plain-text type. A contact card is on that list, which is the whole reason the preset encodes one rather than writing a name and number as a message. This page claims nothing beyond the list: what any particular phone offers to do with a recognised card is for the test scan to show, not for this page to promise.

Switch the content selector to a message and the message rules take over. A reveal link carries the words in a URL fragment that is stripped before any request is made, so it never reaches a server, a CDN or a log; an offline note writes the words into the code and accepts that an iPhone offers to copy or search bare text and Google Lens runs a web search, while Samsung phones and dedicated scanner apps do display it. In both modes, on a framed keepsake with no caption of your own, the message is printed under the code as the caption, so it can be read by eye regardless. For a card none of that machinery is needed, and none of it applies.

What verification proves about a card — and what it cannot

After each render your browser rasterises the finished heart artwork at 3 sizes, 800, 1200, 1600 pixels, runs jsQR on the pixels of each, and compares the decoded bytes to the builder's output. At least 2 of the 3 rasters must match byte for byte; anything less shows a warning and keeps the downloads locked, so an unverified render cannot leave the page as a finished keepsake. The 3 sizes exist because of a measured effect, not for thoroughness: a render can fail at exactly one raster size and decode at the others, depending on how the decoder's sampling grid lands on the modules at that scale. One fixed size would turn that resonance miss into a false failure; the 2-of-3 rule absorbs it, and the result reports which sizes passed.

The comparison is against the exact vCard string — CRLF line endings, the normalised TEL line and all. For a card that is a stronger statement than it first appears: what is verified is not that a QR code was found but that the decoded bytes are this card and no other. A render that decodes to different content is reported ahead of one that does not decode at all, because wrong content is never safe to print.

What this proves is that the artwork decodes cleanly at screen resolution. It cannot see your printer, your paper, or the phone that will do the scanning. And it cannot check the card's meaning: the engine verifies that the digits in the code are the digits the builder wrote, not that they are the right digits. Read the payload box under the preview before you print; nothing downstream will. The worked example above was rendered at build time by the same function the generator runs, and the test suite decodes every occasion example, so a preset that stopped scanning could not ship.

The methodology's print guidance is short, and this page does not extend it: print at least 2 × 2 cm for arm's-length scanning, keep the quiet zone uncropped, and scan a test print with a real phone before the moment that matters. What the engine adds is a floor computed from the module count of the card you actually made, and the printing and scanning guide works through where that floor comes from.

The working minimum module is 0.5 mm, so the worked example — 65 modules across, quiet zone included — must print at least 32.5 mm (1.28 in) wide. That is the larger of the two figures, so for this card the engine’s floor governs rather than the general guidance. Print at whichever is larger for your own card; the readout under the preview states the floor for the content you typed, computed by the same minPrintSize function this page calls. The table above shows how fast the floor moves: from 30.5 mm for a first name and a number to 38.5 mm for the longest fictional card.

Export sizes are in pixels, and a print size follows from them at 300 DPI. Of the engine's 3 PNG presets the generator above offers 2, read from its own size select when this page was built so the list cannot name a size the tool does not download; for the square heart artwork they come out as follows: 1024 px prints at 86.7 mm; 2048 px prints at 173.4 mm. Those figures describe how large a raster happens to come out; the floor above is the figure that must survive, whichever file carries it. The SVG download is vector and carries the same verified artwork at any size — the floor still applies to it.

The quiet zone is part of the code: the 4 clear modules on every side are always drawn, and a cut that enters them removes something the decoder relies on. Then make one test print at the final size and scan it with a real phone — it is the only test that includes the paper. Check two things: that it scans, and that what appears is the card you meant. If a verified code will not scan in print, that is exactly the report wanted; the methodology page says where to send it, and confirmed issues are fixed in the engine and locked in with a new test.

Common misreadings

  • Cropping the quiet zone. The 4-module margin is always drawn and is not blank space around the code; it is part of what the decoder relies on. A trim that enters it costs decode margin the hearts have already spent.
  • Expecting bare text to display. This preset encodes a contact card, which is on the recognised list. Switch it to a message and a code containing bare text carries no scheme, so a phone has nothing to act on — the reason the message modes exist, and the reason a framed keepsake with no caption of its own prints the message under the code as well.
  • Low-contrast heart colours. The gate is 4.5:1 measured with the WCAG formula, and the engine refuses rather than warns. The preset's #1f2937 measures 14.68:1 on white and is accepted; a pale grey #9ca3af measures 2.54:1 and is rejected.
  • Assuming an extra field is free. Each field is a line, each line is characters, and the encoder answers in whole versions. The email in the table above moves the card from version 10 to version 11 and the floor from 32.5 mm to 34.5 mm.
  • Typing a local number. The builder normalises to E.164 and will not infer a country code; 555 123 4567 is refused, +1 (555) 123-4567 is accepted and written as +15551234567.
  • Reading the badge as a print guarantee — or as a check on the number. Verification decodes screen artwork at 800, 1200, 1600 px and compares bytes with the builder's output. Paper, ink and the scanning phone are outside it, and so is whether the digits were the right ones.
  • Expecting to edit a printed code. The card is inside the image, so nothing can lapse — and nothing can be changed after printing either. A new number is a new render, verified again, and a new print.

For the size rule behind every readout on this site, read Printing and scanning a keepsake; the guides index covers the heart style, verification and message keepsakes, and the methodology page holds every measured rule in one place.

Pet tag code FAQ

What does the pet-tag preset actually encode?

A contact card. The preset starts on the Heart modules style with a vCard as its content; for the fields the generator exposes, the builder writes a vCard 3.0 — BEGIN, VERSION, the N and FN name lines, a TEL line when a number is given, an optional EMAIL line and END — with CRLF line endings. It can also write ORG, TITLE and URL lines, which this generator does not expose. The fictional example on this page is 6 lines and 101 characters, and encodes as version 10, 57×57 modules, at error correction H.

Why does the generator refuse my phone number?

The builder normalises every number to E.164 — a plus sign followed by the digits alone — and will not guess a country. Spaces, dots, dashes and brackets are stripped, and the 00 international prefix is accepted as an alias for the plus sign, but a number typed without any prefix, such as 555 123 4567, is refused with the message “Include the country code in international format, e.g. +1 555 123 4567.” A number that is too short, such as +1 234, is refused with “An international number has 7–15 digits and cannot start with 0 after the country code prefix.”

Does adding an email make the code harder to print?

It makes the symbol denser, and the print floor follows. Measured at level H when this page was built: a card with a first name and phone is version 9 (53×53 modules, 61 across with the quiet zone); the worked example with a last name added is version 10 (65 across); the same card with an email is version 11 (69 across, flagged oversized by the engine). At 0.5 mm per module those are print floors of 30.5, 32.5 and 34.5 mm.

What does a phone do with a contact card?

A contact card is one of the payload types the methodology lists as recognised by Apple’s Camera and Google’s ML Kit — links, phone numbers, email addresses, contact cards, calendar events and Wi-Fi joins — whereas bare text is not: a code containing bare text carries no scheme, so a phone has nothing to act on. This page claims nothing beyond that list; what a particular phone offers to do with a recognised card is for the test scan to show.

Why do the hearts have to be a dark colour?

Because the heart renderer measures the foreground against the background with the WCAG formula and rejects any pair under 4.5:1 rather than warning; scanners binarise the image, and low contrast measurably hurts decoding. Measured at build time: the preset’s slate #1f2937 on white is 14.68:1 and is accepted; a mid grey #6b7280 is 4.83:1 and is accepted; a pale grey #9ca3af is 2.54:1 and is rejected. The engine also refuses inverted codes — dark hearts on a light background only.

How big must a pet tag code be printed?

Print at least 2 × 2 cm for arm’s-length scanning, or the engine’s floor for your card if that is larger. The floor is 0.5 mm per module, quiet zone included: the worked example is 65 modules across and must print at least 32.5 mm (1.28 in) wide, which is the larger of the two. The readout under the preview states the floor for the card you typed. Keep the 4-module quiet zone uncropped and scan a test print with a real phone before the moment that matters.

What does the “Verified scannable” badge prove about the card, and what does it not?

It proves this exact artwork was rasterised at 800, 1200, 1600 px, decoded with a real QR decoder at 2 or more of those 3 sizes, and matched the builder’s vCard byte for byte — line endings and the normalised number included. It cannot see your printer, your paper or the phone that will do the scanning, and it cannot check that the digits are the right digits: it verifies that the code carries what the builder wrote, not that what you typed was correct.

What happens if the phone number changes?

The number is inside the image — there is no shortener, no redirect and no subscription keeping the code alive, which is why the code itself cannot expire. The trade-off is that a printed code cannot be edited: a new number means a new render, verified again like any other, and a new print. Nothing on this site has to be renewed for the old print to keep decoding to the old card.

The same contact-card mode works for luggage tags, kids' backpacks, and key rings — anything that should phone home. For the humans of the household, a photo keepsake code can weave the pet's portrait into a frameable code, and a gift tag code handles the "new puppy" announcement box.