Nineteen document utilities for recruiters that run entirely in your browser. The CV never leaves your machine.
Merge two PDFs? Upload the CV. Redact a name? Upload it. Every day, candidate data — salaries, home addresses, right-to-work documents — gets dropped into whatever free tool comes up first on Google. Most of those services keep the file. Some train on it. Almost none of them are covered by the data agreement you signed with your client or your candidate.
These tools do the same jobs without the upload. They are a static web page. The file is opened by JavaScript running in your own tab, changed there, and handed back to you as a download. There is no server to send it to, because there is no server.
Live site: https://openrecruitertools.github.io/recruiter-tools/
Everything is one page: a grid of cards, filtered by category. Drop a file on a
card, set the options, press Run, take the download. A deep link like
/#tool=blind-cv
goes straight to one tool.
Fifteen work today. Four are greyed on the dashboard with a badge, because doing them honestly needs a real office layout engine rather than a browser tab — an approximation would quietly move your page breaks in a document going to a client.
| Tool | Category | In → out | What it does | Worth knowing |
|---|---|---|---|---|
| PDF Merge | .pdf (several) → .pdf | Joins PDFs in the order you drop them | Bookmarks, form fields and attachments are not carried across | |
| PDF Split | .pdf → .pdf or .zip | Keep a page range, remove pages, or burst every page into a zip | Ranges outside the document are trimmed rather than refused; removing every page is refused | |
| PDF Compress | .pdf → .pdf | Re-encodes the JPEGs inside a heavy scan at a quality you choose | A text-only PDF will not shrink — it says so and gives you the original back. JBIG2, CCITT and JPEG 2000 images are skipped | |
| PDF to Word | Conversions | .pdf → .docx | Not available in the browser | Needs a desktop app. Rebuilding paragraphs, tables and styles from positioned glyphs needs a real layout engine |
| Blind CV | .pdf → .pdf | Removes name, email, phone, LinkedIn and optionally pronouns, DOB, address, nationality | The output is a picture of the CV: no longer selectable or searchable. Cannot read text inside a scan. Detail below | |
| PDF Header/Footer | .pdf → .pdf | Text in any of six slots, {page} and {pages} tokens, optional logo |
Draws over the page rather than making room. Latin characters only | |
| PDF to Images | .pdf → .zip | Renders every page to a PNG or JPEG | One image per page in a zip; large documents are limited by your machine's memory | |
| PDF Watermark | .pdf → .pdf | Stamps text across every page: straight, diagonal or tiled | A label, not a lock — removable in any PDF editor. Latin characters only | |
| Remove PDF Password | .pdf → .pdf | Unlocks a password-protected PDF, keeping the text intact | You need the password. This removes a lock you are entitled to remove; it does not break one | |
| Extract Text | .pdf → .txt | Pulls the text out into a plain text file | A scan has no text to pull out, and it says so rather than handing you an empty file. No OCR here | |
| Cover Page | .pdf → .pdf | Puts a branded front sheet on a candidate PDF | Three layouts. Built-in PDF fonts, so Latin characters only | |
| Word to PDF | Conversions | .doc/.docx → .pdf | Not available in the browser | Needs a desktop app. Line breaking, hyphenation and fonts decide where the pages break |
| PPTX to PDF | Conversions | .ppt/.pptx → .pdf | Not available in the browser | Needs a desktop app. Themes, masters, SmartArt and charts have to render as PowerPoint renders them |
| Excel to PDF | Conversions | .xls/.xlsx → .pdf | Not available in the browser | Needs a desktop app. Print areas, fit-to-page scaling and repeated headers decide what a sheet looks like on paper |
| Images to PDF | Images | images (several) → .pdf | Combines images into one PDF, in the order you drop them | PNG, JPEG, WebP, BMP and GIF |
| Excel to CSV | Conversions | .xlsx/.xls/.ods → .csv | Turns one sheet of a spreadsheet into a CSV | One sheet at a time. Formulas become their last calculated value |
| CSV to Excel | Conversions | .csv/.tsv/.txt → .xlsx | Turns a CSV into a real .xlsx workbook | The delimiter is detected; check the preview if your data has commas inside quoted fields |
| Compress Image | Images | image → same format | Shrinks a photo or scan, keeping its format | PNG is lossless, so the quality slider does nothing for one — only a max width shrinks it, and the tool says so |
| Merge Word Documents | Documents | .docx (several) → .docx | Joins .docx files with a page break between them | .docx only, not the older .doc |
Every page carries the same line: This runs in your browser. Nothing is uploaded. And a second one: it works offline.
This is the whole point, so it is not left as a promise.
- The libraries are vendored, not linked. pdf.js, pdf-lib, JSZip and the
rest are copies in
vendor/, pinned with checksums invendor/VERSIONS.md. No CDN, no Google Fonts, no analytics, no trackers, no cookies, no service worker. A page fetches this site's own files and nothing else. - It works with the network off. Load the page, disconnect, and every tool still runs. There is nothing for it to phone home to.
- The build checks it.
scripts/smoke.mjsopens the dashboard in a real headless browser, drives every available tool through its own card with a real file, and fails if the page makes a single request to any origin but its own. That runs on every push and every pull request. If someone adds a CDN link, CI goes red. - The redaction claim is checked too. The same script loads the blind CV back with pdf.js and asserts that no extractable text remains anywhere in it.
Nothing is stored either: no localStorage, no IndexedDB, no cookies. Close
the tab and the file is gone from the browser.
A tool is one file with one exported object. Copy
src/tools/_template.js, register it in
src/registry.js, add a test, run npm test && npm run smoke, open a pull request — the card, its drop zone, its options form, the
progress bar and the download button are all rendered from what you declared.
CONTRIBUTING.md walks through all four steps, and states
the three hard rules: it runs entirely in the browser, no telemetry, and any
library must be permissively licensed (MIT / Apache-2.0 / BSD — no GPL or AGPL),
vendored, and checksummed in vendor/VERSIONS.md.
The most useful thing you can send us is not code, though: which tool do you upload candidate data to today? The most-asked-for tool gets built next.
The whole dashboard is embeddable with one script tag and one container. That is how openrecruitertools.github.io shows it without copying a line of this repository.
<section data-free-tools></section>
<script type="module" src="/recruiter-tools/embed.js"></script>The contract:
-
embed.jsis the only stable entry point. It exportsmountFreeTools(target, config?), plustoolsandgetToolfrom the registry, and auto-mounts into the first[data-free-tools]element on the page if there is one. Everything else insrc/is internal and may move. -
Use an absolute, same-origin path for the
src. Every asset the dashboard loads — its stylesheet, the vendored PDF libraries, the pdf.js worker — is resolved fromimport.meta.url, never from the page URL, so the same file works when the site is served at/and at/recruiter-tools/. It has to be same-origin because the module is a module and the workers are workers. -
It brings its own CSS.
dashboard.cssis linked into<head>once, and everything is scoped to.free-tools, so it will not fight your stylesheet. Override the custom properties on.free-tools(--ft-bg,--ft-accent,--ft-card,--ft-border, …) if you want it in your own colours. -
To mount it yourself, import it instead:
<script type="module"> import { mountFreeTools } from '/recruiter-tools/embed.js'; mountFreeTools('#my-container', { title: 'Free Tools' }); </script>
configtakestools(a subset of the registry),titleanddisclaimer. It returns{ element, destroy() }. -
Deep links keep working:
#tool=<id>scrolls to that card and highlights it, on whichever page the dashboard is embedded in. -
It makes no network requests of its own beyond this site's files, so it does not change the privacy story of the page that embeds it.
A black rectangle drawn over a PDF is not redaction. The words are still in the file and anyone can copy them straight back out. That is how redaction failures end up in the news, and plenty of "redact PDF" tools do exactly that.
Genuinely deleting individual glyphs from a PDF content stream needs a full text engine and metrics for every embedded font. A half-working version of that is worse than nothing, because it looks redacted and is not.
There is a library that does it properly — MuPDF's WASM build has a real
addRedaction / applyRedactions pair — but it is AGPL-3.0, which would
make this whole site AGPL and is incompatible with keeping it MIT. See
vendor/VERSIONS.md.
So this tool takes the route that can actually be guaranteed:
- pdf.js renders each page to a canvas and reports where every run of text sits.
- Matches are found in the page's text — the names you typed, plus patterns for emails, phones, URLs, LinkedIn handles and the optional categories — and mapped back to rectangles on the canvas.
- Those rectangles are painted onto the pixels. Gendered pronouns are handled differently: the word is covered in the paper colour and the neutral form (they / them / their) is written in its place.
- pdf-lib builds a brand-new PDF whose pages are those images.
- No text survives anywhere in the output. There are no text objects in the file at all — nothing to select, copy, search or recover, including text that was hidden, white-on-white, or underneath something else in the original.
- None of the original metadata comes across. No title, author, producer or XMP; no bookmarks, attachments, annotations, form fields, JavaScript or embedded files. The output is a fresh document.
- Nothing leaves your machine.
- That it found everything. Detection is patterns plus the names you type. An unusual phone format, a different spelling, an initial, a personal website or a distinctive job title can all slip through. Look at the preview, every time.
- Anything about scans. If the CV is a photograph of a document there is no text to find. The page is rebuilt exactly as it looked, name and all. There is no OCR here — and the tool warns you when it sees a page with no text.
- Photographs. A profile photo is not removed in this version. Crop it first.
- Exact placement. Boxes come from the positions pdf.js reports, spreading a run of text evenly across its characters. Occasionally a box catches a neighbouring letter, or a word hyphenated across two lines is only half matched.
The cost of the guarantee: the output is a picture. It is no longer selectable or searchable, an ATS cannot parse it, and it is usually larger than the original. That is the price of the text genuinely being gone, and it is the right trade for a blind CV going to a client.
No build step. It is HTML, two stylesheets, and ES modules.
git clone https://github.com/OpenRecruiterTools/recruiter-tools
cd recruiter-tools
npm install # only needed for the tests and linter
npm run serve # http://127.0.0.1:8080A static server is required rather than opening index.html from the
filesystem: pdf.js runs in a module worker, and browsers refuse to load one over
file://. Any static server will do — python -m http.server works fine.
To work on the embed, serve both sites the way GitHub Pages does — the
organisation home page at /, this repository at /recruiter-tools/:
npm run serve:both # expects ../openrecruitertools.github.io
ORG_SITE=../elsewhere npm run serve:bothNode 20 or newer for the tooling.
npm test # vitest + jsdom
npm run test:contract # just the registry contract check
npm run lint # eslint
npm run format:check # prettier
npm run smoke # drives every available tool in headless Chromium
npm run screenshots # the dashboard and the org home page at 1280 into docs-screenshots/npm test covers the parts worth testing on their own: the tool contract over
every module in the registry, the dashboard's rendering and filtering, the
redaction pattern finder and its overlap resolution, the page-range parser, the
text-box and watermark geometry, size formatting, spreadsheet and text handling,
and the options-to-run plumbing of each tool.
npm run smoke needs Chromium. It looks for playwright-core in
PLAYWRIGHT_CORE, then in node_modules, then in a local npx cache; install
one with npm i -D playwright-core && npx playwright install chromium.
index.html the hero and the dashboard
embed.js the stable entry point other pages import
dashboard.css the dashboard's own stylesheet, scoped to .free-tools
styles.css the page around it
tools/*.html redirect stubs to /#tool=<id>, for the old per-tool URLs
src/
registry.js THE TOOL CONTRACT, and the list of tools in card order
dashboard.js renders the grid, the filters and the tool modal
tools/ one file per tool — _template.js to copy, _shared.js helpers
patterns.js what to redact — pure, heavily tested
ranges.js page ranges — pure
geometry.js text boxes, watermark placement — pure
format.js sizes and names — pure
redact.js the flagship: render, paint, rebuild
merge/split/compress/watermark/headerfooter.js
ui.js shared drop-zone and download plumbing
vendor.js loads the vendored libraries
vendor/ pinned libraries + VERSIONS.md
tests/ vitest — tools/ mirrors src/tools/
scripts/ static servers, fixtures, smoke run, screenshots
Issues and pull requests welcome, particularly from recruiters who are not programmers: try a tool on a real CV, and say where it confused you or what it missed. Bug reports about redaction misses are the most valuable thing you can file — please describe the shape of what was missed rather than pasting real candidate data.
Start with CONTRIBUTING.md. By taking part you agree to the Code of Conduct.
Still on the list: profile-photo removal in the blind CV, OCR for scans, and desktop companions for the four office conversions that cannot be done in a tab.
MIT — see LICENSE. Vendored libraries keep their own licences; see vendor/VERSIONS.md.
Part of Open Recruiter Tools — https://github.com/OpenRecruiterTools · Built by Dominic Gonsalves, founder of Formatix AI