DEV Community

monkeymore studio
monkeymore studio

Posted on

I Built a Square QR Code Generator That Runs Entirely in Your Browser

Ever wondered how those clean, professional QR codes with logos in the middle are made? I recently built a square QR code generator that does everything right in your browser—no server involved. Let me walk you through how it works.

Why Build This in the Browser?

Here's the thing: most QR code generators send your data to a server, which then generates the QR code and sends it back. That means every link you encode gets processed on someone else's machine. For a business generating promo codes or a developer adding company logos, that's not ideal.

By keeping everything client-side, your data never leaves your device. Every link, every piece of text you encode stays private. Plus, it's faster—you don't have to wait for server round-trips. Just load the page, type your content, and download.

For teams handling sensitive links—internal company documents, client information, or anything you'd rather not send to third parties—this approach makes a real difference.

How the Square QR Code Generator Works

Here's the complete flow from user input to downloadable PNG:

Step 1: Capturing User Input

The interface is straightforward. Users paste content into a text area—one entry per line for batch processing:

const [text, setText] = useState("");
const [standardContentType, setStandardContentType] = useState<"none" | "logo" | "centerText">("none");
Enter fullscreen mode Exit fullscreen mode

For batch generation, the code splits input by newline:

const lines = text.split("\n").filter((line) => line.trim());
Enter fullscreen mode Exit fullscreen mode

Step 2: Handling the Center Content

The generator supports three approaches: standard (nothing in center), logo image, or text overlay.

Logo Upload

const handleLogoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onloadend = () => setLogoImage(reader.result as string);
  reader.readAsDataURL(file);
};
Enter fullscreen mode Exit fullscreen mode

Simple enough—reads the uploaded image as a base64 data URL, which the QR rendering library can directly use.

Text Overlay

For text in the center, the code creates an image on-the-fly using canvas:

export function createTextImage(text: string, color: string, fontSize: number): string {
  const size = Math.max(fontSize * 3, 200);
  const canvas = document.createElement("canvas");
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext("2d")!;
  ctx.font = `bold ${fontSize}px sans-serif`;
  ctx.fillStyle = color;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(text, size / 2, size / 2);
  return canvas.toDataURL("image/png");
}
Enter fullscreen mode Exit fullscreen mode

This creates a square image with centered text, ready to embed into the QR code.

Step 3: QR Code Generation

The core generation uses the qr-code-styling library:

const qrCode = new QRCodeStyling({
  width: size,
  height: size,
  type: "canvas",
  shape: "square",
  data,
  image: imageUrl,
  dotsOptions: {
    color: dotColor,
    type: "square",
  },
  backgroundOptions: {
    color: bgColor,
  },
  imageOptions: {
    crossOrigin: "anonymous",
    margin: 10,
    hideBackgroundDots: true,
    imageSize: Math.max(0.01, Math.min(1, logoSize / 100)),
  },
});
Enter fullscreen mode Exit fullscreen mode

Key options explained:

  • shape: "square" - gives us the classic square module pattern
  • image - the optional logo/text overlay, centered automatically
  • dotsOptions.color - the foreground color (default black, customizable)
  • hideBackgroundDots: true - clears dots behind the logo for cleaner look
  • imageSize - controls logo size (percentage-based)

Step 4: Exporting the Result

const blob = await qrCode.getRawData("png");
const url = URL.createObjectURL(blob);
const img = await loadImage(url);
URL.revokeObjectURL(url);
return imageToCanvas(img).toDataURL("image/png");
Enter fullscreen mode Exit fullscreen mode

Important detail: the library returns a blob, but we convert it to a canvas then back to dataURL. Why the extra step? Because the raw blob might not play nice with all browsers when displaying in <img> tags. Converting through canvas ensures consistent rendering.

Step 5: Batch Download

For multiple QR codes at once:

const handleDownloadAll = async () => {
  const zip = new JSZip();
  qrCodes.forEach(({ url, name }) => {
    const base64Data = url.replace(/^data:image\/png;base64,/, "");
    zip.file(`${name}.png`, base64Data, { base64: true });
  });
  const content = await zip.generateAsync({ type: "blob" });
  saveAs(content, "qrcodes.zip");
};
Enter fullscreen mode Exit fullscreen mode

Uses JSZip to bundle all generated QR codes into a single ZIP file—clean and efficient for bulk workflows.

Color Customization

Users can customize three colors:

  • Dot color (foreground) - the actual QR code modules
  • Background color - the fill between modules
  • Center text color - when using text overlay mode

All through simple HTML color pickers:

<input
  type="color"
  value={dotColor}
  onChange={(e) => setDotColor(e.target.value)}
/>
Enter fullscreen mode Exit fullscreen mode

Output Size Control

The generator handles standard square output sizes:

const [qrWidth, setQrWidth] = useState(300);
// Clamped between 100-2000 pixels
const size = Math.max(100, Math.min(2000, qrWidth));
Enter fullscreen mode Exit fullscreen mode

300px is the default—good for digital use. For print, bump up to 1000-2000px.

Try It Yourself

The square QR code generator is live at QR code generator. Upload your logo, customize the colors, and generate in seconds.

Need batch processing? Just paste multiple URLs (one per line), and download them all at once.

All processing happens locally—your links never go anywhere near a server.

Top comments (0)