A QR menu looks simple from the outside: encode a URL, place it on a table, and let guests browse. The engineering work starts when that code must carry a restaurant’s visual identity, open reliably on different devices, support multiple languages, and remain easy to update without reprinting table cards.
This post outlines the implementation decisions behind the QR-menu experience in MenuForma, a TypeScript web application for restaurants, cafés, and food businesses.
Treat the QR code as a configuration, not an image
The first mistake is to generate a PNG once and regard it as the final product. A production QR code needs persistent, structured settings. In our case, that includes the dot style and color, two corner styles and colors, a background color, the selected logo source, and optional CTA text.
type QRDesign = {
dotType: string;
dotColor: string;
cornerSquareType: string;
cornerSquareColor: string;
cornerDotType: string;
cornerDotColor: string;
bgColor: string;
logoMode: "restaurant" | "custom" | "none";
logoUrl?: string;
ctaText?: string;
};
Persisting configuration rather than an exported image makes the QR code reproducible. It also means a restaurant can update branding, swap a logo, or regenerate a printable asset while keeping the destination URL unchanged.
Make logo behavior explicit
Logo state is surprisingly easy to model incorrectly. A nullable image URL cannot distinguish between “use the venue logo”, “use a custom uploaded logo”, and “the user explicitly chose no logo”. Those three states have different user intent.
type LogoMode = "restaurant" | "custom" | "none";
function effectiveLogoUrl(
mode: LogoMode,
customLogoUrl: string,
restaurantLogo?: string,
) {
if (mode === "none") return "";
if (mode === "custom") return customLogoUrl;
return restaurantLogo ?? "";
}
This small state machine avoids a common regression: a user removes a logo, saves the design, and sees the default venue logo reappear after a refresh. Backward-compatible migrations are equally important when an earlier version stored only a boolean such as logoEnabled.
Handle cross-origin assets before rendering
QR rendering libraries frequently draw logos onto a canvas. That creates a browser constraint: if an image lacks the necessary CORS headers, the canvas becomes tainted and cannot be exported.
The safest pattern is to proxy application-owned uploads through an image endpoint that returns the image bytes with correct CORS behavior. The renderer can then use crossOrigin: "anonymous" while exporting PNG or SVG assets. It is not glamorous work, but it is what separates a preview that looks correct from a downloadable QR code that works.
function proxyLogoUrl(url: string) {
if (url.startsWith("/manus-storage/")) {
return `/api/img/${url.replace("/manus-storage/", "")}`;
}
return url;
}
Localize the editor as well as the guest-facing menu
International restaurants need more than translated public pages. Admin tools, template labels, help text, validation messages, and export controls should also use translation keys. This keeps the QR designer usable for the team that configures it, not only for the guests who scan it.
const { t } = useTranslation();
<Button>{t("qrDesigner_download")}</Button>
<Label>{t("qrDesigner_logoMode")}</Label>
A practical rule is to keep visual template IDs stable while translating their display names and descriptions. For example, restaurant-elegant is a dependable data value; its visible label can vary by locale without complicating stored designs or analytics.
Prefer type-safe boundaries for menu updates
A QR menu has two sides: a React interface where owners arrange and update content, and a server that persists those changes. Type-safe API boundaries help when that interface evolves. A stack built with React, Vite, Express, tRPC, and Drizzle can share TypeScript types from form validation through database operations.
The payoff is not just fewer type errors. It makes schema changes explicit: adding an allergy field, a localized description, or an ordering flag becomes a change that flows through validation, API contracts, and UI feedback rather than a silent mismatch between layers.
The product rule behind the implementation
The useful question is not “can we generate a QR code?” It is “can a restaurant change its menu tomorrow without replacing every printed table card?” Stable destination URLs, persistent design settings, clear logo states, export-safe media handling, and localized admin controls all support that outcome.
If you are building a digital menu or ordering surface, focus on the operational loop: create, brand, translate, update, and distribute. The QR image is only one part of that loop.
Explore MenuForma to see how these patterns come together for restaurant and café menus.
Top comments (0)