/_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.
- Discover: find every design manager function, its endpoint, and every page, template, module, and setting that calls it.
- Migrate: rebuild each function in a developer project on the latest platform version.
- Test and switch over: point your front-end code at the new endpoints one at a time, verify, and roll back if anything breaks.
- Follow-up: once the migration is verified, update your function dependencies and any HubSpot API calls to the latest versions.
Prerequisites
- Content Hub Enterprise, which serverless functions require.
- The latest HubSpot CLI (version
8.4.0or later), authenticated to the account that owns the functions. Check withhs --versionandhs 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 logsdoesn’t accept--accountand always uses the default account.
Choose what to migrate
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.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.
Migrate with an AI agent (recommended)
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.
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 fromcontext.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 aserverless.json file with one endpoint, and the equivalent *-hsmeta.json file in a developer project.
- Runtime fields:
runtimeandversionare gone. The platform version inhsproject.jsondetermines the runtime. - One file per function: the
endpointsobject is replaced by a separate*-hsmeta.jsonfile for each function. The endpoint’s path and method move intoconfig.endpoint, where the method becomes amethodsarray, such as["POST"]or["GET", "POST"]. Thefilevalue becomesconfig.entrypoint. - Unique identifier: each function needs a
uid, which must be unique across all functions in the project. - Environment variables:
API_BASEhad no equivalent in the new configuration, so this example moves it to a secret and lists it insecretKeysalongsideEXAMPLE_API_KEY.
Function code
The following example shows a function that reads a query parameter, calls an external API, and returns the result.- Response handling: the
sendResponsecallback is replaced by anasyncfunction thatreturns the response object. Promise chains becomeawaitinsidetry/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.paramsstill holds each query parameter as an array of strings, socontext.params.id[0]works as before. - Dependencies: the old function used
axioswithout importing it because HubSpot preloaded it. In a project, you mustrequirethe package and add it tosrc/app/functions/package.json. - Error responses: the new example logs the error and returns a generic message instead of passing
err.messageto the caller. Catch every error: if a function throws, it returns a500response 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.List deployed functions
hs cms function list. Record each endpoint path, method, and secret.Fetch and back up your design manager files
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.Find each function's source
.functions. In each serverless.json, note the environment variables, secrets, and the file each endpoint uses.Find every caller in your code
_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.Check code added outside the design manager
- 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.
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.Confirm on the live site
_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.Record affected pages
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.Check usage and current behavior
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.Phase 2: Migrate
Create a project on the latest platform version
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:platformVersion in hsproject.json is the current version listed in developer platform versioning (2026.09 at the time of writing).Port each function
*-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.jsonfile, the example scopes inapp-hsmeta.json(keepoauth), and the@hubspot/api-clientdependency. - Keep the same path after the prefix (
events/updatestaysevents/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
asyncfunctions thatreturnthe 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 as401only times out. - Move environment variables to secrets with
hs secret add NAME, then list them insecretKeys. 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.
Explain the project in a README
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.
description in app-hsmeta.json to one line that points to the README, since that’s what appears in HubSpot.Upload the project
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.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’srequiredScopesinapp-hsmeta.json, then replace the secret in your code with the built-in token.
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 receivecontext.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
requireandimport, and install the current stable version of each package insrc/app/functions/withnpm install. A package being preloaded in the design manager doesn’t mean a function used it. - Check
axiosandrequest: design manager functions hadaxios0.19 preloaded, so review the axios release notes for changes to error handling and response objects. Therequestpackage is deprecated; replace it withaxiosor the built-infetch.
Phase 3: Test and switch over
Switch one function at a time.Test the new endpoint directly
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.Switch the front end
/_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.Verify the live pages
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 yourdm-backupfolder, 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.
Mark the function verified
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.Clean up
.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.Update dependencies
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.Inventory HubSpot API calls
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.Choose a target version
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.Update the code
@hubspot/api-client, confirm that the SDK release you install supports the date-based endpoints you need.Upload, test, and roll back if needed
Troubleshooting
Related resources
- Serverless functions reference (design manager)
- Serverless functions reference (developer platform)
- Serverless functions for the CMS
- Developer platform versioning
- Determine your app migration path
- Migrate an app to the latest platform version
- Date-based API versioning migration playbook
- Developer MCP server tools
- CMS CLI commands