DEV Community

Cover image for Migrate to Serverless Framework v4: a step-by-step guide
Sascha Mirow-Stanitzok
Sascha Mirow-Stanitzok

Posted on Edited on Originally published at sascha-ms.de

Migrate to Serverless Framework v4: a step-by-step guide

If a project of yours still runs on version 3, now is a good time to migrate to Serverless Framework v4, sooner rather than later. Version 3 has not received security updates or bug fixes since early 2025. Even so, many teams keep postponing the switch because they expect trouble with licensing, sign-in and build plugins. That worry is understandable, but mostly overblown.

The Serverless Framework has been my favorite tool for Lambda-heavy backends for years. That is why I use a small example from a fictional city library to show you how the move from v3 to v4 works: from the license question and the new sign-in all the way to the CI pipeline. You also get the typical pitfalls, including the error messages, so you recognize them before they cost you a Friday afternoon.

In short

For AWS projects, switching to Serverless Framework v4 is manageable: update the CLI, sign in, replace build plugins with the built-in esbuild and give your pipeline an access key or license key. The real hurdle is organizational, because companies with more than 2 million US dollars in annual revenue need a subscription that is billed per deployed service instance.

What you will learn in this article

  • Whether the new Serverless Framework v4 license costs you anything, and how much
  • How to move a v3 project to v4 step by step
  • How to replace serverless-esbuild with the built-in build
  • How your CI pipeline signs in without a browser
  • Which mistakes are typical during the switch and how to fix them

What changes with Serverless Framework v4

Version 4 was released in May 2024 and has now reached version 4.45. According to the vendor, there are hardly any breaking changes for AWS projects. In most cases, an existing serverless.yml keeps working unchanged. Still, a few things change that you should know about before you start.

  • Sign-in is mandatory: The CLI (command line interface) wants to know who is using it. Without a login or key it asks interactively, which makes a pipeline fail.
  • New license: The CLI stays free for individuals and small companies. Larger organizations pay.
  • esbuild is built in: TypeScript handlers are bundled automatically on deploy. Plugins such as serverless-esbuild conflict with this.
  • AWS only: Other cloud providers are no longer supported.
  • Smaller changes: .env files are loaded automatically, parameters preferably move under stages, Git variables are built in, and serverless dev works without the dashboard.

There is one more detail that people tend to miss: if frameworkVersion is missing from your configuration, the CLI updates itself once a day. On a laptop that is convenient. In a pipeline, however, I want to know exactly which version is deploying.

License and cost: do you have to pay?

Before you touch a single line of configuration, settle the license question. It decides whether the switch is a technical ticket or a purchasing process with budget, contract and approval.

The rule is simple: if your organization's annual revenue in the last fiscal year exceeded 2 million US dollars, you need a paid subscription. Below that, you use the CLI for free and do not have to prove anything. What also matters is how usage is counted. Billing is per “service instance”. That means one serverless.yml deployed to a specific AWS account, stage and region. Put simply, it is one CloudFormation stack.

How billing per service instance works

An example makes this concrete. If your library API has three stages (dev, test, prod) in one region, that is three instances. If you also deploy prod to a second region, a fourth one is added. The number of functions, invocations or CLI commands does not matter.

Two rules help you save money. An instance only counts if it exists for more than ten days in a month, so short-lived preview stages are not a problem. And as soon as you touch an old v3 instance with the v4 CLI, it counts as well. If you tear it down with serverless remove, it drops out again. In addition, there is the optional Serverless Dashboard with its own price list. Those costs only apply if you set the app property in your serverless.yml.

What about “just staying on v3”?

Version 3 remains open source and technically keeps working. However, it no longer gets security updates, and its dependencies quietly age. For a project that is supposed to run for years, I consider that the most expensive option; the bill just arrives later. If the license does not work for your company, a planned move to AWS CDK or SST is more honest than standing still. I compared the three tools in Serverless Framework vs. CDK vs. SST.

The starting point before you migrate to Serverless Framework v4

The example is a small API for the fictional city library: an endpoint GET /books/{id} returns a book from a catalog. The handler is written in TypeScript. In v3, that required a build plugin, in this case serverless-esbuild. On top of that, the project loads a .env file and uses parameters per stage.

# serverless.v3.yml – starting point (Serverless Framework v3)
service: library-api
frameworkVersion: "3"

useDotenv: true

plugins:
  - serverless-esbuild

custom:
  esbuild:
    bundle: true
    minify: false
    sourcemap: true

provider:
  name: aws
  runtime: nodejs22.x
  region: eu-central-1
  environment:
    LIBRARY_NAME: ${param:libraryName}

params:
  default:
    libraryName: Test Library
  prod:
    libraryName: ${env:LIBRARY_NAME}

functions:
  getBook:
    handler: src/books.getBook
    events:
      - httpApi:
          method: GET
          path: /books/{id}
Enter fullscreen mode Exit fullscreen mode

The handler stays completely unchanged during the switch. It reads the book ID from the path, looks it up in the catalog and responds with JSON:

// src/books.ts
import type {
  APIGatewayProxyEventV2,
  APIGatewayProxyStructuredResultV2,
} from "aws-lambda";

export interface Book {
  id: string;
  title: string;
  available: boolean;
}

// Small in-memory catalog so the example runs without a database
const catalog: Record<string, Book> = {
  "b-100": { id: "b-100", title: "Serverless for Beginners", available: true },
  "b-200": { id: "b-200", title: "Events Instead of Calls", available: false },
};

const json = (
  statusCode: number,
  body: unknown,
): APIGatewayProxyStructuredResultV2 => ({
  statusCode,
  headers: { "content-type": "application/json" },
  body: JSON.stringify(body),
});

export const getBook = async (
  event: APIGatewayProxyEventV2,
): Promise<APIGatewayProxyStructuredResultV2> => {
  const id = event.pathParameters?.id;
  if (!id) {
    return json(400, { message: "Missing book id" });
  }
  const book = catalog[id];
  if (!book) {
    return json(404, { message: `Book ${id} not found` });
  }
  // LIBRARY_NAME comes from serverless.yml (per stage)
  return json(200, { library: process.env.LIBRARY_NAME ?? "unknown", book });
};
Enter fullscreen mode Exit fullscreen mode

Prerequisites

  • Node.js 18.17 or newer, ideally a current LTS release (I use Node.js 22)
  • An AWS account and the local credentials you have been deploying with so far
  • A Git branch for the migration so you can always go back
  • An account for the Serverless Dashboard (free to create) or a license key from a subscription

Migrate to Serverless Framework v4 in seven steps

I work through the following steps in exactly this order. Each step is small enough to check on its own, and you only touch production at the very end.

Step 1: Take stock

First, look at what you are dealing with. Which version runs locally and in the pipeline? Which plugins are listed in serverless.yml? Are there .env files in the project? The answers determine how much work lies ahead.

# Which version is installed locally?
serverless --version
# Which plugins does the project use?
grep -A5 "^plugins:" serverless.yml
# Are there .env files that v4 will load automatically?
ls -a | grep "^\.env"
Enter fullscreen mode Exit fullscreen mode

Pay special attention to plugins that build code (serverless-esbuild, serverless-webpack, serverless-plugin-typescript, serverless-bundle) and to serverless-plugin-git-variables, which no longer works in v4. Well-known plugins such as serverless-offline, serverless-domain-manager or serverless-step-functions have been tested with v4 by the vendor.

Step 2: Install the CLI and sign in

Install the new version globally and sign in. The login opens a browser where you sign in with email, Google or GitHub. Afterwards, an access key is stored locally on your machine. You do not have to use any dashboard features for that.

npm install -g serverless
serverless --version
serverless login
Enter fullscreen mode Exit fullscreen mode

Expected result: serverless --version reports a 4.x version. If you have a subscription with a license key, you can set the SERVERLESS_LICENSE_KEY variable instead of logging in. More on that in step 7.

Step 3: Pin the version

Add frameworkVersion: "4" to serverless.yml. This way the project only runs with a 4.x CLI, and the daily auto-update is switched off. If you prefer it stricter, pin a more specific version using SemVer syntax.

Step 4: Replace the build plugin with the built-in esbuild

This is the step with the biggest “aha” moment. If you leave serverless-esbuild in the plugin list, v4 stops with this message (error code PLUGIN_TYPESCRIPT_CONFLICT):

Serverless now includes ESBuild and supports Typescript out-of-the-box. But this conflicts with the plugin 'serverless-esbuild'.
You can either remove this plugin and try Serverless's ESBuild support builtin, or you can set 'build.esbuild' to false in your 'serverless.yml'.
Enter fullscreen mode Exit fullscreen mode

You have two options. I recommend the first one: remove the plugin and move the settings from custom.esbuild into the new build.esbuild block. The options there have almost the same names (bundle, minify, sourcemap, external, exclude). For current Node runtimes, v4 leaves the @aws-sdk/* packages out of the bundle by default because the Lambda runtime already provides them.

# Remove the plugin (project dependency and the entry under plugins:)
npm uninstall serverless-esbuild esbuild
# Then rename custom.esbuild to build.esbuild and delete plugins:
Enter fullscreen mode Exit fullscreen mode

The second option is the emergency exit: you set build: { esbuild: false } and keep your old plugin. That makes sense if you have a very special webpack configuration that you do not want to rebuild right away. In the long run, I would still switch, because one plugin less also means one dependency less that might stop being maintained at some point.

A note on source maps: with setNodeOptions: true, the framework sets NODE_OPTIONS=--enable-source-maps on all functions. Stack traces then point to your TypeScript lines instead of the bundle. Also watch out for package.include and package.exclude. The built-in build ignores these old keys and only prints a warning. Therefore, rewrite them as package.patterns, with exclusions prefixed by !.

Step 5: Check .env files and parameters

In v3, you had to set useDotenv: true so that .env and .env.<stage> were loaded. In v4 this happens automatically, so the line can go. This is exactly where a surprise lurks, though. If a project contains a forgotten .env file that used to be ignored, its values now suddenly end up in ${env:...} variables. So check what is in these files. By the way, v4 does not put the files themselves into the deployment package by default.

For parameters, the old params block keeps working. However, the recommended syntax is now stages.<stage>.params. Later on, you can store other per-stage settings there as well. This is what the finished configuration looks like:

# serverless.yml – after the migration (Serverless Framework v4)
service: library-api
frameworkVersion: "4"

# Optional: read the license key from SSM instead of an env variable
# licenseKey: ${ssm:/serverless-framework/license-key}

build:
  esbuild:
    bundle: true
    minify: false
    sourcemap:
      type: linked
      setNodeOptions: true

provider:
  name: aws
  runtime: nodejs22.x
  region: eu-central-1
  environment:
    LIBRARY_NAME: ${param:libraryName}

stages:
  default:
    params:
      libraryName: Test Library
  prod:
    params:
      libraryName: ${env:LIBRARY_NAME}

functions:
  getBook:
    handler: src/books.getBook
    events:
      - httpApi:
          method: GET
          path: /books/{id}
Enter fullscreen mode Exit fullscreen mode

Step 6: Deploy to a test stage

Now comes the first real test. Do not deploy straight to dev or prod, but to a fresh stage. That way you can calmly compare the result with the existing stack.

serverless deploy --stage testv4
# Use the endpoint from the deploy output
curl https://<api-id>.execute-api.eu-central-1.amazonaws.com/books/b-100
# Remove the test stage again
serverless remove --stage testv4
Enter fullscreen mode Exit fullscreen mode

Expected result: the deploy ends with the URL of your HTTP API, and the curl call returns the book as JSON. Afterwards, compare the package size and the function's environment variables in the Lambda console with the old stage. If NODE_OPTIONS suddenly shows up there, that is the source map switch from step 4. If you want to see how to build an HTTP API with Lambda from scratch, have a look at my article HTTP API with API Gateway and Lambda.

Remove the test stage again afterwards. If it stays around for more than ten days in a month, it counts as a service instance. Then deploy the real stages one after another, dev first and prod last.

Step 7: Sign in your CI pipeline without a browser

A pipeline has no browser. That is why it gets a key as an environment variable, stored as a secret in your CI system. You have three options:

Option Use case What to watch
SERVERLESS_ACCESS_KEY Free usage or subscription with dashboard Belongs to one user. Create a dedicated key just for the pipeline.
SERVERLESS_LICENSE_KEY Subscription without dashboard Not tied to a person, does not expire, turns off dashboard features for these deploys.
License key in SSM Many repositories and accounts Since version 4.4.19, the CLI reads the parameter /serverless-framework/license-key automatically.

I like the SSM option best as soon as several teams are involved. The key is stored once per AWS account as a SecureString in Parameter Store, and no repository needs to know it. The CLI reads it with the same AWS credentials it deploys with. Alternatively, you can reference your own path in serverless.yml with licenseKey: ${ssm:/path/to/key} (since version 4.4.5).

# Store the license key once per AWS account and region
aws ssm put-parameter \
  --name "/serverless-framework/license-key" \
  --type "SecureString" \
  --value "<your-license-key>" \
  --region eu-central-1
Enter fullscreen mode Exit fullscreen mode

For GitHub Actions, a lean workflow looks like this. It gets its AWS credentials through OpenID Connect (OIDC). An IAM role trusts your repository, and no long-lived AWS keys are stored in the CI system.

# .github/workflows/deploy.yml – copy into your repository
name: Deploy library-api

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write # needed for the AWS OIDC role

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm test
      - run: npm install -g serverless@4
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
          aws-region: eu-central-1
      - run: serverless deploy --stage dev
        env:
          # Access key from the Serverless Dashboard (or SERVERLESS_LICENSE_KEY)
          SERVERLESS_ACCESS_KEY: ${{ secrets.SERVERLESS_ACCESS_KEY }}
Enter fullscreen mode Exit fullscreen mode

🎬 Deployment flow with Serverless Framework v4 in a CI pipeline – this animated diagram only runs on my site: watch it on sascha-ms.de

Typical migration mistakes and how to fix them

Most problems I would expect during this switch have a simple cause. Here are the most likely candidates:

  • Pipeline hangs or fails at login: SERVERLESS_ACCESS_KEY or SERVERLESS_LICENSE_KEY is missing, or the secret is configured in the wrong job.
  • PLUGIN_TYPESCRIPT_CONFLICT: A build plugin is still in the plugin list. Either remove it or set build.esbuild to false.
  • Function cannot find a module: A package was removed from the bundle with exclude, but the runtime does not provide it. Check external and exclude.
  • Unexpected values in variables: An old .env file is now loaded automatically.
  • Files missing from the package: The build ignores package.include. Switch to package.patterns.
  • Suddenly a new CLI version: frameworkVersion is missing, so the CLI updates itself.

Keep an eye on cost and security

An access key is something like a password for your organization in the Serverless Dashboard. Store it only as a secret, never in serverless.yml or in the repository. Personal keys do not belong in pipelines, because they stop working as soon as that person leaves the team. On the AWS side, OIDC is worth it compared to stored IAM keys. As for license cost: count your stacks per stage, account and region before the migration, and there will be no surprise for purchasing later.

My take

Technically, v4 is a clear step forward for me. The built-in build saves TypeScript projects a plugin that used to cause version conflicts on a regular basis. You also notice the faster releases. At the same time, I understand everyone who flinches at the word “license”. A tool that was free for years now costs larger companies money, and mandatory sign-in feels unfamiliar at first.

My position is still clear: a project on a version without security updates is not an option you should choose for the long term. Either you migrate to v4 and settle the license properly, or you deliberately plan a move to another tool. Both are fine. Only waiting is not. For most small teams and solo developers, the migration takes an afternoon anyway.

FAQ

Is Serverless Framework v4 free?

Yes for individuals and organizations with less than 2 million US dollars in annual revenue. Larger organizations need a subscription that is billed per deployed service instance.

Can I use Serverless Framework v4 without logging in?

No. You either sign in with serverless login or provide an access key or license key through an environment variable or SSM.

Do I still need serverless-esbuild in v4?

Usually not. esbuild is built in and builds TypeScript handlers automatically. If you want to keep the plugin, you have to turn off the built-in build with build.esbuild: false.

Is Serverless Framework v3 still maintained?

No. Since early 2025, v3 no longer receives security updates, bug fixes or new features. The version remains open source, but it is no longer a good choice for new projects.

Your next step

Open a project that still runs on v3 today and do only step 1: write down the version, plugins and .env files, and count the stacks. After that, you know whether you are facing an afternoon of work or a conversation with purchasing. And I am curious: have you already switched, or is the license keeping you on v3 or pushing you towards CDK or SST? Let me know in the comments.

Sources

This post was created with AI assistance.


👉 Next step: Working with AWS CDK? Grab my printable AWS CDK cheat sheet with commands, concepts and a stack skeleton.

Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de/en

Top comments (0)