Best use case
Skill: QR Code Generation is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## Overview
Teams using Skill: QR Code Generation should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/qr-code/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How Skill: QR Code Generation Compares
| Feature / Agent | Skill: QR Code Generation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## Overview
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: QR Code Generation
## Overview
Pattern for generating QR codes from short URLs server-side using the `qrcode` npm package, returning PNG buffers or SVG strings via an Express endpoint, and triggering browser downloads from the React frontend.
## Server-Side Generation
```ts
// api/src/routes/qr.ts
import { Router } from 'express';
import QRCode from 'qrcode';
import type { Database } from 'better-sqlite3';
import { requireAuth } from '../middleware/requireAuth';
export function makeQrRouter(db: Database): Router {
const router = Router();
router.get('/:id/qr', async (req, res) => {
const linkId = Number(req.params.id);
const format = (req.query.format as string) === 'svg' ? 'svg' : 'png';
const link = db
.prepare('SELECT code FROM links WHERE id = ? AND user_id = ?')
.get(linkId, req.session.userId) as { code: string } | undefined;
if (!link) {
res.status(404).json({ error: 'Not found' });
return;
}
const shortUrl = `${process.env.BASE_SHORT_URL}/${link.code}`;
const QR_OPTIONS: QRCode.QRCodeToBufferOptions = {
errorCorrectionLevel: 'M',
margin: 2,
width: 512,
color: { dark: '#1c1917', light: '#ffffff' },
};
res.setHeader('Cache-Control', 'public, max-age=86400');
if (format === 'svg') {
res.setHeader('Content-Type', 'image/svg+xml');
const svg = await QRCode.toString(shortUrl, { type: 'svg', ...QR_OPTIONS });
res.send(svg);
} else {
res.setHeader('Content-Type', 'image/png');
const buffer = await QRCode.toBuffer(shortUrl, QR_OPTIONS);
res.send(buffer);
}
});
return router;
}
```
## QR Code Options Reference
| Option | Value | Notes |
|---|---|---|
| `errorCorrectionLevel` | `'M'` | Medium (15% restoration capacity). Use `'H'` if adding a logo overlay. |
| `margin` | 2 | Quiet zone in modules (not pixels). Minimum 4 recommended by spec, but 2 looks good in UI. |
| `width` | 512 | Output pixel width for PNG. |
| `color.dark` | `#1c1917` | Warm near-black matches the design system. |
| `color.light` | `#ffffff` | White background. |
## Frontend Download Trigger
```ts
// web/src/hooks/useQrDownload.ts
export function useQrDownload() {
const downloadQr = async (linkId: number, format: 'png' | 'svg') => {
const res = await fetch(`/api/links/${linkId}/qr?format=${format}`, {
credentials: 'include',
});
if (!res.ok) throw new Error('QR fetch failed');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), {
href: url,
download: `qr-${linkId}.${format}`,
});
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return { downloadQr };
}
```
## QrModal Component
```tsx
// web/src/components/QrModal.tsx
import { useState, useEffect } from 'react';
import { useQrDownload } from '../hooks/useQrDownload';
interface Props {
linkId: number;
code: string;
baseShortUrl: string;
onClose: () => void;
}
export function QrModal({ linkId, code, baseShortUrl, onClose }: Props) {
const [imgSrc, setImgSrc] = useState<string | null>(null);
const { downloadQr } = useQrDownload();
useEffect(() => {
fetch(`/api/links/${linkId}/qr?format=png`, { credentials: 'include' })
.then(r => r.blob())
.then(blob => setImgSrc(URL.createObjectURL(blob)));
return () => { if (imgSrc) URL.revokeObjectURL(imgSrc); };
}, [linkId]);
return (
<div role="dialog" aria-labelledby="qr-title" style={{ /* overlay */ }}>
<div style={{ /* modal card */ }}>
<div id="qr-title">QR Code</div>
<button onClick={onClose} aria-label="Close">x</button>
{imgSrc ? (
<img src={imgSrc} alt={`QR code for ${baseShortUrl}/${code}`} width={220} height={220} />
) : (
<div style={{ width: 220, height: 220, background: 'var(--bg-subtle)' }} />
)}
<p style={{ fontFamily: 'var(--font-mono)', fontSize: 13 }}>{baseShortUrl}/{code}</p>
<button onClick={() => downloadQr(linkId, 'png')}>Download PNG</button>
<button onClick={() => downloadQr(linkId, 'svg')}>Download SVG</button>
</div>
</div>
);
}
```
## QR Code Caching Strategy
The QR image for a given link never changes (the short URL is immutable). Set `Cache-Control: public, max-age=86400` on the response. The browser will cache the PNG/SVG for 24 hours.
If a link is deleted and the ID is reused (unlikely with AUTOINCREMENT but possible after a DELETE), the new link gets a new code so the cached QR for the old ID is harmless - it points to a deleted short URL which returns 404.
## Error Handling
The `qrcode` package can throw if the input URL is too long to encode at the requested error correction level. The maximum data capacity at level M is approximately 2,332 characters. Long URLs exceeding this should be handled:
```ts
try {
const buffer = await QRCode.toBuffer(shortUrl, QR_OPTIONS);
res.send(buffer);
} catch (err: unknown) {
const e = err as Error;
if (e.message?.includes('too large')) {
res.status(422).json({ error: 'URL too long to encode as QR code' });
} else {
res.status(500).json({ error: 'QR generation failed' });
}
}
```
## Testing the QR Endpoint
```ts
// api/src/routes/__tests__/qr.test.ts
import { describe, test, expect } from 'vitest';
test('GET /api/links/:id/qr returns PNG buffer', async () => {
const res = await app.inject({ method: 'GET', url: '/api/links/1/qr', cookies: session });
expect(res.headers['content-type']).toBe('image/png');
expect(res.rawPayload.length).toBeGreaterThan(100);
});
test('GET /api/links/:id/qr?format=svg returns SVG string', async () => {
const res = await app.inject({ method: 'GET', url: '/api/links/1/qr?format=svg', cookies: session });
expect(res.headers['content-type']).toBe('image/svg+xml');
expect(res.body).toContain('<svg');
});
test('GET /api/links/:id/qr returns 404 for unknown link', async () => {
const res = await app.inject({ method: 'GET', url: '/api/links/99999/qr', cookies: session });
expect(res.statusCode).toBe(404);
});
```Related Skills
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose
Skill: Pastebin Core
## Purpose