How it decides
LinkedIn's feed uses rotating, obfuscated class names and no stable post id, so ordinary selectors break constantly. The extension keys off signals that survive:
- Post boundary. Every feed item is wrapped in an element with
data-lazy-mount-id, and real posts begin their text with "Feed post". - Connection degree. The actor header shows
• 1st/• 2nd/• 3rd+. 1st-degree posts are always kept. - Relationship buttons. For everyone else the decision is read from the real buttons LinkedIn renders (matched on
aria-label): Following / Unfollow means kept, Follow … or Invite … to connect means hidden, and no relationship button at all means your own post or a Page you follow, so kept.
Always removed on top of that: ads ("Promoted by …", "Sponsored", or a Promoted label on a degree-less company post; a connection's post that merely contains the word "Promoted" is not hidden, that false positive was found and fixed), "Suggested" posts, "… follows this Page" items, and recommendation modules such as "Jobs recommended for you" and "People you may know". It can also switch the sort from Top to Recent, but only when the feed is on Top, so a manual choice is never overridden.
Options (toolbar popup)
- Filter feed: master on/off.
- Hide reposts & reactions: stricter mode that also drops posts surfaced because someone reposted, liked or commented, even from connections, leaving only their own original posts. Off by default.
- Dim instead of remove: fade and collapse hidden posts, hover to peek.
- A live count of posts hidden on the current page. Settings sync via
chrome.storage.syncand apply instantly.
What it can touch
| Manifest entry | Why it is there |
|---|---|
permissions: ["storage"] | Your popup settings, kept in chrome.storage.sync. That is the only permission requested. |
host_permissions | None. Not requested, not needed. |
content_scripts.matches: ["https://www.linkedin.com/*"] | HTTPS only, and only linkedin.com. No other site is touched. |
content_scripts.all_frames: false | Top-level document only; nothing is injected into embedded iframes. |
run_at: "document_start" | So the page is filtered as it renders rather than flashing unfiltered first. |
content_security_policy | script-src 'self': no remote code, no eval, no inline script. |
chrome.storage.sync (so they follow your Chrome profile) and nothing else is stored.What it does not do
- Does not post, react, comment, connect, follow or message anyone on your behalf.
- Does not read your messages, connections list, cookies, tokens or credentials.
- Does not load anything from the network, ever.
Honest caveats
- The whole policy lives in
classify()incontent.js. If LinkedIn changes its wording, the Promoted / Suggested labels and the Follow / Connect button checks are the regexes to adjust. textContentconcatenates inline elements with no whitespace ("postSuggestedVennie"), so matching uses substrings anchored to the header region rather than word boundaries; an unusual header layout could slip past.
Security
Follows the repo's shared security guidelines (the OWASP Browser Extension Vulnerabilities Cheat Sheet): storage is the only permission, the content script runs HTTPS-only in the top frame, a strict CSP forbids remote code, no innerHTML or eval, no network calls, and popup↔page messaging checks sender.id. A static audit (tests/owasp-audit.mjs) enforces all of that on every extension in the repo.
Install
- Download or clone the repo.
- Open
chrome://extensionsand turn on Developer mode (top right). - Click Load unpacked and choose the
chrome-extensions/linkedin-remove-anything-i-dont-followfolder.