WP Manifestindependent plugin directory
manifest / developer / bugbottle-wordpress

Bugbottle releases

WordPress plugin for bugbottle: the report panel and a receiving endpoint in one activation

by Mads Holst Jensen · github.com/mahope/bugbottle-wordpress · website

0stars
9release downloads
0forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/mahope/bugbottle-wordpress/releases/download/v1.0.0/bugbottle.zip

Readme

bugbottle for WordPress

The bugbottle panel — see it at bugbottle.dev — and its receiving endpoint, in one activation. Reports land as a private post type in wp-admin — with the page, the viewport, the recent console errors and their stack frames, the requests that failed, the element the reporter pointed at, what they did just before, and — when you turn screenshots on — a picture of what they were looking at, which they can mark up first. Reports are emailed on if you give it a recipient.

Error trackers catch what throws. They cannot catch what merely looks wrong, and they never tell you what the person was doing when it did. "The save button does nothing" is not a report anyone can act on.

  • One activation. No endpoint to write, no service to sign up for. The panel and the route that receives it arrive together.
  • Nothing leaves your site. Reports are rows in your database and files in your uploads directory. There is no vendor in the middle.
  • Your language, your brand. Eight locales, following the site language by default; colour, position, name and logo on the settings screen.
  • No build step, no Composer. The plugin is PHP and two JavaScript files, the second of which is only loaded when screenshots are on.

Requires WordPress 6.4 and PHP 8.1.

Install

  1. In wp-admin, go to Plugins → Add New, search for "Bugbottle", then install and activate. Or download bugbottle.zip from the latest GitHub release and upload it under Plugins → Add New → Upload Plugin.
  2. Go to Bug reports → Settings and set at least an email recipient, or plan to read the reports in wp-admin.

GitHub stays the canonical source and where releases are tagged; the zip on wordpress.org is built from the same tag. To update, use the in-admin updater, or upload the newer zip over the old one — WordPress will ask you to confirm the replacement. Your reports and settings survive either: they live in the database, not in the plugin directory.

Settings

Bug reports → Settings

Setting What it does
Show the report panel on the front end Whether the panel is rendered at all. Off leaves the REST route working, so your own form can still post to it.
Only for logged-in users Hides the panel and refuses reports from visitors who are not signed in.
Accept reports from visitors who are not logged in Anonymous reports, rate-limited to 10 per hour per IP address. Off by default.
Language The language the panel speaks: auto follows the site language, or pick one of Danish, English, Swedish, Norwegian, German, Dutch, French, Spanish.
Primary colour The accent: trigger button, primary action, focus ring.
Position Which corner the floating button sits in.
Brand name, Logo URL Shown in the panel header and on the trigger.
Trigger selector A CSS selector for your own button, e.g. #report-a-bug. Empty means the floating button.
Keyboard shortcut The combination that opens the panel. mod is Command on a Mac and Ctrl everywhere else; mod+shift+b by default. Empty means no shortcut.
Open on error Opens the panel by itself when the page throws an uncaught error. Off by default — it shows the panel to whoever is on the page, customers included.
Contact field Whether the panel asks how the reporter can be reached: not at all, an optional field, or one it refuses to send without. Off by default. Nothing checks what is typed — "ring me on 12345678" is a good answer — and the line is stored with the report, shown on the report screen and rendered as a Contact row. It is personal data you asked for; read The contact field.
Evidence Breadcrumbs (clicks, navigations, submits) and the network log (requests that failed or were slow — method, URL, status and duration, never a body or a header). Both on.
Screenshots Lets the reporter attach a picture of the page, and mark it before sending: a rectangle, an arrow, and a blur that really destroys what it covers. Off by default, and it is the setting to think hardest about — read Please read this part. Turning it on loads a second script, assets/bugbottle-screenshot.js, about 15 kB (6 kB over the wire), on every page the panel is on.
Offline queue Keeps a report the browser could not send and delivers it when the connection is back. Reports wait in the browser for up to seven days. The queue signs what it delivers, with a signature computed at delivery rather than at the moment the report was written, so a site with signing keys accepts it. On by default.
Scrubbing Redacts email addresses, bearer tokens, JWTs, card numbers, IBANs and query values before the report is sent. On by default.
Timings and storage snapshot Records what the page cost — largest contentful paint, layout shift, interaction to next paint, time to first byte, the load events, long tasks, and the JS heap in Chrome — and lists the key names in localStorage and sessionStorage with the length of each value, plus the cookie names. Names only, never values, and never a cookie value at all. Off by default.
Shake to report Opens the panel when the phone is shaken: three shakes inside a second, then a three-second pause. Off by default. On iPhone and iPad it also needs the visitor's permission, which only Safari can ask for and only from a button they pressed — see Shake to report.
Signing key(s) One key per line. With a key set, a report must arrive signed with one of them or it is refused, and the bundled panel signs what it sends. Empty by default. Read Signing requests before you fill it in — a key that ships to the browser is public.
Email recipient Where reports are emailed, through wp_mail — so an SMTP plugin handles delivery. The body is the report as Markdown, with a link to it in wp-admin; it never links the screenshot itself, because that route wants an administrator's session and would only answer 401 from an inbox. When the report carries a contact line that looks like an email address, the mail is sent with it as Reply-To. Empty means reports are only stored.
Email on submit Send the email as soon as a report arrives.

For developers

Two filters, both in includes/class-assets.php:

// Hide the panel on some pages.
add_filter( 'bugbottle_show_panel', fn( $show ) => ! is_cart() );

// Add fields to every report — an app version, a tenant id.
add_filter( 'bugbottle_panel_config', function ( $config ) {
    $config['extra'] = array( 'theme' => wp_get_theme()->get( 'Version' ) );
    return $config;
} );

And one action, after a report is stored:

add_action( 'bugbottle_report_stored', function ( int $post_id, array $report ) {
    // Post it to Slack, open an issue, whatever you like.
}, 10, 2 );

The REST route is POST /wp-json/bugbottle/v1/report. It takes the ordinary bugbottle JSON body, and a logged-in caller identifies itself with the standard X-WP-Nonce header. Everything in the body is validated exactly as bugbottle/server validates it — the rules are ported to PHP in includes/class-validator.php, and includes/class-markdown.php is the same port of the library's toMarkdown, so the summary in wp-admin and in the email is the rendering you already know.

Signing requests

A key that is sent to the browser is public. It is in the page source, so anyone who wants it has it. Signing raises the cost of posting junk to the endpoint from a script that has not read your page; it is spam deterrence beside the rate limit, and it is not authentication. Nothing here secures the endpoint, and it must not be described as if it did.

With Signing key(s) filled in, the route requires the header the library sends:

X-Bugbottle-Signature: t=1757260800000,v1=<64 lowercase hex characters>

t is a unix timestamp in milliseconds and v1 is HMAC-SHA-256(key, "<t>.<body>") over the raw request body. The plugin verifies over the bytes as they arrived — WP_REST_Request::get_body(), which WordPress fills from php://input before it parses anything — and never over json_decode followed by json_encode, which would move key order, spacing and number formatting and take the digest with them.

A request is refused with 401 and Bad signature when the header is missing, malformed, signed with a key that is not listed, more than five minutes away from the server clock in either direction, or carries a digest already accepted inside that window. The reason is deliberately not narrowed down: telling a caller which part they got wrong tells them how to get it right.

Several lines is how a key is rotated. Add the new key, leave the old one until the last cached page carrying it has expired, then delete the old one. The first line is the key the panel is given; every line is a key the route accepts.

The bundled panel signs what it sends: the mount script hands the first configured key to createSigner from bugbottle/sign, which is in the bundle this plugin ships. Filling the setting in is the whole of it. Anything else that posts to the route — your own form, a script, a mobile app — has to compute the same digest, or it will be refused along with the spam.

The offline queue signs too. It delivers a stored report with a fetch of its own, on a later page load, so the mount script hands the same signer to createQueue as to the panel and the queue signs each delivery attempt's bytes as it makes them. That the signature is computed then, and not when the report was written, is the whole point — a report that sat through an hour of outage would otherwise carry a timestamp an hour outside the five-minute skew window and be refused as certainly as if it were unsigned. tests/browser-queue-signature.mjs is the round trip that pins this. Until bugbottle 1.0.0 createQueue had no sign option and the plugin did this by replacing the queue's request function; the library does it since, and 1.0.0 of this plugin passes sign.

The contact field

Contact field is off by default, and that is a decision rather than caution: asking somebody for an address is a promise to answer, and that promise is the site's to make.

With it on, the panel puts one field under the message — "How can we reach you?" — and nothing validates what goes in it. A phone number, a name in your own chat, a typo: all of them are perfectly good answers to that question, and all of them arrive as contact on the report, trimmed, with null bytes stripped and clipped at 200 characters. Set to ask, and refuse to send without it and the panel will not send an empty one, through the same inline error an empty message gets; the field is a text input with inputmode="email" rather than type="email", so a phone number is not announced as invalid.

Where it goes: a Contact row in the Markdown summary, directly under the type, which is where the library's toMarkdown puts it; a section of its own on the report detail screen, a mailto: link when the line looks like an address; and Reply-To on the notification email, again only when it looks like an address. That test is deliberately permissive — a line is only refused when it plainly is not an address — because the cost of getting it wrong one way is a reply nobody can send, and the other way one bounced mail. A line that is not an address is left in the body and never becomes a header.

It is personal data you asked for. Store it like one: it is kept where the rest of the report is kept, it goes when the report goes, and everyone who can read a report can read it. If reports leave wp-admin for somewhere more public, the contact line goes with them.

Timings and storage

Timings and storage snapshot is off by default, and worth understanding before you turn it on.

The timings are the ones a Web Vitals report shows — largest contentful paint, cumulative layout shift, interaction to next paint, time to first byte, DOMContentLoaded, load, the count and total duration of long tasks, and on Chromium the JavaScript heap. Two of them are simplifications, and the library documents them as such rather than hiding them: the layout shift is the sum of the shifts rather than the worst session window, and the interaction figure is the worst interaction rather than the 98th percentile.

The storage snapshot is the part to read twice. It lists the key names in localStorage and sessionStorage with the length of each value, and the names of the cookies. It never records a value, and never a cookie value at all. That is still not nothing: a key called impersonating_user is a fact about the visit, and a length is a hint about a value. Turn it on when you are debugging state, read the report screen before you forward one, and leave it off the rest of the time.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.0.0 Sep 8, 2026 bugbottle.zip 2
v0.6.1 Sep 8, 2026 bugbottle.zip 0
v0.6.0 Sep 8, 2026 bugbottle.zip 1
v0.5.0 Sep 8, 2026 bugbottle.zip 1
v0.4.1 Sep 8, 2026 bugbottle.zip 2
v0.4.0 Sep 8, 2026 bugbottle.zip 1
v0.3.0 Sep 7, 2026 bugbottle.zip 1
v0.2.0 Sep 7, 2026 bugbottle.zip 1
v0.1.0 Sep 7, 2026 bugbottle-wordpress.zip 0