Quick start in the browser
Load the encoder and the API, then ask for an SVG. The second call shows the print check every QRotto code gets.
<div id="code"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcode-generator/1.4.4/qrcode.min.js"></script>
<script src="https://qrotto.com/qrotto-qr.js"></script>
<script>
document.getElementById("code").innerHTML = QRotto.svg("https://example.com/menu", { size: 512 });
const check = QRotto.make("https://example.com/menu");
console.log(check.modules, check.minPrintMm + " mm"); // 25 20 mm
</script>
Node.js: a batch of files
The same file works on a server. This script writes an SVG and a PNG for each table, which suits print runs and label systems.
// npm install qrcode-generator and save qrotto-qr.js next to this file
const fs = require("fs");
const QRotto = require("./qrotto-qr.js");
const tables = ["T1", "T2", "T3"];
for (const t of tables) {
const link = "https://example.com/menu?table=" + t;
fs.writeFileSync(`table-${t}.svg`, QRotto.svg(link)); // sharp at any size
fs.writeFileSync(`table-${t}.png`, QRotto.png(link, { size: 1200 })); // no other dependency
console.log(t, QRotto.make(link).minPrintMm + " mm minimum");
}
Functions
| Function | What you get |
|---|---|
QRotto.make(text, options) | The code as data: modules (grid size), matrix (rows of true and false), minPrintMm, minPrintIn and check (ready, test-one-copy or dense). |
QRotto.svg(text, options) | An SVG string: one path, quiet zone included. Best for print and for files. |
QRotto.canvas(text, options) | Browser only. A canvas element you can show, print or save. |
QRotto.pngDataUrl(text, options) | Browser only. A PNG as a data URL, ready for an img or a download link. |
QRotto.png(text, options) | Node.js only. PNG file contents as a Buffer, with no other dependency. |
QRotto.payload.* | The text phones expect for WiFi, contact cards, email, SMS, calls, WhatsApp, places and events, built with the same rules as the tools on this site. A mistake that would print a broken code, like a WhatsApp number without its country code, stops with a clear error instead. |
Options
| Option | Meaning |
|---|---|
ec | Error correction: L, M, Q or H. Default M. Use H when a logo covers part of the code. |
margin | Quiet zone in modules. Default 4, the standard. Below 4 some phones struggle. |
size | Width in pixels for PNG and canvas, and the width and height attributes of the SVG. Default 1024. |
dark, light | Colours as #rrggbb. Defaults black on white. Light can be transparent; keep dark modules on a light background. |
title | SVG only. A title for screen readers, such as "Menu for table 4". |
Payloads: the right text for each kind of code
Most broken codes are not broken images but wrong text inside them: a WiFi password with an unescaped semicolon, a WhatsApp number with a leading zero. The payload helpers build the format phones expect.
QRotto.payload.wifi({ ssid: "Cafe;Guest", password: "p:w1", security: "WPA" })
// WIFI:T:WPA;S:Cafe\;Guest;P:p\:w1;;
QRotto.payload.vcard({ name: "Sara Khan", phone: "+92 300 1234567", email: "sara@example.com", org: "Corner Cafe" })
QRotto.payload.whatsapp({ number: "+92 300 1234567", message: "Hi, a table for two" })
QRotto.payload.whatsapp({ number: "0300 1234567" })
// Error: drop the 0 at the start and put the country code first
QRotto.payload.email({ to: "hello@example.com", subject: "Booking" })
QRotto.payload.sms({ number: "+1 555 0100", message: "STOP" })
QRotto.payload.phone("+1 555 0100")
QRotto.payload.geo({ lat: 31.588, lng: 74.31 })
QRotto.payload.event({ title: "Open day", start: "2026-12-05T10:00:00Z", location: "Main hall" })
The print size check
make() returns the smallest safe print width: 0.4 mm per module including the quiet zone, and never below 20 mm. A short link needs 20 mm; a full contact card with a job title, email and website (about 240 characters) needs about 28 mm. Print one copy at that size and scan it before a full run.
Empty text, or more than one code can hold (about 2,300 bytes at the default error correction), stops with an Error that says what to change, so a batch job never writes a blank or broken file.
Why it runs on your side
- Private. Customer data, WiFi passwords and internal links never leave your systems.
- No limits and no key. Make one code or a million; nothing to sign up for or rotate.
- Nothing to go down. The codes hold their content, so they keep working whatever happens to QRotto.
Is there a hosted REST API?
Not yet. A hosted endpoint would mean sending your data to a server and sharing limits with other users, which is exactly what this API avoids. If your team needs one, write to hello@qrotto.com and tell us how you would use it.
Licence and versions
Version 1.0.0, MIT licence: use it in commercial work, copy it, change it. You can load it from qrotto.com as above, or save qrotto-qr.js and serve it from your own site. Tested with qrcode-generator 1.4.4 in browsers and 2.0.4 on Node.js, decoding every code it makes. It builds on qrcode-generator by Kazuhiko Arase, also MIT. Using WordPress? The QRotto plugin does the same inside your site.