Documentation

Create a banner, explore the API, or run your own instance. The project guides and policies are collected here in one place.

Project overview #

URL-based repository banners. Think shields.io, but for headers. ✨

GitHub Repo Banner

I believe every repository deserves to look beautiful. Your code is art, your projects deserve stunning visuals to match. But design tools steal hours you don't have. So I built a service that generates gorgeous banners through simple URL parameters. Instant, customizable, and no design tools required. Because great projects deserve great repository banners.

Create a banner Β· Documentation Β· Public usage Β· API reference Β· Self-hosting Β· Brand guidelines Β· Agent skill Β· Terms Β· Privacy

🚁 Deploy Your Own #

Deploy on Railway

When you deploy your own copy, you're directly supporting this project! πŸ’–

✨ Features #

  • 🎨 Backgrounds β€” Solid colors, gradients, transparency, presets, and public HTTPS images.
  • πŸ“Έ Image search and styling β€” Pexels search with more results, image blur, and background color filters.
  • πŸ”€ Typography β€” Google Fonts, emoji, Simple Icons using ![slug], and inline custom images using ![icon src="https://…"].
  • 🧩 Custom Images β€” Position multiple logos or decorations, resize and rotate them, preserve proportions, and arrange them behind or in front of text.
  • πŸ“₯ Output β€” Live preview, README Markdown, image URLs, SVG, and PNG downloads.
  • πŸ’– Optional attribution β€” Toggle the watermark and choose any corner.
  • πŸ“š Accessible controls β€” Keyboard-selectable photos and responsive Code of conduct and MIT license tabs.
  • πŸ“– Project pages β€” Single-page documentation at /docs and a readable public usage report at /usage.
  • πŸ”’ Transparent usage β€” Aggregate counts and saved exports on the official service; optional counting on ordinary self-hosted instances.
  • πŸ–ΌοΈ Community showcase β€” Choose to display an exported design on the Usage page, or export without sharing it.
  • 🐳 Self-hosting β€” Docker Compose with required Redis for persistent search caching and quota control.
  • πŸ€– Agent skill β€” Create banner URLs and README Markdown from compatible AI coding tools.

πŸš€ Quick Start #

Use the Hosted Service #

  1. Open ghrb.waren.build.
  2. Set your text, fonts, and background; optionally add custom images and check the live preview.
  3. Choose Copy Markdown for your README, copy an image URL, or download SVG/PNG.
  4. On the official service, choose whether to showcase this design on the Usage page. Both choices save the exported design and count usage. Declining only keeps it out of the public showcase.

The official service records aggregate usage and saves designs from its built-in export controls as described in the Terms and Privacy notice. The showcase prompt asks only about public display.

You can also create a banner directly with a URL:

![My Project](https://ghrb.waren.build/banner?header=My+Project&bg=1a1a1a-4a4a4a&color=ffffff)

Self-Hosting #

Use Docker Compose to run the app and its required Redis database:

git clone https://github.com/warengonzaga/github-repo-banner.git
cd github-repo-banner
cp .env.example .env
docker compose up --build -d

Open http://localhost:3000. Compose connects Redis automatically and keeps its data in a volume. Pexels search appears only when PEXELS_API_KEY is configured; direct image URLs remain available without a key. Statistics are disabled by default. Redis remains required when tracking is off. See the self-hosting guide for runtime variables, Railway, production builds, and release configuration. The API requires a running server; GitHub Pages alone cannot host it.

πŸ“– Usage Examples #

Backgrounds and Styling #

Use bg=HEX for a solid color, bg=HEX1-HEX2 for a gradient, or eight-digit RRGGBBAA hex for opacity. bg=00000000 is fully transparent. Choose a preset in the UI or browse the preset table and examples.

Use bgimg for a direct public HTTPS image. The UI preserves your colors and presets while disabling them until you clear the image URL. Supported images are JPEG, PNG, GIF, WebP, and AVIF, up to 10 MiB and 16,777,216 decoded pixels across all frames; see image requirements and fallback behavior.

Background styling adjusts brightness, contrast, saturation, and grayscale without changing text, icons, or watermarks. Blur applies only to successfully loaded images. Reset styling restores neutral values while keeping your content; the main Reset restores all defaults. The effects travel with copied URLs and SVG/PNG downloads.

https://ghrb.waren.build/banner?header=My+Project&bgimg=https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg&bgblur=10&bgbrightness=60&bggrayscale=100&color=ffffff

See the API parameter table for ranges and defaults. Invalid effect values use defaults; neutral values preserve the original output.

Custom Icons and Images #

Put an image inline with header or subheader text:

![icon src="https://example.com/logo.png" w="100%"] My Project

Replace the example URL with a direct public image URL. Optional w and h accept 1–304px or 1–200%; percentages follow the surrounding text size. With neither set, the icon follows the text height. With one set, the other preserves its proportions; with both set, the image fits inside that box. Oversized icons shrink to fit the available line space. Existing ![github] icons and emoji still work alongside custom icons.

For freely positioned images, open Custom Images and choose Add image. Set X/Y from the banner's top-left and width/height in pixels on the 1280Γ—304 canvas. Rotation (degrees) turns each image clockwise from 0Β° to 360Β° around its center, keeping the X/Y position and size as the unrotated box. Set 0Β° to reset its angle; 360Β° is a full turn. Preserve proportions fits inside the box; uncheck it to stretch. Choose Behind text or In front of text, then use Lower layer, Raise layer, or Remove image. Images crossing a banner edge, including after rotation, are cropped, and the watermark stays above them. New images start at (0, 0) in a 128Γ—128 box at 0Β°, behind text. The main Reset clears them.

A banner supports five custom images total, including inline icons in both text fields. Each needs a public HTTPS URL of at most 512 characters and a PNG, JPEG, WebP, GIF, or AVIF download of at most 1 MiB and 16,777,216 decoded pixels across all frames. The image data must decode successfully and match its declared content type. SVG, redirects, credentials, and private addresses are not supported. Invalid or unavailable custom images show an error and block exports until fixed or removed.

Custom image settings, including rotation, follow the live preview, copied URLs/Markdown, SVG/PNG downloads, saved official exports, and optional showcase previews. Saved designs keep URLs and settings, not image binaries, so later previews depend on those images remaining available. See the custom image API for parameter details.

Pexels Integration #

Search returns nine landscape thumbnails at a time. Load more images appends the next page until results end. A new search starts fresh; failed requests preserve existing choices for retry. Select photos with a mouse or Tab followed by Enter/Space.

For self-hosting, set PEXELS_API_KEY in the server environment. The key stays server-side. Without it, Pexels search is hidden while the direct image URL input remains available. Direct images, filters, and the agent skill do not need a Pexels key. See configuration and search limits.

Watermark and Output #

The UI enables the watermark by default; the API requires support=true. Copy Markdown and SVG/PNG downloads honor your choice, including the attribution comment in generated Markdown only when support is enabled. Copy Image URL keeps styling while intentionally omitting the watermark. On self-hosted instances with optional tracking, the statistics opt-out travels with all export formats. On the official service, exports count toward aggregate usage regardless of the showcase choice.

Repository Documents #

The README tab contains the generator. Code of conduct and MIT license display bundled repository documents. Tabs support keyboard navigation and adapt to narrow screens.

The main navigation opens Documentation, a single page assembled from this README, the API and self-hosting guides, contribution instructions and policies. Usage displays the current instance's daily page views and banner observations from /stats with refresh, raw JSON access, and coverage notes. Disabled or unavailable tracking is shown explicitly, never as a misleading zero. These observations do not measure individual users or prove how many projects depend on the service. The official Usage page also shows previews of designs their creators explicitly chose to showcase. These previews are rendered from saved settings; remote images or other assets can change. Creators can remove a showcase entry using its removal code.

πŸ”Œ API Reference #

See the API reference for all banner parameters, examples, presets, and responses from /banner, /api/pexels/search, /health, /stats, /exports, and the community showcase endpoints.

🌟 Who Uses This #

Projects and organizations using GitHub Repo Banner:

πŸ€– Agent Skill #

The standalone github-repo-banner skill creates banner URLs and README Markdown using this service, with project branding and explicit preview checks. It needs no other Clean plugin or API key.

With a Codex version supporting plugins, install from the repository's marketplace:

codex plugin marketplace add warengonzaga/github-repo-banner
codex plugin add github-repo-banner@github-repo-banner

Start a fresh session after installation. For a local checkout, use codex plugin marketplace add /absolute/path/to/github-repo-banner.

Example requests:

$github-repo-banner create a banner for this project's README using its existing colors, preview it, and give me the Markdown without editing files
$github-repo-banner use a transparent background and a GitHub icon, then replace the existing README banner
$github-repo-banner create a banner using my self-hosted base URL https://banners.example.com

Other Agent Skills-compatible hosts can load skills/github-repo-banner directly. Copy the whole folder, including references/, into the host's documented skill directory; no plugin manifest is required for this mode. In Codex, a project-local direct installation can use .agents/skills/github-repo-banner/. Do not install both forms in the same project/session if that would expose duplicate skills.

The skill distinguishes generated URLs from verified renders and edits a README only when requested. /banner returns SVG; PNG export remains a separate interactive UI feature. Maintain the skill reference alongside the route, sanitizer, renderer, and generator's Markdown behavior. Run bun scripts/check-banner-skill.ts to check the documented URL example against the route, encoding, limits, fallback, and package linkage. This does not replace a fresh-session or visual check.

πŸ”’ Privacy & Transparency #

The official hosted service records aggregate usage and saves every design submitted through its built-in export controls. Choosing not to showcase does not disable counting or saving. Only an explicit showcase choice makes a preview available on the public Usage page. See the Terms and Privacy notice.

Self-hosted instances keep statistics disabled unless ENABLE_STATS=true. OFFICIAL_HOSTED_INSTANCE=true enables the official-service behavior and takes precedence over ENABLE_STATS. Redis remains required for Pexels caching and quota control. /health reports database availability and the tracking setting; /stats reports current storage availability and measurement definitions.

What the metrics mean #

  • Recorded page views: successful origin GET /, GET /docs, and GET /usage responses, grouped by page. Refreshes, bots and prefetches can count; these are not unique visitors. Assets, API calls, stats refreshes, HEAD requests and failed page responses are excluded.
  • Recorded banner requests: successful origin GET /banner renders whose aggregate write succeeds. Includes UI previews, bots, downloads, and retries. Requests answered by browser/CDN/GitHub image caches never reach this counter. It is not a view, user, installation, or adoption count. Showcase previews use a separate endpoint and do not increment this counter.
  • Recorded export requests: requests from the generator's four export controls whose aggregate write succeeds, with a separate count for requests that save a showcase entry. These count accepted exports, not unique creators or proof that a clipboard write or download completed. Official exports save the design regardless of the showcase choice. Identical retries of a saved export do not count again.
  • Requests with a repository Referer: recorded banner requests with a syntactically valid https://github.com/owner/repo Referer. Hostname and path are validated; queries/fragments are excluded and case is normalized. Headers can be missing or spoofed, and neither repository existence nor public visibility is verified.
  • Estimated unique repositories: the approximate number of distinct normalized repository identifiers in those Referers, using Redis HyperLogLog (about 0.81% standard error). This is an observation estimate, not a complete repository count.
  • Repository Referer coverage: the fraction of recorded banner requests with a usable repository Referer, or null when there are no requests. It does not estimate coverage of all real-world usage.

All metrics cover the current UTC day. First-recorded times identify when each kind of counting began; enabling statistics midday does not imply full-day coverage. Disabled periods, self-hosted opt-outs, Redis outages, and failed writes are omitted. Banner and page writes are asynchronous; recent responses may not appear immediately. Zero means no observations recorded for this window, not no users. Showcase browsing and removal do not add banner or export observations; opening /usage still counts as a page view.

Privacy, retention, and opt-out #

Measurement stores daily aggregate counters and a HyperLogLog sketch of SHA-256-hashed repository identifiers, not raw repository names, full Referers, banner text, IP addresses, cookies, sessions, or visitor identifiers. Daily keys expire seven days after their last write. Exported design records are separate: every official built-in export saves normalized settings and submission metadata, whether or not showcasing is chosen. These records are not exposed through the public gallery unless separately showcased.

Saved exports expire after the operator-configured EXPORT_RETENTION_DAYS period. Official mode requires an explicit value from 1 to 365 days and has no default. Storage holds at most 5,000 saved exports and admits 60 new exports per rolling minute; reaching either limit prevents a new official export until capacity is available, without discarding retained designs. A separate showcase copy remains public until the creator withdraws it or the operator removes it. Save the removal code; the browser may keep it locally to make removal easier. Withdrawing removes the public showcase copy and listing, but does not erase the saved export before its retention period ends or retract copies other people have already obtained. Do not publish secrets or content you lack permission to share. This project's MIT license does not automatically license user designs.

On the official service, stats=false, DNT: 1, and Sec-GPC: 1 do not disable aggregate counting. The showcase prompt controls public display only. On self-hosted instances with optional tracking, use Exclude this banner from usage statistics, append stats=false to a banner or page URL, or send either header. The checkbox follows exported banner URLs; it does not undo earlier page views. A page's query choice applies to that request and is not carried through navigation links. No tracking preference is stored in a cookie.

Operational Pexels caches and quotas remain separate from usage counting. Hosting providers and reverse proxies may keep their own access logs. See the Privacy notice for storage, retention, public sharing and removal details. The legacy /log endpoint still ignores old client payloads.

Migration from the old statistics API #

/stats now returns schemaVersion: 2. The old lifetime totalRepositories and repositories fields are replaced by the daily metrics described in the API reference; clients must update accordingly. Existing repos:tracked data is not imported into the new counters, read, or served. Operators should remove that legacy Redis key after any necessary backup; upgrading does not automatically delete existing data or historical infrastructure logs.

πŸ› οΈ Development #

Node.js Β· TypeScript Β· Hono Β· tsup Β· Bun

bun run check  # Static checks
bun run build      # Production build and declaration generation

Regression checks are in scripts/check-* (run .ts files with Bun and .mjs files with Node). CI runs static checks, the build, regression scripts with disposable Redis, and the Docker smoke check. See CI and releases, security, and resource limits.

🀝 Contributing #

Contributions are welcome! For major changes, open an issue first.

  1. Fork the repository
  2. Create feature branch (git checkout -b feature/amazing-feature)
  3. Commit with Clean Commit convention
  4. Push and open a Pull Request

πŸ“„ License #

MIT License - see LICENSE file.

πŸ‘¨β€πŸ’» Author #

Waren Gonzaga β€’ GitHub β€’ Website


πŸ’»πŸ’–β˜• by Waren Gonzaga | YHWH πŸ™

API reference #

Back to README Β· Self-hosting

GET /banner #

Generate a custom SVG banner.

Parameters #

Parameter Type Required Default Description
header string No "Hello World" Main text (supports emojis and icons)
subheader string No - Optional subtitle text, with the same icon syntax as header
images JSON string No [] Positioned custom images; see custom image settings
bg string No 1a1a1a-4a4a4a Background color in hex format
color string No ffffff Header text color (hex without #)
subheadercolor string No Same as color Subheader text color
headerfont string No - Google Fonts family name for header (e.g., "Roboto")
subheaderfont string No - Google Fonts family name for subheader (e.g., "Playfair Display")
bgimg string No - Valid public HTTPS raster image URL (takes priority over bg, max 2048 URL units and 10 MiB download)
bgblur number No 0 Blur a loaded image, 0–30 pixels
bgbrightness number No 100 Background brightness, 0–200 percent
bgcontrast number No 100 Background contrast, 0–200 percent
bgsaturation number No 100 Background saturation, 0–200 percent
bggrayscale number No 0 Background grayscale, 0–100 percent
support boolean No false Show support watermark
stats boolean No true On self-hosted instances with optional tracking, false excludes this request. Official-hosted aggregate counting ignores this opt-out.
watermarkpos string No bottom-right Watermark position: top-left, top-right, bottom-left, bottom-right

Header and subheader display text are limited to 50 and 60 JavaScript string units respectively, with at most five inline icons per field. Complete icon tokens do not count toward the display-text limit. Raw input is capped at 500 units, or 2,048 units when it contains custom icon syntax. Font names are sanitized and limited to 50 units. Encode query values, especially embedded image URLs containing their own & or #; URLSearchParams handles this when building URLs in code.

Custom Icons and Positioned Images #

Use inline custom icons in header or subheader:

![icon src="https://example.com/logo.png" w="100%"] My Project

Attributes use double quotes. src is required; w and h are optional and each accepts 1–304px or 1–200%. Percentages are relative to the surrounding rendered font size, not the banner. Omitting both sizes uses the text height; specifying one preserves the image's aspect ratio. With both, the image fits inside the requested box while preserving proportions. Available line space limits oversized icons. Existing Simple Icons syntax (![github], ![github](light)) and emoji remain supported. Custom icons do not accept X/Y positions or rotation; use positioned images for these controls.

For independent images, supply images as one JSON-encoded array, at most 4,096 JavaScript string units before URL encoding. Each object accepts only these fields:

Field Default Accepted value
src Required Direct public HTTPS image URL, at most 512 units
x 0 Number from 0 to 1280 pixels from the left edge
y 0 Number from 0 to 304 pixels from the top edge
w 128 Width from 1 to 1280 pixels
h 128 Height from 1 to 304 pixels
rotation 0 Finite number from 0 to 360 degrees clockwise around the image box’s center; decimals are accepted
fit contain contain preserves proportions inside the box; stretch fills it
placement behind behind or front, relative to text

The canvas is 1280Γ—304 pixels; portions outside it, including rotated corners, are cropped. X/Y and width/height define the unrotated box. Rotation uses its center (x + w/2, y + h/2) without changing those values. Omitted or zero rotation preserves existing designs; 360Β° is a full turn. Invalid rotation values return HTTP 400. Images render above the background, in array order from back to front within each placement group. The watermark remains above all images. Background filters do not affect custom icons or positioned images.

This example builds a URL without manually escaping nested quotes or image URL parameters. The image URLs are illustrative; replace them with working direct image URLs.

const query = new URLSearchParams({
  header: '![icon src="https://example.com/logo.png" w="100%"] My Project',
  images: JSON.stringify([
    { src: 'https://example.com/decoration.png', x: 32, y: 32, w: 128, h: 128, rotation: 30, fit: 'contain', placement: 'behind' },
  ]),
});
const bannerUrl = `https://ghrb.waren.build/banner?${query}`;

There is a shared limit of five custom images across positioned layers and inline icons in both text fields; repeated uses still count. Each custom image must serve JPEG, PNG, GIF, WebP, or AVIF with a matching image content type and a download no larger than 1 MiB. Image data must decode successfully; the limit is 16,777,216 decoded pixels across all frames. SVG, redirects, credentials, and non-public addresses are rejected. The server fetches and embeds image data into the SVG. Malformed settings return HTTP 400; an unavailable, oversized, or unsupported custom image returns HTTP 422 instead of a partial banner. The generator disables exports until the custom-image preview succeeds.

Rotation and the other settings travel through copied URLs/Markdown and SVG/PNG downloads. Official saved exports and optional showcase previews keep normalized settings and URLs, not image binaries. Later previews can change or fail if a source changes. Existing designs without images keep their prior behavior.

Background Effects #

Non-finite, non-numeric, and out-of-range values use the parameter defaults. Effects apply in this order: blur, brightness, contrast, saturation, then grayscale. Color filters work on solid colors, gradients, and images while preserving transparency; blur requires a successfully loaded image. Foreground text, icons, and watermarks stay unchanged. Neutral settings preserve existing output and are omitted from UI-generated URLs. Effects are embedded as native SVG filters for preview and SVG/PNG export.

Background Format #

Format Example Description
Gradient 1a1a1a-4a4a4a Two hex colors separated by -, left to right
Solid ffffff Single hex color
Transparent 00000000 Fully transparent
With Opacity ffffff80 RRGGBBAA (about 50% opacity)
Image URL bgimg=https://... Public HTTPS raster image (fetched and embedded as base64)

Response #

  • Content-Type: image/svg+xml
  • Cache-Control: public, max-age=86400, s-maxage=86400 (production)
  • Size: 1280Γ—304px

Invalid custom-image settings return HTTP 400 with a JSON error. Failed custom-image downloads return HTTP 422 with a JSON error; they do not return an incomplete SVG.

GET / #

Interactive banner generator UI with live preview.

GET /docs #

Single-page project documentation, rendered from the bundled README, API reference, self-hosting guide, contribution guide, Terms, Privacy notice, code of conduct and license. Chapter links and a responsive table of contents work without JavaScript.

GET /usage #

Public usage page that fetches this instance's /stats JSON on load and when Refresh statistics is selected. Shows current UTC-day page views by page, recorded banner requests, estimated distinct repository identifiers, Referer counts and coverage. Disabled, empty and unavailable states remain distinct. It does not count individual users, verified projects or lifetime adoption. On the official service, it also shows export counts and the community showcase with more previews available through pagination. Only designs explicitly submitted for showcasing appear. /stats remains the raw JSON endpoint for integrations.

POST /exports #

On the official service, saves every design submitted through a built-in export control and records its usage, regardless of the showcase choice. The choice controls only whether a public showcase copy is created. Ordinary self-hosted instances accept count-only requests, respecting ENABLE_STATS and query/header opt-outs; they do not collect exported designs.

Mutations require a matching Origin header and Content-Type: application/json; request bodies are limited to 12 KiB. The Origin must match PUBLIC_ORIGIN. If unset, official-hosted mode uses https://ghrb.waren.build; ordinary self-hosted mode uses the request origin. An HTTPS-terminating proxy, custom official-mode domain or local test needs its exact external origin configured. See public origin configuration.

Every official export request, including showcase: false, requires:

  • action: markdown, url, svg, or png.
  • showcase: an explicit boolean for public display.
  • id: <Unix milliseconds>-<lowercase UUID v4>, generated when the first submission starts. Keep this exact ID for retries.
  • removalToken: a cryptographically random 64-character lowercase hexadecimal code for withdrawing a showcase copy.
  • policyVersion: 2026-09-29.
  • query: an object mapping banner parameter names to string values.

Settings use the same sanitization and defaults as /banner; no SVG markup or uploaded image binary is accepted. The query.images value is a JSON string limited to 4,096 units; other query values remain limited to 2,048 units. The complete body and normalized record limits still apply, so shorten URLs or reduce design settings if the combined payload is too large. Submit the final exported settings, including watermark omission for the image-URL action. The generator presents the public-sharing choice and constructs this request. Keep the removal token secret and save it before submitting.

A new saved export returns HTTP 201; while a saved or public record exists, an identical retry with the same ID, action, settings, choice and token returns 200 without extending retention, counting again or republishing a withdrawn showcase. A successful response contains saved: true, showcased (the actual publication result), id and expiresAt (Unix milliseconds for the saved export's expiry). A public entry also includes previewUrl. If the gallery is full, the export still saves and counts, with showcased: false and showcaseReason: "full"; the download can proceed with that status. An identical retry after withdrawal returns showcaseReason: "removed" rather than republishing it.

A new ID is accepted only within 15 minutes of its embedded timestamp, with up to five minutes of future clock drift allowed. Existing saved or public records still support identical retries after this window. Once both records are absent, an expired ID returns 409 and cannot republish or count again. Start a new export for a new submission; synchronize an incorrect device clock. No permanent withdrawal markers are stored.

Admission is atomic across instances sharing Redis: at most 60 new exports per rolling minute and 5,000 retained exports, with each encoded record limited to 8 KiB. Rate or retention-capacity exhaustion returns 429 before saving or counting; rate responses include Retry-After: 60. Identical retries of existing records bypass these limits. Expired reservations are reclaimed; accepted records are not silently evicted.

Invalid input returns 400, a disallowed origin or content type returns 403, a changed request reusing an existing ID returns 409, and an oversized body or normalized saved record returns 413. Storage or publication that cannot be confirmed returns 503. Retry the same request after an uncertain outcome; do not assume it was never saved or published. Saving an export does not prove a later clipboard write or browser download completed. Saved exports have no public retrieval endpoint.

An ordinary self-hosted count-only request sends no design fields:

{ "action": "png", "showcase": false }

It returns { "showcased": false, "counted": true } when recording succeeds, or counted: false when disabled, opted out or unavailable. This counting is best effort and does not block self-hosted exports during a statistics outage. Public showcasing is unavailable in ordinary self-hosted mode.

GET /showcase #

Returns up to 12 newest public showcase entries and a nextCursor (null when no more remain). Pass a returned cursor as ?before=... to get older entries. The cursor combines the creation timestamp and entry ID; preserve it as returned and URL-encode it. Each entry contains id, createdAt, a plain-text label derived from its visible header and subheader for accessible previews, and a same-origin previewUrl. The feed excludes non-shared exports. createdAt is a Unix timestamp in milliseconds. An instance with showcasing disabled returns { "enabled": false, "entries": [], "nextCursor": null }.

The gallery holds at most 1,000 entries; when full, new official exports still save and count but do not create a showcase copy. Existing entries remain until creator withdrawal or operator removal. Gallery lists, previews and removals do not increment banner or page counters; opening /usage still counts as a page response.

GET /showcase/:id.svg #

Renders a saved public design using the existing banner renderer. It is a preview from normalized settings, not a permanent pixel snapshot: remote backgrounds and other assets can change or become unavailable. The endpoint does not expose the entry's removal token.

DELETE /showcase/:id #

Withdraw a showcase entry by sending JSON containing its private removalToken. The same configured-origin and JSON content-type checks as /exports apply:

{ "removalToken": "<the 64-character removal code saved at submission>" }

A successful removal returns { "removed": true } and deletes the public showcase copy and its gallery listing. It does not delete the separate saved export before its configured expiry. Repeating a removal for an absent entry succeeds when a saved export still establishes its submission, or when its ID has expired and cannot be submitted again. An absent fresh ID returns 409 because the initial export may still be in flight; retry removal until confirmed. An invalid code format returns 400; a code that does not match an existing entry returns 403; unavailable storage returns 503. Copies already downloaded or cached elsewhere cannot be recalled. See the Privacy notice if the code is unavailable or the entry needs to be reported.

GET /api/pexels/search #

Server-side Pexels search. Accepts q (default nature, normalized and limited to 100 units) and page (default 1). Returns up to nine landscape photos per page:

{
  "photos": [
    {
      "id": 123,
      "alt": "Example landscape",
      "photographer": "Example photographer",
      "url": "https://images.example.com/landscape.jpg",
      "thumb": "https://images.example.com/thumbnail.jpg"
    }
  ],
  "total": 10,
  "page": 1,
  "hasMore": true
}

The image URLs above are illustrative. Request the next page while hasMore is true; an empty page ends pagination. Missing Pexels configuration or unavailable Redis returns HTTP 503, exhausted per-process concurrency or shared Redis budget returns 429 with Retry-After: 60, and upstream failures return 500. See Resource limits.

GET /health #

Readiness endpoint with Cache-Control: no-store. Returns HTTP 200 when Redis answers a ping. A disconnected or unresponsive database returns HTTP 503 with status: "degraded" and database.available: false. The tracking setting does not affect readiness; it is not a guarantee that every write succeeded.

Response (stats disabled):

{
  "status": "ok",
  "timestamp": "2026-02-01T00:00:00.000Z",
  "database": { "available": true },
  "stats": {
    "enabled": false
  }
}

Response (stats enabled):

{
  "status": "ok",
  "timestamp": "2026-02-01T00:00:00.000Z",
  "database": { "available": true },
  "stats": {
    "enabled": true,
    "endpoint": "/stats"
  }
}

GET /stats #

Returns current UTC-day aggregate observations with Cache-Control: no-store. Disabled instances return { "schemaVersion": 2, "enabled": false, "message": "Stats tracking is disabled" }. Unavailable storage returns HTTP 503 with available: false, rather than a misleading zero.

Example enabled response (the API also includes detailed note and privacy fields):

{
  "schemaVersion": 2,
  "enabled": true,
  "available": true,
  "window": {
    "day": "2026-09-29",
    "timezone": "UTC",
    "firstRecordedAt": "2026-09-29T08:00:00.000Z"
  },
  "recordedBannerRequests": 100,
  "requestsWithRepositoryReferer": 20,
  "estimatedUniqueRepositories": 12,
  "repositoryRefererCoverage": 0.2,
  "pageViews": {
    "generator": 80,
    "documentation": 15,
    "usage": 5,
    "total": 100,
    "firstRecordedAt": "2026-09-29T08:05:00.000Z"
  },
  "exports": {
    "total": 30,
    "showcased": 8,
    "firstRecordedAt": "2026-09-29T08:10:00.000Z"
  },
  "coverage": "partial"
}

pageViews and exports are additive fields in schema version 2. Page views count successful origin GET page responses, not unique visitors; refreshing /stats does not increment them. exports.total counts accepted export requests; exports.showcased counts those that newly publish a design. First-recorded times are separate, and null before the first observation of that kind, so a mid-day rollout does not imply full-day coverage. Official-hosted mode saves and counts all accepted exports, including those not showcased; identical retries of saved exports do not count again. Self-hosted count-only exports follow the optional tracking setting and opt-outs. Storage failures and incomplete requests can leave observations unrecorded.

Official-hosted mode always enables aggregate counting; stats=false, DNT: 1, and Sec-GPC: 1 only exclude requests on self-hosted instances with optional tracking. A showcase decision never controls counting. See Privacy & Transparency for definitions, exclusions, retention, self-hosted opt-out, and legacy-data migration.

Usage Examples #

Gradient Background

https://ghrb.waren.build/banner?header=Vibe+Coding%F0%9F%9A%80&bg=ec4899-3b82f6&color=ffffff

Gradient Example

Solid Color with Subheader

https://ghrb.waren.build/banner?header=OSSPH&subheader=Leading+Open+Source+Software+Community+in+the+Philippines&bg=E7F9FF&color=0060A0

Solid Color Example

With Emojis & Custom Fonts

https://ghrb.waren.build/banner?header=%F0%9F%A6%9EOpenClaw&subheader=Your+own+personal+AI+assistant.&bg=fee2e2&color=bb2c2c&headerfont=Roboto&subheaderfont=Inter&support=true

Emoji Example

With Brand Icons (Simple Icons)

https://ghrb.waren.build/banner?header=![github]+Hello+World&bg=1a1a1a-4a4a4a&color=ffffff
https://ghrb.waren.build/banner?header=![react]+![typescript]+Modern+Stack&bg=14b8a6-06b6d4&color=ffffff

Icon Example

Use ![slug] syntax to embed any icon from Simple Icons. Over 3000+ brand icons available! Icons automatically adapt to your text color.

Transparent/Opacity

https://ghrb.waren.build/banner?header=Transparent&bg=00000000&color=ffffff
https://ghrb.waren.build/banner?header=Semi-Transparent&bg=ffffff80&color=000000

Custom Background Image #

https://ghrb.waren.build/banner?header=My+Project&bgimg=https://images.pexels.com/photos/1261728/pexels-photo-1261728.jpeg&color=ffffff

Use a direct public HTTPS URL serving JPEG, PNG, GIF, WebP, or AVIF, up to 10 MiB and 16,777,216 decoded pixels across all frames. The data must decode successfully and match its declared content type. URLs containing credentials, blocked private addresses, redirects, and SVG images are not supported. The server embeds the image as base64 and scales it to cover the banner. An invalid URL uses the bg selection; an accepted URL whose download fails uses the default gradient.

When the UI accepts an image URL, it disables background colors and presets while preserving their values. Clear the image URL to use those settings again.

Color Presets #

The UI includes presets for quick access:

Gradients #

Name Background Text Color Preview
Midnight 1a1a1a-4a4a4a ffffff Midnight
Vibe ec4899-3b82f6 ffffff Vibe
Ocean 14b8a6-06b6d4 ffffff Ocean
Railway 431586-9231A8 ffffff Railway
Cloudflare F38020-FBAB41 ffffff Cloudflare
Waren 013B84-016EEA ffffff Waren
OSSPH E7F9FF-90C4E8 0060A0 OSSPH

Solid Colors #

Name Background Text Color Preview
Sky 87ceeb 1e3a8a Sky
Molty fee2e2 bb2c2c Molty
Claude fde8e3 de7356 Claude
GPT 10a37f ffffff GPT
Minimal f3f4f6 1f2937 Minimal

Special #

Name Background Text Color Preview
Transparent 00000000 ffffff Transparent

Tip: Create any custom gradient or color using hex codes directly in the URL.

Self-hosting #

Back to README Β· API reference

git clone https://github.com/warengonzaga/github-repo-banner.git
cd github-repo-banner
cp .env.example .env
docker compose up --build -d

Open http://localhost:3000. Compose runs one Node 22 app and one Redis 8 service. It supplies REDIS_URL automatically; Redis is not published to the host. The app runs as a non-root user and binds to localhost by default. Put a reverse proxy in front for public HTTPS access. Set PORT in .env to change the host port.

Redis uses a named volume and AOF persistence (default every-second fsync). App restarts preserve cached searches and quota usage until expiry. This is not zero-loss durability: abrupt host failure may lose recent writes. docker compose down preserves the volume; adding --volumes deletes it. Back up the volume as needed.

Redis has a 128 MiB data budget with noeviction, so memory pressure rejects writes instead of silently discarding quota or statistics. The container has a 256 MiB memory limit, including overhead; adjust these together if workload requires it. On an external Redis service, use noeviction and persistence too. Deleting Redis data resets caches and quotas.

Run Locally Without Docker #

Use Node.js 22, Bun 1.3.9, and a running Redis service:

bun install --frozen-lockfile
cp .env.example .env
# Set REDIS_URL in .env to your Redis connection URL.
bun dev

For a production build, run bun run build, then bun run start with NODE_ENV=production. Redis supports redis:// and TLS rediss:// URLs. Production secrets belong in the service's runtime environment, never in the image.

The banner API and Pexels proxy require the running server. GitHub Pages alone cannot run these endpoints, and a GitHub Actions secret does not automatically become a Railway runtime variable.

Environment Variables #

Every instance serves its own documentation at /docs and public usage page at /usage. The usage page reads that same instance's /stats endpoint; it does not contact the official service for statistics. By default, set ENABLE_STATS=true to expose daily observations, or leave it off to show an explicit tracking-disabled state. The raw JSON API remains available at /stats. Saved exported designs and public community showcasing require official-hosted mode. Count-only export requests follow the optional tracking setting.

PORT=3000                         # Server port (host port with Compose)
NODE_ENV=development              # Docker image uses production
REDIS_URL=redis://localhost:6379   # Required; Compose supplies its internal URL
ENABLE_STATS=false                # Optional daily aggregate observations
OFFICIAL_HOSTED_INSTANCE=false     # Keep false for ordinary self-hosting
EXPORT_RETENTION_DAYS=            # Required in official mode: integer 1–365
PUBLIC_ORIGIN=                    # External origin when behind a proxy
PEXELS_API_KEY=                    # Optional server-side Pexels search

See .env.example. Disabling tracking does not disable Redis caching or quota control. With optional tracking enabled, individual banner and page requests can opt out with stats=false, DNT: 1, or Sec-GPC: 1. No API key is needed for direct image URLs or banner rendering.

Public origin and reverse proxies #

Set PUBLIC_ORIGIN to the exact external origin when a reverse proxy or hosting platform terminates HTTPS, for example https://banners.example.com. Supply only the scheme, hostname and optional port, with no path, query, fragment or credentials. This allows the browser's export and showcase-removal requests to pass the origin check even when the app receives internal HTTP traffic. Arbitrary forwarded headers are not trusted for this check.

When PUBLIC_ORIGIN is unset, official-hosted mode defaults to https://ghrb.waren.build. Ordinary self-hosted mode instead uses the request origin, which works for direct HTTP access. Set an explicit value for a custom official-mode domain or local test, such as http://localhost:3000; the official default does not follow the current request hostname.

Official-hosted mode #

The official service uses OFFICIAL_HOSTED_INSTANCE=true. This forces aggregate counting even if ENABLE_STATS=false, removes the optional statistics control, saves every design submitted through its built-in export controls, and enables the public showcase. In this mode, stats=false, DNT: 1, and Sec-GPC: 1 do not disable counting or export saving. The explicit showcase choice controls public display only.

Leave this flag off for ordinary self-hosting. If you enable the same behavior on your own instance, make the operator and applicable service policies clear to your users; this repository's official-service Terms do not identify you as its operator. The site's notices must match how you configure it.

Official mode also requires EXPORT_RETENTION_DAYS, an explicit integer from 1 to 365 with no default. Startup fails if it is missing or invalid. Choose and disclose the retention period before deploying official mode. Each saved export expires after that period; identical retries do not extend it. Ordinary self-hosting does not save export designs and does not require this setting.

Public showcase copies persist separately until withdrawn or removed. Withdrawal removes the public copy and listing, not the saved export before its expiry. The gallery holds at most 1,000 entries; when full, new exports still save and count while returning a clear non-publication status. Existing entries are not silently evicted. Back up Redis as needed; retained backups require their own handling and retention policy.

Upgrade and Availability #

Breaking configuration change in 2.0.0: every deployment now requires Redis, even with ENABLE_STATS=false. Existing deployments must provision Redis and set REDIS_URL before upgrading. Startup fails with a credential-free error if the URL is missing/invalid or Redis cannot be reached; the HTTP listener does not start.

During an outage, /health returns 503 and Pexels search returns 503 without bypassing the request budget. The app reconnects automatically when Redis recovers. Plain banner rendering remains available directly from the running process, but a hosting platform may remove unhealthy instances from traffic. Statistics remain optional with the default self-hosted configuration; /stats reports storage failures when tracking is enabled. Ordinary self-hosted export counting is best effort and does not block exporting during a statistics outage. Official built-in exports require available storage for the saved design; unconfirmed saves or publications are reported rather than silently claiming success. Retry the same export after storage recovers.

Railway Deployment #

Deploy on Railway

  1. Add a Redis service and persistent storage to your Railway project.
  2. Set the banner service's REDIS_URL to that Redis service's connection URL.
  3. Keep ENABLE_STATS=false, or set it to true to enable aggregate usage observations. Set PEXELS_API_KEY only if you want image search.
  4. Set PUBLIC_ORIGIN to the service's external HTTPS origin, such as https://banners.example.com.
  5. Deploy using this repository's Dockerfile and configure /health as the readiness endpoint.

The existing hosted template may need its Redis service and variables added before deployment. A repository change does not update a saved Railway template automatically.

Statistics availability #

Recording failures emit a payload-free operational error. The affected process returns /stats as unavailable for the remainder of that UTC day, since later writes cannot recover lost observations. This health signal is process-local and resets on restart; it is not fleet-wide monitoring. Counter and cardinality reads use one Redis transaction.

CI and Release Workflow #

.github/workflows/build-flow.yml uses Build Flow's pinned app workflow. Redis regression checks and the Docker/Compose smoke check run first, followed by native CI and CodeQL gates. CI uses Node 22 and the project's pinned Bun 1.3.9 for frozen dependency installation, static checks, type checking, and the production build. The final Build check requires both the integration job and Build Flow to succeed.

Build Flow's native development publishing targets Linux AMD64 images on Docker Hub (warengonzaga/github-repo-banner) and GHCR (ghcr.io/warengonzaga/github-repo-banner):

Event Image tag
Eligible PR into dev pr-<sha>
Push to dev, or dev β†’ main PR dev-<sha>
Other eligible same-repository PR into main patch-<sha>

The suffix is the seven-character source revision selected by Build Flow. Bot-authored PRs follow its native validation-only policy. Fork PRs also validate without publishing because they cannot receive registry credentials. Manual runs use Build Flow's native branch policy.

For an eligible main release, Build Flow plans the version, finalizes the versioned source and tag, publishes Linux AMD64 and ARM64 images, and creates the GitHub Release last. Production image tags include X.Y.Z, X.Y, X, and latest. Release and publishing runs on main are serialized. Versioning, tagging, and release ordering are managed by the upstream app workflow.

Docker Hub uses repository secrets DOCKER_HUB_USERNAME and DOCKER_HUB_ACCESS_TOKEN; the credentials need push access to warengonzaga/github-repo-banner. GHCR uses only the automatically supplied GITHUB_TOKEN with packages: write. No GHCR personal token is needed. The publishing job also has security-events: write for the action's default security scan reports.

Build Flow's native publishing policy requires at least one selected registry to succeed; inspect the per-registry results when confirming availability in both. Package visibility and registry access are configured in the registry; successful publication alone does not guarantee anonymous pulls. Railway can still build the Dockerfile directly from the repository.

Commands #

bun dev      # Development with hot-reload
bun run build    # Production build
bun run start    # Start production server

Security #

  • Input sanitization (XSS prevention)
  • Hex color validation
  • Header length limits
  • Shared image URL validation and public-address checks before HTTPS downloads
  • Image content-type, size, timeout, cache, and concurrency limits
  • Pexels credentials stay server-side; cached searches and request budgets reduce upstream calls

Fonts, icons, emoji assets, and remote images can depend on external services; deployments need outbound access for these features. Background downloads can fall back to a gradient. A failed custom inline icon or positioned image instead returns HTTP 422 and blocks the generator’s exports until the image is fixed or removed. Custom images need no additional API key or storage service.

Resource limits #

Backgrounds, inline custom icons, and positioned images share a process-local 32 MiB accounted-string cache with a 60-second TTL and at most four active downloads/decodes. Up to sixteen additional loads wait for a slot; beyond that, new loads are rejected. Requests for the same URL and size limit share in-flight work. Background downloads are limited to 10 MiB; each custom icon or positioned image is limited to 1 MiB. Failed or saturated background downloads use the existing fallback; custom-image failures return an error. Sharp validates image bytes with raster-only buffer decoders before caching. The declared media type must match the decoded format. All animation frames count toward a 16,777,216-pixel limit, and decoding has a five-second processing deadline. The Docker image includes Sharp’s native runtime dependencies. These limits bound retained image data, not total process memory; decoding temporarily allocates additional memory.

Each banner permits five custom images across positioned layers and both text fields. Custom URLs are limited to 512 units, and the images JSON parameter to 4,096 units before URL encoding. Only direct public HTTPS raster images are accepted; SVG, redirects, credentials, and private addresses are rejected. See custom image settings for geometry, formats, and inline syntax.

Pexels responses live in Redis for five minutes, limited to 64 KiB per response. Each process permits four concurrent upstream fetches and coalesces identical in-flight requests. A Redis script atomically enforces 100 upstream attempts per rolling hour per API key; failed upstream attempts count too. Queries are normalized and cache keys hash the query URL and API key. Quota entries hold timestamps and random request identifiers, with a one-hour expiry. Cache hits consume no quota; excess uncached requests receive HTTP 429. The budget survives app restarts and is shared only by instances using the same Redis database and API key. Other tools using that Pexels account remain outside this budget.

Image caching remains in process memory; image binaries are not stored in Redis. Saved exports and showcase copies store normalized settings, including custom image URLs and layout, rather than rendered image binaries. Sources must remain available for later previews. Existing saved designs without custom images require no migration. Official mode admits at most 60 new exports per rolling minute and retains at most 5,000 saved records, each limited to 8 KiB; public copies are separately capped at 1,000. These shared Redis limits reject new submissions before saving or counting, without evicting retained designs. Plan retention and capacity together; a full archive prevents new official exports until space expires. Existing identical retries still work at capacity. Bounded expiry and rolling-admission indexes contain only export IDs and timestamps; no permanent withdrawal markers are needed. Tracking opt-outs do not disable operational search caches or quota entries.

Regression Checks #

Use an empty, disposable Redis instance for integration checks; never point them at production. TEST_REDIS_URL is required and does not fall back to REDIS_URL.

bun run build
TEST_REDIS_URL=redis://127.0.0.1:6379 bun scripts/check-pexels-route.ts
TEST_REDIS_URL=redis://127.0.0.1:6379 bun scripts/check-redis-connection.ts
bun scripts/check-redis-startup.ts
sh scripts/check-docker.sh

Pexels responses in these checks are fixtures; no live API key is needed. The Docker check creates and removes its own containers and volume, without reading local .env secrets.

Terms of service #

Back to README Β· Privacy notice

Policy version: 2026-09-29

These terms describe use of the official GitHub Repo Banner service at ghrb.waren.build, maintained by Waren Gonzaga. Self-hosted services are operated separately and may have different settings and policies.

Usage counting and saved exports #

The official service records aggregate usage and saves the normalized design settings submitted through its built-in copy and download controls. This applies to every official export, whether or not you choose to showcase it. The showcase prompt controls public gallery display only; it does not turn counting or saving on or off.

Saved exports expire after the operator-configured retention period, which official mode requires before startup. The EXPORT_RETENTION_DAYS setting accepts 1 to 365 days and has no default. The deployed service must disclose its selected period. The Privacy notice describes these records, aggregate metrics and separate showcase copies.

The official service does not use stats=false, DNT: 1, or Sec-GPC: 1 to disable counting or export saving. Ordinary self-hosted instances offer optional counting without collecting exported designs.

Optional community showcase #

When an export control asks whether to showcase a design, choosing to share permits a separate public showcase copy and preview on the Usage page. Choosing not to share still saves the exported design and counts usage, but does not add it to the public gallery.

Only use content you are entitled to use and share, including text, icons, photographs and other assets. Do not include passwords, access tokens, sensitive personal information or material that violates others' rights. Public previews may be viewed, copied and downloaded by other people. Follow the project's Code of conduct.

You retain any rights you hold in your design. Saving an export or showcasing it does not transfer ownership or automatically place it under the software's MIT license. The project being open source does not make every input a public gallery submission. Banner URLs are accessible to anyone who has them, independently of the showcase choice.

Showcase removal and availability #

Save the removal code or removal link provided for a showcase submission. The browser may keep the code locally for convenience, but clearing browser storage can remove that copy. Treat the code as a secret: anyone with it can withdraw that showcase entry.

Public showcase copies remain until withdrawn or removed by the operator, independently of the saved export's expiry. Withdrawal removes the public copy and gallery listing. It does not erase the saved export before its retention period ends or recall copies already obtained by others. The service does not promise permanent gallery hosting; entries may be removed for policy violations, rights concerns or service maintenance.

If you cannot use the removal code, contact the maintainer through project issues with the entry ID. Do not post removal codes or sensitive content in a public issue.

Previews are rendered from settings, not archived image snapshots. External images, fonts or other assets may change or become unavailable. If the gallery is full, the service still saves the export and counts usage, but explains that the design was not showcased. A storage failure or a service capacity/rate limit can prevent an official export from completing; retrying the same saved export does not extend its retention, count it again or republish a withdrawn showcase.

Software and service #

The repository's source code is provided under the MIT license, including its warranty disclaimer. Use the hosted service responsibly and do not attempt to bypass validation, resource limits or access controls. For questions about these terms or to report a showcase entry, use project issues.

Privacy policy #

Back to README Β· Terms of Use

Policy version: 2026-09-29

This notice describes the official GitHub Repo Banner service, maintained by Waren Gonzaga. It distinguishes required aggregate counting and saved exported designs from the optional public community showcase. Self-hosted operators control their own deployment and infrastructure logs.

Aggregate usage #

The official service counts successful origin banner and page responses and accepted built-in exports to report service activity. The public Usage page shows current UTC-day observations, not individual users or verified project adoption. Caches, bots, retries, outages and missing Referer headers affect the totals. The metrics do not promise to include every possible download or clipboard action, and accepting an export does not prove the browser completed its copy or download.

Aggregate measurement stores daily counters for banners, each page and exports, plus a Redis HyperLogLog sketch of SHA-256-hashed repository identifiers from valid GitHub Referer paths. These metrics do not contain raw repository names, full Referers, banner content, IP addresses, cookies, sessions or visitor identifiers. Repository estimates do not establish that a repository exists or is public. Exported design records, described below, are separate from these counters.

Daily metric keys expire seven days after their last write. Counting and export saving are required on the official service, including when a user declines showcasing. stats=false, DNT: 1, and Sec-GPC: 1 do not disable them. These counting opt-outs remain available for ordinary self-hosted instances, which do not collect exported designs.

Saved exported designs #

Every official built-in copy or download request saves the normalized design settings needed to render that export, its action and showcase choice, an entry ID combining a submission timestamp and random UUID, timestamps, the policy version, and a hash of its removal code. Settings can include banner text, styling and a background image URL. The service retains these as records of exported designs; they have no public retrieval endpoint. The showcase confirmation controls only whether a separate copy appears publicly, not whether the export is saved or counted.

Saved exports expire after the operator-configured EXPORT_RETENTION_DAYS period. Official mode requires an explicit value from 1 to 365 days, with no default; the deployed service must disclose its chosen period. Identical retries do not extend that expiry. Withdrawing a public showcase does not remove the saved export before its retention period ends.

The normal renderer also processes design settings to generate banners. Banner URLs contain those settings and are accessible to anyone who has the URL. Do not include secrets or sensitive personal information. Using an open-source project or rendering a banner does not itself submit a design to the public gallery.

Public showcase copies #

Explicitly choosing to showcase permits a separate copy of the saved design to be rendered publicly on the Usage page. Declining keeps the saved export out of this gallery. The public feed returns the entry ID, creation time, a text description from the visible header and subheader, and preview URL; it does not return settings or removal codes. The preview exposes the design's visible content, and external assets may change or become unavailable.

Public copies are kept until the creator withdraws them or the operator removes them, independently of the saved export's expiry. Permanent hosting is not promised. When the gallery is full, the export still saves and counts, but the response and interface explain that it was not publicly showcased.

Showcase removal #

Save the removal code or removal link provided when submitting for showcasing. The generator may retain up to 100 removal codes in this browser's local storage for convenience. This is a per-design capability, not a visitor identifier or analytics cookie. It is not synchronized between browsers; clearing local storage removes this convenience copy.

Use the removal link or the Usage page's removal control to withdraw the public copy and its gallery listing. This does not delete the saved export before its configured expiry, reduce historical aggregate counts, or erase copies already downloaded or stored by others. An identical export retry does not republish a withdrawn entry. Anyone with the code can remove that showcase entry, so do not publish it.

If you no longer have the code, or need to report someone else's entry, contact the maintainer through project issues using the entry ID. Do not post sensitive details or removal codes in a public issue. The operator may need to establish which entry you mean and your relationship to it before acting.

The service rejects expired submission IDs once their saved and public records are gone, without permanent withdrawal markers. Capacity and rate controls keep bounded indexes of export IDs and expiry or admission timestamps, without design content or visitor identifiers. The admission index expires after one minute of inactivity; expired retention-index entries are removed on the next accepted export. An unconfirmed in-flight submission must be checked again before removal can be confirmed.

Operational storage and third parties #

Redis is required even when an ordinary self-hosted instance disables tracking. Pexels search responses expire after five minutes; query and API-key cache identifiers are hashed. Quota entries contain timestamps and random identifiers and expire within an hour of the last allowed request. Background images have a short in-process cache; large image binaries are not stored in Redis. Tracking opt-outs do not disable these operational caches or quotas.

Pexels searches and rendering can contact external photo, font, icon, emoji and image services. Hosting providers and reverse proxies may keep access logs independently of application measurement. Operators are responsible for their log and backup settings; this notice does not claim those systems never retain request data. The legacy /log endpoint ignores old client payloads.

For metric definitions and API behavior, see Privacy & Transparency and the API reference. For questions about this notice, use project issues.

Contributing #

Thank you for your interest in contributing! Every contribution helps make this project better. πŸŽ‰

πŸš€ Quick Start with contribute-now #

This project recommends using contribute-now β€” a developer CLI that automates git workflows so you can focus on shipping, not on memorizing git commands.

Setup #

bunx contribute-now setup

Or install globally:

bun install -g contribute-now
contribute setup

Note: contribute-now runs on Bun at runtime. Install Bun first if you don't have it.

Once set up, use contribute (or the short alias cn) to handle branching, syncing, staging, committing, and opening PRs automatically.

πŸ”§ Manual Workflow #

If you prefer the manual approach:

Prerequisites: Node.js ^22, Bun 1.3.9, Git, and a running Redis service are required. See self-hosting for Docker Compose and Redis setup.

  1. Fork the repository
  2. Clone your fork and install dependencies:
    git clone https://github.com/<your-username>/github-repo-banner.git
    cd github-repo-banner
    bun install --frozen-lockfile
    cp .env.example .env
    # Set REDIS_URL in .env to your development Redis URL.
    
  3. Create a feature branch:
    git checkout -b feature/your-feature-name
    
  4. Make your changes and commit using the Clean Commit convention
  5. Push your branch and open a Pull Request

πŸ“ Commit Convention #

This project strictly follows the Clean Commit convention by WGTech Labs.

Format #

<emoji> <type>: <description>

With optional scope:

<emoji> <type> (<scope>): <description>

Commit Types #

Emoji Type When to use
πŸ“¦ new Adding new features, files, or capabilities
πŸ”§ update Changing existing code, refactoring, improvements
πŸ—‘οΈ remove Removing code, files, features, or dependencies
πŸ”’ security Security fixes, patches, vulnerability resolutions
βš™οΈ setup Project configs, CI/CD, tooling, build systems
β˜• chore Maintenance tasks, dependency updates, housekeeping
πŸ§ͺ test Adding, updating, or fixing tests
πŸ“– docs Documentation changes and updates
πŸš€ release Version releases and release preparation

Rules #

  • Always include the matching emoji prefix β€” the emoji is mandatory, not optional
  • Use lowercase for type β€” never capitalize it
  • Use present tense β€” write "add" not "added"
  • No period at the end of the description
  • Keep description under 72 characters
  • Scope is optional β€” when used, keep it short (one word) and lowercase

Examples #

πŸ“¦ new: simple icons support for banner headers
πŸ”§ update (api): improve hex color validation
πŸ—‘οΈ remove: deprecated banner endpoint
πŸ”’ security: patch XSS in query parameter handling
βš™οΈ setup (ci): add GitHub Actions workflow
β˜• chore (deps): bump hono to latest version
πŸ§ͺ test: add unit tests for banner generation
πŸ“– docs: update API reference with new parameters
πŸš€ release: version 2.0.0

Tip: contribute-now natively supports and enforces the Clean Commit convention β€” just use cn commit or cn push and it guides you through the right format automatically.

πŸ› οΈ Development #

bun dev          # Start development server with hot-reload
bun run build    # Production build
bun run start    # Start production server
bun run check    # Static checks

Development container builds #

The Build Flow workflow uses the upstream app workflow's native container publishing. Redis regression checks and the Docker smoke check run before Build Flow's CI and CodeQL gates.

Eligible PRs into dev publish pr-<sha> images; pushes to dev and dev β†’ main PRs publish dev-<sha> images. Other eligible same-repository PRs into main publish patch-<sha> images. The suffix is the seven-character source revision selected by Build Flow. Docker Hub and GHCR are the native registry targets. Bot-authored PRs use Build Flow's validation-only policy. Fork PRs remain validation-only through its supported PR publishing setting because they cannot receive registry credentials.

These are development container images. Versioned production images and GitHub Releases follow Build Flow's native main release process. Publishing policy and tags stay with Build Flow; the repository does not add a separate artifact-upload or release implementation.

πŸ› Reporting Issues #

Before opening an issue, please check if one already exists. When reporting a bug, include:

  • A clear description of the problem
  • Steps to reproduce it
  • Expected vs actual behavior
  • Relevant environment details (OS, Node version, etc.)

For major changes, open an issue first to discuss what you'd like to change.

πŸ“„ Code of Conduct #

Please read and follow our Code of Conduct. We are committed to providing a welcoming and inclusive environment for everyone.


πŸ’»πŸ’–β˜• by Waren Gonzaga | YHWH πŸ™

Code of conduct #

Our Pledge #

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

Our Standards #

Examples of behavior that contributes to a positive environment for our community include:

  • Demonstrating empathy and kindness toward other people
  • Being respectful of differing opinions, viewpoints, and experiences
  • Giving and gracefully accepting constructive feedback
  • Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
  • Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

  • The use of sexualized language or imagery, and sexual attention or advances of any kind
  • Trolling, insulting or derogatory comments, and personal or political attacks
  • Public or private harassment
  • Publishing others' private information, such as a physical or email address, without their explicit permission
  • Other conduct which could reasonably be considered inappropriate in a professional setting

Enforcement Responsibilities #

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.

Scope #

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.

Enforcement #

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at opensource@warengonzaga.com. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

Enforcement Guidelines #

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

1. Correction #

Community Impact: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

Consequence: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

2. Warning #

Community Impact: A violation through a single incident or series of actions.

Consequence: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

3. Temporary Ban #

Community Impact: A serious violation of community standards, including sustained inappropriate behavior.

Consequence: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

4. Permanent Ban #

Community Impact: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

Consequence: A permanent ban from any sort of public interaction within the community.

Attribution #

This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.

Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.

For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.

MIT license #

MIT License

Copyright (c) 2026 Waren Gonzaga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.