Capture browser bugs with evidence, review them in one workspace, and hand them off cleanly without losing screenshots, recordings, steps, console logs, or debugger payloads.
Why Snag · Product Flow · What Ships · Quick Start · Monorepo Map · Commands
See Snag in action
Connect extension
Connect the browser extension with a one-time code from the workspace so the device can start sending captures.
Image report
Take a screenshot, circle the problem, blur sensitive parts, add a note, and submit it as a report.
Video report
Record a short video through the extension and send it as a report with the same workspace flow.
See details
Open the full report details to review the captured steps, network activity, and supporting debugger context before triage.
Create ticket
Turn a report into a ticket, keep the evidence attached, and move the issue through to a done state.
Bug report widget
Show the website widget that customers can open on a live site to capture a bug and send a report without joining the workspace.
Snag is the evidence layer around delivery tools.
It does not try to replace Jira, GitHub, or Trello. It complements them.
Snag owns:
- capture
- screenshots and recordings
- reproduction context
- share links and handoff packages
- verification-friendly issue review
Your external tracker still owns delivery execution. Snag makes sure the bug arrives there with the context intact.
flowchart LR
A[Capture the broken state] --> B[Review in the reports queue]
B --> C[Turn it into a bug issue]
C --> D[Share or sync outward]
D --> E[Jira / GitHub / Trello]
E --> F[Verify the fix back in Snag]
The core product shape is simple:
- capture the bug
- keep the evidence attached
- triage it in one queue
- hand it off without rewriting context
- verify the fix with the same evidence trail
| Surface | What it does |
|---|---|
| Reports queue | Incoming bug reports with screenshots, recordings, steps, console output, and network traces |
| Bug backlog | Issue-centric triage and verification workspace with external sync awareness |
| Public sharing | Share bug records safely without exposing private debugger payloads by default |
| Capture keys | Public intake for widgets, forms, and server-side relay flows outside signed-in workspace sessions |
| Website widget | Self-service bottom-right Report a bug launcher with screenshot capture, annotation, and public intake |
| Browser extension | One-time code connect flow for fast in-browser capture and submission |
| Capture SDK | Workspace package for embedding capture flows and widget surfaces from repo-native code |
| Integrations | Sync-oriented handoff model for external delivery systems while Snag remains the evidence layer |
| Billing and org controls | Plans, members, invitations, and workspace-level settings |
A QA lead records a broken checkout flow, sends the report into Snag, reviews the attached evidence in the queue, and turns it into a tracked bug issue.
A customer-facing site uses a capture key to open a public upload session and create a report without requiring a signed-in Snag session.
The team keeps evidence and verification in Snag, but pushes delivery work into Jira or GitHub when the bug is ready for execution.
| Snag owns | Your delivery tracker owns |
|---|---|
| bug capture | sprint planning |
| screenshots and recordings | backlog execution |
| console and network evidence | engineering workflow management |
| reproduction context | delivery status conventions |
| share links and handoff packages | release process |
| verification context | ticket lifecycle inside the tracker |
That split is intentional. Snag is strongest when it stays focused on evidence, review, and handoff quality.
pnpm bootstrapThis installs root workspace packages and Composer dependencies for apps/server.
This repo includes a Windows-friendly XAMPP command that assumes Apache and MySQL already exist outside the app runtime.
cd apps/server
php artisan snag:xamppWhat it does:
- derives a local
/snagapp URL - ensures the database exists
- applies migrations
- starts Vite, queue worker, scheduler, and Reverb
- keeps Apache and MySQL external under XAMPP
Use this if you want a local Docker runtime without XAMPP.
cp apps/server/.env.example apps/server/.env
pwsh ./scripts/docker/dev-up.ps1
docker compose -f docker-compose.yml -f docker-compose.dev.yml exec app php artisan key:generateRaw compose equivalent:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --buildDocker dev services include:
nginxappworkerschedulerreverbpostgresredisminiomailpit
Notes:
- if
APP_KEYwas empty, generate it once and rerunpwsh ./scripts/docker/dev-up.ps1 - the dev overlay runs migrations automatically on
appstartup - frontend assets come from the built image and are synced into a shared volume
- this is a stable no-HMR Docker flow; after frontend changes, rebuild the stack or run
pnpm --dir apps/server buildbefore restarting it
Use this when you want an immutable-image style stack with separate nginx and php-fpm, but still on one local machine.
cp apps/server/.env.example apps/server/.env
pwsh ./scripts/docker/prod-build.ps1
docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm app php artisan key:generate
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm app php artisan migrate --forceNotes:
- if
APP_KEYwas empty, generate it once before the long-runningup -dcycle - the prod overlay keeps
nginx,app,worker,scheduler, andreverbseparate - production services run as non-root where possible and use read-only filesystems with writable volumes only for required paths
- for real production, point
.envat managed Postgres/Redis/object storage instead of the bundled local containers
For local development, you can create or upgrade a workspace user directly:
cd apps/server
php artisan snag:grant-plan you@example.com studio --create-missingThe Chromium extension uses an explicit one-time code exchange instead of relying on ambient first-party cookies.
Build it with:
pnpm --dir apps/extension buildThen load:
chrome://extensions- enable
Developer mode - choose
Load unpacked - select
apps/extension/dist
Connect flow:
- open
Settings -> Extension Connectin Snag - copy the one-time code
- paste the code into the extension popup with the HTTPS API base URL (or
localhostduring local development) and device name - exchange it for a revocable token
- turn on
Start reportingto enable the floating recorder on allowed pages - capture the active tab and submit the report
Full reference: Browser Extension
Capture keys let external surfaces create Snag reports without a signed-in workspace session.
Use them for:
- website feedback widgets
- public bug forms
- embedded "Report a problem" buttons
- server-side relays
The public flow lives under:
POST /api/v1/public/capture/tokensPOST /api/v1/public/capture/upload-sessionsPOST /api/v1/public/capture/finalize
Full reference: Capture Keys
Snag includes a self-service website widget builder for non-technical installs.
Workspace owners and admins can open Settings -> Capture -> Website widgets, create one widget per site, limit allowed domains, customize the visitor-facing copy, and copy a ready-to-paste snippet.
Install snippet:
<script async src="https://snag.example.com/embed/widget.js" data-snag-widget="ww_..." data-snag-base-url="https://snag.example.com"></script>Visitor flow:
- a fixed
Report a buglauncher appears in the bottom-right corner - clicking it captures the current page immediately
- the visitor can annotate the screenshot, add a short note, and continue
- the widget asks for a final send confirmation
- the widget shows a short success state without exposing a public share link
What the website widget sends:
- a screenshot of the visible page
- page URL and page title
- recent user actions leading up to the report
- console entries
- network request metadata without request or response bodies
- viewport size
- browser locale, user agent, platform, timezone, and referrer
- widget id and site label
- the visitor's short note
- optional explicit user context that the host site passes in
What it does not send:
- cookies
- localStorage or sessionStorage
- form field values
- request or response bodies
- arbitrary page data dumps
Optional host-side user context:
<script async src="https://snag.example.com/embed/widget.js" data-snag-widget="ww_..." data-snag-base-url="https://snag.example.com"></script>
<script>
window.SnagWebsiteWidget?.setUserContext('ww_...', {
id: 'usr_123',
email: 'customer@example.com',
name: 'Jane Customer',
account_name: 'Acme Corp'
});
</script>Current v1 limits:
- screenshot only, no video capture
- fixed bottom-right placement
- copy-paste script install only, no npm package
- prefers a real browser screenshot on secure contexts when
getDisplayMedia()is available - falls back to compatibility visible-page capture when real tab capture is unavailable or denied
- real tab capture requires
HTTPSorlocalhostand shows the browser's built-in picker prompt - compatibility capture can still differ from the exact browser composited output on heavily protected or unusual pages
Snag includes a few diagnostics-only surfaces for local testing and QA of the extension and website widget.
These routes are available only in local, testing, and e2e environments:
/_diagnostics/capture-widget- theAir Supply Co.storefront used to test the website widget against a realistic landing page/_diagnostics/extension-preview- a browser page for checking extension-facing UI flows/_diagnostics/extension-recorder- a recorder diagnostics page plus/_diagnostics/extension-recorder/ping
The widget embed loader is also hosted directly by the app:
GET /embed/widget.js
flowchart LR
UI[Inertia and Vue workspace] --> HTTP[Laravel controllers and requests]
HTTP --> WORKFLOWS[Report and issue workflows]
WORKFLOWS --> DB[(Organization data)]
WORKFLOWS --> STORAGE[(Artifact storage)]
WORKFLOWS --> EVENTS[Reverb and queue jobs]
EXT[Chromium extension] --> API[Authenticated and public capture APIs]
PUBLIC[Widgets and public forms] --> API
API --> WORKFLOWS
| Path | Purpose |
|---|---|
apps/server |
Main Laravel + Inertia application |
apps/extension |
Chromium extension for browser-side capture |
apps/docs |
VitePress documentation site |
packages/capture-core |
Shared capture client for authenticated and public upload flows |
packages/shared |
Shared DTOs and contracts |
packages/ui |
Shared Vue UI package |
sdks/capture |
Repo-local SDK package for widget and capture embedding |
tests |
End-to-end coverage |
From the repository root:
pnpm bootstrap
pnpm build
pnpm test
pnpm typecheck
pnpm lint
pnpm analyzeUseful app-level commands:
cd apps/server
php artisan test
php artisan snag:xampp
php artisan snag:grant-plan you@example.com studio --create-missingUseful Docker commands:
pwsh ./scripts/docker/dev-up.ps1
pwsh ./scripts/docker/dev-down.ps1
pwsh ./scripts/docker/dev-down.ps1 -RemoveVolumes
pwsh ./scripts/docker/prod-build.ps1
docker compose -f docker-compose.yml -f docker-compose.dev.yml config
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

