Skip to content
malinelePublic

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Latest commit

Β 

History

2 Commits

Folders and files

Repository files navigation

VoidIn

Hide the LinkedIn feed. Keep everything else. Enter the Void.

VoidIn is a lightweight, privacy-focused Manifest V3 extension for Chrome and Firefox. It completely hides the LinkedIn main feed stream while leaving the rest of the layout β€” top navigation, messaging, notifications, left and right sidebars, and job search β€” fully intact and functional.

  • 🚫 No feed. The endless scroll, the suggested posts, the recruiter spam β€” gone.
  • 🧭 Navigation intact. Top nav, My Network, Jobs, Messaging, your profile, search β€” all still work.
  • πŸ”’ No tracking. No analytics. No remote config. The extension never makes a network request. State is stored locally in chrome.storage.sync.
  • πŸƒ Zero dependencies. Pure vanilla HTML, CSS, and JavaScript. No frameworks, no build step, no transpilation. You can audit the entire codebase in five minutes.

Table of contents


Install

Chrome / Edge / Brave

  1. Open chrome://extensions (or edge://extensions, brave://extensions).
  2. Toggle Developer mode on (top-right).
  3. Click Load unpacked.
  4. Select the VoidIn/ directory (the one containing manifest.json).
  5. The VoidIn icon appears in your toolbar. Click the puzzle icon and pin VoidIn so it stays visible.

Firefox

  1. Open about:debugging#/runtime/this-firefox.
  2. Click Load Temporary Add-on…
  3. Select the manifest.json file inside VoidIn/.
  4. The extension is active until you restart Firefox. For permanent loading, the add-on must be signed through addons.mozilla.org.

Build from source

There is no build step. Clone the repo, point your browser's "Load unpacked" at the cloned directory, and you're done:

git clone https://github.com/malinele/voidin.git
cd voidin
# Then load this directory as an unpacked extension

How to use

  1. Navigate to https://www.linkedin.com/feed/.
  2. The feed is gone. The rest of the page (top nav, sidebars, messaging widget, search) is intact.
  3. Click the VoidIn icon in your toolbar to open the popup.
  4. Use the Enter the Void / Return to Chaos toggle to switch states.
  5. State is synced across your devices via chrome.storage.sync (when signed into Chrome).

The extension only runs on LinkedIn's /, /feed, and /feed/ pages. On /mynetwork/, /jobs/, /messaging/, /in/<user>/, and other pages, nothing is hidden β€” only a single [VoidIn] feed_not_applicable log line appears in the console for transparency.


How it works

LinkedIn rotates its obfuscated class names frequently. We don't rely on specific class names. We use a multi-strategy, defense-in-depth approach in content.js:

  1. URL guard β€” shouldHideOnThisPage() only proceeds on /, /feed, /feed/. Other pages are no-ops. This is critical because LinkedIn's /mynetwork/, /jobs/, and /messaging/ pages share the same 3-column layout as /feed/ but the central column is not the feed β€” it's "people you may know", "job listings", "message threads", etc. Hiding those by mistake makes the page go blank.

  2. Main column detection β€” findMainColumn() tries 9 semantic selectors in priority order (main[role="main"], main, [role="main"], .scaffold-layout__main, [class*="scaffold-layout__main"], etc.). If none match, a structural fallback picks the middle child of .scaffold-layout or .application-outlet.

  3. Feed root detection β€” findFeedRoot() runs 4 strategies in order:

    1. Walk up from known feed markers ([data-view-name*="feed"], .feed-shared-update-v2, etc.) to the direct child of main.
    2. Scan direct children of main for nested feed markers.
    3. Heuristic: if the first direct child of main has 2+ <article> / [role="article"] / [data-urn] descendants, treat it as the feed.
    4. Last resort: findLargestChildOfMain() β€” keeps diving into single-child wrappers that contain 2+ <aside> elements (signals a layout container), up to 5 levels deep. Then picks the largest non-aside, non-layout-wrapper child. <section> elements get a +5000 score boost because modern LinkedIn wraps the feed in a <section> between two <aside> rails.
  4. Hard guard in hideFeed() β€” short-circuits if the candidate contains any <aside> descendants. This is the last line of defense against accidentally hiding a page layout container. Even if all strategies above return the wrong element, this guard prevents the catastrophic "hide everything" outcome.

  5. Recovery on init β€” unHideAll() is called at the start of init() to undo any incorrect hide from a previous extension version. Refresh = automatic recovery.

  6. MutationObserver β€” throttled to 120 ms, watches document.body for child-list, class-attribute, and data-view-name-attribute changes, and re-hides any feed chunks the SPA lazy-loads.

  7. CSS at document_start β€” content.css is injected at document_start so the feed never paints before the JavaScript kicks in. No flicker.

The DOM structure for the modern LinkedIn home feed (as of 2026) is:

main
└─ div (outer wrapper, ~370 descendants)
   └─ div (layout container, 3 children)
      β”œβ”€ aside (left rail β€” navigation)
      β”œβ”€ section (THE FEED)   ← VoidIn hides this
      └─ aside (right rail β€” recommendations)

Diagnostic

The content script auto-dumps a JSON diagnostic to the console on init, after 1.5 s, and after 4 s. To read it:

  1. Open LinkedIn.
  2. Open DevTools (F12) β†’ Console.
  3. In the filter input, type VoidIn.
  4. Find the line [VoidIn vX.Y.Z] DIAGNOSTIC (auto-dumped on init β€” copy this back to me):.
  5. Below it, there's a JSON block with path, shouldHideOnThisPage, mainChildren, mainGrandchildren, mainGreatGrandchildren, feedRootFound, feedRootTag, feedRootClass, hiddenCount, and the full selector/match-result tables.

Note on MV3 isolated worlds: in Manifest V3, content scripts execute in an isolated JavaScript world. Setting window.__voidin = fn inside content.js does not make __voidin callable from the page's DevTools console. The auto-dumped diagnostic is the way to inspect the extension state from the console.


Compatibility

Browser Minimum version Notes
Chrome 105+ Required for :has() selector support in content.css
Edge 105+ Same as Chrome (Chromium-based)
Brave 105+ Same as Chrome (Chromium-based)
Firefox 121+ Required for :has() selector support
Safari not supported Safari Web Extensions have a different API; PRs welcome

Older browsers will still install the extension, but the CSS-level instant-hide path may not work and you'll see a brief flash of the feed before the JavaScript kicks in.


Privacy

VoidIn is zero-network, zero-analytics, zero-telemetry.

  • The extension makes no network requests of its own.
  • It does not read, transmit, or store any LinkedIn page content.
  • It persists only the on/off toggle in chrome.storage.sync.
  • The only DOM access is reading the structure of *.linkedin.com to find the feed; no content is read or stored.
  • No third-party scripts, no remote config, no update pings.

You can verify this yourself: open the extension's background page in DevTools (chrome://extensions β†’ VoidIn β†’ "service worker" link) and watch the network tab. The only requests will be to LinkedIn itself, not from VoidIn.

See SECURITY.md for the full audit checklist and vulnerability reporting policy.


Troubleshooting

The feed is still showing

  1. Open the popup. Is the toggle on (lime green)?
  2. Open DevTools β†’ Console. Filter by [VoidIn]. Look for [VoidIn] feed_hidden β€” that's a successful hide.
  3. If you see [VoidIn] feed_not_applicable, you are not on a feed page (/, /feed/, /feed). VoidIn intentionally does not run on /mynetwork/, /jobs/, etc.
  4. If you see [VoidIn] error, copy the full JSON diagnostic and open an issue.

The page is blank (everything is hidden)

This is the worst-case scenario. As of v1.6.1 it should be impossible because of the URL guard, the aside guard, and the auto-recovery on init. If you do see it:

  1. Toggle the VoidIn popup to OFF. Everything reappears.
  2. If the toggle doesn't fix it, hard refresh the page (Cmd+Shift+R / Ctrl+Shift+R).
  3. If it's still broken, copy the diagnostic and open an issue.

LinkedIn updated their DOM

LinkedIn rotates obfuscated class names frequently. The hide logic is designed to be resilient, but if LinkedIn makes a major structural change the hide will silently fail and you'll see the feed back. Open an issue with the diagnostic JSON β€” we can usually ship a fix within a day.

Console noise

Some of the errors you'll see in your DevTools console are not from VoidIn. The most common ones:

  • chrome-extension://invalid/ ... β€” comes from a previously-uninstalled extension's content script that's still trying to load. Not us.
  • Uncaught (in promise) Error: Minified React error #418 β€” LinkedIn's own React code (hydration mismatch). Not us.
  • Attribute 'exception.tags' of type 'object' could not be converted to a proto attribute. β€” LinkedIn's telemetry code. Not us.
  • VIDEOJS: WARN: ... β€” LinkedIn's video player. Not us.

To see only VoidIn's output, filter the console by VoidIn. You should see only:

[VoidIn] initialized Object
[VoidIn] feed_hidden Object
[VoidIn vX.Y.Z] debug helper installed β€” call __voidin() to inspect the DOM
[VoidIn vX.Y.Z] DIAGNOSTIC (auto-dumped on init β€” copy this back to me):
[VoidIn vX.Y.Z] DIAGNOSTIC (auto-dumped on init β€” copy this back to me):
[VoidIn vX.Y.Z] DIAGNOSTIC (auto-dumped on init β€” copy this back to me):

(__voidin is installed in the content script's isolated JavaScript world and is not directly callable from the page's DevTools console. The auto-dumped diagnostic is the source of truth.)


Contributing

We welcome bug reports, feature requests, documentation improvements, and pull requests. See CONTRIBUTING.md for the dev setup, the selector/heuristic checklist, and the manual test matrix.

If you want to add a new feed-hiding strategy, follow the strategy pattern in content.js: each strategy returns null (or the candidate) and never throws. The findFeedRoot() function chains them in priority order.


Security

See SECURITY.md for what VoidIn accesses, what it does not do, the permission set, and how to report a vulnerability.


License

MIT Β© 2026 Nenad Mitrovic. Use it, fork it, modify it, ship it.

Changelog

See CHANGELOG.md for the full version history.


Built with care. Not affiliated with LinkedIn or Microsoft.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages