Skip to content

Instantly share code, notes, and snippets.

@mathix420
Last active July 7, 2025 14:29
Show Gist options
  • Save mathix420/e0604ab0e916622972372711d2829555 to your computer and use it in GitHub Desktop.
Save mathix420/e0604ab0e916622972372711d2829555 to your computer and use it in GitHub Desktop.
Bypass Medium Paywall - Working late 2023 - Greasy Fork, Violentmonkey, Tampermonkey - Click the RAW button to install
// ==UserScript==
// @name Medium Paywall Bypass
// @namespace Violentmonkey Scripts
// @run-at document-start
// @match *://*.medium.com/*
// @match *://medium.com/*
// @match *://*/*
// @grant none
// @version 2.4
// @inject-into content
// @updateURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @downloadURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @website https://freedium.cfd
// @author Mathix420, ZhymabekRoman
// @description Don't forget to remove `@match` filters you don't want.
// ==/UserScript==
// initCall is telling us if we need to inject the title observer
function mediumRedirecter(initCall = false) {
if (
// Allow seeing original articles that were already redirected to freedium.
!window.location.href.endsWith('#bypass') &&
// Do not redirect when editing on medium.
!window.location.href.includes("/edit?source=") &&
// Detect if we are on a medium website (regardless of the domain)
document.head?.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/')
) {
window.location.href = 'https://freedium.cfd/' + window.location.href;
} else if (initCall && /(.*\.|^)medium\.com$/.test(window.location.host)) {
// Observe <title> changes
new MutationObserver(function(mutations) {
// If title change is detected, check if a freedium redirect is required
if (mutations[0].target.textContent) mediumRedirecter();
}).observe(
document.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
}
}
mediumRedirecter(true);
@lonelam
Copy link

lonelam commented Jan 18, 2024

to automatically redirect to the freedium site:

// ==UserScript==
// @name        Medium Paywall Bypass
// @namespace   Violentmonkey Scripts
// @run-at      document-start
// @match       *://*.medium.com/*
// @match       *://medium.com/*
// @match       *://*/*
// @grant       none
// @version     1.5
// @inject-into content
// @updateURL   https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @downloadURL https://gist.githubusercontent.com/mathix420/e0604ab0e916622972372711d2829555/raw/medium.user.js
// @website     https://freedium.cfd
// @author      Mathix420, ZhymabekRoman
// @description Don't forget to remove `@match` filters you don't want.
// ==/UserScript==

(setInterval(function () {
  // 1. Allow seeing original articles that were already redirected to freedium.
  // 2. Do not redirect when editing on medium.
  if (window.location.href.endsWith('#bypass') || window.location.href.includes("/edit?source=")) {
    return;
  }

  const mediumPostUrlProperty = document.head.querySelector('meta[property="al:android:url"]')
  if ((mediumPostUrlProperty || {}).content && mediumPostUrlProperty.content.includes('medium://p/')
      && window.location.href.match(/^https?:\/\/(www\.)?(medium\.com\/|[\w-]+\.medium\.com\/|[\w-]+\.[\w-]+\/).*/
)) {
    window.location.href = 'https://freedium.cfd/' + window.location.href;
  }
}), 1000);

@mathix420
Copy link
Author

Hi @ZhymabekRoman

Violentmonkey doesn't support it

Surprisingly I just saw it in the settings of my Violentmonkey. Anyway I'm surprised that using document-body as the docs says The script will be injected **if** the body element exists. maybe it's a typo but all the other descriptions says when. It's pretty strange.
Sorry for being this picky on this change, but as it drastically increase the time of redirect I'm a bit reluctant to it. Also it works perfectly on my end with violentmonkey, would you consider trying with this one ? Might also be better for your privacy.


Hello @lonelam, thanks for your recommendation, but I don't understand your change can you give me some context? As the condition checking for medium meta tag meta[property="al:android:url"] already allows us to redirect every medium article (even those not hosted by the medium domain).

@ZhymabekRoman
Copy link

I'm using Violentmonkey, maybe the other extensions are breaking headers loading (I have about ~45 extensions).

@lonelam
Copy link

lonelam commented Jan 20, 2024

Hi @ZhymabekRoman

Violentmonkey doesn't support it

Surprisingly I just saw it in the settings of my Violentmonkey. Anyway I'm surprised that using document-body as the docs says The script will be injected **if** the body element exists. maybe it's a typo but all the other descriptions says when. It's pretty strange. Sorry for being this picky on this change, but as it drastically increase the time of redirect I'm a bit reluctant to it. Also it works perfectly on my end with violentmonkey, would you consider trying with this one ? Might also be better for your privacy.

Hello @lonelam, thanks for your recommendation, but I don't understand your change can you give me some context? As the condition checking for medium meta tag meta[property="al:android:url"] already allows us to redirect every medium article (even those not hosted by the medium domain).

The Medium site is an SPA and navigating to a new page will not trigger a 'load' event. And therefore, the script will not triggered if I navigate from a medium's page to another,
So a manual refresh is needed before I jump to freedium, do the detection intervally will prevent the extra manual operation.

@mathix420
Copy link
Author

@ZhymabekRoman okay, let me know if I still need to change the code !


@lonelam Oh I see! Sorry I missed the setTimeout part from your code! I also noticed this behavior earlier, I was thinking about implementing something like this https://stackoverflow.com/q/6390341/9799292

@lonelam
Copy link

lonelam commented Jan 21, 2024

@ZhymabekRoman okay, let me know if I still need to change the code !

@lonelam Oh I see! Sorry I missed the setTimeout part from your code! I also noticed this behavior earlier, I was thinking about implementing something like this https://stackoverflow.com/q/6390341/9799292

That's an awesome performance impovement, Listening the events like locationchange or hashchange will be more economically and more effective for the detection.

@mathix420
Copy link
Author

mathix420 commented Jan 21, 2024

@lonelam I just updated the script, it can now detect title changes (only when using medium.com domain as custom domains does not seems to host SPAs) and trigger a redirect if needed!
PS: small bug fix, make sure to be on V2.1

@ZhymabekRoman
Copy link

Achtung!

Sooo, it was going to take a while, but now we have it. Our whole Github organization is not public for now.

Reddit community, that was beginning all of that also gone - https://www.reddit.com/r/paywall/comments/15jsr6z/bypass_mediumcom_paywall/

We have moved to Codeberg - https://codeberg.org/Freedium-cfd

Medium, thank you >.

@jt-z
Copy link

jt-z commented Mar 8, 2024

Thanks all Freedium-cfd members!

@ZhymabekRoman
Copy link

@mathix420 Can you fix this error? Sometimes this error appears:

TypeError: can't access property "querySelector", document.head is null

@mathix420
Copy link
Author

@ZhymabekRoman I've fixed the type error, but if the head tag is missing it won't be able to detect the medium page before js load.
Sorry for the delay, I got lot of work these days!

@ZhymabekRoman
Copy link

@mathix420 Thank you! I really appreciate your efforts

@a-pav
Copy link

a-pav commented Apr 18, 2024

I think you can simplify the below lines using Optional chaining (?.):

First, completely remove

- const mediumPostUrlProperty = ((document.head || {}).querySelector ? document.head.querySelector('meta[property="al:android:url"]') : {}) || {}

Then inside the first if statement, replace

- (mediumPostUrlProperty.content && mediumPostUrlProperty.content.includes('medium://p/'))
+ document.head?.querySelector('meta[property="al:android:url"]')?.content?.includes('medium://p/')

Personally, I'm not going to use // @match *://*/*. So I put this into a detectMediumWebsite() function and commented it out.

Thanks for your work.

P.S. By the way, are you sure that the MutationObserver is actually registered?

@rodolfogoulart
Copy link

Can you remove? // @match *://*/*

do not make sense to be for all sites.

@ZhymabekRoman
Copy link

do not make sense to be for all sites.

Medium has too many sub-domains

@a-pav
Copy link

a-pav commented Apr 25, 2024

Medium has too many sub-domains

// @match *://*.medium.com/* Should be enough for matching sub-domains.

@ZhymabekRoman
Copy link

Should be enough for matching sub-domains.

What about devopsquare.com, blog.devops.dev, blog.stackademic.com, ai.plainenglish.io, bettermarketing.pub and etc? It's impossible for us to say how many Medium sites there are.

@a-pav
Copy link

a-pav commented Apr 25, 2024

You are right. I didn't know those type of sites exist. But they are just different domains, technically speaking.

To recap:
// @match *://*.medium.com/* Is enough for matching sub-domains.
// @match *://*/* Is needed for matching any possible domain who happens to be a Medium website.

@yluom
Copy link

yluom commented Apr 26, 2024

freedium is offline so unfortunately this script doesn't work anymore for now

@ZhymabekRoman
Copy link

All works as expected. Yeah, we made some codebase refactor, to improve speed and some big bug fixes. And there were some minor downtimes.

@mathix420
Copy link
Author

mathix420 commented May 14, 2024

Thanks @a-pav I wasn't aware that optional chaining was supported on tampermonkey. Yes, the mutationobserver is being registered, try commenting the section and click a link in this page for example https://medium.com/@francais you'll notice no redirect.

@rjmsilveira
Copy link

What a wonderful script. Thank you for sharing 🙇

@zzJinux
Copy link

zzJinux commented Jul 16, 2024

In case freedium goes unstable, how about disabling redirects for free posts? This is the predicate to check if it is a premium content: document.querySelector('.meteredContent') != null

@mathix420
Copy link
Author

@zzJinux yes, I thought about that too a couple months ago, I will try to implement it once I have more free time

@zzJinux
Copy link

zzJinux commented Jul 16, 2024

@mathix420 I have already locally patched the script to add the predicate. I hope your version will do so.

@mathix420
Copy link
Author

@zzJinux if you want you can fork this gist and update it so I can replicate your changes

@zzJinux
Copy link

zzJinux commented Jul 18, 2024

@mathix420 Just a few lines change: https://gist.github.com/zzJinux/364725e7c61810719286d94e88a4e38c I haven't had any issues.

@ZhymabekRoman
Copy link

Guys, sorry, yeah I known that's my fault. I need your help: Freedium-cfd/web#16 (comment)

@vgeorgiev69
Copy link

vgeorgiev69 commented Jul 6, 2025

Not sure if I'm doing something wrong, but the above script doesn't work as of the time of writing (at least in Firefox). This works though:

// ==UserScript==
// @name        Medium Paywall Bypass
// @namespace   Violentmonkey Scripts
// @run-at      document-idle
// @match       *://*.medium.com/*
// @grant       none
// @version     2.5
// @author      Mathix420, ZhymabekRoman
// ==/UserScript==

(function() {
  function mediumRedirecter() {
    if (
      !window.location.hostname.includes('freedium.cfd') &&
      window.location.hostname.includes('medium.com') &&
      !window.location.href.endsWith('#bypass') &&
      !window.location.href.includes("/edit?source=")
    ) {
      window.location.href = 'https://freedium.cfd/' + window.location.href;
    }
  }

  mediumRedirecter();
})();

@ZhymabekRoman
Copy link

Thanks for your effort.

The original script works great for me. However, your solution has one small problem: it can't work on domains other than medium.com. Here is a small list of third-party domains:

  • uxplanet.org
  • osintteam.blog
  • ahmedelfakharany.com
  • drlee.io
  • generativeai.pub
  • towardsdev.com
  • infosecwriteups.com
  • thetaoist.online
  • devopsquare.com
  • bettermarketing.pub
  • itnext.io
  • betterprogramming.pub
  • curiouse.co
  • betterhumans.pub
  • uxdesign.cc
  • thebolditalic.com
  • codeburst.io
  • writingcooperative.com
  • entrepreneurshandbook.co
  • storiusmag.com
  • javascript.plainenglish.io
  • code.likeagirl.io
  • medium.datadriveninvestor.com
  • blog.det.life
  • python.plainenglish.io
  • blog.stackademic.com
  • ai.gopubby.com
  • blog.devops.dev
  • levelup.gitconnected.com
  • ai.plainenglish.io

And so on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment