How to save LinkedIn posts permanently

[Code updated on Sept. 9, 2026 to work with Linkedin interface changes]

Are you trying to copy a Linkedin post and all its comments?

If so, this article will show you how to do that. Linkedin makes it intentionally difficult to save old posts and to search through them. With this method, you can save insightful posts to your personal computer – and search through their text for years to come.

Step 1: set up the “Bookmarklet”

First, create a browser “Bookmarklet” that you can reuse to expand long posts. For any Linkedin post, it will load all comments, expand all threads and click “…see more” on individual comments.

Drag the button below into your “Bookmarks bar” to create a clickable bookmarklet:

To see the code behind this bookmarklet, see the Appendix at the bottom of this post.

Note: this bookmarklet only works on the English-language Linkedin interface. That’s because it’s looking for the word “Collapse” to find the “Collapse replies” link under expanded threads. To adapt the bookmarklet to another language, go to the Appendix and replace “Collapse” with the word Linkedin uses for the equivalent of “Collapse replies” in your language. Then generate a new bookmarklet using the tool linked in that section.

Step 2: open the Linkedin post you want to save

Find the post that you want to save to your computer. Open it so it is on it’s own standalone page. One way to do this is to click the “…” icon near the post an “Copy link to post” and then paste it into your browser URL bar. You will then see all the comments – without any other posts on the page.

I also recommend that you click on the “sort” link under the post (1) and change it to “Most recent” (2). This will give you an impartial time-based listing of comments, instead of having Linkedin highlight the ones their algorithm wants to boost.

Step 3: click the Bookmarklet you created

Click on the bookmarklet (special bookmark) you created in Step 1. This will run Javascript code that will simulate clicks on every “…see more” and “Load previous replies” link on the page.

Wait until you see the “This page is ready for saving” message … it might take 20 seconds to load all the content from every comment.

Our code clicks every highlighted link in the screenshot.

Step 4: save the entire page to your coputer

When the bookmarklet finishes loading the comments, you can save the page as-is to your own computer. You have 2 options here.

Option 1: Save as PDF

Open your browser’s “Print” menu and choose “Save as PDF”. Take a look at the settings before you finish saving – I recommend that you check the “Background graphics” setting. This setting will ensure that all images appear in the PDF.

Option 2: Save as “Complete Web Page”

Your other option is to save the raw HTML code of the page with the post, together with all supporting graphics.

Do this by clicking CTRL+S (or going to the File->Save) menu. Be sure to choose the “Webpage, Complete” option.

This option will save all supporting files together with your page.

You will end up with an .html page of your post and a folder with the same name. The folder will contain all images that are present on the page – for offline viewing.

Refresh your Linkedin window to get rid of the “ready for saving” message at the top.

Happy saving!

With your “Linkedin Post Saver” bookmarklet, you can now save any Linkedin post you want. Just follow steps #2 -> #4.

Appendix: the full code for the bookmarklet

Here is the easy-to-read code for this bookmarklet. You can turn this code into a bookmarklet yourself by pasting it into the tool at https://chriszarate.github.io/bookmarkleter/ (check only the first 2 checkboxes).

/* changes in v04: added "Scroll down until there's no more content" code, and refreshed CSS click targets & logic on links - will only work in EN */
	
	
// Set up an image that shows if we're using the latest version of the tool. To keep up with Linkedin interface changes
var jacobdiv = document.createElement('div');
jacobdiv.innerHTML = '<div style="position: fixed; left: 30vw; top:0;z-index:100;"><img src="https://jacobfilipp.com/i/linkedinPostSaverV04.gif"></div>';
document.body.appendChild( jacobdiv );

// add no-print style to header
const mstyle = document.createElement('style');
mstyle.textContent = `@media print{ .no-print{display:none;}}`;
document.head.appendChild(mstyle);



// these are counters that tell us when to stop clicking on webpage items (because the content of the area with comments isn't changing/no more elements to click)
var buttonCounter = 0;
var lengthOfBodyOld = document.querySelector('section[aria-label="Primary content"]').innerHTML.length;
var lengthOfBodyNew = 0;

// simulate a click on each element
const cevent = new MouseEvent("click", {view: window,bubbles: true,cancelable: true,});


// recursive function that simulates clicks
// to do: change to async code
function myclicker( buttonCounter )
{

	//console.log( "buttonCounter: "+buttonCounter+"\n"+"lengthOfBodyOld: "+lengthOfBodyOld+"\n"+"lengthOfBodyNew: "+lengthOfBodyNew );
	
	// if we go through 5 executions without changes, display the "done" message and exit
	if( buttonCounter == 5 )
	{ 
		jacobdiv.innerHTML = '<div class="no-print" style="position: fixed; left: 30vw; top:0;z-index:100;background: #a6f8a6;padding: 1ex;box-shadow: -1px 0px 24px 0px rgba(0,0,0,0.75);"><b>Page is ready to &quot;Print as PDF&quot;</b></div>';
		console.log( "finished the run!" )
		return 0;
	}
	
	// put all "load more comments" links into an array
	//read more:
	var read_more = document.querySelectorAll('button[data-testid="expandable-text-button"]:not(.wealreadyclicked)')
		
		
	//See previous replies:
		
		const parents_prev_replies = [...document.querySelectorAll('p:not(.wealreadyclicked)')]
		.filter(el => el.textContent.trim() === 'See previous replies')
		.map(el => el.parentElement);
		
	// join the above 2 into a common array
	var liArray = [...parents_prev_replies, ...read_more ]
	
	// no more links? page not changing? then increment our counter
	if( liArray.length == 0 || lengthOfBodyOld == lengthOfBodyNew)
	{
		buttonCounter++;
		
	}
	else
	{
		buttonCounter = 0;
		
		// go through every link we found
		for( i=0; i < liArray.length; i++ )
		{
			// the "collapse" button has the exact same classes as the "more replies" button, so we have to look into innertext to make sure we don't click it
			if( liArray[i].innerText.includes("Collapse") ) 
			{
				liArray[i].classList.add('wealreadyclicked');
			}
			else
			{
				liArray[i].dispatchEvent(cevent);				// click it!
				liArray[i].classList.add('wealreadyclicked');
			}
			
			
			
		}
	}
	
	// wait for 6 seconds so Linkedin loads the content, and repeat the entire cycle to find new comment links
	setTimeout(function(){myclicker(buttonCounter)}, 6000);
	lengthOfBodyOld = lengthOfBodyNew;
	lengthOfBodyNew = document.querySelector('section[aria-label="Primary content"]').innerHTML.length;
}




// scroll to the very bottom, and keep scrolling until there are no more height changes
(async () => {
	const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

	let lastHeight = 0;
	let stableCount = 0;
	let selectorTxt = 'section[aria-label="Primary content"]';

	while (stableCount < 5) {
		document.querySelector('#workspace').scrollTo(0, document.querySelector(selectorTxt).offsetHeight);
		await delay(1000);

		const height = document.querySelector(selectorTxt).offsetHeight;
		console.log( "new height: " + height );

		if (height === lastHeight) {
			stableCount++;
		} else {
			stableCount = 0;
			lastHeight = height;
		}
	}

	document.querySelector('#workspace').scrollTo(0, document.querySelector(selectorTxt).offsetHeight);
	
	// execute the clicker function once the scrolling is done
	console.log( "Finished scrolling. Clicking all expandable links now." )
	myclicker( 0 );
	
})();


Get monthly updates. Who’s dating whom on the blog? Who’s my baby’s daddy? Pinocchio vs. Pinochet who wins?