Lightweight PHP library that uses GitHub Issues as a bug tracker for your website. Automatically captures and deduplicates server errors, collects user feedback and feature requests, and displays public issue status — all backed by GitHub's API.
No database required. No external services. Just your website and a GitHub repo.
- 🐛 Automatic Error Reporting — PHP errors, exceptions, and fatal crashes are automatically filed as GitHub Issues
- 🔍 Smart Deduplication — Fingerprints errors so the same bug creates one issue with an occurrence counter, not hundreds of duplicates
- 💬 User Feedback Forms — Drop-in bug report and feature request forms with CSRF protection and spam prevention
- 📋 Public Issue Board — Display moderated issues on your website (only issues you label as
publicare shown) - 🌐 JavaScript Error Capture — Client-side errors are captured via
ghissue.jsand reported to the same system - 🏢 Multi-Project Support — One GitHub repo can track issues for multiple websites using project labels
- ⚡ Rate Limiting — Built-in protection against API flooding during error storms
- 🔒 Private Traces — Stack traces and server details stay in your (private) GitHub repo, never exposed to end users
- PHP 8.0+
curlextension (typically included by default)- A GitHub account with a Personal Access Token
composer require kasmok/ghissue-phpClone or download this repo, then include the single-file autoloader:
require_once '/path/to/ghissue-php/ghissue.php';That's it — all classes are autoloaded on demand. No build step, no manual requires.
Go to GitHub Settings → Tokens and create a token with repo scope (or public_repo for public repos only).
Option A: Config file (recommended for multi-endpoint sites)
Copy ghissue-config.example.php to your project, fill in your values, then:
use GhIssue\GhIssue;
$tracker = GhIssue::fromConfig(__DIR__ . '/ghissue-config.php');Option B: Inline
use GhIssue\GhIssue;
$tracker = new GhIssue(
token: getenv('GHISSUE_TOKEN'),
owner: 'your-username',
repo: 'your-issue-repo',
project: 'my-website'
);The storage directory (/tmp/ghissue by default) is created automatically.
Run this once to create the labels ghissue-php uses:
$tracker->setupLabels();$tracker->registerErrorHandler([
'comment_endpoint' => '/ghissue/comment.php',
]);That's it. Now any unhandled exception automatically creates a GitHub Issue like this:
[my-website] PDOException: Connection refused
Field Value Class PDOExceptionMessage SQLSTATE[HY000] [2002] Connection refusedFile app/Database.phpLine 42
If the same error occurs again, it won't create a duplicate — instead, it adds a comment to the existing issue with a timestamp.
Register the error handler early in your application bootstrap:
$tracker->registerErrorHandler([
// Show built-in error page to users
'show_error_page' => true,
// URL for user comments on the error page
'comment_endpoint' => '/ghissue/comment.php',
// Error levels to capture (default: everything except notices)
'error_levels' => E_ALL & ~E_NOTICE & ~E_DEPRECATED,
// Include server environment details in report
'include_environment' => true,
// Include HTTP request details in report
'include_request' => true,
]);$tracker->report(
'Slow database query detected',
'Query took 12.3s on the /products endpoint',
'warning'
);Add a feedback form to your website:
$feedback = $tracker->feedback();
$csrfToken = $feedback->generateCsrfToken();
$submitEndpoint = '/feedback';
$project = $tracker->getProject();
include 'vendor/kasmok/ghissue-php/templates/feedback-form.php';Process submissions:
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$result = $tracker->feedback()->submit($_POST);
// $result = ['success' => true, 'message' => 'Thank you...']
}Feedback issues are created with a needs-review label. Add the public label in GitHub to make them visible on the public board.
Display approved issues on your website:
$board = $tracker->board();
// Render the built-in template
echo $board->render(
state: 'open', // 'open', 'closed', or 'all'
page: 1,
type: null // or 'bug', 'enhancement', etc.
);
// Or fetch raw data for your own template
$data = $board->getIssues('open', 1);
// $data = ['issues' => [...], 'total' => 5, 'page' => 1, 'has_more' => false]Include ghissue.js on your pages:
<script src="/path/to/ghissue.js"></script>
<script>
GhIssue.init({
endpoint: '/ghissue/client-error.php',
project: 'my-website',
maxErrors: 10, // Max errors per page load
sampleRate: 1.0, // Report 100% of errors (0.5 = 50%)
ignorePatterns: [
/chrome-extension/, // Ignore browser extension errors
/moz-extension/,
]
});
</script>Set up the PHP endpoint (/ghissue/client-error.php):
use GhIssue\ClientErrorHandler;
$handler = new ClientErrorHandler(
$tracker->github(),
$tracker->getProject(),
['allowed_origins' => ['https://yoursite.com']]
);
header('Content-Type: application/json');
echo json_encode($handler->handle());Use one GitHub repo with different project identifiers for each site:
// On site_a.com
$tracker = new GhIssue($token, 'kasmok', 'site-issues', 'site_a');
// On site_b.com
$tracker = new GhIssue($token, 'kasmok', 'site-issues', 'site_b');Issues are tagged with project:site_a, project:site_b, etc. You can use GitHub's label filters to view issues per site, or see everything at once.
When an error occurs, ghissue-php:
- Generates a fingerprint — A hash of the error type, normalized message, file path, and line number
- Searches GitHub Issues — Looks for an open issue with a matching
fingerprint:abc123def456label - If found — Adds a "recurring occurrence" comment with a timestamp (no duplicate created)
- If not found — Creates a new issue with the fingerprint label
The fingerprint normalizer strips variable data (IDs, timestamps, UUIDs) from error messages so that User 123 not found and User 456 not found produce the same fingerprint.
- Automatic errors arrive with the
auto-reportedlabel - User feedback arrives with
needs-reviewanduser-submittedlabels - Review in GitHub — Triage, label, and assign issues as normal
- Make public — Add the
publiclabel to any issue you want visible on your website's public board - Use Claude Code — With the GitHub MCP server, you can triage issues conversationally
| Option | Default | Description |
|---|---|---|
storage_dir |
sys_get_temp_dir() . '/ghissue' |
Directory for rate limiting, cache, and CSRF tokens |
| Option | Default | Description |
|---|---|---|
rate_limit |
30 |
Max GitHub API calls per hour |
error_levels |
E_ALL & ~E_NOTICE & ~E_DEPRECATED |
PHP error levels to capture |
show_error_page |
true |
Show built-in error page to users |
error_template |
Built-in template | Path to custom error page template |
comment_endpoint |
null |
URL for user comment submissions |
include_environment |
true |
Include server env in issue body |
include_request |
true |
Include HTTP request in issue body |
labels |
[] |
Additional labels for all issues |
chain_handlers |
true |
Call previous error handler after ours |
| Option | Default | Description |
|---|---|---|
rate_limit_per_ip |
5 |
Max submissions per hour |
csrf_protection |
true |
Require CSRF token |
csrf_lifetime |
3600 |
Token lifetime in seconds |
pending_label |
needs-review |
Label for new submissions |
| Option | Default | Description |
|---|---|---|
public_label |
public |
Label that marks issues as publicly visible |
cache_ttl |
300 |
Cache lifetime in seconds |
per_page |
20 |
Issues per page |
- Never expose your GitHub token to the client side. All API calls happen server-side.
- Use a dedicated GitHub account (bot account) with limited repo access for the token.
- The token needs only
repo(private repos) orpublic_repo(public repos) scope. - Consider using a private repo for issues so stack traces aren't publicly visible.
- Set
allowed_originson the client error handler to prevent abuse. - The feedback form includes honeypot spam protection and CSRF tokens.
See the examples/ directory for complete working examples:
basic-setup.php— Minimal setupfeedback-form.php— Feedback form page + endpointclient-error-endpoint.php— JavaScript error receivercomment-endpoint.php— Error page comment handlerpublic-board.php— Public issue display
Contributions are welcome! Please feel free to submit issues and pull requests.
MIT License — see LICENSE for details.