Skip to content

Mariano Rodrigo

AI Solutions Engineer building production systems with artificial intelligence, automation, and full-stack architecture. This is my public engineering lab: architecture decisions, implementation reports, experiments, and lessons from real systems.

Rebuilding LinkedIn's Save to PDF: a browser extension that runs entirely on the client

LinkedIn Save to PDF: from a profile to a recruiter-ready PDF in one click

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 PDF1-click exportDeep 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.local briefly 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, scripting and tabs, scoped to https://*.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

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.