Development
This one's for anyone hacking on Client Reporter itself.
Client Reporter is a Laravel 13 app built with Livewire 4, Tailwind CSS 4 and PHP 8.3+. It sticks to standard Laravel conventions, with Laravel Pint for code style, Larastan/PHPStan at level 5 for static analysis, and PHPUnit for tests. See CONTRIBUTING.md for the full contributor guide, including the branch and pull request flow.
Local setup
The full, authoritative setup steps live in CONTRIBUTING.md. The short version:
git clone https://github.com/coysh-digital/client-reporter.git
cd client-reporter
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate # default SQLite needs no configuration
npm run build.env.example ships with production-safe values. For a local copy served over plain http://, set APP_ENV=local, APP_DEBUG=true and SESSION_SECURE_COOKIE=false in .env (otherwise the session cookie is never sent and you cannot stay signed in).
Serve the app with php artisan serve, and run the Vite dev server with npm run dev for hot asset reloading. The composer setup script rolls the install/migrate/build steps into one command if you'd rather.
Domain model
The core hierarchy is:
Client → Sites → Integrations → Metrics/Snapshots → Reports
- Client - an agency customer. Carries its own branding (which cascades from global → client → site).
- Site - a website belonging to a client. Integrations attach here (or once at the workspace level, and auto-match to sites).
- Integration / connection - a connected data source for a site (analytics, ecommerce, uptime, CMS, billing, …). Credentials are stored encrypted.
- Metrics & snapshots - the collected data.
client-reporter:collectqueues collectors for due connections; the results are stored as metrics and metric snapshots and used to build the reports and the dashboard. - Reports - built from reusable templates and a drag-and-drop block builder. When a report is generated it's frozen to an immutable snapshot, so its web, shared, emailed and PDF copies all stay in sync. Reports go out as branded web pages, secure share links, PDF exports, branded email, and through the client portal.
Roles and access
Staff accounts use a role hierarchy that's enforced through policies and gates:
- Administrator - full access, including settings and user management.
- Manager - manages clients, sites, integrations, reports and branding.
- Viewer - read-only staff access.
Separately, client portal users (role: client) get a locked-down, agency-branded area that shows only their own sites and reports. They pass the access-portal gate and don't pass access-admin. Route middleware (auth, active, can:*) enforces these boundaries; see routes/web.php.
Coding standards
- Laravel Pint handles code style (Laravel preset plus
declare_strict_types, alphabetically-ordered imports, no unused imports - seepint.json). Run./vendor/bin/pintto fix things, or./vendor/bin/pint --testto check the way CI does. - PHPStan / Larastan at level 5 has to pass with no new errors (
phpstan.neonanalysesapp/andtests/, with model-property checks on). Run./vendor/bin/phpstan analyse --memory-limit=512M. - PHPUnit for tests - the suite's at 267 tests right now. Any new behaviour needs to come with tests.
Running the check suite
Run everything CI runs with one command:
composer check # pint --test, phpstan, then php artisan testOr the individual tools:
php artisan test # tests
./vendor/bin/pint # fix style (--test to check only)
./vendor/bin/phpstan analyse --memory-limit=512M # static analysisPlease make sure composer check passes before you open a pull request.
Coverage
composer coverage # HTML report in storage/coverage, summary on stdoutNeeds PCOV or Xdebug installed for the PHP you run tests with.
Screenshots
A Playwright script signs in and captures every admin screen at desktop and phone widths, which is the quickest way to eyeball a design change or refresh the documentation images:
npm run shots # every screen → storage/app/screenshots
npm run shots -- dashboard clients # just those screens
SHOTS_DOCS=1 npm run shots # also refresh docs/images/*.pngIt reads SHOTS_URL, SHOTS_EMAIL, SHOTS_PASSWORD, SHOTS_CHROME and SHOTS_OUT from the environment (defaults suit a DDEV install with the seeded admin) and needs a local Chrome or Chromium.
Project structure
The application code lives under app/:
| Path | Contains |
|---|---|
app/Livewire | Livewire components - the admin UI, install wizard, settings, clients, sites, reports, integrations, portal. |
app/Integrations | The Integration SDK and every first-party integration (manifest, config fields, auth, collectors, report blocks). |
app/Reporting | The reporting engine - report blocks (Reporting/Blocks/…), builders and rendering. |
app/Console/Commands | Artisan commands: client-reporter:collect, :check-updates, :sync-billing, :update, :make-integration. |
app/Jobs | Queued jobs, including RunConnectorCollection. |
app/Http | Controllers for OAuth callbacks, public/portal reports and PDF export. |
app/Models | Eloquent models (Client, Site, SiteIntegration, Metric, MetricSnapshot, Report, User, Setting, BrandingProfile, …). |
app/Support | Cross-cutting helpers (Settings, EnvWriter, UpdateChecker, AuditLogger, DateRange, …). |
app/Billing, app/Importers, app/Enums, app/Mail, app/Providers | Billing ledger, bulk site importers, enums, mailables and service providers. |
The product-specific configuration is in config/client-reporter.php (see Configuration). Scheduled work is defined in routes/console.php; routes in routes/web.php.
dompdf-safe report views
PDF export defaults to the dompdf renderer, which only supports a subset of modern CSS. So when you're writing report blocks and their views, keep the markup dompdf-friendly:
- Stick to simple, table- and block-based layouts rather than CSS grid/flex tricks that dompdf can't render.
- Don't lean on features Browsershot would render but dompdf wouldn't - the same view has to produce an acceptable PDF under dompdf.
- Test both the web preview and the PDF export (
/reports/{report}/pdf) whenever you change a block.
Browsershot (headless Chromium) is there as an opt-in on a VPS for pixel-perfect output, but your blocks should still render correctly under dompdf. See Configuration.
Building an integration
Integrations are first-class, discoverable Composer packages. You can scaffold one with:
php artisan client-reporter:make-integration "Matomo"That gives you the skeleton - manifest, config fields, auth method, collectors, metrics and report blocks - ready for you to fill in. Third-party integrations are picked up automatically from any installed package that declares an extra.client-reporter.integrations array, so they don't need any core changes. See Creating an integration for the full SDK guide, and the contract test helpers for testing your integration.
Contributing
See Contributing and integration pull requests for how to propose an integration for the core project, and CONTRIBUTING.md for the branch naming, commit and pull request process, the issue templates, and what's deliberately out of scope.
A note on AI
In the spirit of being upfront: I used AI tooling while building this plugin, to think through approaches, to speed up some of the groundwork, and to help draft these docs. It was a tool in the workshop, not the builder. Every line of code was reviewed, tested and put in place by a developer who understood it, and nothing shipped that I could not explain and stand behind. The judgement calls and the final code are human.