Library / SDK
toss/es-toolkit avatar
toss/es-toolkit

es-toolkit 1.53.1: two registries, two package names, and a browser build with no tree shaking

A modern JavaScript utility library that's 2-3 times faster and up to 97% smaller-a major upgrade to lodash.

11,393 stars641 forksTypeScriptMIT

At a glance

What is it?
es-toolkit is a TypeScript utility library from Toss that positions itself as a Lodash replacement. The interesting parts are structural: a sideEffects flag, one CDN file, and a compat layer whose coverage nobody itemises for you.
Who is it for?
Adopt es-toolkit for new code if you want typed utilities with per-module entry points, and test your Lodash migration function by function rather than assuming the compat layer is complete. Do not take the CDN browser global file as your production path if bundle size is the reason you are switching, since a single global file cannot be tree shaken.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • toss/es-toolkit11,393 ★ · +137
Star history
Repository2026-07-282026-11-14
toss/es-toolkit11.3k11.4k

137 Stars in the last 109 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

sideEffects false is the flag the whole size argument rests on

The published manifest starts with one line that does more work than the marketing copy does:

code
  "sideEffects": false,

That tells a bundler no module in this package has import time side effects, so anything you do not import can be dropped. Combined with the exports map, which is split per module rather than fronted by one barrel file, it is the mechanism behind the project's tree shaking claim and its figure of reducing JavaScript code by up to 97 percent against other libraries. That figure is the project's own, published on a separate bundle size page, and no independent number appears in the repository.

The consequence of asserting no side effects is that the claim has to stay true. If any module in this library does something on import, a bundler is entitled to delete it on the assumption it was unused, and your build succeeds while your behaviour changes. You are trusting every export of the package on that point, not just the ones you import.

./util/hash resolves to a different file on Node than on a browser

One export in the map is conditional rather than a plain path:

The exports map in package.json is split per module (./array, ./function, ./math, ./object, ./predicate, ./promise, ./string and others), with wildcard subpaths such as ./array/* for single files, so you import from a specific area rather than one barrel file.

es-toolkit/compat is the Lodash path, and its coverage is not itemised

Replacing an existing Lodash dependency is treated as a first class goal rather than an afterthought, with a dedicated entry point:

json
    "./compat": "./src/compat/index.ts",

The compatibility entry is separate from the rest of the library, so importing from it and importing from the main entry are two different decisions with two different API shapes. That separation is what makes an incremental migration possible: change one module's import, leave the rest of the codebase on Lodash, and compare.

What is missing is the detail that would let you plan that migration. The repository points to a compatibility page on the project site rather than enumerating which Lodash functions are covered, which are renamed, and which behave differently. So the practical check is function by function: for each Lodash call in your codebase, verify the corresponding behaviour in the compat docs before you swap the import.

jsdelivr and unpkg serve one global file, so script tag users get the whole library

Two distribution fields point at the same artefact:

code
  "jsdelivr": "./dist/browser.global.js",
  "unpkg": "./dist/browser.global.js",

This is the path for people who load the library from a script tag rather than through a bundler, and it is a single file. A single file cannot be tree shaken, because there is nothing in the resolution graph to drop from; whatever is in the global build is what the browser downloads. So the same package offers two experiences with opposite size characteristics, and the manifest is what tells you which one you are getting.

Worth being clear about what the headline numbers mean, then. The performance and size claims are made for tree shaking, that is, for consumers who import individual modules through a bundler. If your build pipeline is a script tag or a global object, those numbers are not the ones describing your download, and nothing in the repository publishes a size for the global file. Check your own pipeline before quoting the percentage in a decision.

The same library is published under two names on two registries

es-toolkit is on npm and also on JSR, under a different package name. The example in the repository makes the swap explicit:

tsx
// import from '@es-toolkit/es-toolkit' in jsr.
import { chunk, debounce } from 'es-toolkit';

The manifest also carries a jsr.json at the repository root, and the JSR badge points at @es-toolkit/es-toolkit. So the same source tree reaches you as es-toolkit on npm or as @es-toolkit/es-toolkit on JSR, and nothing in these pages tells a team to standardise on one.

The hazard is mundane and expensive. Two names means two packages in a dependency graph, and if one module imports the npm name while another imports the JSR name you have two copies of the same library, two sets of module instances, and a bundle larger than the one you were trying to build. It also means type identity does not line up across the two, so a value passed between code that imports each name is not recognised as the same type. Pick one at the start of a migration and check the lockfile for the other.

packageManager pins yarn 4.12.0 and the .yarn directory is committed

Contributors are not left to choose a toolchain. The manifest pins it:

code
  "packageManager": "[email protected]",

The repository root carries a .yarnrc.yml, a yarn.lock, and a checked in .yarn directory, so the intended package manager and its configuration travel with the source rather than living in your global setup. There is also an .nvmrc for the Node version, which sits alongside a tsconfig.json, a vitest.config.mts for the test runner, and a tsdown.config.ts for the build.

The consequence is on onboarding rather than on usage. Nobody consuming es-toolkit from npm has to match any of this, but someone opening a pull request has to honour the pinned package manager version or the lockfile resolution will not be the one reviewers expect. Combined with the stated 100 percent test coverage claim, it means a contributor's local run is the first place a change gets measured, so getting the toolchain wrong is a good way to submit work that never ran.

Agent Skills and a Claude Code plugin ship inside the library itself

The project distributes guidance for AI coding tools, and gives two commands:

bash
npx skills add toss/es-toolkit
code
/plugin marketplace add toss/es-toolkit
/plugin install es-toolkit@es-toolkit-plugin

The first is the general route, aimed at tools including Claude Code, Cursor and Copilot through the skills directory. The second is specific to Claude Code and goes through its plugin marketplace, and the repository root backs both with an es-toolkit-plugin directory and a .claude-plugin directory, plus AGENTS.md and CLAUDE.md.

Worth noting what this does and does not give you. It means an agent that follows project instructions can be pointed at this library's conventions rather than at Lodash habits, which is a reasonable thing to want in a codebase mid migration. The README points to an AI Integration guide on the project site for details. If you enable it, read what it tells your tool to do before trusting the output.

Type, packaging, coverage and vulnerability checks are all config files

The repository root is a list of automated guarantees rather than prose ones: .attw.json for consumer side type checking, packlint.config.mjs for package layout, osv-scanner.toml for dependency advisories, codecov.yml for coverage, eslint.config.mjs and a prettier config for style, plus SECURITY.md, CHANGELOG.md and a NOTICE file in the repository alongside COPYING-MIT.

None of that is a runtime guarantee, and the distinction matters. Type correctness tells you shapes agree, packaging checks tell you the published artefact has the right shape, an advisory scanner tells you about known vulnerabilities, and 100 percent coverage tells you every line ran. None of them tells you a function does what you expected, and coverage in particular says nothing about whether the assertions are meaningful. Use CHANGELOG.md for the release history, since it sits in the repository rather than behind the site, and read the site pages for behaviour.

Editorial conclusion

Adopt es-toolkit for new code if you want typed utilities with per-module entry points, and test your Lodash migration function by function rather than assuming the compat layer is complete. Do not take the CDN browser global file as your production path if bundle size is the reason you are switching, since a single global file cannot be tree shaken. Before you start, check that your bundler honours sideEffects false and that only one of the two package names is in your dependency graph.

Frequently asked questions

what is es toolkit

es-toolkit is a JavaScript utility library written in TypeScript, published by Toss under the MIT licence with a homepage at es-toolkit.dev. It offers everyday helpers such as debounce, delay, chunk, sum and pick, with strong type annotations and built-in type guards like isNotNil.

Do we still need Lodash?

The project ships a compatibility layer at es-toolkit/compat intended as a drop-in replacement path for Lodash, and its exports map has a dedicated ./compat entry separate from the main one. That lets you migrate a module at a time, though the repository points to a compatibility page rather than itemising which Lodash functions are covered.

es toolkit vs

The README states one replacement relationship, with Lodash, through the es-toolkit/compat entry point. It does not compare es-toolkit with Radash, Ramda or any other library.

es-toolkit alternative

es-toolkit presents itself as an alternative to lodash, claiming 2-3 times better performance in modern JavaScript environments and up to 97 percent less JavaScript code, both published on its own site. It is also used by open-source projects including Storybook, Recharts, ink and CKEditor.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/toss-es-toolkit.svg)](https://hysenlabs.com/projects/toss-es-toolkit)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/toss-es-toolkit.svg)](https://hysenlabs.com/projects/toss-es-toolkit)

Was this useful?