+1 (726) 207-9872

Upgrading a Service from Serverless Framework V3 to V4, Step by Step

Serverless Framework V3 is unmaintained, and its plugin ecosystem is decaying around it. V4 (May 2024) is the supported line, and upgrading is the most common piece of work we do for clients in 2026. This is the procedure we follow, written for a typical Node.js service on AWS. Budget half a day for a simple service and a week for a large one with many plugins.

0. Understand what changed

Before touching anything, know the headline differences between V3 and V4:

  • Authentication is mandatory. Every serverless command that touches AWS requires either serverless login (interactive) or a license key / access key in the environment. This applies to CI.
  • Licensing. Free for organizations under $2M in annual revenue; paid, per Service Instance, above it. Read our licensing explainer if that affects you.
  • AWS only. V4 removed support for other cloud providers. If your serverless.yml targets Azure or Google Cloud, V4 is not an upgrade path.
  • Built-in TypeScript bundling. V4 bundles TypeScript and JavaScript with esbuild out of the box. serverless-esbuild, serverless-webpack, and serverless-plugin-typescript become redundant and can conflict.
  • New variable resolvers and a stages block for per-stage parameters, plus serverless dev for live local development against your deployed stage.

The official reference is the Upgrading to V4 guide. Keep it open.

1. Inventory the service

Record the current state so you can diff against it later:

node --version                    # Lambda runtime should already be nodejs20.x or nodejs22.x
npx serverless --version          # confirm you are on 3.x
npx serverless print --stage prod > /tmp/v3-resolved.yml
npx serverless package --stage prod --package /tmp/v3-package

serverless print resolves every variable and gives you the effective configuration. The package output contains the CloudFormation template V3 would deploy; you will compare V4's template against it in step 6.

List your plugins:

grep -A 20 '^plugins:' serverless.yml

2. Install V4 and authenticate

npm install --save-dev serverless@4
npx serverless --version          # 4.x

Authenticate. For a developer laptop:

npx serverless login

For CI and for anyone who prefers not to use the Dashboard, set a license key or access key as an environment variable. The license keys guide explains which one applies to your account:

export SERVERLESS_LICENSE_KEY=...   # license-key accounts
# or
export SERVERLESS_ACCESS_KEY=...    # Dashboard accounts

Never commit either value. Put it in your CI secret store and in your local .env (ignored by git).

3. Triage plugins

Work through the plugin list with three buckets:

Remove (now built in):

# Before (V3)
plugins:
  - serverless-esbuild
  - serverless-plugin-typescript

# After (V4): delete them and configure the built-in bundler if needed
build:
  esbuild:
    bundle: true
    minify: true
    sourcemap: true
    target: node22
    exclude:
      - '@aws-sdk/*'   # already present in the Node 18+ runtimes

If you genuinely need your own bundler for a while, disable the built-in one with build: { esbuild: false } and keep the plugin; do not run both.

Keep (compatible): serverless-offline, serverless-iam-roles-per-function, serverless-prune-plugin, and most plugins that only add CloudFormation resources generally work. Upgrade each to its latest release and check its README for a V4 note.

Replace: serverless-dotenv-plugin is usually unnecessary because V4 honors useDotenv: true. Plugins that relied on V3 internals (custom variable sources, lifecycle hacks) are the ones that break; check their issue trackers for a V4 compatibility thread before assuming.

4. Update serverless.yml

Bump the framework version pin and the runtime:

frameworkVersion: '4'

provider:
  name: aws
  runtime: nodejs22.x
  region: us-east-1

Move stage-specific values into the new stages block. In V3 most teams used a custom map keyed by stage; V4 makes this first-class:

stages:
  default:
    params:
      tableName: orders-${sls:stage}
      logLevel: debug
  prod:
    params:
      logLevel: info

functions:
  createOrder:
    handler: src/orders.create
    environment:
      TABLE_NAME: ${param:tableName}
      LOG_LEVEL: ${param:logLevel}

Check variable syntax that changed behavior. ${env:VAR} now fails hard when VAR is unset unless you supply a default (${env:VAR, 'fallback'}), and ${ssm:...} and ${cf:...} resolve under the credentials V4 is running with. A surprising number of "it worked on V3" failures are variables that silently resolved to empty strings before.

5. Deploy to a fresh stage

Never upgrade in place on production. Deploy to a throwaway stage first:

npx serverless deploy --stage v4check --verbose

Read the output carefully for deprecation warnings. Then exercise the service: invoke functions, hit the API, and run your integration tests against the new stage.

npx serverless invoke --function createOrder --stage v4check --path test/events/create.json
npx serverless logs --function createOrder --stage v4check --tail

6. Diff the CloudFormation templates

This is the step most teams skip and later regret. Package with V4 and compare to the V3 package you saved in step 1:

npx serverless package --stage prod --package /tmp/v4-package
diff <(jq -S . /tmp/v3-package/cloudformation-template-update-stack.json) \
     <(jq -S . /tmp/v4-package/cloudformation-template-update-stack.json)

Expect differences in bundling artifacts and hashes. Investigate anything that changes a logical resource ID, a function name, an API Gateway resource, or an IAM statement. A changed logical ID means CloudFormation will replace the resource, which for a DynamoDB table or an S3 bucket is data loss. If you find one, fix the configuration until the ID is stable; do not proceed on the assumption it will be fine.

7. Update CI

Your pipeline needs three things: the V4 package installed, the license or access key in the environment, and AWS credentials. If you are still using long-lived AWS access keys in CI, this is a good moment to switch to OIDC; we cover that in CI/CD for Serverless Framework Apps with GitHub Actions and OIDC. The minimal change:

- run: npm ci
- run: npx serverless deploy --stage ${{ github.ref_name == 'main' && 'prod' || 'staging' }}
  env:
    SERVERLESS_LICENSE_KEY: ${{ secrets.SERVERLESS_LICENSE_KEY }}

8. Promote and verify

Deploy to staging, then production, with --verbose on, and confirm:

  • CloudFormation shows UPDATE_COMPLETE with no replaced resources.
  • Function ARNs and API endpoints are unchanged (serverless info --stage prod).
  • Alarms and dashboards still receive metrics (function names are part of metric dimensions).
  • Remove the v4check stage: npx serverless remove --stage v4check.

What to do if it goes wrong

A failed stack update rolls back automatically. A successful update that replaced a resource does not; that is why step 6 exists. Keep the V3 lockfile on a branch until production has completed at least one full deploy cycle on V4, and keep the serverless print output from step 1 as the record of what the service used to resolve to.

If your estate has dozens of services, or a plugin nobody on the team understands, our V4 upgrade and migration service starts with the same inventory and ends with every stack on a supported footing. Get in touch.