","url":"https://docs.bugpin.io/widget/installation#method-1-script-tag-recommended"},{"@type":"HowToStep","position":3,"name":"Verify the widget loads","text":"Open your website and look for the BugPin floating button in the bottom-right corner. Click it to test submitting a bug report.","url":"https://docs.bugpin.io/widget/installation"}]}
Skip to main content

Widget Installation

The BugPin widget is a lightweight bug reporting tool (under 50KB gzipped) that can be added to any website with a single script tag or installed as an npm package. It captures screenshots, lets users annotate issues with drawing tools, and submits reports to your self-hosted BugPin server. This guide covers both installation methods and includes framework-specific examples for React, Next.js, Vue, Angular, and .NET.

Installation Methods

Add the following script tag to your HTML, just before the closing </body> tag:

<script
src="https://your-bugpin-server.com/widget.js"
data-api-key="your-project-api-key"
async
></script>

Replace:

  • your-bugpin-server.com with your BugPin server URL
  • your-project-api-key with the API key from your project settings

The widget automatically detects the server URL from the script source - no additional configuration needed!

Method 2: npm Package

Install the widget as an npm package for use with bundlers (webpack, Vite, etc.):

npm install @arantic/bugpin-widget

Then import and initialize in your application:

import BugPin from '@arantic/bugpin-widget';

await BugPin.init({
apiKey: 'your-project-api-key',
serverUrl: 'https://your-bugpin-server.com'
});
info

When using the npm package, you must provide the serverUrl parameter because the widget code is bundled into your application. Unlike the script tag method where the server URL is automatically detected from the script source, npm usage requires explicit configuration.

TypeScript Support:

import BugPin from '@arantic/bugpin-widget';

await BugPin.init({
apiKey: 'your-project-api-key',
serverUrl: 'https://your-bugpin-server.com'
});

Note: Widget appearance (theme, position, colors, button text) is configured in the BugPin Admin Console and automatically fetched by the widget. You only need to provide the API key and server URL in your code.

Using Environment Variables (Recommended):

For production applications, store your API key in environment variables:

// Vite (React, Vue)
await BugPin.init({
apiKey: import.meta.env.VITE_BUGPIN_API_KEY,
serverUrl: import.meta.env.VITE_BUGPIN_SERVER_URL
});

// Next.js
await BugPin.init({
apiKey: process.env.NEXT_PUBLIC_BUGPIN_API_KEY,
serverUrl: process.env.NEXT_PUBLIC_BUGPIN_SERVER_URL
});

Then add to your .env file:

# Vite (React, Vue) - .env
VITE_BUGPIN_API_KEY=your-project-api-key
VITE_BUGPIN_SERVER_URL=https://bugpin.example.com

# Next.js - .env.local
NEXT_PUBLIC_BUGPIN_API_KEY=your-project-api-key
NEXT_PUBLIC_BUGPIN_SERVER_URL=https://bugpin.example.com

Fetch from Backend:

For added security, fetch the configuration from your backend:

async function initBugPin() {
const response = await fetch('/api/bugpin-config');
const config = await response.json();

await BugPin.init({
apiKey: config.apiKey,
serverUrl: config.serverUrl
});
}

initBugPin();

Programmatic Initialization

For more control (e.g., initializing after user login), you can initialize the widget programmatically:

<script src="https://bugpin.example.com/widget.js" async></script>
<script>
window.addEventListener('load', async function() {
if (window.BugPin) {
await window.BugPin.init({
apiKey: 'your-project-api-key',
serverUrl: 'https://bugpin.example.com' // Optional - auto-detected from script src
});
}
});
</script>
tip

When loading via script tag, serverUrl is optional and will be automatically detected from the script's source URL. Only specify it if you need to override the default behavior.

JavaScript API

The widget exposes a global BugPin object for programmatic control. Use this when you want to:

  • Custom trigger: Hide the default button and open the reporter from your own UI (e.g., a "Report Bug" menu item)
  • Contextual triggers: Automatically open the reporter after an error occurs
  • Keyboard shortcuts: Trigger the reporter with a hotkey

Available methods:

BugPin.init(config)

Initialize the widget by telling it which project to connect to. The widget then fetches all appearance settings (position, theme, colors) from the Admin Console automatically.

Parameters:

  • apiKey (required): Your project's API key
  • serverUrl (optional when using script tag, required for npm package): BugPin server URL

Returns: Promise<void> - Resolves when the widget is initialized and config is fetched from the server.

await BugPin.init({
apiKey: 'your-project-api-key',
serverUrl: 'https://bugpin.example.com' // Auto-detected if loaded via script tag
});

BugPin.open()

Open the bug report dialog programmatically.

BugPin.open();

BugPin.close()

Close the bug report dialog programmatically.

BugPin.close();

Example: Custom Trigger Button

// Your own "BugPin" button in your app's UI
document.getElementById('bugpin-launcher-btn').addEventListener('click', () => {
BugPin.open();
});

// Or with a keyboard shortcut (Ctrl+Shift+B)
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.shiftKey && e.key === 'B') {
BugPin.open();
}
});

Single Page Applications (SPA)

The widget works with SPAs built with React, Vue, Angular, and other frameworks. It automatically detects route changes and captures the current URL.

React / Vite (npm)

import { useEffect } from 'react';
import BugPin from '@arantic/bugpin-widget';

function App() {
useEffect(() => {
BugPin.init({
apiKey: import.meta.env.VITE_BUGPIN_API_KEY,
serverUrl: import.meta.env.VITE_BUGPIN_SERVER_URL
});

return () => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
};
}, []);

return <div>Your app content</div>;
}

Note: While BugPin.init() returns a Promise, you don't need to await it in useEffect. The widget initializes in the background and appears when ready.

React / Vite (script tag)

import { useEffect } from 'react';

function App() {
useEffect(() => {
const script = document.createElement('script');
script.src = import.meta.env.VITE_BUGPIN_SERVER_URL + '/widget.js';
script.async = true;
script.setAttribute('data-api-key', import.meta.env.VITE_BUGPIN_API_KEY);
document.body.appendChild(script);

return () => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
};
}, []);

return <div>Your app content</div>;
}

Next.js (npm)

import { useEffect } from 'react';
import BugPin from '@arantic/bugpin-widget';

function App() {
useEffect(() => {
BugPin.init({
apiKey: process.env.NEXT_PUBLIC_BUGPIN_API_KEY,
serverUrl: process.env.NEXT_PUBLIC_BUGPIN_SERVER_URL
});

return () => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
};
}, []);

return <div>Your app content</div>;
}

Next.js (script tag)

import { useEffect } from 'react';

function App() {
useEffect(() => {
const script = document.createElement('script');
script.src = process.env.NEXT_PUBLIC_BUGPIN_SERVER_URL + '/widget.js';
script.async = true;
script.setAttribute('data-api-key', process.env.NEXT_PUBLIC_BUGPIN_API_KEY);
document.body.appendChild(script);

return () => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
};
}, []);

return <div>Your app content</div>;
}

Vue / Nuxt (npm)

<script setup>
import { onMounted, onUnmounted } from 'vue';
import BugPin from '@arantic/bugpin-widget';

onMounted(() => {
BugPin.init({
apiKey: import.meta.env.VITE_BUGPIN_API_KEY,
serverUrl: import.meta.env.VITE_BUGPIN_SERVER_URL
});
});

onUnmounted(() => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
});
</script>

Vue / Nuxt (script tag)

<script setup>
import { onMounted, onUnmounted } from 'vue';

onMounted(() => {
const script = document.createElement('script');
script.src = import.meta.env.VITE_BUGPIN_SERVER_URL + '/widget.js';
script.async = true;
script.setAttribute('data-api-key', import.meta.env.VITE_BUGPIN_API_KEY);
document.body.appendChild(script);
});

onUnmounted(() => {
const widget = document.getElementById('bugpin-widget');
if (widget) widget.remove();
});
</script>

.NET Examples

ASP.NET Core / Razor Pages

Add to your _Layout.cshtml or _Host.cshtml:

<!-- In Shared/_Layout.cshtml -->
<!DOCTYPE html>
<html>
<head>
<!-- Your head content -->
</head>
<body>
@RenderBody()

<!-- BugPin Widget -->
<script
src="https://bugpin.example.com/widget.js"
data-api-key="@Configuration["BugPin:ApiKey"]"
async
></script>
</body>
</html>

Configure the API key in appsettings.json:

{
"BugPin": {
"ApiKey": "your-project-api-key",
"ServerUrl": "https://bugpin.example.com"
}
}

Blazor Server

Add to Pages/_Host.cshtml:

<!DOCTYPE html>
<html>
<head>
<!-- Your head content -->
</head>
<body>
<app>@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))</app>

<!-- BugPin Widget -->
<script
src="@Configuration["BugPin:ServerUrl"]/widget.js"
data-api-key="@Configuration["BugPin:ApiKey"]"
async
></script>
</body>
</html>

Blazor WebAssembly

Add to wwwroot/index.html:

<!DOCTYPE html>
<html>
<head>
<!-- Your head content -->
</head>
<body>
<div id="app">Loading...</div>

<!-- BugPin Widget -->
<script
src="https://bugpin.example.com/widget.js"
data-api-key="your-project-api-key"
async
></script>
</body>
</html>

For dynamic initialization in Blazor, use JavaScript interop:

// Services/BugPinService.cs
public class BugPinService
{
private readonly IJSRuntime _js;

public BugPinService(IJSRuntime js)
{
_js = js;
}

public async Task InitializeAsync(string apiKey, string serverUrl)
{
await _js.InvokeVoidAsync("BugPin.init", new
{
apiKey = apiKey,
serverUrl = serverUrl
});
}

public async Task OpenAsync()
{
await _js.InvokeVoidAsync("BugPin.open");
}
}

Content Security Policy (CSP)

If your site uses a Content Security Policy, you need to add directives to allow the BugPin widget to function properly.

Required Directives

Add these directives to your existing CSP configuration:

script-src 'self' https://your-bugpin-server.com;
connect-src 'self' https://your-bugpin-server.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
media-src 'self' data:;

Directive Breakdown:

  • script-src: Allows the widget JavaScript to load from your BugPin server
  • connect-src: Allows API requests to submit reports and fetch configuration
  • style-src: Allows inline styles used by the widget (Shadow DOM encapsulation)
  • img-src: Allows screenshots captured as data URLs
  • media-src: Allows video recordings/uploads as data URLs

Implementation Methods

You can add CSP directives using either HTTP headers (recommended) or HTML meta tags.

Add the Content-Security-Policy header to your server responses. The implementation varies by server:

Apache (.htaccess or httpd.conf):

Header set Content-Security-Policy "script-src 'self' https://your-bugpin-server.com; connect-src 'self' https://your-bugpin-server.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:;"

Nginx (nginx.conf):

add_header Content-Security-Policy "script-src 'self' https://your-bugpin-server.com; connect-src 'self' https://your-bugpin-server.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:;";

Node.js / Express:

app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"script-src 'self' https://your-bugpin-server.com; connect-src 'self' https://your-bugpin-server.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:;"
);
next();
});

Next.js (next.config.js):

module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Content-Security-Policy',
value: "script-src 'self' https://your-bugpin-server.com; connect-src 'self' https://your-bugpin-server.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:;"
}
]
}
];
}
};

ASP.NET Core (Startup.cs or Program.cs):

app.Use(async (context, next) =>
{
context.Response.Headers.Add(
"Content-Security-Policy",
"script-src 'self' https://your-bugpin-server.com; connect-src 'self' https://your-bugpin-server.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:;"
);
await next();
});

Django (settings.py with django-csp):

CSP_SCRIPT_SRC = ["'self'", "https://your-bugpin-server.com"]
CSP_CONNECT_SRC = ["'self'", "https://your-bugpin-server.com"]
CSP_STYLE_SRC = ["'self'", "'unsafe-inline'"]
CSP_IMG_SRC = ["'self'", "data:"]
CSP_MEDIA_SRC = ["'self'", "data:"]

Method 2: HTML Meta Tag

If you cannot modify server headers, add a <meta> tag to your HTML <head>:

<meta http-equiv="Content-Security-Policy"
content="script-src 'self' https://your-bugpin-server.com;
connect-src 'self' https://your-bugpin-server.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
media-src 'self' data:;">
warning

HTTP headers are preferred over meta tags. Some CSP directives (like frame-ancestors) don't work in meta tags.

Merging with Existing CSP

If you already have a CSP, merge the BugPin directives with your existing ones:

Example - Existing CSP:

script-src 'self' https://cdn.example.com;
img-src 'self' https://images.example.com;

Merged with BugPin:

script-src 'self' https://cdn.example.com https://your-bugpin-server.com;
img-src 'self' https://images.example.com data:;
connect-src 'self' https://your-bugpin-server.com;
style-src 'self' 'unsafe-inline';
media-src 'self' data:;

Next Steps

We use cookies for analytics to improve our website. More information in our Privacy Policy.