Browser extension (Chrome 102+ and Firefox 112+) that copies a GitHub pull request as a rich-text hyperlink. Pasting in Slack, email, or any rich-text editor renders as Pull Request 1234: Title with only the identifier portion as a clickable link — matching Azure DevOps's "Copy" button on PR pages.
- Download the
copy-pr-link-vX.Y.Z.zipasset from the latest release. - Unzip it somewhere permanent — Chrome loads the folder by path, so deleting or moving it breaks the extension.
- Open
chrome://extensions/, toggle Developer mode on, click Load unpacked, and select the unzipped folder. - Pin the extension to your toolbar.
New releases require redownloading the zip and reloading the extension — Chrome does not auto-update unpacked extensions.
If the release includes a Mozilla-signed .xpi asset, use that — it installs
permanently:
- Download the
.xpiasset from the latest release. - Open
about:addons, click the gear icon → Install Add-on From File…, and select the.xpi(or drag it onto the Add-ons page).
Otherwise, load the zip temporarily:
- Download the
copy-pr-link-vX.Y.Z.zipasset. - Open
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on… and select the zip file (or
manifest.jsoninside the unzipped folder).
Note: Temporarily loaded add-ons are removed when Firefox restarts. For a persistent install, the extension must be signed by Mozilla or Firefox must be configured to allow unsigned extensions (see Firefox Developer Edition / Nightly).
- Open
chrome://extensions/. - Toggle Developer mode on (top-right).
- Click Load unpacked and select this directory.
- Pin the extension to your toolbar.
- Open
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on… and select
manifest.jsonin this directory. - Pin the extension to your toolbar.
- Navigate to any
https://github.com/<owner>/<repo>/pull/<number>page. - Click the toolbar icon — or press
Alt+Shift+C— to copy. - The icon flashes a green ✓ when the copy succeeds, or a red ! if it fails (e.g. the clipboard was blocked).
- On non-PR pages the icon is grayed out.
- Paste in Slack / Gmail / Notion / docs → renders as
Pull Request 1234: TitlewithPull Request 1234as the hyperlink.
- Chrome: Visit
chrome://extensions/shortcutsand rebind Copy current PR as a rich-text link. - Firefox: Visit
about:addons, click the gear icon → Manage Extension Shortcuts, and rebind the command.
Requires Node.js 22+ (see .nvmrc).
npm install # install dev tooling (Vitest, ESLint, Prettier, web-ext)
npm test # run the unit tests
npm run coverage # run tests with coverage (100% enforced on all shipped code)
npm run lint # ESLint
npm run lint:ext # web-ext lint (validates the manifest for Firefox)
npm run format # auto-format with Prettier
npm run build # package a clean .zip into dist/
npm run check # everything CI runs: format check + lint + web-ext lint + testsnpm run build produces dist/copy-pr-link-v<version>.zip containing only the
runtime files (manifest.json, background.js, src/, icons/). Load that
unpacked, or upload it to a release.
Releases are one click: keep the changelog's [Unreleased] section up to
date, then run the Prepare Release workflow (Actions → Prepare Release →
Run workflow) and pick a bump level. It bumps every version field in
lockstep, promotes the changelog, tags, and publishes a GitHub Release with
the zip — plus store publishing when credentials are configured.
See RELEASING.md for the full runbook: what the automation does, the manual fallback, browser-store credential setup (Firefox signing and Chrome Web Store), the first-time Chrome Web Store submission checklist, and troubleshooting.
The code is split so the logic is unit-testable in Node, separate from the browser-only wiring:
src/pr.js— pure logic, no browser APIs:parsePrUrl(URL → PR parts),parsePrTitle(document title → PR title),formatPrLink(PR + title → the link parts). Exercised bytests/pr.test.js.src/clipboard.js— the in-page clipboard writer (writeRichLinkInPage): a self-contained function that background.js injects into the page viachrome.scripting.executeScript, so it must not reference imports or module state. Tested under jsdom intests/clipboard.test.js.background.js— the extension layer: wires up the toolbar action, keyboard command, and tab listeners, and injects the clipboard write into the page. Tested against a mockedchromeAPI intests/background.test.js. It's loaded as an ES module ("background": { "type": "module" }), which is why the manifest requires Firefox 112+.
Coverage is enforced at 100% (statements, branches, functions, lines) across
all shipped code, and tests/manifest.test.js guards the manifest against
drifting out of sync with the package version, shipped icons, and the command
wiring.
manifest.json— MV3 manifestbackground.js— extension entry point / service worker (action, command, and tab listeners)src/pr.js— pure, testable PR-parsing and link-formatting helperssrc/clipboard.js— the self-contained clipboard writer injected into the pagetests/pr.test.js— Vitest unit tests forsrc/pr.jstests/clipboard.test.js— Vitest unit tests for the clipboard writer (jsdom)tests/background.test.js— Vitest unit tests for the extension wiring, run against a mockedchromeAPItests/manifest.test.js— manifest/package consistency checksicons/icon-{16,48,128}.png— toolbar iconsicons/icon.svg— source for regenerating the PNGs (Octiconsgit-pull-request, MIT)icons/LICENSE-octicons.txt— MIT license attribution
activeTab— read the active tab's title and URL when you click.scripting— inject the clipboard write into the page's context.clipboardWrite— write the formatted link to the clipboard.https://github.com/*— so the icon can be enabled / disabled as you navigate.
- The clipboard write prefers
navigator.clipboard.write()for rich HTML copy and falls back todocument.execCommand('copy')when needed for browser compatibility. - Slack's "Check this link" anti-phishing warning fires for rich-text anchors whose visible text contains
#. The display formatPull Request N(no#) sidesteps the warning. - Icon credit: GitHub Octicons (
git-pull-request), MIT licensed. Seeicons/LICENSE-octicons.txt.
MIT.