Skill: QR Code Generation

## Overview

7 stars

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

$curl -o ~/.claude/skills/qr-code/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/web-applications/link-shortener/skills/qr-code/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/qr-code/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How Skill: QR Code Generation Compares

Feature / AgentSkill: QR Code GenerationStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/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

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

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

7
from heldernoid/agentic-build-templates

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

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Pastebin Core

7
from heldernoid/agentic-build-templates

## Purpose