Rebuilding LinkedIn's Save to PDF: a browser extension that runs entirely on the client
LinkedIn quietly removed its “Save to PDF” option. For anyone applying for a job, recruiting, or just archiving a profile, a one-click export disappeared. So I rebuilt it, as an open-source Chrome extension, with one firm constraint: it had to run entirely on the client. The goal was not only to bring the feature back, but to end up with a better export than LinkedIn’s own.
The constraint that shaped everything
No servers, no accounts, no tracking. Every step, reading the profile, building
the layout, generating the PDF, happens inside your browser. The only storage it
touches is chrome.storage.local, used briefly to hand the extracted data to the
print view, and cleared the moment the PDF is produced. Nothing is collected and
nothing is transmitted.
That constraint is not a marketing line, it is an architecture decision. A profile is personal data. The moment you send it to a server to “process” it, you own a privacy problem: logs, retention, a breach surface, a privacy policy to honor. Keeping everything local removes that entire class of problem. The tradeoff is that the browser has to do all the work, with no backend to lean on.
The hard part: no API, a moving DOM, and truncation
LinkedIn has no public API for this, so the extension reads the rendered page through content scripts. That comes with the usual pain: the DOM changes, the markup is deeply nested, and the profile renders in whatever language the account uses. The parsing is structural rather than text-based, so it works with LinkedIn profiles in any language.
The harder problem is truncation. The main profile page shows only the first few
entries of experience, education and skills; the rest sits behind “Show all”. To
capture everything, the optional Full Profile Export opens the same profile’s
/details/<section>/ pages in temporary background tabs, one at a time, reads the
complete lists, and closes them. It is slower, and it is opt-in, but it recovers
the sections a plain export would miss.
Generating the PDF
There is no PDF library bundled. The extension builds a clean, print-optimized view and uses the browser’s native print-to-PDF. That keeps the extension small, avoids shipping a rendering engine, and produces text-based, selectable, ATS-friendly output rather than a flattened image. The print view renders in light mode for consistent results, and you choose which sections to include: photo, contact info, experience, education, skills.
There is an ATS angle too. LinkedIn’s original PDF was designed for printing, not necessarily for applicant tracking systems, and its two-column layout is exactly the kind of structure those parsers struggle with. The extension instead generates a simple single-column document with selectable text, which is easier for both humans and ATS parsers to read in order.
One click, or the full profile
The extension ships two modes, and it helps to line them up against LinkedIn’s
own PDF. The 1-click export is instant and covers the essentials. The Deep
export opens the truncated /details/ pages to rebuild the complete profile.
LinkedIn’s built-in PDF sits in between, in a two-column layout that ATS parsers
tend to dislike. Same profile, three exports:
| LinkedIn’s PDF | 1-click export | Deep export | |
|---|---|---|---|
| One click, instant | ✓ | ✓ | ✗ |
| Profile photo | ✗ | ✓ | ✓ |
| Full role descriptions | ✓ | ✗ | ✓ |
| All skills, not just the top few | ✗ | ✗ | ✓ |
| All languages | ✓ | ✗ | ✓ |
| Pick which sections to include | ✗ | ✓ | ✓ |
| Single-column, ATS-friendly | ✗ | ✓ | ✓ |
| Generated locally, nothing leaves the browser | ✗ | ✓ | ✓ |
Feature for feature, the extension produces the better document. The Deep export is strictly more complete than LinkedIn’s PDF: it keeps the photo and recovers every role description, every skill and every language, and it comes out as clean, single-column, ATS-friendly text generated entirely on your machine. LinkedIn’s one remaining edge is the instant single click, and that is exactly what the 1-click mode matches. So you get both: instant when you want speed, complete when you want everything, and in neither case does your profile leave the browser.
The engineering decisions
For readers who build, the moving parts are deliberately few. It is a Manifest V3 extension, and each piece has one job:
- Content scripts read the rendered profile straight from the page.
- A background service worker orchestrates the Full Profile Export, opening and closing temporary tabs one at a time.
chrome.storage.localbriefly carries the extracted data to the print view, then is cleared.window.print()drives the browser’s native print-to-PDF, so no rendering engine ships with the extension.- Structural, language-independent selectors target the DOM by shape rather than by text, so the parser survives a profile in any language.
- A narrow permission set,
activeTab,storage,scriptingandtabs, scoped tohttps://*.linkedin.com/*and nothing else.
A reader can audit exactly what it can touch, which matters for a tool that handles profile data. The recurring theme is failing gracefully: without an API, every selector is a bet on how LinkedIn renders today, so the parser is built to degrade rather than break. The whole thing is open source.
What building it taught me
Building the extension reminded me that browser extensions are often closer to systems engineering than to frontend development. Without an API, every release depends on understanding how another application renders its interface, anticipating change, and designing parsers that fail gracefully. The interface is the easy part. The hard part is that you are integrating with a surface nobody designed to be integrated with, and it keeps moving under you.
Install and source
- Chrome Web Store: LinkedIn Save to PDF (Ingenarte)
- Source: github.com/Ingenarte/Linkedin_Save_to_PDF
Takeaway
The original goal was simply to bring back a missing feature. It ended up becoming an exercise in privacy-first architecture, browser automation, DOM parsing, and designing software that keeps personal data exactly where it already is: on the user’s machine. That is the part I would keep. For a tool that reads someone’s profile, the right default is to do the work where the data lives, and never let it leave.