Skip to main content
Serverless functions uploaded to the design manager (served from /_hcms/api/) are being sunset. Serverless functions built with developer projects (served from /hs/serverless/) are the supported replacement.
  • November 3, 2026: you can no longer create new design manager serverless functions.
  • November 17, 2026: you can no longer edit existing design manager serverless functions, including staging and deploying serverless packages through the private beta. Existing functions keep running.
  • February 1, 2027: design manager serverless functions stop running, and requests to /_hcms/api/ fail.
Learn more in the changelog announcement.
Serverless functions require Content Hub Enterprise. If your account doesn’t have Content Hub Enterprise, you don’t have any actively used design manager serverless functions, and you don’t need to follow this guide.
HubSpot can’t migrate your functions automatically, because each function’s code and the pages that call it are unique to your site. This guide walks you through three phases, plus a recommended follow-up:
  1. Discover: find every design manager function, its endpoint, and every page, template, module, and setting that calls it.
  2. Migrate: rebuild each function in a developer project on the latest platform version.
  3. Test and switch over: point your front-end code at the new endpoints one at a time, verify, and roll back if anything breaks.
  4. Follow-up: once the migration is verified, update your function dependencies and any HubSpot API calls to the latest versions.
The recommended path is to hand the steps to an AI coding agent using the migration prompt. You can also follow the steps yourself.

Prerequisites

  • Content Hub Enterprise, which serverless functions require.
  • The latest HubSpot CLI (version 8.4.0 or later), authenticated to the account that owns the functions. Check with hs --version and hs accounts list.
  • Node.js v20 or later installed locally, which is what the HubSpot CLI requires. Your functions don’t run on your machine, so your local version doesn’t determine the Node version your deployed functions use.
  • If you’re using AI to help migrate: the HubSpot developer MCP server installed in your agentic coding tool. It gives the agent current HubSpot documentation and project schemas, which it needs to produce working code.
  • Permissions in your HubSpot Account to edit the templates, modules, and website settings that call your functions.
  • If you have more than one account in the CLI: run hs accounts use <account> first, or add --account <account> to each command. hs project logs doesn’t accept --account and always uses the default account.

Choose what to migrate

Recommended for most people: migrate only the serverless functions. Leave your templates and modules where they are, and change only the endpoint URL they call. This is the smallest, safest change, and the only option we recommend if you’re not a developer.
You can choose to move the front-end code that calls a function into the same project. The front-end and the function then deploy together in one build, so they can’t drift out of sync, and a single rollback reverts both. To move a HubL module into a project, put it in a hubl-modules folder and rename its files with a .hubl extension, as described in migrate a HubL theme to a project. You can also rebuild it as a React module. Either way, it becomes a new module in your account. If you have many functions, consider grouping them into more than one project by what they’re for, such as one project for form handling and one for a product catalog. Your old .functions folders are often a good starting point. A project deploys and rolls back as one unit, and its app’s scopes apply to every function in it, so separate projects keep a rollback or a scope change from affecting unrelated features. Each project needs its own install and build history, though, so keep a few related functions in one project. Endpoint paths and secrets are shared across the whole account, whichever project uses them. Copy the prompt below into an AI coding agent, such as Claude Code, Cursor, or VS Code with GitHub Copilot, that has the HubSpot developer MCP server and the HubSpot CLI available. Run it from an empty working folder. The agent asks for your approval before it uploads, deploys, or changes anything live.

Migrate design manager serverless functions to developer projects, update their callers, and verify each one with a rollback path.

Open in Cursor

Migrate manually

If you prefer to migrate by hand, the rest of this page is for you: the key differences between the two systems, the three phases, the follow-up, and troubleshooting.

Key differences

The table below lists only what changes. Behavior that works the same in both systems, such as reading a JSON request body from context.body, reading context.contact, and reading secrets with process.env.NAME, is omitted.

Code changes at a glance

The following examples show how each part of a design manager function translates to a developer project. They aren’t a copy-and-paste replacement for your own function. Use them to understand which parts change and why.
Function configuration
The following example shows a serverless.json file with one endpoint, and the equivalent *-hsmeta.json file in a developer project.
What changed:
  • Runtime fields: runtime and version are gone. The platform version in hsproject.json determines the runtime.
  • One file per function: the endpoints object is replaced by a separate *-hsmeta.json file for each function. The endpoint’s path and method move into config.endpoint, where the method becomes a methods array, such as ["POST"] or ["GET", "POST"]. The file value becomes config.entrypoint.
  • Unique identifier: each function needs a uid, which must be unique across all functions in the project.
  • Environment variables: API_BASE had no equivalent in the new configuration, so this example moves it to a secret and lists it in secretKeys alongside EXAMPLE_API_KEY.
Function code
The following example shows a function that reads a query parameter, calls an external API, and returns the result.
What changed:
  • Response handling: the sendResponse callback is replaced by an async function that returns the response object. Promise chains become await inside try/catch. Await every request before you return, including follow-up calls whose result the caller doesn’t need: work that’s still running when the function returns may never finish.
  • Query parameters: unchanged. context.params still holds each query parameter as an array of strings, so context.params.id[0] works as before.
  • Dependencies: the old function used axios without importing it because HubSpot preloaded it. In a project, you must require the package and add it to src/app/functions/package.json.
  • Error responses: the new example logs the error and returns a generic message instead of passing err.message to the caller. Catch every error: if a function throws, it returns a 500 response whose body includes the raw error message, which anyone who calls the endpoint can see.
  • Logging: don’t carry over log lines that print secrets or environment values. Anyone with access to the app can read its logs.
Calling the function from a module
In your front-end code, change only the URL prefix, for example from /_hcms/api/events/update to /hs/serverless/events/update. Because the function path stays the same, the request method, headers, and body don’t change.

Phase 1: Discover

Build an inventory before you change anything. It’s your migration checklist and, later, your test plan.
1

List deployed functions

Run hs cms function list. Record each endpoint path, method, and secret.
2

Fetch and back up your design manager files

In an empty folder, run hs cms fetch / ./dm-backup. Keep this folder; it’s your backup, and the source for rolling back each caller.On a large account, this can take 20 minutes or more. If the output ends with “Not all files … were successfully fetched,” fetch the missing folders again, at least every .functions folder and every folder that holds templates or modules.
3

Find each function's source

Search the backup for folders ending in .functions. In each serverless.json, note the environment variables, secrets, and the file each endpoint uses.
4

Find every caller in your code

Search the backup for _hcms/api, and for the last segment of each endpoint path on its own (for example, update for events/update), since paths are often built from variables or joined strings. Check templates, partials, modules (module.html, module.js, fields.json), and standalone JavaScript files.A caller doesn’t always look like a URL. It can be a prefix that’s joined to a path at runtime, or a value stored in a meta tag, a data attribute, a module field, a theme setting, or a configuration object. Record where each one lives and how it’s set, since that’s also how you’ll change it and roll it back.
5

Check code added outside the design manager

Endpoints are sometimes called from code added in settings or editors depending on what they do:
  • Settings > Content > Pages > Templates: site header HTML and site footer HTML, for every domain.
  • Settings > Content > Blog > Templates: header and footer HTML, for every blog.
  • Each page’s and post’s head and footer HTML, in the page’s Settings > Advanced.
  • Rich text and custom HTML modules on pages.
  • External sites or apps that call your HubSpot domain directly.
To check every website and landing page’s head and footer HTML at once, use the CMS pages API, for example hs api "/cms/v3/pages/site-pages?limit=100" and hs api "/cms/v3/pages/landing-pages?limit=100", and search each page’s headHtml and footerHtml. Check blog posts and the settings above in HubSpot. Your domains are listed in Settings > Content > Domains & URLs.
6

Confirm on the live site

Open your key pages, then in your browser’s developer tools Network tab, filter on _hcms/api. Every request that matches is a dependency that belongs on your list. Other /_hcms/ requests, such as the cookie banner and live chat, come from HubSpot and aren’t your functions.
7

Record affected pages

For each template or module that calls a function, list the pages that use it. These are the pages you’ll test. The pages API responses from the previous step include each page’s templatePath and currentState, so you can find pages that use a template, including unpublished drafts. Before you decide a change won’t affect visitors, confirm from this data that no published page uses the template or module.
8

Check usage and current behavior

For each function, run hs cms function logs <endpoint> to see recent calls, and call each endpoint that only reads data. A function with no recent calls and no callers may not need migrating. A function that already returns errors, or returns success while its work fails (for example, because it authenticates with an expired API key), needs fixing or retiring, not matching. If you retire or skip a function, add a comment to the top of its design manager file with the decision, the reason, the date, who decided, and a link to this guide, then upload it with hs cms upload. That keeps the decision with the code for whoever finds it later. If the folder’s serverless.json uses nodejs12.x, the upload fails with “Serverless runtime ‘nodejs12.x’ is no longer supported”. Don’t change the runtime to get it through, since that changes the function you’re keeping as a fallback; record the decision in your inventory instead.
Your inventory should have one row per function:

Phase 2: Migrate

1

Create a project on the latest platform version

If you’re grouping functions into several projects, repeat this phase for each one, and give each project a name that says what it’s for.Run hs project create, and create an app with static auth, private distribution, and the App Function (endpoint) feature. Apps that use OAuth can’t use serverless functions. To skip the prompts, run:
Confirm that platformVersion in hsproject.json is the current version listed in developer platform versioning (2026.09 at the time of writing).
Don’t build on 2025.1 or 2025.2. Version 2025.1 is sunset, and 2025.2 doesn’t support serverless functions. If you already have a project on a version older than 2026.03, migrate it to the latest version first. Projects already on 2026.03 run Node.js 24 and don’t need to be migrated at this time, but use the latest version for new projects.
2

Port each function

Create one *-hsmeta.json and .js pair per endpoint, using the key differences. Change only what the new platform requires, and keep HubSpot API calls on the versions they use today; you’ll update those in the follow-up.
  • Remove what the template added that you don’t use: the example function and its *-hsmeta.json file, the example scopes in app-hsmeta.json (keep oauth), and the @hubspot/api-client dependency.
  • Keep the same path after the prefix (events/update stays events/update), so the front-end change is only a prefix swap.
  • Start each new function file with a migration note comment: the old endpoint and file it came from, the date, who did the migration (and whether an AI agent did the port), each change made and why, and a link to this guide. Someone who opens the file later, including you, can then tell where it came from. Never put secret values in it.
  • Convert callbacks to async functions that return the response. Give every retry or polling loop a limit: a function stops after 15 seconds, so a loop that keeps retrying on an error such as 401 only times out.
  • Move environment variables to secrets with hs secret add NAME, then list them in secretKeys. If a function hard-codes an API key or token, move it to a secret too, and regenerate the key, since anyone who has the file can use it.
  • Add only the packages each function imports to src/app/functions/package.json. HubSpot preloaded a set of packages for every design manager function, but that doesn’t mean every function used them. See handle dependencies.
  • If a function authenticates to HubSpot APIs with a private app token, service key, or API key, see move HubSpot API authentication.
Please note: if a function uses context.limits, follow check rate limits and contact data before you switch any traffic to it.
3

Explain the project in a README

Add a src/README.md file, so anyone who finds the project later knows why it exists. Put it in src/, because hs project upload sends only hsproject.json and the src/ folder; files at the project root don’t reach HubSpot. Include:
  • that the functions moved from the design manager because of the sunset, the date, who migrated them, and a link to this guide;
  • each old endpoint and its new one, and any functions you retired or skipped, and why;
  • the secrets and scopes the functions need (names only, never values);
  • how to roll back, and your known-good build once you have one.
Also set the app’s description in app-hsmeta.json to one line that points to the README, since that’s what appears in HubSpot.
4

Upload the project

Run hs project validate, then hs project upload. Note the build number. If your functions use the app’s built-in token, install the app after the first upload with hs project install-app.
Your design manager functions keep running during this phase, so nothing changes for site visitors yet. Your new endpoints are public as soon as you upload, though, because hs project upload deploys the build immediately. Don’t call endpoints that write data until you’re ready to test them.

Move HubSpot API authentication

Functions in the design manager typically authenticated to HubSpot APIs with a token stored as a secret. Today that’s most likely a service key, or a legacy private app token. You have two options:
  • Keep the existing secret: the secret is reused as long as you list its name in secretKeys. This is the fastest path, but the key stays outside your project.
  • Use the app’s built-in token (recommended): every function in a project can read process.env.PRIVATE_APP_ACCESS_TOKEN, which is scoped to the app in your project. Add the same scopes your service key or private app has to the app’s requiredScopes in app-hsmeta.json, then replace the secret in your code with the built-in token.
To keep behavior identical, copy the exact scopes from the service key’s details page in the HubSpot Development area. Add only the scopes your functions use. To check which scopes a call needs, open that endpoint’s page in the API reference and use the scopes it lists. Don’t go by scope names alone: similar-sounding scopes can cover different operations, such as reading a table versus reading its rows. Some older scopes have been retired and replaced by more granular ones. hs project validate doesn’t check this, so if the deploy fails with a message that a scope “has been replaced by more specific scopes,” use the scopes the message names. The built-in token is available only after the app is installed in the account. After your first upload, run hs project install-app from the project folder. Until the app is installed, HubSpot API calls that use the token return 401. If you change the app’s scopes later, upload the project and run hs project install-app again, so the token gets the new scopes. If a function authenticates with an API key (a hapikey query parameter, often read from context.secrets), it’s already failing: API keys were sunset on November 30, 2022, and reusing the secret won’t help. Switch it to the built-in token. API keys had no scopes, so look up the scopes for each API the function calls in the API reference, as described above.

Check rate limits and contact data

Design manager functions receive context.limits, which reports how many executions per minute and how much execution time remain. Functions in developer projects don’t receive context.limits, and there’s no replacement field. If your function used it to slow down before reaching the account limits, remove that logic and handle 429 responses instead, for example by retrying after a delay in the front-end code that calls the function. Functions in developer projects receive the same context.contact as design manager functions, with the fields vid, email, firstname, lastname, lastmodifieddate, ip_country_code, isLoggedIn and listMemberships. As before, it’s present for visitors who have a HubSpot cookie and are known contacts, including logged-in members, and absent for anonymous visitors. If your function sets Set-Cookie headers or depends on context.contact.isLoggedIn or listMemberships, test those cases on a live page, not only with direct requests.

Handle dependencies

Dependencies are a common source of migration issues, so plan for them before you upload.
  • Add only what each function uses: search your function files for require and import, and install the current stable version of each package in src/app/functions/ with npm install. A package being preloaded in the design manager doesn’t mean a function used it.
  • Check axios and request: design manager functions had axios 0.19 preloaded, so review the axios release notes for changes to error handling and response objects. The request package is deprecated; replace it with axios or the built-in fetch.

Phase 3: Test and switch over

Switch one function at a time.
1

Test the new endpoint directly

Call https://<domain>/hs/serverless/<path> with the same method, query, and body your front end sends, and compare the response to the old endpoint. Status code, body shape, and headers should match. Check the app’s Logs tab for errors.
2

Switch the front end

Change this function’s callers from /_hcms/api/ to /hs/serverless/. Upload changed design manager files with hs cms upload, and publish any page or setting you edited in HubSpot. Callers in page, post, or settings HTML can only be edited in HubSpot, not with the CLI. If one file calls several functions, for example through a shared prefix variable, change only this function’s call, so you test and roll back one function at a time.Edit copies of the files, not the ones in your dm-backup folder, so the backup stays a copy of what’s live today.Before you edit a caller in HubSpot, copy its current text somewhere safe. Your dm-backup folder only covers design manager files, so this copy is how you’ll restore that caller.Live pages can take about a minute to show the change, so wait, then hard-refresh before you test.
3

Verify the live pages

On each page in your inventory, use the feature the way a visitor would. Watch the browser console and Network tab for failed requests. If a template or module is used only on draft pages, preview the page in HubSpot instead, and call the new endpoint on each domain the page could be published to.
4

Roll back if anything fails, then fix

  • Functions only: restore the old /_hcms/api/ URL in every caller you changed: re-upload the original design manager files from your dm-backup folder, and paste back the text you saved for any caller you edited in HubSpot. Restoring a file doesn’t undo edits made in HubSpot. The old function is still running.
  • Front end in the project: redeploy an earlier build from the project’s Builds tab, or with hs project deploy.
Then read the logs, fix the function, upload a new build, and repeat from the first step.
Practice once before you need it: after your first switch, roll that caller back, confirm the page calls the old endpoint and works, then switch it again.
If you moved front-end code into the project, first deploy one build where the new function exists but the front end still calls the old URL. That build is a known-good rollback point that keeps visitors unaffected while you fix newer builds.
5

Mark the function verified

Update its status in your inventory once every page passes. Add a comment to the top of the old design manager file noting the new path, the date and who migrated it, and upload it, so anyone who opens the old file knows it’s been replaced. A nodejs12.x folder won’t accept the upload, so record the note in your inventory instead. Note the build number once every function is verified; it’s your known-good build for the follow-up.
6

Clean up

Only after every function is verified, remove the old .functions folders from the design manager and delete secrets that nothing uses. Deleting a folder also deletes its migration and retirement comments, so keep your inventory and each project’s README as the record.

Follow-up: Update dependencies and HubSpot API versions

Once every function is verified, make two further updates while the code is fresh. Do them as separate changes, so a problem with a dependency or an API version can’t be confused with a problem in the migration.
1

Update dependencies

Update the packages in src/app/functions/package.json to their latest stable versions, and review each package’s release notes for breaking changes. Upload a new build and repeat the Phase 3 tests for each function that uses an updated package.
2

Inventory HubSpot API calls

If your functions call HubSpot APIs, update them to the latest date-based API version. Search your function code for api.hubapi.com, api.hubspot.com, versioned paths such as /v3/ or /v4/, and @hubspot/api-client. For each call, record the endpoint, method, current version, and whether a date-based equivalent exists.
3

Choose a target version

Use the latest GA date-based version (for example, 2026-09). In the API reference, copy each endpoint path exactly as documented. Endpoints without a date-based equivalent stay on their current version for now.
4

Update the code

Define the API version in one constant rather than in every URL. A new API version can change request and response shapes, not only paths, so compare each function’s response before and after the change. If you use @hubspot/api-client, confirm that the SDK release you install supports the date-based endpoints you need.
5

Upload, test, and roll back if needed

Upload a new build and repeat the Phase 3 tests for each function. If anything fails, redeploy your known-good build, then fix and try again.
For a full walkthrough, see the date-based API versioning migration playbook.

Troubleshooting

Last modified on November 3, 2026