<gh-feedback>Add a feedback button to directly file GitHub Issues from any website.
| Style | |
| Icon | |
| Size | |
| Color | |
| Border | |
| Position (FAB only) |
| Theme | |
| Color | |
| Categories |
Copy gh-feedback.js into your project and add to your HTML:
<script type="module" src="gh-feedback.js"></script>
<gh-feedback
repo="https://github.com/you/repo"
endpoint="https://your-worker.workers.dev/feedback"
></gh-feedback>
A hosted copy is also available as a convenience:
<script type="module" src="https://gh-feedback.synapticnoise.com/gh-feedback.js"></script>
The component needs a backend to securely file GitHub Issues (so your token stays server-side). See Proxy Setup below, or use any backend that accepts the JSON payload.
| Attribute | Type | Default | Description |
|---|---|---|---|
repo |
string | -- | GitHub repository URL or owner/repo format. Required. |
endpoint |
string | -- | URL of the proxy endpoint (recommended). Component POSTs JSON here. |
token |
string | -- | GitHub PAT for direct mode. Exposed in page source -- use proxy mode for public sites. |
trigger |
string | button |
Trigger style: button (inline button), fab (floating action button), link (text link), or none (no trigger; open programmatically). |
icon |
string | memo |
Icon: chat, bug, megaphone, lightbulb, pencil, flag, memo, none. |
size |
string | md |
Size: sm, md, lg, or a number for pixel size (e.g. 40). |
color |
string | #238636 |
Primary color (hex). Sets the FAB, button, and accent color. |
position |
string | bottom-right |
FAB corner position: bottom-right, bottom-left, top-right, top-left. Only applies when trigger="fab". |
theme |
string | light |
Popup color theme: light or dark. |
labels |
string | -- | Comma-separated labels added to every issue (e.g. "feedback,ui"). |
button-text |
string | -- | Text shown on the trigger. For FAB, makes it pill-shaped. |
no-severity |
boolean | -- | When present, hides the severity selector. Severity (Low / Medium / High / Critical) is shown by default for bug reports. |
types |
string | bug,feature,question |
Comma-separated list of feedback types to show. Available: bug, feature, question, ui, docs, performance. |
border-color |
string | -- | Border color on the trigger element. |
border-width |
string | -- | Border width in pixels on the trigger element. |
border-radius |
string | -- | Border radius in pixels on the trigger element (overrides default). |
popup-color |
string | -- | Accent color used inside the popup (e.g. submit button). Defaults to the primary color. |
no-type-icons |
boolean | -- | When present, hides the icons next to feedback type names in the popup. |
The proxy keeps your GitHub token on the server. Users never see it. The included backend runs on Cloudflare Workers (free tier) and deploys with Wrangler, Cloudflare's CLI tool.
Wrangler is the CLI for Cloudflare Workers. You don't need to install it separately — npx runs it directly. Log in to connect it to your Cloudflare account:
npx wrangler login
This opens your browser for authentication. Once approved, Wrangler stores your credentials locally.
Copy the worker/ directory from this repo into your project, or start fresh:
mkdir gh-feedback-worker && cd gh-feedback-worker
cp -r /path/to/gh-feedback/worker/* .
npm init -y
Open wrangler.toml and set these two values:
GITHUB_REPO — change "owner/repo" to your GitHub repository (e.g., "acme/webapp")ALLOWED_ORIGINS — change "*" to your website's URL (e.g., "https://your-site.com"). During development, include localhost too: "https://your-site.com,http://localhost:3000". The default * allows any site — restrict before going liveCreate a fine-grained Personal Access Token:
gh-feedback-proxyThen store it as a secret in your worker (secrets are encrypted and never appear in plain text). Run this from the worker directory created in step 3:
npx wrangler secret put GITHUB_TOKEN
# Paste the token when prompted
npx wrangler deploy
Wrangler prints the worker URL, e.g., https://gh-feedback-proxy.your-account.workers.dev.
<gh-feedback repo="https://github.com/you/repo" endpoint="https://gh-feedback-proxy.your-account.workers.dev/feedback"></gh-feedback>
curl -X POST https://gh-feedback-proxy.your-account.workers.dev/feedback \
-H "Content-Type: application/json" \
-d '{"title": "Test issue from curl", "type": "bug"}'
You should get back {"issueNumber": ..., "issueUrl": "..."} and see the issue in your GitHub repo.
Multi-repo note: The worker always files issues to the repo configured in GITHUB_REPO. To file to multiple repos, deploy one worker per repo.
The component POSTs JSON to any URL. The worker/ template is for Cloudflare Workers, but any backend works. The contract:
POST { title, type, severity?, description, labels?, repo?, context? }
--> 201 { issueNumber, issueUrl }
--> 4xx/5xx { error: "message" }
Your backend receives this JSON, creates a GitHub Issue using a server-side token, and returns the result. The key requirement is that the endpoint holds the GitHub token server-side -- it never reaches the browser.
Any of these work:
api/feedback.js with GITHUB_TOKEN env varnetlify/functions/feedback.jsmain.ts fileThe Cloudflare Worker in worker/ is a good reference implementation (~80 lines). Adapt it to your platform.
If you don't want to set up a proxy, you can pass a GitHub token directly:
<gh-feedback repo="https://github.com/you/repo" token="ghp_yourTokenHere"></gh-feedback>
| Event | Detail | Cancelable | Description |
|---|---|---|---|
gh-feedback:submit |
{ title, type, description, labels, repo, severity? } |
Yes | Fires before submission. Call preventDefault() to cancel. |
gh-feedback:filed |
{ issueNumber, issueUrl } |
No | Fires after issue is successfully created. |
gh-feedback:error |
{ error } |
No | Fires when submission fails. error contains the message shown to the user. |
document.querySelector('gh-feedback')
.addEventListener('gh-feedback:filed', (e) => {
console.log(`Issue #${e.detail.issueNumber}: ${e.detail.issueUrl}`);
});
Set the getContext property to a function that returns an object. The data is attached to the issue body in a collapsible details section.
const fb = document.querySelector('gh-feedback');
fb.getContext = () => ({
url: location.href,
viewport: `${innerWidth}x${innerHeight}`,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
});
Type definitions are included in gh-feedback.d.ts. Event listeners are fully typed:
import type { GhFeedback } from './gh-feedback';
const fb = document.querySelector('gh-feedback')!;
fb.addEventListener('gh-feedback:filed', (e) => {
console.log(e.detail.issueNumber); // number
});
fb.addEventListener('gh-feedback:error', (e) => {
console.error(e.detail.error); // string
});
Exported types: FeedbackType, Severity, TriggerStyle, Position, Theme, IconName, Size, SubmitDetail, FiledDetail, ErrorDetail, GhFeedbackEventMap.
For fine-grained control beyond the color attribute, override CSS custom properties on the gh-feedback element:
| Property | Light Default | Dark Default |
|---|---|---|
--gh-feedback-primary | #238636 | #238636 |
--gh-feedback-primary-hover | #2ea043 | #2ea043 |
--gh-feedback-bg | #ffffff | #1c2128 |
--gh-feedback-text | #1f2328 | #e6edf3 |
--gh-feedback-border | #d1d9e0 | #444c56 |
--gh-feedback-input-bg | #f6f8fa | #22272e |
--gh-feedback-error | #d1242f | #d1242f |
--gh-feedback-success | #238636 | #238636 |
--gh-feedback-fab-bg | (primary) | (primary) |
--gh-feedback-fab-text | #ffffff | #ffffff |
<style>
gh-feedback {
--gh-feedback-primary: #6f42c1;
--gh-feedback-primary-hover: #8250df;
}
</style>
<gh-feedback repo="https://github.com/you/repo" endpoint="..." theme="dark"></gh-feedback>
Proxy mode (recommended): Your GitHub token stays on the server. The browser only communicates with your worker endpoint.
Direct mode: The token is in the HTML source. Only for internal tools or pages behind auth.
If your site uses a strict Content Security Policy, add the proxy endpoint to connect-src:
Content-Security-Policy: connect-src 'self' https://your-worker.workers.dev;
Shadow DOM <style> elements are CSP-exempt in modern browsers, so no style-src changes are needed.
Run the unit tests (worker):
npm install
npm test
Run the Playwright E2E tests:
npx playwright install --with-deps chromium
npx playwright test
Run all tests:
npm run test:all
MIT -- use it anywhere.