How to create a npm package in 2023

Creating an npm package in 2023 can feel daunting.

Thankfully there’s a wonderful project by the Deno team called dnt. Dnt lets you write your package with Deno, run your tests in Node and Deno, then compiles it into an npm package. Deno has a top-notch developer experience with native TypeScript support and a standard library based on Go’s.

Dnt handles all the bundling for you and lets you polyfill things for different platforms. For example, I wrote the JavaScript SDK for Lago, an open-source billing platform, using dnt. Deno supports URLPattern but Node does not. With dnt it’s as simple as telling the build script for all instances of URLPattern you see, replace it with the urlpattern-polyfill npm package.

Here is the full script:

import { build, emptyDir } from "[email protected]/mod.ts";

await emptyDir("./npm");

await build({
  entryPoints: ["./mod.ts"],
  outDir: "./npm",
  typeCheck: false,
  shims: {
    // see JS docs for overview and more options
    deno: "dev",
    custom: [
        package: {
          name: "urlpattern-polyfill",
          version: "^6.0.2",
        globalNames: [
            name: "URLPattern",
            exportName: "URLPattern",
  compilerOptions: {
    target: "Latest",
    lib: ["dom", "es2022"],
  package: {
    // package.json properties
    name: "lago-javascript-client",
    sideEffects: false,
    version: "v0.23.0-beta",
    description: "Lago JavaScript API Client",
    repository: {
      type: "git",
      url: "git+",
    keywords: ["Lago", "Node", "API", "Client"],
    contributors: ["Lovro Colic", "Jérémy Denquin", "Arjun Yelamanchili"],
    license: "MIT",
    bugs: {
      url: "",
    homepage: "",

// post build steps
Deno.copyFileSync("LICENSE", "npm/LICENSE");
Deno.copyFileSync("", "npm/");

You can see the code output on npm.