Laravel Github Readme
#Laravel GitHub Readme
Fetch, render and disk-cache GitHub repository READMEs in your Laravel application. The package issues conditional If-None-Match (ETag) requests with a short freshness window, reuses the cached HTML on 304 Not Modified responses, falls back to stale content on network/API errors, and rewrites relative assets and links to absolute GitHub URLs.
[!WARNING] The HTML returned by this package is untrusted — it comes from third-party GitHub READMEs. The built-in renderer strips raw HTML (
html_input => 'strip') so an embedded<script>in a README cannot become stored XSS, but you should still run the output through an HTML sanitizer (for examplejeffersongoncalves/laravel-html-sanitizer) before rendering it in a browser. If you supply a customrendererthat allows raw HTML, sanitizing the output is mandatory.
[!CAUTION] Pass only trusted repository URLs to
fetchHtml(). The URL determines which GitHub API endpoint is called with your configured token. An attacker-controlled URL can therefore drive token-scoped requests against arbitrary repositories (private-repo read access, rate-limit abuse). If the URL can come from user input, validate it against an allowlist ofowner/repovalues you control before callingfetchHtml()— for examplein_array(GitHubReadme::repoFromUrl($url), $allowedRepos, true).
#Installation
You can install the package via composer:
composer require jeffersongoncalves/laravel-github-readme
Publish and run the migration (it creates the github_readme_cache table):
php artisan vendor:publish --tag="laravel-github-readme-migrations" php artisan migrate
Optionally publish the config file:
php artisan vendor:publish --tag="laravel-github-readme-config"
#Usage
use JeffersonGoncalves\GitHubReadme\GitHubReadme; use JeffersonGoncalves\HtmlSanitizer\Facades\HtmlSanitizer; // example sanitizer // Fetch the default-branch README, rendered to HTML and cached on disk. $html = GitHubReadme::fetchHtml('https://github.com/owner/repo'); // Fetch a specific branch/tag/ref. $html = GitHubReadme::fetchHtml('https://github.com/owner/repo', '2.x'); // ALWAYS sanitize before display — the HTML is untrusted. echo HtmlSanitizer::clean($html);
#Post-processing helpers
The rendered HTML can be further decorated with the following static helpers (all pure, all safe to chain):
// Open off-site links in a new tab with rel="nofollow noopener". $html = GitHubReadme::markExternalLinks($html, 'mysite.test'); // Lazy-load every image except the first (kept eager as the LCP candidate). $html = GitHubReadme::lazyloadImages($html); // Wrap wide tables in a horizontally-scrollable container. $html = GitHubReadme::wrapTables($html);
Other available helpers: GitHubReadme::repoFromUrl(), GitHubReadme::rewriteRelativeAssets(), and GitHubReadme::rewriteRelativeLinks().
#How caching works
- Freshness window — within
check_interval_minutesof the last successful check, the cached HTML file is served straight from disk with no GitHub call at all. - Conditional request — outside the window a conditional
If-None-Matchrequest is made. A304 Not Modifiedreuses the cached file (and does not count against the rate limit). - Re-render — a
200response re-renders the markdown, rewrites relative assets/links, stores the file on disk and updates thegithub_readme_cacherow. - Stale fallback — on a network/API error the stale cached file is served when present.
#Configuration
return [ // The filesystem disk used to store rendered README HTML files. 'disk' => env('GITHUB_README_DISK', 'local'), // Minutes a freshly verified README is served without contacting GitHub. 'check_interval_minutes' => (int) env('GITHUB_README_CHECK_INTERVAL', 10), // Optional GitHub token (falls back to config('services.github.token')). 'token' => env('GITHUB_TOKEN', env('GITHUB_README_TOKEN')), // User-Agent header sent on every GitHub API request. 'user_agent' => env('GITHUB_README_USER_AGENT', 'laravel-github-readme'), // Request timeout in seconds. 'timeout' => (int) env('GITHUB_README_TIMEOUT', 8), // Optional renderer callable: fn (string $markdown): string. // Null uses the internal League\CommonMark renderer (GFM + heading permalinks). 'renderer' => null, ];
#Using a dedicated disk
By default the framework's built-in local disk is used. To isolate cached READMEs, define a dedicated disk in config/filesystems.php and point the disk option at it:
// config/filesystems.php 'disks' => [ 'github' => [ 'driver' => 'local', 'root' => storage_path('app/github'), 'throw' => false, ], ],
GITHUB_README_DISK=github
#Custom renderer
Provide your own markdown renderer (for example to add server-side syntax highlighting) by setting renderer to any callable:
'renderer' => fn (string $markdown): string => \JeffersonGoncalves\Markdown\Markdown::render($markdown, headingPermalinks: true),
The built-in renderer strips raw HTML (html_input => 'strip'). If your custom renderer keeps raw HTML (e.g. html_input => 'allow'), you are responsible for sanitizing the result before it reaches a browser.
#Testing
composer test
#Changelog
Please see CHANGELOG for more information on what has changed recently.
#Contributing
Please see CONTRIBUTING for details.
#Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
#Credits
#License
The MIT License (MIT). Please see License File for more information.