zipavlin / redline
Laravel package for Redline - a localized, on-site, visual bug reporting.
Fund package maintenance!
Requires
- php: ^8.3|^8.4
- composer-runtime-api: ^2.0
- ezyang/htmlpurifier: ^4.19
- firebase/php-jwt: ^7.1
- illuminate/contracts: ^11.0||^12.0||^13.0
- illuminate/database: ^11.0||^12.0||^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^9.0||^10.0||^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- ryangjchandler/orbit: ^1.4
Suggests
- ryangjchandler/orbit: Required for FileStorageProvider (Laravel 12+ only).
README
A Laravel package for self-hosted, on-page bug-reporting, with a reporting widget and dashboard.
Testers can pin comments, annotate screenshots, record videos, and view existing
pins without leaving the page; developers review and resolve reports in a built-in
dashboard. All data lives in your own storage (SQLite by default).
Requirements
| PHP | 8.3 or 8.4 |
| Laravel | 11.x, 12.x, or 13.x |
| Extension | pdo_sqlite (for the default storage backend) |
Get started
composer require zipavlin/redline php artisan redline:install
Interactive redline:install walks you through the storage backend, auth provider, guard,
mail setup, and disabled features, writes your choices to .env, publishes config and assets,
and creates a first (admin) user.
Activation
The package is partially gated - know what turns on where:
- Publishing and the artisan commands (
redline:install,redline:user,redline:remove) are always available. - The dashboard (SPA at
/{redline.path}, default/redline) is gated by the guard - The API routes and widget injection are also gated by the guard (default when
APP_ENV=staging)
Configuration
All settings are read from config/redline.php, which maps to these env variables:
| Variable | Default | Purpose |
|---|---|---|
REDLINE_PATH |
redline |
URL prefix for all routes (/redline/api/..., /redline) |
REDLINE_STORAGE_PROVIDER |
Zipavlin\Redline\Providers\Storage\DatabaseStorageProvider |
Storage backend class |
REDLINE_STORAGE_PATH_BASE |
redline |
Base dir under storage/app/ |
REDLINE_STORAGE_PATH_SCREENSHOTS |
screenshots |
Screenshot subdir |
REDLINE_STORAGE_PATH_VIDEOS |
videos |
Video subdir |
REDLINE_STORAGE_PATH_DATABASE |
database |
SQLite file subdir |
REDLINE_GUARD_PROVIDER |
Zipavlin\Redline\Providers\Guard\EnvironmentGuardProvider |
When Redline is active |
REDLINE_GUARD_EXCLUDE |
(none) | Comma-separated paths to skip injection on (exact, subpath, /*, glob) |
REDLINE_AUTH_PROVIDER |
Zipavlin\Redline\Providers\Auth\OpenAuthProvider |
Registration/role model |
REDLINE_AUTH_TOKEN |
3600 |
JWT TTL (seconds) |
REDLINE_AUTH_REMEMBER_TOKEN |
2592000 |
"Remember me" JWT TTL (30 days) |
REDLINE_AUTH_GUEST |
false |
Allow anonymous report submission |
REDLINE_AUTH_GUEST_ORIGINS |
(none) | Comma-separated origins allowed to submit guest reports |
REDLINE_AUTH_GUEST_THROTTLE |
20 |
Guest submissions per minute per IP |
REDLINE_AUTH_ALLOW_SELF_ADMIN |
false |
Allow public registration to claim admin (off by default) |
REDLINE_AUTH_PIN_REPORTER |
(none) | PinAuthProvider: reporter PIN |
REDLINE_AUTH_PIN_DEVELOPER |
(none) | PinAuthProvider: developer PIN |
REDLINE_AUTH_DOMAINS |
(none) | DomainAuthProvider: allowed email domains (comma-separated) |
REDLINE_AUTH_THROTTLE |
10 |
Auth endpoints rate limit (per minute per IP) |
REDLINE_BOOTSTRAP_THROTTLE |
120 |
Bootstrap endpoint rate limit (per minute per IP) |
REDLINE_COOKIE_SECURE |
true |
Secure flag on auth/CSRF cookies |
REDLINE_COOKIE_SAME_SITE |
lax |
SameSite on auth/CSRF cookies |
REDLINE_COOKIE_DOMAIN |
(none) | Cookie domain override |
REDLINE_ALLOWED_ORIGINS |
(none) | Escape hatch for cookie-mutation Origin checks (reverse proxies) |
REDLINE_DISABLED |
(none) | Comma-separated features to disable: screenshots, videos, files, comments, mentions |
REDLINE_MAIL |
true |
Kill switch for all Redline emails |
REDLINE_CLOUD_URL |
(none) | Remote Redline API URL (cloud backend - planned, not yet implemented) |
REDLINE_VITE_URL |
(none) | Dev-only: serve the frontend from the Vite dev server (see "Frontend development") |
Keep the data out of your app's git history:
/storage/app/redline/should be gitignored.
Auth providers & roles
Roles are reporter / developer / admin (plus a shared guest identity for
guest-mode submissions). How roles are claimed depends on the auth provider:
| Provider | How users get in | Role selection |
|---|---|---|
OpenAuthProvider (default) |
Anyone can register | Free choice (admin excluded unless REDLINE_AUTH_ALLOW_SELF_ADMIN=true) |
PinAuthProvider |
Anyone can register with a PIN | PIN decides: reporter PIN → reporter, developer PIN → developer; role selector hidden |
DomainAuthProvider |
Registration limited to REDLINE_AUTH_DOMAINS (+ email verification) |
Provider-defined |
ApprovalAuthProvider |
Self-register, then admin approves | Pending until approved |
ClosedAuthProvider |
No self-registration - admin creates users | Admin assigns |
Notes:
- If no admin exists, the first developer to register is auto-promoted to admin.
- With the default
REDLINE_AUTH_ALLOW_SELF_ADMIN=false, public registration can never create an admin - seed the first one with:php artisan redline:user
- Authentication is a JWT (HS256) delivered as an HttpOnly
redline_tokencookie. State-changing cookie-authenticated requests must echo the non-HttpOnlyredline_csrfcookie back in theX-Redline-CSRFheader (double-submit CSRF). Cross-origin clients useAuthorization: Bearerand skip CSRF.
Capabilities by role
| Action | Reporter | Developer | Admin |
|---|---|---|---|
| Submit reports / comment | ✅ | ✅ | ✅ |
| Edit own report/comment body | ✅ | ✅ | ✅ |
| Change report status | limited¹ | ✅ | ✅ |
| Move pins, upload media | own only | ✅ | ✅ |
| Delete reports/comments | own only | own only | ✅ |
| Manage users | ❌ | ❌ | ✅ |
¹ A report owner may only Reopen (resolved → in progress) or Confirm (resolved → done); developers/admins drive the main lifecycle transitions.
Using the widget
The widget appears as a floating toolbar (bottom-right by default) on every page of the activated environment. Click any mode button - on first use you are prompted for identity (name/email/role via the identity modal).
- Comment / pin - click any element to pin a comment
- Screenshot - captures the page, then lets you crop and annotate
- Video - records the current tab via
getDisplayMedia(no audio; 2 min max) - View - toggle pin visibility: all / open reports
- Identity chip - settings
Dashboard
https://your-staging-site.com/redline
The dashboard is a Vue SPA served at /{redline.path} (default /redline).
It lists reports and report details, users and user details and simple analytics dashboard.
Artisan commands
# Interactive setup (storage, auth, mail, features) - writes .env, publishes assets php artisan redline:install # Create a user / grant admin php artisan redline:user # Permanently delete all data and remove redline from project php artisan redline:remove
Storage backends
Database (default) - DatabaseStorageProvider
Eloquent models on a dedicated SQLite connection at storage/app/redline/redline.db.
Requires pdo_sqlite. Schema (numbered migrations 0001–0005) is created by
php artisan redline:install.
JSON files - FileStorageProvider
One JSON file storage via ryangjchandler/orbit. Requires Laravel 12+. Optional dependency -
install with composer require ryangjchandler/orbit when you choose this backend.
Security considerations
- Widget injection and the API are gated by the guard (
APP_ENV=stagingby default); publishing and artisan commands remain available for operational use. - JWT auth cookie is HttpOnly (JS can never read it) and never returned in JSON
bodies; double-submit CSRF via the
redline_csrfcookie +X-Redline-CSRFheader. - Guest reporting is opt-in (
REDLINE_AUTH_GUEST) with configured-origin checks and a per-IP throttle;REDLINE_ALLOWED_ORIGINSis the reverse-proxy escape hatch. - Uploaded files are renamed to
{ulid}.{ext}(original names discarded) and served only through a controller, never via public URLs. - Report/comment bodies are sanitized with
htmlpurifier. - Public registration can never create an admin by default (
REDLINE_AUTH_ALLOW_SELF_ADMIN=false).
Frontend development
All frontend source lives in the sibling redline-app repo (Vue 3 + TypeScript + Vite + Nuxt UI). To develop against this package locally:
- In
redline-app:npm ci && npm run dev(Vite dev server on:5173, serves both widget and dashboard entries as ESM). - In the host app set
REDLINE_VITE_URL=http://localhost- the blade shells then loadsrc/widget.ts/src/dashboard.tsdirectly from Vite with hot reload.
Production bundles are built in redline-app (npm run build → dist/), copied into
this repo's public/ (committed), and published via
php artisan vendor:publish --tag=redline-assets.
Testing
composer test
Current baseline: 251 passed, 3 skipped (the skips are Orbit-gated
FileDriverGrammarTest tests that skip when ryangjchandler/orbit is not installed).
Changelog
See CHANGELOG.md.
Credits
License
The MIT License (MIT). See LICENSE.md.