Skip to content

Add a center image

A center image can brand a QR code, but it intentionally covers encoded modules. Prepare the source before rendering, keep the center image modest, and test the final artifact with real scanners.

  • Start with a QR code that scans without a center image.
  • Choose the renderer and prepare the source type it accepts.
  • Use high error correction for center images containing logos: .errorCorrection('H') or errorCorrectionLevel: 'H'.

Every visual renderer uses the same center-image geometry:

centerImage: {
source: preparedSource,
size: 0.3,
padding: 1,
clearBackground: true,
}
Option Default Accepted value Meaning
source none renderer-specific prepared source Image content; required when centerImage is set
size 0.4 finite number greater than 0 and at most 1 Image box relative to matrix width, excluding the quiet zone
padding 1 non-negative finite number Cleared space around the image, in modules
clearBackground true boolean Replaces covered modules and padding with the light color

The source is centered and contained without cropping. size: 1 is valid value, not a promise that the result will scan. Start around 0.2–0.3, preserve padding, and increase only after testing.

Output Source type Preparation
Core SVG QRCodeDataImageURL Convert bytes or a Blob to an embedded data:image/... URL
Browser Canvas or PNG-backed Image loaded CanvasImageSource Decode an Image element, canvas, video frame, or ImageBitmap first
Node.js PNG PNG Buffer Read or download valid PNG bytes before rendering
Framework Adapter SVG’s QRCodeDataImageURL in options.centerImage.source Prepare outside render; memoize options when useful
Framework Adapter Image or Canvas loaded CanvasImageSource in options.centerImage.source Store the decoded source in state before mounting output

QRCodeSDK never reads an image path or fetches a URL on your behalf.

In Node.js, convert already-read bytes to an embedded image URL:

import {readFile} from 'node:fs/promises';
import {type QRCodeDataImageURL, QRCodeSVGRenderer, qrcode} from '@qrcodesdk/core';
const bytes = await readFile('./logo.png');
const source = `data:image/png;base64,${bytes.toString('base64')}` as QRCodeDataImageURL;
const svg = qrcode('https://qrcodesdk.dev')
.errorCorrection('H')
.render(QRCodeSVGRenderer({centerImage: {source, size: 0.3}}));

In a browser, FileReader.readAsDataURL() prepares the same source type from a Blob or uploaded File.

Decode the image before calling the synchronous Canvas or Image renderer:

import {QRCodeCanvasRenderer} from '@qrcodesdk/browser';
import {qrcode} from '@qrcodesdk/core';
const source = new Image();
source.src = '/logo.png';
await source.decode();
const canvas = qrcode('https://qrcodesdk.dev')
.errorCorrection('H')
.render(QRCodeCanvasRenderer({centerImage: {source, size: 0.3}}));

Unloaded or zero-sized sources throw synchronously. When you render an Image element, export a Canvas, or create a PNG data URL, the browser must also keep the Canvas origin-clean. Use a same-origin image or a cross-origin image served with CORS permission and loaded with the matching crossOrigin setting.

import {readFile} from 'node:fs/promises';
import {qrcode} from '@qrcodesdk/core';
import {QRCodePNGRenderer} from '@qrcodesdk/node';
const source = await readFile('./logo.png');
const png = qrcode('https://qrcodesdk.dev')
.errorCorrection('H')
.render(QRCodePNGRenderer({centerImage: {source, size: 0.3}}));

The Node renderer accepts PNG bytes only. It decodes and alpha-composites the source in memory.

React, Vue, Svelte, and Angular do not load image sources inside their QR components. Prepare browser sources in application state, then pass stable options after decoding.

import {useState} from 'react';
import {QRCodeImage} from '@qrcodesdk/react';
export function QRCodeWithLogo() {
const [source, setSource] = useState<HTMLImageElement>();
async function loadLogo(url: string) {
const image = new Image();
image.src = url;
await image.decode();
setSource(image);
}
return source ? (
<QRCodeImage
payload="https://qrcodesdk.dev"
options={{matrix: {errorCorrectionLevel: 'H'}, centerImage: {source, size: 0.3}}}
/>
) : (
<button type="button" onClick={() => void loadLogo('/logo.png')}>
Load logo
</button>
);
}
<script setup lang="ts">
import {computed, shallowRef} from 'vue';
import type {QRCodeImageOptions} from '@qrcodesdk/browser';
import {QRCodeImage} from '@qrcodesdk/vue';
const source = shallowRef<HTMLImageElement>();
const options = computed<QRCodeImageOptions | undefined>(() =>
source.value
? {matrix: {errorCorrectionLevel: 'H'}, centerImage: {source: source.value, size: 0.3}}
: undefined,
);
async function loadLogo(url: string) {
const image = new Image();
image.src = url;
await image.decode();
source.value = image;
}
</script>
<template>
<QRCodeImage v-if="options" payload="https://qrcodesdk.dev" :options="options" />
<button v-else type="button" @click="loadLogo('/logo.png')">Load logo</button>
</template>
<script lang="ts">
import type {QRCodeImageOptions} from '@qrcodesdk/browser';
import {QRCodeImage} from '@qrcodesdk/svelte';
let source = $state<HTMLImageElement>();
const options: QRCodeImageOptions | undefined = $derived(
source ? {matrix: {errorCorrectionLevel: 'H'}, centerImage: {source, size: 0.3}} : undefined,
);
async function loadLogo(url: string) {
const image = new Image();
image.src = url;
await image.decode();
source = image;
}
</script>
{#if options}
<QRCodeImage payload="https://qrcodesdk.dev" {options} />
{:else}
<button type="button" onclick={() => loadLogo('/logo.png')}>Load logo</button>
{/if}
import {Component, signal} from '@angular/core';
import {QRCodeImage} from '@qrcodesdk/angular';
@Component({
selector: 'app-qrcode-with-logo',
imports: [QRCodeImage],
template: `
@if (source(); as image) {
<qrcode-image
[options]="{matrix: {errorCorrectionLevel: 'H'}, centerImage: {source: image, size: 0.3}}"
payload="https://qrcodesdk.dev" />
}
`,
})
export class QRCodeWithLogo {
readonly source = signal<HTMLImageElement | undefined>(undefined);
async loadLogo(url: string) {
const image = new Image();
image.src = url;
await image.decode();
this.source.set(image);
}
}

Test the rendered or downloaded artifact—not only the preview—at the smallest expected size and after taking screenshots, optimizing images, printing, or compressing. Test several scanner apps and representative devices. If scanning is unreliable, reduce the image, restore padding and the four-module quiet zone, increase module size, simplify curved styling, or shorten the payload.

For exact source constraints, see the SVG, Canvas, PNG-backed Image, or PNG Buffer reference.