Web SDK
The BugCapturer Web SDK adds a floating feedback widget to any website with a single <script> tag. It is designed for UAT / staging sites: testers click the floating ball, capture and annotate a screenshot instantly, and the report lands in your BugCapturer share list and your team’s Feishu Bitable — no browser extension required for visitors.
How it works
Section titled “How it works”- Floating ball, draggable — visitors can drag it to any edge (position is remembered across pages); click to open the feedback window
- Instant capture — the page is screenshotted the moment the window opens, so transient errors are never lost; re-capture or crop to a region at any time
- Single-step editor — annotate (pen / rectangle / undo), describe the issue, review diagnostics and submit in one nearly-full-screen window
- Automatic diagnostics — console errors and failed network requests (level, status code, method, URL, duration) are silently collected and attached to the report
- Reporter identity — fill the
reporterfield in the config (or callBugCapturer.identify()after login) and every report carries the tester’s name / email, so collaborators know who to talk to - Sync to your table — bind the project to a Feishu Bitable integration and every SDK report is dispatched automatically, reporter fields included
- Style isolation — the widget renders inside a Shadow DOM and never conflicts with your site’s CSS
Step 1: Create a project
Section titled “Step 1: Create a project”- Sign in at app.bugcapturer.com and open Settings → Projects
- Follow the 3-step wizard:
- Name the project (e.g. “Checkout UAT”)
- Domain whitelist — add the exact hosts that may load the widget (e.g.
uat.example.com) or one-level wildcards (e.g.*.example.com) - Route & widget — optionally bind an integration (Feishu Bitable / generic webhook), choose the floating ball’s default corner, and toggle whether contact info is required for anonymous testers
Each project gets a public SDK key starting with bcpk_. The key is public by design — authorization is enforced by your domain whitelist and rate limits.
Step 2: Embed the code
Section titled “Step 2: Embed the code”Copy the snippet from the project card and paste it into every page where the widget should appear, before </body>:
<script> window.BugCapturerConfig = { key: "bcpk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", position: "bottom-right", requireContact: false, // leave empty for anonymous; to record the reporter, call BugCapturer.identify() // after login — do NOT hardcode a name here (all users would share it, see Step 3) reporter: { uid: "", name: "", email: "" } };</script><script src="https://api.bugcapturer.com/sdk/bugcapturer.js" async></script>That’s it — the floating ball appears on your site.
Step 3: Tell reports who submitted them (recommended)
Section titled “Step 3: Tell reports who submitted them (recommended)”The floating ball is a shared component: you embed it once and every signed-in user on the site uses the same one. But it does not know who is currently signed in — BugCapturer deliberately does not read your site’s cookies or localStorage to guess identity (a privacy red line, and the same approach Capture.dev, Shakebugs and Sentry take: all of them require an explicit identify / registerUser call).
So the site must explicitly tell the SDK who the current user is.
You can skip this step: reports will be submitted anonymously. In that case, turn on “contact info required” in the project settings so every report still carries a way to reach the reporter.
⚠️ The most common mistake: hardcoding one person’s name / email in the embed snippet. That makes every report — regardless of who submitted it — carry the same name. Always fill it with the currently signed-in user.
Which approach to use depends on your site
Section titled “Which approach to use depends on your site”Case A: server-rendered sites (admin panels / PHP / JSP / Thymeleaf / template engines)
Section titled “Case A: server-rendered sites (admin panels / PHP / JSP / Thymeleaf / template engines)”Your page is already rendered by the server for the signed-in user, so just put the user data into the config block — replace the ${...} below with your template engine’s syntax:
<script> window.BugCapturerConfig = { key: "bcpk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", reporter: { uid: "${user.id}", // your template variables name: "${user.name}", email: "${user.email}" } };</script><script src="https://api.bugcapturer.com/sdk/bugcapturer.js" async></script>Put this in the page template that should show the widget (usually your shared layout / header or footer template). The server then renders a per-user config, so each report carries its own reporter.
Case B: SPA / decoupled front end (Vue / React / Angular)
Section titled “Case B: SPA / decoupled front end (Vue / React / Angular)”User data usually lives in front-end state after login, not in the HTML. Call identify() after a successful login; if you want to call it as early as possible on page load (when the SDK may not have finished loading yet), use this official helper — it handles the timing for you:
function identifyReporter(user) { if (!user) return; var apply = function () { // This is the only real call — identical to the login-callback form below BugCapturer.identify({ uid: user.id, name: user.name, email: user.email }); }; if (window.BugCapturer && BugCapturer.ready) BugCapturer.ready(apply); // SDK loaded: wait for ready else window.addEventListener('bugcapturer:ready', apply, { once: true }); // not loaded yet: wait for the event}Vue (in a router guard):
router.afterEach(() => identifyReporter(store.state.user));React (in a root useEffect):
useEffect(() => { identifyReporter(user); }, [user]);Key points:
- Call it after login — that’s when the user data is available.
- Let the SDK handle timing:
ready()runs immediately if already ready and queues otherwise; thebugcapturer:readyevent even covers “the SDK hasn’t started loading yet”. Do not useif (window.BugCapturer) { ... }— when the SDK isn’t loaded yet it silently skips, and the identity is quietly lost. - Re-call after switching accounts: when a user logs out and another logs in, call it again to overwrite.
- Repeated calls overwrite: the last call always wins.
Case C: sites without login (marketing pages / client acceptance)
Section titled “Case C: sites without login (marketing pages / client acceptance)”No action needed. Keep submissions anonymous and enable “contact info required” in the project settings so visitors can leave an email or IM handle.
Identity field rules
Section titled “Identity field rules”| Field | Description | Limit |
|---|---|---|
uid | Your system’s unique user ID | ≤ 64 chars |
name | Display name | ≤ 100 chars |
email | Email address | ≤ 254 chars, format validated server-side |
- At least one of the three fields must be present to count as “identified” (all empty = anonymous).
- Over-long or malformed fields are dropped automatically and never block the submission.
- Once identity is set, the “Contact” field is hidden in the form (the reporter is already known).
How to confirm identity is working
Section titled “How to confirm identity is working”- Open an embedded page with a signed-in account and click the floating ball;
- If the window header shows ”✓ Submitting as xxx”, identity is in effect; if it says “Submitting anonymously”, it was not passed in;
- Submit a report and check Feedback or the bound Feishu Bitable to confirm the reporter name / email column is filled.
Troubleshooting: if it stays anonymous — ① make sure
identify()is called after login; ② runwindow.BugCapturerin the browser console to confirm the SDK loaded; ③ confirm the fields are non-empty strings; ④ check whether your Bitable has the reporter columns configured (a missing column is silently skipped, not an error).
Configuration reference
Section titled “Configuration reference”All options live in window.BugCapturerConfig:
| Option | Type | Default | Description |
|---|---|---|---|
key | string | — (required) | Project SDK key (bcpk_ prefix) |
api | string | https://api.bugcapturer.com | API origin; only override for self-hosted deployments |
position | string | bottom-right | Initial ball position: bottom-right / bottom-left / top-right / top-left |
requireContact | boolean | false | Require contact info for anonymous submissions (must match project settings) |
reporter | object | { uid: "", name: "", email: "" } | Identity attached to every report. Server-rendered sites can template it per user; SPAs should use identify() instead. Never hardcode a fixed value — leave empty for anonymous |
lang | string | browser language | Widget language: zh or en |
tipDelay | number | 5 | Seconds of page dwell time before the ball’s tip pops up; 0 shows it right after load |
tipDuration | number | 5 | How long the tip stays visible (seconds) |
tipOnce | boolean | true | Show the tip only once per visitor; set false to show it on every page load |
JS API
Section titled “JS API”| Method | Description |
|---|---|
BugCapturer.identify({ uid, name, email }) | Attach the reporter identity (call after login; the last call wins); at least one field required, invalid fields are dropped server-side |
BugCapturer.ready(fn) | Run fn once the SDK is ready (immediately if already ready). Use it to avoid timing issues when injecting identity on page load |
window event bugcapturer:ready | Equivalent signal to ready(); when your script may run before the SDK, window.addEventListener('bugcapturer:ready', fn) is the safest form |
BugCapturer.open() | Programmatically open the feedback window |
BugCapturer.close() | Programmatically close the feedback window |
The Esc key always closes the window.
Security & limits
Section titled “Security & limits”- Domain whitelist — submissions are only accepted when the request Origin / Referer matches your project’s whitelist; the widget also refuses to submit from unauthorized pages
- Rate limits — 30 reports / day / project and 10 reports / day / IP; the widget shows a friendly message when the limit is reached
- Screenshot format — PNG up to 10 MB; URLs are desensitized (sensitive query values such as tokens are masked) before storage
Does the widget slow down my site?
The script is small, loaded with async, and rendered inside a Shadow DOM. Diagnostics collection is passive (error listeners) — no polling, no network chatter until a report is submitted.
Can visitors without the Chrome extension use it?
Yes — that’s the point of the SDK. If the extension happens to be installed, the SDK delegates capture to it for pixel-perfect screenshots; otherwise it falls back to an in-page capture.
I changed the domain whitelist — do I need to re-embed?
No. Whitelist, routing and widget settings take effect immediately; the embed code never changes.
Where do SDK reports show up?
In Feedback (tagged SDK, filterable by project) and in any integration bound to the project. Reports follow the owner’s 90-day retention.
Is the SDK key secret?
No. It only identifies the project; the domain whitelist and rate limits provide the actual protection. Rotate it by deleting and re-creating the project.
Why can’t the SDK identify the signed-in user automatically?
Because the SDK runs in the visitor’s browser, while your login state lives in your own server / front-end state — and there is no standard convention linking the two. BugCapturer also does not read your site’s cookies or localStorage to “guess” identity: that is a privacy red line and keeps your site friendly to security reviews. Passing identity explicitly is the most robust and controllable approach (the same model used by Capture.dev, Shakebugs and Sentry).
Can I just put my own name into reporter in the embed snippet?
No. That makes every report carry the same name no matter who submitted it. Do it properly as shown in Step 3: server-rendered sites template reporter per user; SPAs call BugCapturer.identify() after login.
When exactly should an SPA call identify()?
After a successful login, and again whenever the page (re)loads and user data becomes available; re-call it after switching accounts to overwrite. If your call may run before the SDK has finished loading, wrap it with BugCapturer.ready(fn) or the bugcapturer:ready event (see the identifyReporter helper in Step 3, Case B). Do not rely on a bare if (window.BugCapturer) check — it silently skips when the SDK isn’t loaded yet, so the identity never takes effect.
Can I pass only name and no email?
Yes. At least one of uid / name / email is enough to count as identified; missing fields are simply left empty. When email is provided, the form’s “Contact” field is hidden automatically.
Identity always shows as anonymous — how do I debug it?
① Confirm identify() is called after login; ② run window.BugCapturer in the console to confirm the SDK loaded; ③ confirm the values are non-empty strings; ④ check that your bound Feishu Bitable has the reporter name / email columns configured (a missing column is skipped silently, not an error).