A development debugbar for the Marko Framework.
marko/debugbar adds a browser toolbar and stored request profiler to Marko apps so you can inspect request data, timings, memory usage, app messages, database queries, logs, rendered views, Inertia payloads, API responses, and optional configuration values while building locally.
The package is inspired by fruitcake/laravel-debugbar, but it is implemented around Marko's current module, plugin, and response lifecycle.
Use this package only in development. Debugbars expose application internals by design.
- Auto-registers as a Marko module.
- Enables itself from
APP_DEBUGorDEBUGBAR_ENABLED. - Injects into HTML responses before
</body>. - Leaves JSON/API responses unchanged while still storing a debug snapshot.
- Adds
X-Marko-Debugbar,X-Marko-Debugbar-Id,X-Marko-Debugbar-Url, andServer-Timingheaders. - Provides a
/_debugbarprofiler page for stored requests. - Provides a
debugbar()helper for messages and timing. - Captures calls through Marko
ConnectionInterface,LoggerInterface, andViewInterface. - Detects Marko Inertia HTML and JSON page payloads without requiring a hard Inertia package dependency.
- Masks common secrets in request and config collectors.
- Renders inline CSS and JavaScript, so no asset publishing is required.
Install as a development dependency:
composer require marko/debugbar --devFor local development against a split Marko workspace, use a path repository:
{
"repositories": [
{
"type": "path",
"url": "../marko-debugbar",
"options": {
"symlink": true,
"versions": {
"marko/debugbar": "0.1.3"
}
}
}
],
"require-dev": {
"marko/debugbar": "^0.1.0"
}
}Then update Composer:
composer update marko/debugbar --with-dependenciesIf you are developing the package itself against a specific Marko release line, set the root package version when running Composer:
COMPOSER_ROOT_VERSION=0.3.1 composer installThe package ships config/debugbar.php. Marko merges module config automatically, and app-level config/debugbar.php can override it.
Minimal .env setup:
APP_ENV=local
APP_DEBUG=true
DEBUGBAR_ENABLED=true
DEBUGBAR_THEME=autoSupported environment variables:
DEBUGBAR_ENABLED=true
DEBUGBAR_INJECT=true
DEBUGBAR_CAPTURE_CLI=false
DEBUGBAR_THEME=auto
DEBUGBAR_STORAGE_ENABLED=true
DEBUGBAR_STORAGE_PATH=storage/debugbar
DEBUGBAR_STORAGE_MAX_FILES=100
DEBUGBAR_ROUTE_PREFIX=_debugbar
DEBUGBAR_ROUTE_OPEN=false
DEBUGBAR_COLLECTORS_MESSAGES=true
DEBUGBAR_COLLECTORS_TIME=true
DEBUGBAR_COLLECTORS_MEMORY=true
DEBUGBAR_COLLECTORS_REQUEST=true
DEBUGBAR_COLLECTORS_RESPONSE=true
DEBUGBAR_COLLECTORS_INERTIA=true
DEBUGBAR_COLLECTORS_VIEWS=true
DEBUGBAR_COLLECTORS_DATABASE=true
DEBUGBAR_COLLECTORS_LOGS=true
DEBUGBAR_COLLECTORS_CONFIG=false
DEBUGBAR_OPTIONS_MESSAGES_TRACE=false
DEBUGBAR_OPTIONS_DATABASE_WITH_BINDINGS=true
DEBUGBAR_OPTIONS_DATABASE_SLOW_THRESHOLD_MS=100Themes:
autolightdark
Collects messages added through the helper or Debugbar instance.
debugbar('Loaded dashboard');
debugbar('Payment failed', 'error', ['invoice' => $invoiceId]);Convenience methods are also available:
debugbar()?->debug('Starting import');
debugbar()?->info('Report generated');
debugbar()?->warning('Slow external API');
debugbar()?->error('Payment failed', ['invoice' => $invoiceId]);Shows total request duration and custom measures.
$result = debugbar()?->measure('build report', function () {
return buildReport();
});Manual start and stop calls are available:
debugbar()?->startMeasure('external api');
// ...
debugbar()?->stopMeasure('external api');Shows start, current, and peak memory usage.
Shows method, URI, query data, posted data, and request headers. Common sensitive values such as passwords, tokens, secrets, API keys, and authorization headers are masked.
Shows response type, size, headers, and a truncated preview. JSON/API responses are not modified, but the request snapshot is still written to storage and linked through the X-Marko-Debugbar-Url header.
Detects Marko Inertia responses from the final response body. It shows the component name, URL, version, prop count, prop keys, and partial reload headers when present.
This collector works for initial HTML page loads and X-Inertia JSON responses.
Captures renders that go through Marko's ViewInterface, including Latte drivers that implement the view contract.
Captured data:
- Method:
renderorrenderToString - Template name
- Data keys
- Start offset
- Duration
- Output size
Captures query and execute calls when database access goes through Marko's ConnectionInterface.
Captured data:
- Query type:
queryorexecute - SQL
- Bindings, configurable
- Start offset
- Duration
- Row count
The collector highlights slow queries based on DEBUGBAR_OPTIONS_DATABASE_SLOW_THRESHOLD_MS.
Current limitation: prepared statement execution is not captured unless it goes through ConnectionInterface::query() or ConnectionInterface::execute().
Captures calls through Marko's LoggerInterface, including PSR-style level methods and direct log() calls.
Disabled by default because it can expose application internals. Enable it only when needed:
DEBUGBAR_COLLECTORS_CONFIG=trueThe config collector masks common sensitive keys:
*.key*.password*.secret*.token*.api_key*.private_key
Override debugbar.options.config.masked in app config for project-specific rules.
After installation, no controller changes are required for the toolbar to render on HTML responses.
To add your own message:
use Marko\Routing\Attributes\Get;
use Marko\Routing\Http\Request;
use Marko\Routing\Http\Response;
class HomeController
{
#[Get('/')]
public function index(Request $request): Response
{
debugbar('Rendering home page', 'info', [
'path' => $request->path(),
]);
return Response::html('<html><body>Hello</body></html>');
}
}For Inertia pages, call the helper before returning the Inertia response:
debugbar('Rendering Inertia page', 'info', [
'component' => 'Landing',
]);
return $this->inertia->render($request, 'Landing', assetEntry: 'app/web/resources/js/app.js');Every captured request gets a stable debug ID. The injected toolbar starts in a compact collapsed state with request method, duration, memory, message count, query count, log count, and URI visible. Click Expand or any collector tab to open the inline detail panel, and click Collapse to return to the compact rail.
The toolbar also includes an Open link to the full profiler page:
/_debugbar/{id}
The index page lists stored requests with request summary cards and activity metrics:
/_debugbar
The request detail page includes summary cards, collector navigation, and the complete stored collector payload. The raw dataset is also available as JSON:
/_debugbar/{id}/json
By default, profiler routes are available only when the debugbar is enabled and the request comes from 127.0.0.1 or ::1. Set DEBUGBAR_ROUTE_OPEN=true only for trusted local/dev environments.
Run the package quality checks:
COMPOSER_ROOT_VERSION=0.3.1 composer validate --strict
COMPOSER_ROOT_VERSION=0.3.1 composer test
COMPOSER_ROOT_VERSION=0.3.1 composer analyse
COMPOSER_ROOT_VERSION=0.3.1 composer cs:checkFix style:
COMPOSER_ROOT_VERSION=0.3.1 composer cs:fixThe module binds a singleton Marko\Debugbar\Debugbar and boots it after Marko config and routing are available.
During boot, the debugbar starts an output buffer. It collects the final response body, stores a JSON snapshot under storage/debugbar, and adds debug headers. When the body looks like HTML and DEBUGBAR_INJECT=true, the renderer inserts the collapsed toolbar before the closing </body> tag. JSON-looking responses are returned unchanged.
Database, log, and view collection use Marko plugins:
DatabaseConnectionPlugintargetsMarko\Database\Connection\ConnectionInterfaceLoggerPlugintargetsMarko\Log\Contracts\LoggerInterfaceViewPlugintargetsMarko\View\ViewInterface
This approach fits Marko's current extension system without requiring framework changes.
- Injection currently uses output buffering because Marko does not yet expose a formal global middleware registration API for packages.
- The profiler route prefix is configurable for generated links, but Marko route attributes are static today, so the bundled routes are registered under
/_debugbar. - Route name/controller metadata is not shown yet because the matched route is not exposed on the request or response.
- Ajax and redirect history are stored as individual snapshots, but the toolbar does not yet include a history dropdown.
- Prepared statements are not collected unless execution goes through
ConnectionInterface::query()orConnectionInterface::execute(). - Assets are inline by design for the first release.
- Move response injection to package-provided global middleware when Marko exposes that hook.
- Add route/controller collector data.
- Add exception collection when Marko exposes a central exception event.
- Add an Ajax/history dropdown.
- Add editor links with local/remote path mapping.
- Consider an adapter around
php-debugbar/php-debugbarif Marko's route and asset story makes that a net simplification.
This package is inspired by Laravel Debugbar's development workflow: collector configuration, response injection, request capture, and simple helper/facade-style logging.
References:
MIT. See LICENSE.