Parsers

Turn scanned text into structured data with parseContent (URLs, Wi-Fi, contacts, events, payments, product codes, GS1, driver licenses), parseGS1 and parseAAMVA.

Edit on GitHub

A barcode’s data is just text. QRGen’s parsers tell you what it means: a URL, Wi-Fi credentials, a contact card, a product GTIN, a GS1 element string with expiry date and batch, or a driver license. They are pure functions: no engine, no network, no DOM. They never throw; when the input doesn’t match, they return { type: "text" } or null.

import { parseContent, parseGS1, parseAAMVA } from "qrgen-sdk";

If you only need the parsers (for example on a server that receives scan results), import them from qrgen-sdk/parsers, which doesn’t include the scanning engine. The same parsers, with the same output shapes, exist in every QRGen SDK.

parseContent

parseContent(data, options?) detects the kind of content and returns an object with a type field:

parseContent("WIFI:T:WPA;S:Office;P:correct horse;;");
// { type: "wifi", ssid: "Office", password: "correct horse", security: "WPA", hidden: false }
type Fields Recognized input
url url http:// and https:// URLs; www. hosts get https:// prepended
gs1-digital-link url, gs1 URLs with a GS1 primary key path such as /01/<GTIN>
email to, subject?, body? mailto:, MATMSG:, bare addresses
phone number tel:, or a bare number starting with +
sms number, body? SMSTO:, sms:, mmsto:
wifi ssid, password?, security, hidden WIFI: (with backslash escapes)
geo latitude, longitude, altitude?, query? geo: URIs
contact format, name?, organization?, title?, phones, emails, urls, address?, note? vCard (BEGIN:VCARD) and MECARD:
event summary?, start?, end?, location?, description? iCalendar BEGIN:VEVENT or BEGIN:VCALENDAR
payment scheme, address?, name?, iban?, bic?, amount?, currency?, reference? EPC/SEPA (BCD), bitcoin:, ethereum:, other coin URIs, upi://pay
product gtin, kind, checksumValid 8, 12, 13 and 14 digit product codes
gs1 gs1 GS1 element strings (HRI or raw)
aamva aamva North American driver license and ID card data
text text Anything else

Options

Option Description
symbology The symbology the data came from. Pass barcode.symbology: it lets the parser read 8 digits from a UPC-E as UPC-E, and stops it from treating digits inside a QR code as a product number.
now Reference Date for the age and expiry fields of aamva results.
scanner.addEventListener("scan", ({ detail: { barcode } }) => {
  const content = parseContent(barcode.data, { symbology: barcode.symbology });
});

In TypeScript, ParsedContent is a discriminated union, so checking type narrows the fields:

import { parseContent, type ParsedContent } from "qrgen-sdk";

function describe(content: ParsedContent): string {
  switch (content.type) {
    case "url":
      return `Link to ${new URL(content.url).hostname}`;
    case "wifi":
      return `Wi-Fi network ${content.ssid}`;
    case "product":
      return `Product ${content.gtin}${content.checksumValid ? "" : " (bad check digit)"}`;
    default:
      return content.type;
  }
}

Examples

URLs and GS1 Digital Link

parseContent("https://example.com");
// { type: "url", url: "https://example.com" }

parseContent("https://id.gs1.org/01/09501101530003/10/ABC123?17=250101");
// { type: "gs1-digital-link", url: "https://id.gs1.org/...", gs1: { values: { "01": "09501101530003", "10": "ABC123", "17": "250101" }, elements: [...] } }

Email, phone and SMS

parseContent("mailto:hi@example.com?subject=Hello");
// { type: "email", to: "hi@example.com", subject: "Hello" }

parseContent("tel:+15551234567");
// { type: "phone", number: "+15551234567" }

parseContent("SMSTO:+15551234567:On my way");
// { type: "sms", number: "+15551234567", body: "On my way" }

Wi-Fi

parseContent("WIFI:T:WPA;S:Office;P:correct horse;;");
// { type: "wifi", ssid: "Office", password: "correct horse", security: "WPA", hidden: false }

parseContent("WIFI:S:Guest;;");
// { type: "wifi", ssid: "Guest", security: "nopass", hidden: false }

Location

parseContent("geo:40.7128,-74.0060?q=New%20York");
// { type: "geo", latitude: 40.7128, longitude: -74.006, query: "New York" }

Contacts

parseContent("BEGIN:VCARD\nVERSION:3.0\nFN:Jane Doe\nORG:Example Inc.\nTEL:+15550100\nEMAIL:jane@example.com\nEND:VCARD");
// { type: "contact", format: "vcard", name: "Jane Doe", organization: "Example Inc.", phones: ["+15550100"], emails: ["jane@example.com"], urls: [] }

parseContent("MECARD:N:Doe,Jane;TEL:5550100;EMAIL:jane@example.com;;");
// { type: "contact", format: "mecard", name: "Jane Doe", phones: ["5550100"], emails: ["jane@example.com"], urls: [] }

Calendar events

parseContent("BEGIN:VEVENT\nSUMMARY:Launch\nDTSTART:20261001T170000Z\nDTEND:20261001T180000Z\nLOCATION:Online\nEND:VEVENT");
// { type: "event", summary: "Launch", start: "2026-10-01T17:00:00Z", end: "2026-10-01T18:00:00Z", location: "Online" }

Dates are converted to ISO 8601; all-day dates become YYYY-MM-DD.

Payments

parseContent("BCD\n002\n1\nSCT\nBPOTBEB1\nRed Cross\nBE72000000001616\nEUR12.30\n\n\nDonation");
// { type: "payment", scheme: "epc", bic: "BPOTBEB1", name: "Red Cross", iban: "BE72000000001616", currency: "EUR", amount: 12.3, reference: "Donation" }

parseContent("bitcoin:1BoatSLRHtKNngkdXEeobR76b53LETtpyT?amount=0.01&label=Tip");
// { type: "payment", scheme: "bitcoin", address: "1BoatSLRHtKNngkdXEeobR76b53LETtpyT", amount: 0.01, name: "Tip", currency: "BTC" }

parseContent("upi://pay?pa=shop@upi&pn=Shop&am=10");
// { type: "payment", scheme: "upi", address: "shop@upi", name: "Shop", amount: 10, currency: "INR" }

For ethereum: URIs, amount is the raw value parameter (in wei). Other coin schemes (litecoin:, dogecoin:, bitcoincash:) return scheme: "other".

Product codes

Numeric data of 8, 12, 13 or 14 digits is returned as a product with its GTIN padded to 14 digits and a check digit test:

parseContent("5901234123457");
// { type: "product", kind: "ean13", gtin: "05901234123457", checksumValid: true }

parseContent("9780306406157");
// { type: "product", kind: "isbn", gtin: "09780306406157", checksumValid: true }

parseContent("036000291452", { symbology: "upca" });
// { type: "product", kind: "upca", gtin: "00036000291452", checksumValid: true }

parseContent("01234565", { symbology: "upce" });
// { type: "product", kind: "upce", gtin: "00012345000065", checksumValid: true }

kind is ean13, ean8, upca, upce, isbn (13 digits starting with 978 or 979) or gtin14. A UPC-E is expanded to its UPC-A form before padding, so the GTIN matches what your product database stores. Because every UPC-A is also a valid EAN-13 with a leading zero, both forms produce the same gtin: use gtin as your lookup key.

GS1 and driver licenses

parseContent("(01)09501101530003(17)250101(10)ABC123");
// { type: "gs1", gs1: { values: { "01": "09501101530003", "17": "250101", "10": "ABC123" }, elements: [...] } }

AAMVA data returns { type: "aamva", aamva } with the same object as parseAAMVA() below.

parseGS1

parseGS1(input) parses GS1 element strings in three forms and returns null for anything else:

import { parseGS1 } from "qrgen-sdk";

// 1. Human-readable (HRI) form, as QRGen scanners return GS1 data
parseGS1("(01)09501101530003(17)250101(10)ABC123");

// 2. Raw form with GS (ASCII 29) separators and an optional symbology prefix (]C1, ]d2, ]Q3, ]e0)
parseGS1("]C101095011015300031725010110ABC123\u001d21SN-42");

// 3. GS1 Digital Link URLs
parseGS1("https://id.gs1.org/01/09501101530003/10/ABC123?17=250101");

The result:

{
  "elements": [
    { "ai": "01", "title": "GTIN", "description": "Global Trade Item Number", "value": "09501101530003", "checkDigitValid": true },
    { "ai": "17", "title": "USE BY or EXPIRY", "description": "Expiration date", "value": "250101", "date": "2025-01-01" },
    { "ai": "10", "title": "BATCH/LOT", "description": "Batch or lot number", "value": "ABC123" }
  ],
  "values": { "01": "09501101530003", "17": "250101", "10": "ABC123" }
}
Field Description
elements Every element in order: ai, title (the GS1 data title), description, value, plus the fields below when they apply.
values AI to value map for quick lookups. The first occurrence wins.
digitalLink The original URL, for Digital Link input.

Per-element extras:

Field When Example
date Date AIs 11, 12, 13, 15, 16, 17 (and 7250) "250101" becomes "2025-01-01". Day 00 means the last day of the month: "261200" becomes "2026-12-31". Two-digit years use the GS1 sliding century rule.
number Decimal AIs such as 310n to 369n (measures) and 390n to 395n (amounts) (3103)001250 is 1.250 kg, number: 1.25. The last AI digit is the number of decimals.
iso AIs that start with an ISO currency or country code (391n, 393n, 421, …) (3913)978550 is iso: "978" (EUR), number: 0.55
checkDigitValid AIs with a mod-10 check digit (GTIN, SSCC, GLN, …) true or false
const gs1 = parseGS1("(01)09501101530003(3103)001250(3922)1999(15)261200");
const weight = gs1.elements.find((e) => e.ai === "3103").number; // 1.25
const price = gs1.elements.find((e) => e.ai === "3922").number;  // 19.99
const bestBefore = gs1.elements.find((e) => e.ai === "15").date; // "2026-12-31"

The AI table covers the identifiers used on products, logistics labels and healthcare packs. In HRI input, a parenthesized number that isn’t a known AI is kept as part of the previous element’s value. GS1_AIS exposes the table (GS1_AIS["17"] is { title: "USE BY or EXPIRY", fixed: 6, numeric: true, date: true, ... }).

Raw numeric strings without a separator are ambiguous, so parseGS1 only accepts them when they start with an AI of predefined length (such as 01 or 00) or contain a separator. Plain numbers like 12345 return null.

GS1 helpers

import { computeCheckDigit, formatGS1, gs1Date, isValidCheckDigit, parseDigitalLink } from "qrgen-sdk";

isValidCheckDigit("09501101530003"); // true (works for GTIN-8/12/13/14, SSCC, GLN)
computeCheckDigit("0950110153000");  // 3
gs1Date("261200");                   // "2026-12-31"
formatGS1(parseGS1("https://id.gs1.org/01/9501101530003/10/ABC123")); // "(01)09501101530003(10)ABC123"
parseDigitalLink("https://example.com/01/09501101530003"); // Digital Link only; null for other input

In Digital Link URLs, GTINs shorter than 14 digits are zero padded, and both numeric AIs (/01/) and short names (/gtin/, /lot/, /ser/, ?exp=) are understood.

parseAAMVA

parseAAMVA(data, options?) parses the PDF417 on the back of US and Canadian driver licenses and ID cards:

import { parseAAMVA } from "qrgen-sdk";

const id = parseAAMVA(licenseData);
if (id) console.log(id.fullName, id.dateOfBirth, id.expiryDate, id.isUnder21);

It returns null for other input. The full field list, age verification and privacy guidance are in ID scanning. parseAamvaDate(value, canadian) converts a single AAMVA date, and AAMVA_FIELDS maps element codes such as DAQ to labels.

Esc

Share this page

Parsers · Docs | QRGen

Star QRGen on GitHub Add a "Powered by QRGen" badge