Feature spec
BeginnerCategory
scanning-devices
Build with AI
2–4 hours with FlutterFlow or Lovable
Custom build
2–3 days custom dev
Running cost
$0/mo (up to ~1K codes) · $25–50/mo at 10K users with Supabase Pro
Works on
Everything it takes to ship Dynamic QR Code Generation — parts, prompts, and real costs.
Dynamic QR code generation creates codes that point to a stable short URL you control — when you update the destination, the printed code still works. Three pieces: a client-side QR renderer (qrcode.react or qr_flutter — both free), a Supabase redirect table that maps slugs to destination URLs, and an Edge Function that handles the 302 redirect and logs scan analytics. Build time with Lovable or FlutterFlow is 2–4 hours at $0–25/month.
What Dynamic QR Code Generation Actually Is
A dynamic QR code always encodes the same short URL — something like yourdomain.com/r/abc123 — but the redirect target behind that URL can be changed at any time without reprinting the code. A restaurant changes its menu URL; the QR codes already on the tables still work. An event organiser updates the check-in page URL; the codes on printed tickets still resolve correctly. The QR image itself is generated entirely in the browser by a free library in milliseconds. The dynamic behaviour comes from the redirect service you build on top: a Supabase table mapping slugs to destination URLs, and an Edge Function that looks up the current destination and issues the 302. Scan analytics are a natural add-on because the Edge Function handles every scan.
What users consider table stakes in 2026
- QR code image renders instantly in the browser without a server round-trip — generation is entirely client-side
- User can change the destination URL after the code is created without reprinting — the physical code stays valid
- Download the QR code as PNG or SVG directly from the browser with one click
- Optional custom branding: choose foreground and background colours, and overlay a logo image in the centre
- Scan count displayed per code in the management dashboard, updated after each scan
- Expiry date option that redirects to a configurable fallback page with a clear message when the code is past its expiry
Anatomy of the Feature
Six components — three on the client and three server-side. The redirect layer and error correction configuration are where first builds most commonly go wrong.
QR code render engine
UIqrcode.react (React) or qr_flutter (Flutter) renders the QR image client-side from the stable redirect URL slug. Supports both SVG and PNG output. Logo overlay is composited on top of the canvas using the Canvas API's drawImage() after the QR is drawn. Error correction level must be set to H (30% fault tolerance) when a logo is used — the default level L fails at 7% occlusion.
Note: Never render a QR code pointing to a preview URL (e.g. lovable.app/preview/...) — those URLs expire when the project is republished. Always point to your own stable redirect slug.
Redirect and short URL layer
BackendEach QR code points to a stable path on your domain (e.g. yourdomain.com/r/abc123). A Supabase Edge Function reads the slug from the URL, looks up the current destination_url in the qr_codes table, checks the is_active flag and expires_at timestamp, and returns an HTTP 302 to the destination. Updating the destination is a simple UPDATE on one row — no reprint needed.
Note: The redirect function needs no CORS headers — it issues a 302 and the browser follows it. Only API calls from browser JavaScript need CORS headers. Keep the redirect Edge Function and data API separate.
QR code management table
DataSupabase qr_codes table with columns: id, owner_id, slug (unique), destination_url, label, scan_count, expires_at, is_active, created_at. RLS policy restricts read and write to the owner. A unique constraint on slug prevents duplicate redirect paths. scan_count is incremented by the Edge Function on each successful redirect.
Note: Use a short alphanumeric slug generator (nanoid or similar) for readable URLs. Avoid sequential integers — they are easy to enumerate and expose your QR inventory count.
Scan analytics tracker
BackendThe redirect Edge Function, after issuing the 302, inserts a row into qr_scan_events (qr_code_id, scanned_at, user_agent, ip_hash) and increments scan_count on the qr_codes row. ip_hash stores SHA-256 of the IP address for privacy-preserving uniqueness estimation rather than raw IP logging.
Note: Use the Edge Function's service role key for these writes — scan events are written by the server, not by the authenticated user scanning the code.
Download and export UI
UIA download button triggers canvas.toBlob() to produce a PNG file, or serialises the SVG element as a string for SVG export. qrcode.react's renderAs='svg' prop enables vector export suitable for print at any size. For PNG, canvas.toDataURL('image/png') creates a data URL used as the anchor href with a download attribute.
Note: Set canvas resolution to at least 512x512 pixels for print quality. Browser default canvas resolution is 72 DPI; use devicePixelRatio scaling for sharper output.
Branding customiser
UIReact controlled inputs for foreground colour, background colour, and an image upload for the logo. The logo file is uploaded to Supabase Storage and its public URL stored alongside the QR code record. The logo is composited onto the QR canvas using drawImage() centred in the quiet zone after QR rendering completes. Maximum logo size is 20% of the QR canvas area.
Note: Require error correction level H whenever a logo is used. At level L the logo occludes enough of the code to cause scan failures, especially on smaller prints.
The data model
Two tables: the QR code registry and the scan event log. Run this in the Supabase SQL editor — it creates both tables with RLS, the unique slug constraint, and the index for analytics queries.
1create table public.qr_codes (2 id uuid primary key default gen_random_uuid(),3 owner_id uuid references auth.users(id) on delete cascade not null,4 slug text unique not null,5 destination_url text not null,6 label text,7 scan_count int not null default 0,8 expires_at timestamptz,9 is_active bool not null default true,10 created_at timestamptz not null default now()11);1213alter table public.qr_codes enable row level security;1415create policy "Owners can manage their QR codes"16 on public.qr_codes for all17 using (auth.uid() = owner_id)18 with check (auth.uid() = owner_id);1920create index qr_codes_owner_idx21 on public.qr_codes (owner_id, created_at desc);2223create table public.qr_scan_events (24 id uuid primary key default gen_random_uuid(),25 qr_code_id uuid references public.qr_codes(id) on delete cascade not null,26 scanned_at timestamptz not null default now(),27 user_agent text,28 ip_hash text29);3031alter table public.qr_scan_events enable row level security;3233create policy "Owners can view scan events for their codes"34 on public.qr_scan_events for select35 using (36 qr_code_id in (37 select id from public.qr_codes where owner_id = auth.uid()38 )39 );4041create policy "Service role can insert scan events"42 on public.qr_scan_events for insert43 to service_role44 with check (true);4546create index qr_scan_events_code_time_idx47 on public.qr_scan_events (qr_code_id, scanned_at desc);Heads up: The qr_scan_events insert policy restricts writes to the service role — only your Edge Function (which runs with the service role key) can log scans. This prevents users from inflating their own scan counts by calling the table directly from the browser.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
FlutterFlow with the qr_flutter package and a Custom Action handles QR rendering and PNG download — best for mobile apps where QR generation and scanning coexist in the same app.
Step by step
- 1Add qr_flutter to FlutterFlow Settings → Pubspec Dependencies
- 2Create a Custom Action RenderQrCode that takes a redirect URL string and returns a widget; display it inside a Container on the QR creator page using a Custom Widget wrapper
- 3Add a Supabase Backend Action to INSERT a row into qr_codes with the slug (generated with nanoid), destination_url, owner_id, and label when the user taps Create
- 4Create a second Custom Action DownloadQrPng that calls RenderRepaintBoundary to capture the QR widget as a PNG and saves it to the device gallery using image_gallery_saver
- 5Add a list page bound to qr_codes filtered by owner_id showing label, scan_count, and a Copy Link button
Where this path bites
- qr_flutter rendering and the download action both require Custom Action Dart code — not a fully visual build
- Logo overlay compositing on Flutter requires additional Canvas painting logic in the Custom Action
- Flutter Web output for QR download is less polished than iOS/Android — PNG download uses a web-specific path
Third-party services you'll need
QR generation is entirely free client-side. Storage and redirect infrastructure are the only potential costs:
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| qrcode.react | Client-side QR code rendering for React — SVG and Canvas output, logo support, colour customisation | Open source, free | Free |
| qr_flutter | Flutter package for client-side QR rendering on iOS, Android, and web | Open source, free | Free |
| Supabase | qr_codes table, qr_scan_events table, RLS, and Edge Function for the redirect service | Free tier: 2 projects, 500MB DB, 500K Edge Function invocations/mo | Pro $25/mo (8GB DB) |
| Supabase Storage | Storing uploaded logo images for QR branding | Free tier: 1GB storage | $0.021/GB/mo (approx) beyond free tier |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Client-side QR rendering costs nothing. Edge Function redirect calls and scan_events inserts are well within Supabase free tier 500K invocations/mo. No logo storage cost below 1GB.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
QR code points to preview URL and breaks after republish
Symptom: AI tools sometimes generate QR codes containing the Lovable preview URL (e.g. abc.lovable.app/preview/...) or the V0 sandbox URL as the encoded value. When the project is republished, that URL changes or expires, and every printed QR code becomes invalid.
Fix: Always generate QR codes that encode your own stable redirect slug (yourdomain.com/r/abc123), not the AI tool's preview URL. Set the QR value to the redirect path immediately when the Supabase row is inserted, not from the browser's current URL.
Logo overlay breaks QR scanability
Symptom: Adding a logo to the centre of a QR code obscures modules that the decoder needs to reconstruct the data. The default error correction level in qrcode.react is level L, which only tolerates approximately 7% occlusion. A logo covering 15–20% of the code causes scan failures on stricter decoders, particularly on older iOS camera apps.
Fix: Set error correction to level H (30% fault tolerance) in qrcode.react using the level='H' prop whenever a logo is used. Keep the logo to a maximum of 20% of the QR canvas area. Test the resulting code with multiple devices before printing at scale.
Expired QR codes return a blank page instead of a helpful message
Symptom: AI tools often implement expiry logic in the redirect Edge Function by returning a 404 response when expires_at is past. QR scanner apps show an empty browser screen with no explanation — the user does not know whether they scanned correctly or the code is genuinely expired.
Fix: Return a proper HTML response page (not a 404) when a code is expired or inactive, with the owner's contact information or a renewal call-to-action. Pass a configurable expired_redirect_url field to the qr_codes table so owners can choose where expired codes redirect.
Bulk QR code generation hits Supabase insert rate limits
Symptom: Generating 100 or more QR codes at once by calling individual INSERT statements in a loop — common in AI-generated code — triggers Supabase free tier rate limiting almost immediately, causing 429 errors and partially created batches.
Fix: Use a single batch INSERT statement with multiple rows: INSERT INTO qr_codes (slug, destination_url, ...) VALUES (...), (...), (...). Supabase's PostgREST accepts batched inserts as a JSON array. For very large batches, add a 100ms pause between batches of 50 rows.
Redirect Edge Function missing analytics insert due to async fire-and-forget gap
Symptom: The Edge Function issues the 302 redirect response quickly, but the scan_events INSERT and scan_count increment run after the response is sent. If the function runtime terminates before the async operations complete, scan events are silently lost.
Fix: Use Supabase Edge Function's waitUntil() equivalent — ensure the analytics inserts are awaited before returning the Response. In Deno Deploy (which powers Supabase Edge Functions), use EdgeRuntime.waitUntil(promise) to extend the function lifetime past the response.
Best practices
Always use level H error correction when adding a logo to a QR code — level L (the default) breaks at 7% occlusion, level H tolerates 30%
Generate the QR short URL slug using a random ID (nanoid) rather than sequential integers — sequential IDs expose your total code count and are easy to enumerate
Store the destination URL in Supabase and point the QR image to your redirect slug from day one, even before you need dynamic updating — retrofitting a static QR to a redirect system means reprinting everything
Render QR images at minimum 512x512 pixels for digital use and 1024x1024 for print — low-resolution QR codes fail on cameras at distance
Provide an expiry landing page that explains why the code is expired and gives the owner's contact info — blank 404 pages cause user confusion and support requests
Cache the QR render result as an image URL stored in Supabase Storage for codes that are shared or embedded in emails — regenerating the image on every page load is unnecessary work
Test every generated QR code with at least two different camera apps before distributing — iOS Camera, Google Lens, and dedicated QR apps decode with slightly different tolerances
When You Need Custom Development
Lovable and FlutterFlow handle straightforward QR management dashboards well. These requirements typically exceed what AI-tool builds deliver cleanly:
- White-label redirect domains — you need scanners to see your brand's domain (go.yourbrand.com/r/abc123) rather than a shared Supabase domain in the redirect URL
- Bulk import from CSV with thousands of codes, each with a unique destination URL — batch processing at this scale needs server-side generation, not a browser form
- Printable PDF generation with QR codes embedded in branded templates — @react-pdf/renderer with server-side QR generation and precise layout control
- Advanced analytics with geolocation, device type breakdown, and campaign attribution beyond scan count per code
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
What makes a QR code 'dynamic' versus static?
A static QR code encodes the destination URL directly — if the URL changes, every printed code is invalid. A dynamic QR code encodes a stable short URL that redirects to the current destination. The redirect target lives in a database you control. Changing the destination is a database update; the physical code never needs reprinting.
Can I change where a QR code redirects after it has been printed?
Yes — that is the entire point of a dynamic QR. Update the destination_url column on the qr_codes row in Supabase. The next scan of the same physical code immediately follows the new destination. No reprint required.
How do I add my logo to a QR code without breaking it?
Set the QR error correction level to H (30% fault tolerance) using the level='H' prop in qrcode.react before adding any logo. Keep the logo to a maximum of 20% of the total QR canvas area. At level L (the default), a logo covering 15% of the code will cause scan failures on many devices.
How do I track how many times a QR code has been scanned?
Route every scan through your redirect Edge Function. When the function receives a request for /r/abc123, it looks up the destination, issues the 302, increments scan_count on the qr_codes row, and inserts a row into qr_scan_events with the timestamp and a hashed IP. Display scan_count on the management dashboard.
Can I set an expiry date on a QR code?
Yes. Add an expires_at timestamptz column to qr_codes. In the redirect Edge Function, check whether the current timestamp is past expires_at before issuing the 302. If expired, return an HTML expiry page explaining the code is no longer active. Store a configurable redirect URL for expired codes so owners can send users somewhere useful.
How do I export a QR code as a high-resolution PNG or SVG?
For SVG: use qrcode.react with renderAs='svg', then serialise the SVG element to a string and trigger a download. For PNG: draw the QR onto an HTML canvas at 1024x1024 (or higher for print), composite the logo if needed, then call canvas.toBlob('image/png') and attach the result to an anchor tag with the download attribute.
Can I build dynamic QR code generation in FlutterFlow without a separate backend?
The QR rendering itself (using qr_flutter) works entirely on-device. But the 'dynamic' part — updating the destination after printing — requires a backend redirect service. Without a server-side redirect, changing the destination means reprinting the code. You need at minimum a Supabase table and Edge Function for the redirect, which FlutterFlow can connect to via Backend Actions.
What is the best error correction level for branded QR codes?
Level H (30% data restoration capacity) when adding any logo or design overlay. Level M (15%) for clean codes without a logo in everyday use. Level L (7%) only for digital-only codes displayed on screens where printing quality and physical damage are not concerns. Most AI tools default to level L — change it explicitly in your prompt or component configuration.
Need this feature production-ready?
RapidDev builds dynamic qr code generation into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.