HTML Feedback Widget
New to BetaHub? BetaHub is a bug tracking and feedback platform for game developers. This widget adds a feedback button to your web game or application. Learn more →
A lightweight, embeddable feedback widget for web games and applications. Allow your users to submit bug reports, feature suggestions, and support tickets directly from your app with a beautiful, non-intrusive interface.

Overview
The BetaHub HTML Widget is a single-file JavaScript solution that adds a floating feedback button to your web game or application. When clicked, it opens a modal where users can report bugs, suggest features, or request support.
Perfect for:
- Browser-based games (PixiJS, Phaser, Three.js, Babylon.js)
- Unity WebGL exports
- Web applications with player/user feedback needs
- Any HTML5 project needing feedback collection
Key Benefits:
- Zero Dependencies - Pure vanilla JavaScript, no libraries required
- Isolated Styling - Uses Shadow DOM to prevent CSS conflicts with your game
- Lightweight - Single file, minimal performance impact
- Clean Design - Pastel blue light theme with professional, clean UI
- Flexible Contact Options - 5 different modes for handling user emails
- Custom Metadata - Attach game version, player level, and any contextual data
Quick Start
1. Include the Widget
Option A: CDN (Recommended - Fastest)
<script src="https://cdn.betahub.io/widget/latest/betahub-widget.js"></script>
Option B: Self-Hosted
Download betahub-widget.js from the latest GitHub release and include it:
<script src="path/to/betahub-widget.js"></script>
Which should I use?
- CDN: Faster setup, automatic updates, better caching, no downloads needed
- Self-hosted: More control, works offline, no external dependency
2. Initialize the Widget
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-auth-token'
});
That’s it! A feedback button will appear in the bottom-right corner of your page.
3. Get Your Credentials
- Go to your BetaHub project dashboard
- Navigate to Settings → Integrations → Auth Tokens tab
- Create a new auth token with these permissions:
can_create_bug_reportcan_create_feature_requestcan_create_support_ticket
- Copy your project ID (shown at top) and the generated token
- Use them in the widget initialization
Contact Information Modes
The widget offers five different ways to handle user contact information, allowing you to choose the approach that best fits your use case.
Quick Decision Guide
| Your Situation | Use This Mode | Configuration |
|---|---|---|
| Public game, anonymous players | Anonymous (Mode 1) | Default - no email config |
| Closed beta, known testers | Required Email (Mode 2) | requireEmail: true |
| Users logged in, want transparency | Prefilled Visible (Mode 3) | userEmail + showEmailField: 'always' |
| Users logged in, prefer clean UI | Prefilled Hidden (Mode 4) | userEmail + showEmailField: 'never' |
| Not sure / mixed scenarios | Prefilled Auto (Mode 5) | userEmail only (default) |
Still not sure? Start with Prefilled Auto (Mode 5) - it adapts intelligently based on whether users are logged in.
Detailed Mode Explanations
Mode 1: Anonymous (Default)
Best for public feedback where user identity is optional.
BetaHubWidget.init({
projectId: 'pr-123',
authToken: 'tkn-abc'
// No email configuration
});
Behavior:
- Bug Reports & Suggestions: No email field shown, submissions are anonymous
- Support Tickets: Email field shown and required (support always needs contact info)
Use when:
- You want to lower the barrier for feedback
- User identity isn’t critical for bugs/suggestions
- You have a public game with anonymous players
Mode 2: Required Email
Best for tracking all feedback to specific users.
BetaHubWidget.init({
projectId: 'pr-123',
authToken: 'tkn-abc',
requireEmail: true
});
Behavior:
- All Feedback Types: Email field shown and required
Use when:
- You need to follow up on all feedback
- You want to associate feedback with user accounts
- You’re running a closed beta with known testers
Mode 3: Prefilled Visible Email
Best for logged-in users where email is known and you want transparency.
BetaHubWidget.init({
projectId: 'pr-123',
authToken: 'tkn-abc',
userEmail: 'player@example.com',
showEmailField: 'always'
});
Behavior:
- All Feedback Types: Email shown as read-only (user sees but cannot edit)
Use when:
- Users are logged into your game/app
- You want users to see what email will be used
- Transparency is important to your users
Mode 4: Prefilled Hidden Email
Best for silent user tracking without showing email in the UI.
BetaHubWidget.init({
projectId: 'pr-123',
authToken: 'tkn-abc',
userEmail: 'player@example.com',
showEmailField: 'never'
});
Behavior:
- All Feedback Types: Email sent in authorization header but not visible in UI
Use when:
- Users are logged in and you have their email
- You want a cleaner UI without showing email
- You want to track feedback by user without UI clutter
Mode 5: Prefilled Auto (Smart Default)
Best for most use cases - automatically shows email when provided.
BetaHubWidget.init({
projectId: 'pr-123',
authToken: 'tkn-abc',
userEmail: 'player@example.com'
// showEmailField defaults to 'auto'
});
Behavior:
- All Feedback Types: Shows read-only email field automatically when
userEmailis provided - If
userEmailis not provided, behaves like Anonymous mode
Use when:
- You want smart defaults without overthinking configuration
- Some users are logged in, others aren’t
- You want the widget to handle email display intelligently
How Virtual Users Work
When you provide userEmail, the widget:
- Sends the email in the authorization header:
FormUser tkn-abc,email:user@example.com - BetaHub creates or reuses a “virtual user” linked to that email
- All feedback from that email is grouped together in BetaHub
- Users can receive email notifications about their feedback updates
- You can search and filter feedback by user email in BetaHub
Note: Support tickets always require contact information. If using anonymous mode, users must manually enter their email for support tickets.
Configuration
Required Parameters
| Parameter | Type | Description |
|---|---|---|
projectId |
string | Your BetaHub project ID (format: pr-...) |
authToken |
string | Authentication token with appropriate permissions (format: tkn-...) |
Optional Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
apiBaseUrl |
string | 'https://app.betahub.io' |
BetaHub API endpoint (change for self-hosted instances) |
releaseLabel |
string | null |
Version label for bug reports (auto-creates release if doesn’t exist). If not provided, bugs are assigned to the project’s latest release |
position |
string | 'bottom-right' |
Button position: 'bottom-right', 'bottom-left', 'top-right', 'top-left' |
buttonText |
string | 'Feedback' |
Custom text for the floating button |
userEmail |
string | null |
Pre-filled user email (creates/links virtual user) |
requireEmail |
boolean | false |
Require email for bugs/suggestions (tickets always require email) |
showEmailField |
string | 'auto' |
Email field visibility: 'auto', 'always', 'never' |
customFields |
object | {} |
Custom metadata included with every submission |
Full Configuration Example
BetaHubWidget.init({
// Required
projectId: 'pr-1234567890',
authToken: 'tkn-your-auth-token-here',
// Optional - API & UI
apiBaseUrl: 'https://app.betahub.io',
releaseLabel: '1.2.3', // Version label (auto-creates release if not exists)
position: 'bottom-left',
buttonText: '🐛 Report Bug',
// Optional - Contact Information
userEmail: 'player@example.com',
requireEmail: false,
showEmailField: 'auto',
// Optional - Custom Metadata
customFields: {
platform: 'web',
playerLevel: 15,
currentScene: 'battle-arena',
sessionId: 'abc-123-def-456',
buildType: 'release'
}
});
These custom fields are automatically included with every bug report, feature request, and support ticket submission. Note that custom fields must be predefined in your BetaHub project settings (Settings → Custom Fields) and marked as “Tester Settable” to accept values from the widget.
Feedback Types
The widget supports three types of feedback:
Bug Reports
For crashes, errors, and unexpected behavior.
Required Fields:
- Description (max 2000 characters)
- Steps to Reproduce (max 1000 characters)
- Email (only if
requireEmail: trueor using prefilled email)
API Endpoint: POST /projects/{projectId}/issues.json
Best Practices:
- Encourage users to be specific about what happened
- Steps to reproduce help developers fix bugs faster
- Automatic metadata (game version, scene, etc.) provides critical context
Feature Suggestions
For feature requests, improvements, and ideas.
Required Fields:
- Description (max 2000 characters)
- Email (only if
requireEmail: trueor using prefilled email)
API Endpoint: POST /projects/{projectId}/feature_requests.json
Best Practices:
- Let users explain why they want the feature
- Custom metadata helps prioritize features by player segment
- Anonymous suggestions encourage more honest feedback
Support Tickets
For help requests, questions, and general support.
Required Fields:
- Description (max 2000 characters)
- Email (always required - support needs contact info)
API Endpoint: POST /projects/{projectId}/tickets.json
Best Practices:
- Support always requires email for follow-up
- If using anonymous mode, users must manually enter email
- If using prefilled email modes, email is automatically included
Game Engine Integration
PixiJS
// Add to your game initialization
const app = new PIXI.Application();
// Initialize BetaHub widget
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
userEmail: currentUser.email, // If user is logged in
customFields: {
gameVersion: '1.0.0',
renderer: app.renderer.type === PIXI.RENDERER_TYPE.WEBGL ? 'WebGL' : 'Canvas',
fps: app.ticker.FPS.toFixed(2)
}
});
Phaser
// In your game's create() function
class GameScene extends Phaser.Scene {
create() {
// Your game setup...
// Initialize BetaHub widget
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
customFields: {
gameVersion: '1.0.0',
scene: this.scene.sys.settings.key, // Access scene key via sys.settings
worldTime: this.time.now // Milliseconds since scene started
}
});
}
}
Unity WebGL
<!-- In your Unity WebGL HTML template (Unity 2020.1+) -->
<!DOCTYPE html>
<html>
<head>
<!-- BetaHub widget - include via CDN or self-hosted -->
<script src="https://cdn.betahub.io/widget/latest/betahub-widget.js"></script>
</head>
<body>
<canvas id="unity-canvas"></canvas>
<script src="Build/build.loader.js"></script>
<script>
const canvas = document.querySelector("#unity-canvas");
const config = {
dataUrl: "Build/build.data",
frameworkUrl: "Build/build.framework.js",
codeUrl: "Build/build.wasm",
};
// Initialize Unity with loading progress callback
createUnityInstance(canvas, config, (progress) => {
console.log(`Loading: ${Math.round(progress * 100)}%`);
}).then((unityInstance) => {
console.log("Unity loaded successfully");
// Initialize BetaHub after Unity loads
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
customFields: {
unityVersion: unityInstance.Module.unityVersion || 'unknown',
platform: 'webgl',
buildType: 'release'
}
});
}).catch((error) => {
console.error("Unity failed to load:", error);
});
</script>
</body>
</html>
Note: Unity 2020.1+ uses a different loader API than earlier versions. If you’re using Unity 2019 or earlier, refer to the Unity documentation for the appropriate loader syntax.
Three.js
// After setting up your Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
// Initialize BetaHub widget
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
customFields: {
gameVersion: '1.0.0',
renderer: 'WebGL',
drawCalls: renderer.info.render.calls,
triangles: renderer.info.render.triangles
}
});
React
import { useEffect } from 'react';
function App() {
useEffect(() => {
// Load widget script
const script = document.createElement('script');
script.src = '/betahub-widget.js';
script.onload = () => {
window.BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
userEmail: currentUser?.email, // From your auth system
customFields: {
appVersion: '1.0.0',
environment: process.env.NODE_ENV,
userId: currentUser?.id
}
});
};
document.body.appendChild(script);
return () => {
// Cleanup - remove script and widget container
if (document.body.contains(script)) {
document.body.removeChild(script);
}
const container = document.querySelector('#betahub-widget-container');
if (container && document.body.contains(container)) {
document.body.removeChild(container);
}
};
}, []);
return <div>Your App</div>;
}
Programmatic API
The widget provides two main programmatic methods for runtime control:
Opening the Widget
You can open the widget programmatically from your code:
// Open the feedback widget
BetaHubWidget.open();
Use Cases:
Custom Feedback Buttons:
document.getElementById('report-bug-btn').addEventListener('click', function() {
BetaHubWidget.open();
});
Keyboard Shortcuts:
document.addEventListener('keydown', function(e) {
// Press F1 to open feedback
if (e.key === 'F1') {
e.preventDefault();
BetaHubWidget.open();
}
});
Updating Custom Fields Dynamically
Update custom fields after initialization to capture real-time game state when feedback is submitted:
// Update custom fields as game state changes
BetaHubWidget.updateCustomFields({
level: '5',
score: '1250',
health: '75'
});
This method is particularly useful for:
- Capturing current game state (level, score, health) when feedback is submitted
- Updating player progress dynamically during gameplay
- Including real-time context with bug reports without re-initializing the widget
How it works: updateCustomFields() merges new fields with existing customFields (doesn’t replace them). Call this method anytime after init() to update values. Updated fields will be included in the next feedback submission.
Example: Dynamic Game State Tracking
// Initialize once at game start
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
customFields: {
gameVersion: '1.0.0',
platform: 'web'
}
});
// Update dynamically as player progresses
function onLevelChange(newLevel) {
BetaHubWidget.updateCustomFields({
currentLevel: newLevel.toString(),
levelStartTime: Date.now()
});
}
function onScoreUpdate(newScore) {
BetaHubWidget.updateCustomFields({
score: newScore.toString()
});
}
function onHealthChange(newHealth) {
BetaHubWidget.updateCustomFields({
health: newHealth.toString()
});
}
Example: Context-Specific Feedback
// When player encounters an error in a specific level
function onLevelError(levelId, errorType) {
// Update custom fields with error context
BetaHubWidget.updateCustomFields({
errorLevel: levelId,
errorType: errorType,
errorTimestamp: Date.now()
});
// Open widget for user to report the issue
BetaHubWidget.open();
}
Notes:
- All custom field values should be strings for consistency
- Fields are merged, not replaced - existing fields remain unless overwritten
- Custom fields must be predefined in BetaHub project settings (Settings → Custom Fields) and marked as “Tester Settable”
Custom Metadata
Custom fields allow you to attach contextual data to all feedback submissions (bug reports, feature requests, and support tickets). This is incredibly powerful for debugging and prioritizing feedback.
Setup Required: Custom fields must be created in your BetaHub project settings (Settings → Custom Fields) and marked as “Tester Settable” before the widget can use them. See game engine integration guide for step-by-step instructions.
Version Labels vs Custom Fields: Use the releaseLabel parameter to assign bug reports to specific releases in BetaHub (e.g., releaseLabel: '1.2.3'). This creates or assigns to a release, which you can track separately. If releaseLabel is not provided, bug reports are automatically assigned to your project’s latest release. Custom fields like buildNumber or buildType are additional metadata for your own tracking purposes.
Common Use Cases
Game Context:
customFields: {
// Build tracking (metadata - different from releaseLabel)
buildNumber: '2025.11.01',
buildType: 'release', // 'debug', 'release', 'beta'
// Player context
playerLevel: 15,
playerClass: 'warrior',
playTime: 3600, // seconds
// Game state
currentScene: 'battle-arena',
difficulty: 'hard',
checkpoint: 'boss-3',
// Technical info
platform: 'web',
browser: navigator.userAgent,
sessionId: 'unique-session-id'
}
Performance Metrics:
customFields: {
fps: currentFPS,
memoryUsed: performance.memory?.usedJSHeapSize,
loadTime: window.performance.timing.loadEventEnd - window.performance.timing.navigationStart
}
A/B Testing:
customFields: {
experimentGroup: 'variant-b',
featureFlags: JSON.stringify(activeFeatureFlags)
}
Viewing Custom Fields in BetaHub
All custom fields appear in the bug report details page in BetaHub, allowing you to:
- Filter and search by custom field values
- See patterns across similar bugs (e.g., all bugs on level 5)
- Prioritize based on affected player segments
- Debug with full context of the game state
Customization
Button Position
Position the feedback button in any corner:
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
position: 'bottom-left' // or 'top-right', 'top-left', 'bottom-right'
});
Button Text
Customize the button label:
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
buttonText: '🐛 Report Issue'
});
Color Palette
The widget uses a fixed pastel blue light theme. To customize colors, edit the getStyles() method in betahub-widget.js:
// Find and replace these colors:
// Primary brand color
background: #237390; // Change to your brand color
// Button hover state
background: #1E627B; // Darker shade of your brand color
// Button active state
background: #144252; // Even darker shade
Color Guidelines:
- Maintain sufficient contrast for accessibility (WCAG AA: 4.5:1 minimum)
- Test hover and active states for visibility
- Keep semantic colors (success, error, warning) distinct from brand colors
Browser Compatibility
The widget requires browsers with Shadow DOM support:
- Chrome: 53+
- Firefox: 63+
- Safari: 10.1+
- Edge: 79+
- Opera: 40+
These browsers cover 95%+ of modern web users. For older browsers, the widget will gracefully fail to load without breaking your game.
Security
Token Security
Best Practices:
- Never commit auth tokens to version control
- Use environment-specific tokens (dev/staging/production)
- Rotate tokens regularly from the BetaHub dashboard
- Use different tokens for different environments
Example - Environment Variables:
BetaHubWidget.init({
projectId: process.env.BETAHUB_PROJECT_ID,
authToken: process.env.BETAHUB_AUTH_TOKEN
});
Rate Limiting
To prevent abuse, BetaHub enforces rate limits:
- Default: 8 submissions per day per feedback type per IP address
- Customizable: Adjust limits in your BetaHub project settings
- Behavior: When limit is reached, users see a friendly error message
CORS Configuration
The widget uses CORS for API requests. BetaHub automatically allows requests from:
- Any origin (for public projects)
- Whitelisted domains (configurable in project settings)
If you encounter CORS errors:
- Check your project CORS settings in BetaHub
- Ensure you’re using HTTPS (HTTP may be blocked)
- Verify your domain is whitelisted if using domain restrictions
Troubleshooting
Widget Not Appearing
Check Console for Errors:
// Open browser console (F12) and look for errors
Common Causes:
- Invalid
projectIdorauthToken - Script not loaded (check network tab)
- JavaScript errors in your game blocking widget initialization
Solution:
// Test with a minimal setup first
BetaHubWidget.init({
projectId: 'pr-your-id',
authToken: 'tkn-your-token'
});
Submissions Not Arriving
Check Network Tab:
- Look for failed POST requests to BetaHub API
- Check response status codes
Common Causes:
- Invalid auth token or expired token
- Missing required permissions on token
- CORS issues (see CORS section above)
- Rate limit exceeded
Solution:
- Verify token has correct permissions in BetaHub dashboard
- Check rate limits in project settings
- Test with a fresh token
Styling Conflicts
The widget uses Shadow DOM to prevent CSS conflicts, but if you experience issues:
Common Causes:
- Global CSS overriding Shadow DOM (rare)
- z-index conflicts with your game UI
Solution:
/* If the button is hidden behind your game */
#betahub-widget-container {
z-index: 10000 !important;
}
Email Field Not Showing/Hiding Correctly
Check Configuration:
// Mode 1: Anonymous (no email for bugs/suggestions)
BetaHubWidget.init({ projectId: '...', authToken: '...' });
// Mode 2: Required (email required for all)
BetaHubWidget.init({ projectId: '...', authToken: '...', requireEmail: true });
// Mode 3: Prefilled visible
BetaHubWidget.init({ projectId: '...', authToken: '...', userEmail: 'user@example.com', showEmailField: 'always' });
// Mode 4: Prefilled hidden
BetaHubWidget.init({ projectId: '...', authToken: '...', userEmail: 'user@example.com', showEmailField: 'never' });
// Mode 5: Prefilled auto (smart default)
BetaHubWidget.init({ projectId: '...', authToken: '...', userEmail: 'user@example.com' });
Remember: Support tickets always require email, regardless of configuration.
Advanced Usage
Dynamic Configuration Updates
You can reinitialize the widget with different settings:
// Initial setup
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token'
});
// Later, when user logs in
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
userEmail: loggedInUser.email,
customFields: {
userId: loggedInUser.id,
playerLevel: loggedInUser.level
}
});
Note: Calling init() multiple times may create duplicate widget instances. For single-page applications, initialize the widget once and avoid re-initialization unless necessary. If you need to update custom fields dynamically, use the updateCustomFields() method instead of re-initializing.
Conditional Loading
Only load the widget in specific environments:
// Only in production
if (process.env.NODE_ENV === 'production') {
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token'
});
}
// Only for beta testers
if (user.isBetaTester) {
BetaHubWidget.init({
projectId: 'your-project-id',
authToken: 'tkn-your-token',
userEmail: user.email
});
}
Tracking Submission Success
The widget doesn’t expose submission callbacks, but you can monitor network requests:
// Listen for successful submissions
window.addEventListener('betahub-submission-success', function(e) {
console.log('Feedback submitted successfully!');
// Show custom thank you message
});
Note: This event is not yet implemented in the current version but is planned for future releases.
File Structure
betahub-html-widget/
├── betahub-widget.js # Main widget (single file, zero dependencies)
├── demo.html # Live demo with documentation
└── README.md # Technical documentation
Next Steps
- View API Documentation for advanced integrations
- Configure Webhooks to get real-time notifications
- Set Up External Tools to sync feedback with Jira, Asana, etc.
- Explore Unity Plugin for native games
Getting Help
For issues with the HTML widget:
- GitHub Issues - Report bugs or request features
- Discord Community - Get help from the community
- Support - Contact BetaHub support team
The HTML Feedback Widget is open source and contributions are welcome! Visit the GitHub repository to contribute.