What the Studio does

This page is the complete capability list — nothing here is aspirational, and everything here is free to use today. Each feature links back to how it works, and the example below is a real scannable code composed at build time by the same engine that runs in the editor. Under each feature you will also find the engine rule behind it, with the figures computed here at build time on the example's own link and on fictional inputs named as such, rather than typed into the copy. The rule that shapes everything else — why every decorated code carries print-grade redundancy — has its own guide, Error correction, chosen for print; the methodology page sets out what the studio does and does not model.

A project-based editor

A keepsake in the Studio is a project: a title, an occasion (wedding, memorial, anniversary, graduation, new arrival, celebration), the content the code opens, and the full design. Signed in, projects save to My projects and reopen in the editor exactly as you left them. Signed out, the editor still does everything except save — and if you start designing before creating an account, your in-progress design survives the signup round trip and saves itself once you are in.

Under the hood a project is one serializable description — the editor state — and one shape serves every use: the live editor, the stash that carries an unsaved design across the signup round trip, the saved record, and reopening. It holds the title, the occasion, whether the code opens a link or a hidden message and the raw text of either, the frame choice, the three palette colors, the caption, the module style, and whether the design is photo-woven. The photo itself is deliberately not part of the state: photos are processed on-device only and never serialized, so a reopened photo-woven project shows its saved artwork and asks for the photo again before re-editing.

Parsing that state is total. The state carries a version number (currently 1), and corrupt JSON, foreign shapes, unknown occasions, malformed colors and future versions all read as "nothing", so the editor simply starts fresh instead of wedging. Two fictional cases were fed to the parser while this page was built — a stash cut off mid-string, and an otherwise complete state whose occasion is not one the Studio knows — and both parsed as nothing; the build fails if either ever parses as a project.

Each occasion suggests a frame template when you have not chosen one. It is a suggestion only — every template stays available for every occasion:

  • Wedding suggests the floral frame.
  • Memorial suggests the classic frame.
  • Anniversary suggests the heart frame.
  • Graduation suggests the confetti frame.
  • New arrival suggests the heart frame.
  • Celebration suggests the confetti frame.

Five frame templates, recolorable

Heart, floral, ring, classic, and confetti — or no frame at all. Each template exposes three color inputs (frame, secondary, accent), so a design can match invitations or a printed program. Recoloring can never break scanning: colors only touch the frame art, while the code sits untouched on its own opaque white panel, composed above the decoration by construction.

A caption sets the words under the code — names and a date, a dedication, an instruction. Its length is bounded by the engine, which states the limit in its own words below.

The frame choice is one of 6 values: the 5 templates plus "none", which composes the bare code. The composite is built in a fixed order — a page background, then the frame art, then the opaque panel, then the rendered QR markup nested verbatim inside a scaled group, then the caption — so there is no layer in which decoration could reach a module. That is why the palette never enters the scannability calculation: in the example below the ring frame's primary color would measure 4.9:1 if it were used for the modules, but it is not; the modules on the panel measure 21.0:1, and only the second number describes the code.

The engine validates what you give it and answers in plain words, as three fictional test inputs show. Each palette color must be a hex color — handed the color name "gold" instead, it replies: “Palette primary must be a hex color like #be123c (got “gold”).” A deliberately oversized test caption of 103 characters is refused with: “Captions are limited to 80 characters.” And a template the engine does not have — a made-up "oval" — is refused with: “Unknown frame template “oval”. Use one of: heart, floral, ring, classic, confetti.”

Opens the Studio — scan me
Ring frame with the default palette — rendered by the engine that built this page

The example above, taken apart by the same code that drew it: the link it opens encodes at QR version 5, a symbol 37 modules on a side, rendered as 45 modules on a side once the 4-module quiet zone required by ISO/IEC 18004 is added on every edge — the quiet zone is always rendered and cannot be disabled. Of its 1369 cells, 690 are dark. The caption is drawn beneath the panel in the frame's primary color; the panel itself keeps the modules on pure white.

Module styles that still scan

Switch the code's dark modules between classic squares and hearts. The corner finder and alignment landmarks always stay classic squares — restyling them is what breaks scanning on decorated codes, so the Studio simply never does it. Every artistic style encodes at error-correction level H, the highest the QR standard offers.

The artistic engine states its production rules as law, and each entry point enforces them rather than trusting the caller. Error correction is H for every artistic style, without exception — decoration spends decode margin, and only the ~30% recovery of EC-H leaves enough. Handed a model encoded at level M, the heart-module renderer refuses it: “Artistic styles require error correction level H — re-encode with ecLevel "H".”

Finder and alignment patterns stay squares; timing modules may be styled. In the example above, the 690 dark modules divide into 99 in the three finder patterns, 17 in the single alignment pattern a version-5 symbol carries, 22 in the timing row and column, and 552 modules the engine classes as data — so 574 of them may be drawn as hearts and 116 must remain squares. Each heart spans 0.97 of its cell, so a scanner's sampling grid still reads the cell as decisively dark; the default deep-rose hearts measure 9.6:1 against white.

Contrast is a rule here, not a warning. Foreground against background must reach 4.5:1 by the WCAG 2.x formula and the code must not be inverted. Were the heart template's pale secondary color used for the modules themselves, the engine would answer: “Artistic styles require at least 4.5:1 contrast (got 1.89:1) — decoration already spends decode margin.” Light modules on a dark background are refused separately: “Artistic styles require dark modules on a light background — inverted codes are rejected.”

Why level H, and what it costs

Keepsakes are printed, framed behind glass, photographed at an angle, and read in candlelight, so the Studio spends more of the symbol on redundancy than a screen-only code would. The honest trade-off is density: a fictional 25-character link encodes at version 2 (25 modules a side) at level M and version 4 (33 a side) at level H, while a fictional 106-character album link grows from version 6 (41 a side) to version 10 (57 a side). More modules across the same printed width means smaller modules, which is why very long destinations plus high correction can produce codes that need to be printed larger. The guide works through the whole trade.

Photo-woven codes

Weave a photo into the code itself: each data cell shows the photo quantized to three tones around a pure code-colored center that scanners treat as authoritative. The photo is processed entirely on your device — it is never uploaded, and it is never stored when you save the project (the saved composition is; the working photo is not). Faces and simple subjects read best.

The mechanism, in the engine's own description of the blend — “data cells split 3×3: the eight ring subcells show the photo quantized to three tones” — with the tones cut at the thresholds its constants hold: dark below a luminance of 85, light above 175, a mid tone between. The center square, 0.5 of the cell's side, is drawn last in the pure module color and is the part a decoder reads. Finder, alignment and timing cells and the quiet zone stay fully pure, and no logo may be stacked on a photo-woven code, because the photo has already spent the margin.

Photo weaving also caps the payload. Dense, high-version symbols shrink the authoritative centers below decode viability, so the engine refuses any content that encodes above QR version 4 — about 34 characters of plain text at level H, fewer with emoji or accents. The link in this page's own example is already too long: asked to weave a photo into it, the engine replies, “Photo-woven codes need short content — use a short link or pick another style. This content needs a version-5 code; photo weaving is decode-proven only up to version 4 (about 34 characters, fewer with emoji or accents).” The shorter fictional link used above encodes at version 4 and weaves without complaint. Of the three styles, only photo weaving carries a payload cap; a frame with classic or heart modules has none beyond the standard's own capacity, which the encoder enforces for every style — the over-long payload refused in the next section shows where that cap falls.

What a code can open

A link — your album, tribute page, playlist, video — or a hidden message shown as text when scanned. The content is encoded directly into the image: no shortener, no redirect through our servers, nothing that can expire or be held hostage. For Wi-Fi cards, vCards, and other utility formats, use our sibling site GenerateQRCodes.net.

The two payload types are the whole vocabulary: a link or a text message, stored exactly as you typed it. The destination is placed in the symbol as entered, with no shortening, tracking parameters, or redirection added, and the symbol is a standard ISO/IEC 18004 QR code from a tested encoder — any camera app that reads QR codes reads these. Two inputs the encoder refuses outright, with the exact messages it returns: an empty field — “Enter the content to encode.” — and content beyond the capacity of the largest symbol the standard defines at the chosen correction level — “This content is too long for a QR code. Shorten it or lower the error correction level.”

One limit follows from that directly. Verification covers the format of what you encode — that the symbol decodes back to exactly the bytes you entered — and says nothing about the future of the service those bytes point to. If the host of an album link later removes the album, the code still decodes, to a destination that no longer answers; no amount of error correction reaches that far. The methodology's advice for the longest-lived keepsakes is to point codes at destinations you control, and the other limits the engine does not model are collected in What the studio does not model.

Decode verification, in your browser

Every render is rasterized and decoded with a real QR decoder (jsQR) right in your browser. The “Verified scannable” badge appears only when the decoded bytes match your content exactly, and downloads stay disabled until it does. A pretty code that cannot be decoded is a failure, and the Studio treats it as one — the methodology page documents the rules this enforces.

Verification runs at 3 raster sizes — 800, 1200, 1600 px — and the badge is earned when at least 2 of them decode to an exact byte match. One raster is not enough on its own because a single fixed size can false-negative through sampling resonance: a render that happens to fail at one size can decode cleanly at the others. When fewer than 2 rasters pass, a mismatch anywhere is reported first — a render that decodes to different content is never safe — then an undecodable render, then a rasterization error.

How to read a verdict. The example's own code was rasterized at every size and decoded while this page was built. The all-pass and wrong-content cases below are those live results; the two partial-pass cases are scripted outcomes for the combining rule — not a live render — which substitute the blank-raster result shown last for each size that is treated as missing. These are the engine's words for each case:

All rasters pass: Verified scannable at 800, 1200, 1600 px (3 of 3 rasters) — decoded back to your exact content.

One raster misses, the other two pass: Verified scannable at 800, 1600 px (2 of 3 rasters) — decoded back to your exact content.

Only one raster passes: Cannot verify: this render decoded at only 1 of 3 raster sizes (at least 2 must match). Do not print it — adjust the style (larger size, stronger contrast, shorter content, or a calmer photo) until verification passes.

The render decodes, but to the wrong content: Cannot verify: the render decoded to DIFFERENT content than you entered. Do not print it — this render is not safe to use.

No code is found at all: Cannot verify: no QR code was detected in this render. Do not print it — adjust the style (larger size, stronger contrast, or a calmer photo) until verification passes.

What verification cannot promise is the print. It decodes the artwork the Studio produced, at the raster sizes above, in your browser; it does not see your printer, paper or ink. The contrast floor and the size readouts come from scanning practice, not from any one print process, and how a particular printer, paper and ink reproduce a pair of colours is outside what the engine measures — which is why the methodology's instruction for that gap is to test a proof print before a large run.

Free, watermark-free export

Download verified designs as PNG (1024 px or 2048 px, sharp for cards and large prints) or as SVG, which scales to any print size without pixelation. Exports are free, carry no watermark, and are plain files on your device — they keep working whether or not this site, or your account, exists.

The engine's PNG presets and the physical size each one prints at 300 DPI, computed by the engine's own print-size function; the editor's menu offers the two larger presets:

PNG preset Prints at 300 DPI as
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

The readout is defined for a square PNG, so for a bare code exported at a preset those figures describe the whole image, quiet zone included. The engine's own canvas renderer for bare codes places module edges at rounded pixel boundaries so adjacent modules never leave antialiasing seams, even when the output size is not an integer multiple of the module count; it is the renderer this page used to rasterize the example for verification. The SVG carries the same 4-module quiet zone and the same verbatim module path, so it is the format to choose when the print size is not yet decided. Either file is independent of your account and of the retention described next.

Saved projects and the 30-day window

A free account does exactly one thing: it stores the projects you choose to save. Free projects are kept for 30 days from the day they are saved — every row in My projects shows its own “kept until” date, computed from the database record itself, and the cutoff is enforced in the database rather than politely hidden by the interface. Reopen, duplicate (the copy gets its own 30 days), or delete any project; delete everything at once from the account page.

The mechanism is worth stating exactly, because it is a property of the database row, not a policy the interface applies. When a project is saved, its row is written with an expiry timestamp 30 days ahead, and the row-level security policy that grants you access to your own projects is the same policy that requires them to be unexpired — so an expired row is invisible to every client, not merely hidden by one. Invisibility therefore does not wait on a scheduled cleanup that might lag, and it is not a filter in the interface that could be bypassed: the row stops being readable at its expiry instant, and a background purge later removes expired rows permanently. The guide How the 30-day retention works walks through the same sequence step by step.

What the record holds is equally exact: "the saved record contains the project's settings and the composed design — the working photos themselves are not uploaded." A "kept until" label is read from that record's own timestamp, not computed by the page, so the list and the database cannot disagree. Download anything you want to keep; the downloaded file is independent of all of this.

Live Keepsakes — a hosted, two-device reveal

Live Keepsakes is the one part of the Studio that is deliberately not a downloadable file: a signed-in sender writes a message, gets a code on their screen, and the moment someone scans it, the message plays on the recipient's phone with an animated reveal — hearts, balloons, confetti, sparkles, or stars — while the sender's screen celebrates in real time and can receive a "love back."

Being live is a trade, stated plainly: the message is hosted in our database (unlike everything else in the Studio, which never leaves your browser), and each live keepsake ends 30 days after it is created — it is a moment, not a monument. Static keepsakes you download remain the opposite: plain files that encode their content directly and never expire. Opens are anonymous by design — the sender sees when the keepsake was opened, never who opened it or from where — and any photo the sender places on their own screen is never uploaded.

A live keepsake has its own vocabulary of 5 occasions, separate from the Studio's, and each one fixes the particle shape the recipient's screen draws:

  • A love note — hearts.
  • A birthday surprise — balloons.
  • Congratulations — confetti.
  • A proposal — sparkles.
  • For a friend — stars.

The draft is validated against the database's own contract before anything is created, and every error is returned at once. The message may be at most 500 characters and the sender-private label at most 120; a fictional draft with a 501-character message, a 121-character label and the Studio occasion “Wedding”, which is not a live occasion, was validated while this page was built and came back with 3 errors:

  • Choose one of the occasions.
  • The message can be at most 500 characters.
  • The private label can be at most 120 characters.

A message made only of spaces is refused with: “Write the message your keepsake reveals.” The code the sender holds up encodes a plain address on this site under /r/ with the keepsake's id as a query parameter — a static route whose query string every static host serves unchanged — and the recipient page shows its not-found state for anything that is not a well-formed id, rather than querying with it.

Both screens are driven by small state machines that never move backwards, and unknown events never change state. The sender's card starts on its photo; a tap moves it to "dissolving", and the end of the dissolve lands on "qr". A tap on the finished code stays at "qr", and a stray dissolve callback before any tap leaves the card at "photo". The recipient's screen starts at "loading": a successful load moves to "revealing" and the end of the animation to "open"; a keepsake that does not exist or has ended moves to "gone", and a network or parse failure — retryable, unlike a missing one — to "error". Those three are terminal: a "gone" screen handed a late successful load stays "gone".

Reduced motion is an input to both machines, not an afterthought. With it, a tap moves the sender's card straight to "qr" and a successful load moves the recipient straight to "open" — the dissolve and reveal stages are skipped entirely. The bursts follow the same rule. The recipient's reveal is the largest at 120 particles; on the sender's screen an open bursts 45 and a love back 90, and under reduced motion every one of them is 0, so the painter never starts its loop.

What a recipient can be shown is fixed by the read path itself: the anonymous read returns only the occasion, the message and the style, and the client copies exactly those three fields, so it structurally cannot surface the sender's account or private label. Opens and love-backs reach the sender as events carrying a time and a kind — the honest status line is built from nothing else.

Frequently asked questions

Can recoloring a frame make the code harder to scan?

No. The three palette inputs touch only the frame art, the panel's border stroke and the caption — never a module — and the framed composite nests the rendered QR markup verbatim on an opaque white panel drawn above that art, so decoration cannot touch a module by construction. The code's own contrast is what scanning depends on: in the example on this page the modules measure 21.0:1 against the panel, whatever the frame around them is colored. Recolor the modules themselves and the artistic engine checks the ratio against its 4.5:1 floor and rejects anything below it.

Why was my photo-woven code refused for a link that works in every other style?

Photo weaving is capped at QR version 4 — about 34 characters of plain text at error-correction level H, fewer with emoji or accents — because denser symbols shrink the authoritative center of each cell below what a decoder can rely on. The link in this page's own example encodes at version 5, so the engine rejects it for weaving with the message: “Photo-woven codes need short content — use a short link or pick another style. This content needs a version-5 code; photo weaving is decode-proven only up to version 4 (about 34 characters, fewer with emoji or accents).” Use a shorter link, or keep the photo out of the code and choose a frame instead.

What does “Verified scannable at 800, 1600 px (2 of 3 rasters)” mean?

Every render is rasterized at 3 pixel sizes (800, 1200, 1600 px) and decoded at each one; the badge is earned when at least 2 decode to an exact byte match with your content. A single fixed raster can false-negative through sampling resonance, so one miss alone is not allowed to flip the verdict. The line therefore names the rasters that passed. A render that decodes at only one size is reported as cannot-verify (the engine's undecodable verdict when the other rasters found no code; a rasterization failure is reported as its own error), and a render that decodes to different content anywhere is reported as a mismatch ahead of everything else.

Why are the download buttons disabled?

Because the current render has not been verified. The Studio treats a code it cannot decode back to your exact content as a failure, not as a cosmetic warning, and keeps PNG and SVG export closed until the verdict is "verified". Change what the message tells you to change — larger size, stronger contrast, shorter content, or a calmer photo — and the buttons open as soon as a render passes.

What exactly is inside a saved project — and what is not?

The methodology page states it precisely: "the saved record contains the project's settings and the composed design — the working photos themselves are not uploaded." The settings are the editor state — title, occasion, what the code opens, frame choice, palette, caption, module style, and whether the design is photo-woven — and the composed design is the finished artwork. The photo in a photo-woven project is deliberately not part of that state: a reopened photo-woven project shows its saved artwork and asks for the photo again before re-editing.

What does the "kept until" date mean, and what happens when it arrives?

It is the expiry timestamp written on the project's own database record when the project was saved, 30 days ahead. Once that instant passes, the row-level security policy that grants you access to your own projects — and that also requires them to be unexpired — no longer returns the row to any client, so the project is unreadable from that moment on, and a background purge later removes expired rows for good. Anything you downloaded is a plain file on your device and is unaffected by any of this.

Who can see a live keepsake, and what can the sender see?

The anonymous read path a recipient uses returns only three fields — occasion, message, and style — and the client parses that row as an allowlist, copying exactly those fields, so it structurally cannot surface the sender's account or private label. The sender's screen receives open and love-back events with a kind and a time and nothing else: when the keepsake was opened, never who opened it or from where.

Does the Studio count how many times a code is scanned?

No. Static codes cannot report scans, and the studio adds no tracking to your destinations — the link or message is placed in the symbol exactly as entered, with no shortener or redirect that could observe a scan. If you need scan counts, that is a different product; we would rather say so than quietly add a middleman to your keepsake.

What we store, and what we never see

Composition happens in your browser. Signed out, nothing you type or upload leaves your device. Signed in and saving, we store the project you chose to save — content, design settings, and the composed artwork — plus your email and sign-in credentials. Working photos never leave the browser. The privacy policy states the full list plainly.