Upgrade Checker (Node 26, Laravel 13 and TypeScript 7)

Paste your package.json, composer.json or tsconfig.json and see what breaks moving to Node 26, Laravel 13 or TypeScript 7, on which line, and the fix.

./upgrade-checker

Which version are you moving to?

Read only in your browser. No request carries its contents.

grep -rnE "_stream_(readable|writable|duplex|transform|passthrough|wrap)|writeHeader\(|module\.register\(|createRequire|experimental-transform-types|localStorage|QuotaExceededError|fetch\(" --include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts' --include='*.mts' --include='*.cts' --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build . ; grep -rnE "FROM node:|node-version" Dockerfile* .github/ 2>/dev/null

Run it at the project root. Without this step only the files above are checked; with it, your code too. Do not paste your .env.

Result: Node 26

Paste the manifest and what breaks shows up here, sorted by impact.

Check by hand (cannot be detected from outside)

  • Look for fossils in your dependencies too: grep -rln "_stream_" node_modules --include="*.js" | head. Explained in the article →
  • After switching to 26, npm rebuild (or delete node_modules and reinstall) and rebuild the Docker image on the new base. Explained in the article →
  • Run the suite with deprecations visible: node --pending-deprecation --trace-deprecation ./node_modules/.bin/vitest run. Explained in the article →
  • fetch headers that come from a database, a form or another API: clean them (.trim()) where they enter. Undici 8 throws TypeError on a \n instead of sanitizing it. Explained in the article →
  • During install, watch for EBADENGINE warnings: they name the dependency that does not declare Node 26. Explained in the article →

Rules taken from this site’s Node 26 breakdown and checked on 2026-09-27. /en/blog/what-breaks-upgrading-to-node-26/

What this tool does

Paste your project’s manifest —package.json if you are moving to Node 26, composer.json for Laravel 13, tsconfig.json for TypeScript 7— and it returns what breaks, sorted by impact, with the concrete change for each case. If you also paste the output of the step 2 command, it checks your code too and points at the file and line.

The rules are not generic: they come from this site’s breakdowns of what breaks when upgrading to Node 26, what actually breaks in Laravel 13 and what breaks in TypeScript 7. Every finding links the section that explains it, with before-and-after code.

Why grep output and not your repository

The step 2 command is a grep -rn that looks for only the patterns that change in that version. Its output carries the path and line number of every match (tests/Feature/CheckoutTest.php:18:…), so the tool can tell you where each problem is without seeing the rest of your code. Everything is analyzed in the browser and no request carries what you paste.

What it catches for Node 26

  • An engines.node that excludes 26, or that still allows Node 22 or older.
  • Native addons (bcrypt, better-sqlite3, canvas…) that need a binary for ABI 147, and node-sass, which will not get one.
  • Observability agents and loaders (dd-trace, @opentelemetry/*, tsx…) that live in the now-deprecated module.register() layer.
  • The --experimental-transform-types flag, which no longer exists.
  • In your code: require('_stream_readable') and friends, res.writeHeader(), extensionless require in ESM packages, fetch calls Undici 8 may reject, and the Docker image or CI matrix still pinned to an older version.

What it catches for Laravel 13

  • laravel/framework and php constraints that do not allow 13, and, if you are on 10 or 11, the warning to go one major at a time.
  • The dependencies that move with the framework: laravel/tinker ^3.0, phpunit ^12.0, pest ^4.0 and laravel/boost ^2.0.
  • laravel/helpers, whose array_first() clashes with the PHP 8.5 polyfill’s.
  • In your code: VerifyCsrfToken in tests and routes, upsert() with an empty uniqueBy, $event->exceptionOccurred, QueueBusy, domain routes, polymorphic pivots, Str factories, extend callbacks, the pagination::default views and Js::from.

What it catches for TypeScript 7

  • The tsconfig.json options that no longer exist: baseUrl, target: "es5", downlevelIteration, moduleResolution: "node" and "classic", module: "amd"/"umd"/"systemjs"/"none", and esModuleInterop, allowSyntheticDefaultImports or alwaysStrict set to false. It reads the file with comments and trailing commas, the way tsc does.
  • It hands you the rewritten paths block without baseUrl, each target relative to the tsconfig and starting with ./. It is the change people get half right: remove only baseUrl and you get TS5090.
  • From package.json: whether your typescript range jumps to 7 without you deciding, which dependencies need the API 7.0 does not ship (typescript-eslint, ts-jest, ts-morph, Vue, Svelte, Astro, MDX), and the compatibility package that leaves you compiling with 6 without noticing.
  • From pasted output: whether npx tsc and the real binary disagree, the TS5101 (“is deprecated and will stop functioning in TypeScript 7.0”) that gives away you are still on 6, and CI scripts expecting exit code 2.

What no tool sees from outside

Some changes depend on your environment, not your files: Laravel’s cache and session prefixes when they are not pinned in .env, or fetch headers that come from a database. That is why the result ends with a manual checklist. A clean report is a good sign, not a green light: run the whole suite before moving production.

Frequently asked questions

What exactly does it check?

From the manifest, the version constraints and config: whether engines.node allows 26, whether php and laravel/framework allow 13, which tsconfig options TypeScript 7 removes and the dependencies that must move with them, plus packages known to cause trouble (native addons, observability agents, laravel/helpers). From the grep output, the code patterns that change: _stream_ modules, writeHeader, VerifyCsrfToken, upsert with an empty uniqueBy, exceptionOccurred and the rest of each version's list.

Is my code uploaded anywhere?

No. Everything is analyzed in your browser with JavaScript and no request carries what you paste. Even so, you do not need to paste the whole project: the step 2 command only extracts the lines that matter. Never paste your .env.

Why does it ask for grep output instead of the repository?

Because grep -rn output carries the path and line number of every match, so the tool can tell you where each problem is without seeing the rest of your code. You can also paste a single file if you prefer: it is numbered from line 1.

If nothing is flagged, can I upgrade safely?

It is a good sign, but not enough. Some changes are invisible from outside, like Laravel cache prefixes that depend on your .env, or dynamic fetch headers in Node. That is why the tool ends with a manual checklist. Run the whole suite before moving production.

I get "Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0". What do I do?

That is error TS5101: you are still compiling with TypeScript 6 and it warns you the option no longer exists in 7. Silencing it with ignoreDeprecations does not help, because on 7 the compiler will not start. Paste your tsconfig.json into the tool and it returns the paths block rewritten without baseUrl, with the relative targets TypeScript 7 requires.

Will it cover other versions?

Yes. Every rule comes from a what-breaks-when-you-upgrade breakdown published on this site. The next ones will be the versions from that series that people look up most.

Reviews & ratings

No reviews yet. Be the first to leave one!

Write a review

Your rating *