QR Generator — Complete Reference
Dashboard Location: Sidebar → My Venues → QR Generator
URL Slug:/wifi-qr-generator
Navigation Sort: 30 (4th item in "My Venues" group)
Navigation Group: My Venues
Navigation Icon:heroicon-o-qr-code(in sidebar),heroicon-o-squares-2x2(page definition)
Access: All customers + all admins (universal feature, no plan restriction)
Overview
The QR Generator page creates scannable WiFi QR codes that guests can use to connect to a venue's WiFi network instantly. When a guest scans the QR code with their phone camera, it automatically connects them to the WiFi network without manually entering the SSID or password.
This is a universal feature — available to all customers regardless of their subscription plan or server type. There are no plan gating restrictions.
Access Control
public static function canAccess(): bool
{
$user = auth()->user();
if (!$user) return false;
if ($user->isAdmin()) return true;
return $user->role === 'customer';
}- Admins: Always have access; can see all sites across all users
- Customers: Always have access; see only their assigned sites
- Navigation item is shown/hidden based on
canAccess()viashouldRegisterNavigation()
Page Layout
The page consists of three main sections:
1. Site Selector Panel
A white card at the top with:
- Title: "WiFi QR Code Generator"
- Subtitle: "Generate a scannable QR code for your guest WiFi network"
- Site Dropdown: Shown only when user has multiple sites
Site Selection Behaviour:
| Scenario | Behaviour |
|---|---|
| No sites | Shows empty state with computer icon: "No sites available — You don't have any sites with UniFi controllers configured." |
| Single site | Auto-selects the only site; no dropdown shown |
| Multiple sites | Shows "Select Location" dropdown; first site auto-selected on page load |
Dropdown Field:
| Property | Value |
|---|---|
| Label | "Select Location" |
| Wire Model | wire:model.live="selectedSiteId" |
| Options | All user's sites, ordered by name |
| Display | Site name |
| Value | Site ID |
When the selection changes, updatedSelectedSiteId() fires and calls loadSiteData().
2. QR Code Display (when SSID exists)
Shown when a site is selected AND the site has a wifi_network_name (SSID) configured.
Action Buttons Bar
A top border-separated row (hidden during print) with two buttons:
| Button | Colour | Icon | Action |
|---|---|---|---|
| Download PDF | Green (bg-green-600) | Document download icon | wire:click="downloadPdf" — Generates and downloads a PDF file |
Grey (bg-gray-600) | Printer icon | onclick="window.print()" — Opens browser print dialog |
QR Code Content Area
Centered layout within a max-width container (max-w-2xl):
Title Section:
- "Free WiFi Access" —
text-lg font-semibold - "Scan to Connect Instantly" —
text-sm text-gray-500
- "Free WiFi Access" —
QR Code Image — Rendered as inline SVG within a white bordered container:
- Container:
bg-white p-6 rounded-xl shadow-lg border-2 border-gray-200 - QR code display area:
400×400px - QR code is generated server-side as SVG and rendered with
{!! $qrCodeSvg !!}
- Container:
SSID Display Box:
- Grey background pill:
bg-gray-100 px-6 py-4 rounded-lg - Label: "Network Name" —
text-xs uppercase tracking-wider - Value: The SSID in monospace bold —
text-xl font-bold text-primary-600 font-mono
- Grey background pill:
Footer:
- "Powered by" —
text-xs text-gray-400 - "CaptiFi.io" —
text-sm font-bold text-primary-600
- "Powered by" —
3. No SSID Warning (when SSID missing)
Shown when a site is selected but has no wifi_network_name. Yellow warning box with:
- Title: "No SSID Configured"
- Message: "This site doesn't have a WiFi network name (SSID) configured. Try syncing from UniFi or configure it in the site settings."
QR Code Generation
WiFi String Format
The QR code encodes a WiFi connection string in the standard format:
WIFI:T:nopass;S:{ESCAPED_SSID};;| Parameter | Value | Description |
|---|---|---|
T | nopass | Network type — open network (no password required). CaptiFi uses captive portal authentication, so the WiFi itself is open. |
S | {SSID} | The network SSID name, with special characters escaped |
Special Character Escaping:
The following characters are escaped in the SSID before encoding:
| Character | Escaped As |
|---|---|
\ | \\ |
; | \; |
, | \, |
" | \" |
: | \: |
QR Code Parameters
| Parameter | Value | Description |
|---|---|---|
| Size | 400px | Width and height of the QR code in pixels |
| Error Correction | H (High, 30%) | Highest error correction level — recommended for WiFi QR codes as they may be printed and subject to wear |
| Margin | 1 | Minimal quiet zone border for optimal scanning |
| Format | SVG | Output format for screen display |
Service Class: WifiQrCodeService
Dependencies:
Public Methods:
| Method | Parameters | Returns | Description |
|---|---|---|---|
generateWifiString() | string $ssid | string | Creates the WIFI:T:nopass;S:{ssid};; format string with character escaping |
generateQrCode() | Site $site | string (SVG) | Generates QR code as SVG string. Throws InvalidArgumentException if no SSID. |
generateQrCodePng() | Site $site | string (data URL) | Generates QR code as base64-encoded SVG data URL for PDF embedding |
syncAndGetSsid() | Site $site | array | Attempts to sync SSID from UniFi controller. Returns ['success' => bool, 'ssid' => string, 'message' => string] |
generatePdf() | Site $site | PDF | Generates a downloadable A4 portrait PDF with QR code |
SSID Sync
The QR Generator can automatically fetch the WiFi network SSID from the site's UniFi controller.
Auto-Sync on Page Load
When loadSiteData() runs and the site has no wifi_network_name:
- Calls
WifiQrCodeService::syncAndGetSsid() - If successful, refreshes the site model to get the updated SSID
- Proceeds to generate the QR code
Manual Sync Button
The "Try Syncing from UniFi" button (shown when no SSID): 2. Invokes WifiQrCodeService::syncAndGetSsid() 3. On success: shows green notification "SSID Synced" and reloads site data 4. On failure: shows warning notification with error message
Sync Requirements
| Requirement | Check |
|---|---|
| Site has a server configured | $site->server must exist |
| Server is UniFi type | server_type must be unifi or unifi_api |
If sync fails, the user can still see the QR code if the SSID was previously configured manually in the site settings.
PDF Download
Generation Process
- User clicks "Download PDF"
downloadPdf()validates: site selected, site exists, SSID configured- Calls
WifiQrCodeService::generatePdf() - PDF is rendered from Blade template with site data
- Streamed as download with filename:
wifi-qr-{slug(site-name)}.pdf
PDF Template
Paper: A4 Portrait
PDF Layout:
| Element | Styling |
|---|---|
| Content Area | 170mm wide, centered with 30mm top margin |
| Venue Logo | Max 150×80px, centered (only shown if available) |
| Title | "Free WiFi Access" — 24px, #333 |
| Subtitle | "Scan to Connect Instantly" — 14px, #666 |
| QR Code Container | 330px wide, 15px padding, 2px #e0e0e0 border, 10px border-radius |
| QR Code Image | 300×300px |
| SSID Box | 330px wide, #f5f5f5 background, 8px border-radius |
| SSID Label | "Network Name" — 11px, uppercase, #666, letter-spacing 1px |
| SSID Value | 18px bold, Courier New monospace, #219ebc (CaptiFi brand cyan) |
| Footer | "Powered by" (10px, #999) + "CaptiFi.io" (14px bold, #219ebc) + generation date (9px, #ccc) |
Template Variables:
| Variable | Source |
|---|---|
$site | Site model instance |
$ssid | $site->wifi_network_name |
$qrCodePng | Base64 SVG data URL from generateQrCodePng() |
$venueName | $site->name |
$generatedDate | now()->format('F j, Y') (e.g., "January 28, 2026") |
Print View
The page includes comprehensive CSS @media print styles that:
- Hide everything except the
#qr-display-sectiondiv - Hide the action buttons bar within the QR display section
- Position the QR section as absolute, full-width, white background
- Set A4 page with portrait orientation and zero margins
- Match PDF layout dimensions:
- Content width: 170mm
- Top margin: 30mm auto
- Logo: max 80×150px
- QR container: 330px wide, 15px padding
- QR code: 300×300px
- SSID box: 330px wide
- Force colour printing with
print-color-adjust: exact - Override dark mode styles to white/black for printing
This ensures the browser print output closely matches the PDF download.
Security
Site Access Verification
When loading site data, the page performs a security check:
$user = auth()->user();
if (!$user->isAdmin() && !$site->users->contains($user)) {
// Show "Unauthorized" notification
// Reset selectedSiteId to null
return;
}This prevents users from accessing QR codes for sites they don't own, even if they manipulate the site ID.
Site Query Scoping
- Customers: Sites filtered through
whereHas('users', ...)pivot relationship
Component Properties
| Property | Type | Default | Description |
|---|---|---|---|
selectedSiteId | ?int | null | Currently selected site ID |
sites | Collection | Empty | All sites available to the user |
currentSsid | ?string | null | The SSID of the selected site |
qrCodeSvg | ?string | null | Generated QR code SVG markup |
logoUrl | ?string | null | URL to the venue's logo image |
Data Flow
1. Page Mount
└─ If sites exist → auto-select first site → loadSiteData()
2. loadSiteData()
├─ Find site by selectedSiteId
├─ Security check (user owns site)
├─ If no wifi_network_name → attempt auto-sync from UniFi
├─ Set currentSsid from site
└─ If SSID exists → WifiQrCodeService::generateQrCode() → set qrCodeSvg
3. Site Change (dropdown)
└─ updatedSelectedSiteId() → loadSiteData()
4. Sync SSID (button click)
5. Download PDF (button click)
└─ downloadPdf() → WifiQrCodeService::generatePdf() → stream response
6. Print (button click)
└─ window.print() → browser handles with @media print CSSRelated Files
| File | Purpose |
|---|