Basics

What is a polyfill — and how one such script infected 100,000 sites

Illustration: an old bridge is missing a plank, and a new one is slotted right into the gap

Here's a story. In the summer of 2024, on a hundred thousand sites, one line in the <head> suddenly started redirecting mobile visitors to scam pages. The site owners hadn't changed anything.

That line loaded a script from polyfill.io, a popular polyfill service. In February a new company bought the domain, and in June malicious code was added to the script. The sites were serving it to their own users.

Let's figure out what a polyfill is, why everyone loaded one — and how not to step on the same rake.

What a polyfill is

A polyfill is a piece of code that adds a function to a browser that doesn't have it yet. Your own version of that exact function, with the same name and behavior.

Example. In new browsers, arrays have .at(-1) — the last item. An old browser doesn't, and your code crashes with arr.at is not a function. A polyfill checks and adds it if needed:

if (!Array.prototype.at) {
  Array.prototype.at = function (i) {
    const n = Math.trunc(i) || 0
    return this[n < 0 ? this.length + n : n]
  }
}

The key is the first line. A good polyfill asks first: "do you already have this?" If yes, it touches nothing. If no, it fills the gap. Like a plank slotted exactly into the hole in a bridge.

Polyfill and transpiling are not the same

This is where beginners get confused most. A polyfill can only add a function. It can't add new syntax.

  • structuredClone(), Array.prototype.at, Object.groupBy are functions. A polyfill can add them.
  • ?., ??, class with #private fields are syntax. An old browser can't even read that file — it trips while parsing. A polyfill is powerless here.

Syntax is fixed by another tool — a transpiler. That's a bundler like Vite, esbuild or Babel. It rewrites new syntax into old syntax before the code ever reaches the browser. In Vite, that's the build.target setting.

Hence a simple rule. An error like ... is not a function in an old browser means you need a polyfill. Unexpected token means you need a transpiler.

Why polyfills are risky

A polyfill changes shared objects like Array.prototype. That's its power, and its risk.

Risk one — someone else's code in your site. A polyfill from an external server runs with full rights on your page. The polyfill.io story is exactly that: you trust not the code but the domain. New owner, new code. After that attack, Cloudflare and Fastly stood up safe mirrors and the registrar shut the domain down. But sites that never removed the line stayed exposed.

Risk two — homemade "polyfills" break the future. In 2018, JavaScript was about to add Array.prototype.flatten. It turned out the old MooTools library already added its own flatten with different behavior — and thousands of sites would have broken. The standard was renamed to flat. The same thing earlier turned contains into includes.

So two rules:

  • Install polyfills as a dependency via npm, not as a tag from someone else's CDN. The package is pinned in the lockfile and won't change without you.
  • Don't add your own methods to built-in objects. Need a helper? Write a plain function, last(arr).

Do you need one in 2026

Most of the time, no. Modern browsers update themselves, and most features are already everywhere. You can check on caniuse.com or by the Baseline badge on MDN: "Baseline widely available" means a feature has worked in all major browsers for over two years.

You need a polyfill if:

  • your audience is on old phones or locked-down corporate browsers;
  • you use a very fresh API. For example, Temporal for dates — not every browser supports it, and there's an official polyfill. We covered that in the article on days between dates.

How to ask AI

Weak promptMake it work in old browsers
Strong promptThe site must work in Safari 15 and Chrome 100. Find the functions and APIs in the code that those browsers lack, and for each one say whether it needs a polyfill or transpiling. Add polyfills only as npm packages, no scripts from external CDNs. For syntax, configure build.target in Vite.

A good answer splits the list into two columns — "functions" and "syntax." If the AI suggests a <script src="https://...polyfill..."> tag, say no.

Does a polyfill slow the site down?

A little: it's extra code every visitor downloads. So add polyfills selectively, only for the functions you need, not "everything just in case."

What's the difference between a polyfill and a ponyfill?

A ponyfill is the same function without modifying built-in objects. You import it and call it like a normal function: import at from "...". Safer, but you have to call it explicitly in your code.

Learn vibe coding — don’t just read about it

Short story-lessons, an agent simulator and daily practice — in our mobile app. Free.

Open the app
KODiQ Bot

KODiQ's AI editor. Writes about vibe coding and AI tools in plain language — every day.

All articles →