Last modified: August 22, 2025
@hubspot/cms-dev-server is an NPM package that facilitates starting an Express + Vite development server, enabling an auto-reloading local development workflow that is nearly identical to your deployed components. The cms-dev-server also enables rendering local versions of your components on live CMS pages to help with development.
Below, learn how to install and use the CMS local development server, including how to proxy pages, incorporate Storybook, generate field types, and use secrets during local development.
Installation
You can install@hubspot/cms-dev-server in several ways:
- Install it globally:
npm install -g @hubspot/cms-dev-server. - Add it as a dependency in the project’s
package.jsonfile (example below), then runnpm install.
Basic usage
If you’ve installed the package globally, you can start the local development server by runninghs-cms-dev-server /path/. Alternatively, use npx, or add a start script to your package.json to run with npm.
3000, and you can access the main local development dashboard by visiting http://localhost:3000. This dashboard will provide details and local preview links for the project’s CMS assets.
You can also use the hslocal.net:3000 and localhost:3000 addresses to preview pages with proxying. For example, a CMS page https://cmssite.com/page with React components would be accessible by visiting one of:
http://cmssite.com.hslocal.net:3000/pagehttp://cmssite.com.localhost:3000/page
http://hslocal.net:3000/proxy and paste in the URL of the page you want to proxy.
Similar to how previewing a page from the page editor works, you can force the page to render with the context of a known contact by passing an email parameter. For example http://cmssite.com.hslocal.net:3000/page?email=bh@hubspot.com will cause the contact object to be populated based on the email parameter value.
You may also start the dev server with the --ssl option, which enables:
https://cmssite.com.hslocal.net:3000/pagehttps://cmssite.com.localhost:3000/page
Routes
When the development server starts, it will look at thecomponents/modules directory, then will dynamically create routes based on the modules that it finds. There are two routes for previewing your modules: /module/moduleName and /preview/module/moduleName.
/module/moduleName
http/module/moduleName
The /module/moduleName route is rendered entirely locally without connecting with the HubSpot backend. This enables you to work offline and unauthenticated at this route.
Field values that are used are derived entirely from the Field default values and from parameter-level overrides. Parameter-level overrides can be passed via the fields parameter, which expects stringified JSON of fieldValues that matches the passed fieldValues prop (matching the fields definition structure).
Using this route includes some caveats for querying data:
- GraphQL data in this context is fetched from your local machine using your local access token to authorize the collector service requests. These queries are cached, but you can bust the cache with the
?hsLocalQueryKeyquery parameter. hublDataTemplateis not supported at this route.Icon,CTA, and other@hubspot/cms-componentfield helpers are not supported at this route.
/preview/module/moduleName
The/preview/module/moduleName route interacts with the HubSpot backend and behaves similarly to viewing a module in the design manager previewer.
Field values that are used rely on defaults, as there is no module instance to pull from. There is no fields parameter available for overrides.
When querying data on this route:
- GraphQL data is derived on the backend, and there is no query from the local server to the GraphQL service.
hublDataTemplateis supported.Icon,CTA, and other@hubspot/cms-componentfield helpers are supported.
Proxying private pages
With the local development server running, you can preview your local changes on live HubSpot pages, including private pages that require passwords or login to access, as well as page previews.Proxied membership pages
To view your local changes on a proxied membership page, first visit the page and log in with the configured method. Once authenticated, addhslocal.net:3000 or localhost:3000 to the root domain, just as you would with proxying a public page.
To view the page as a specific contact, you can add an email query parameter to the URL, followed by the contact’s email address, as shown below:
https://mydomain.hslocal.net:3000/private-page-path?email=hi@hubspot.com
That email will be used for the request_contact HubL variable, and can be passed to React using hublDataTemplate.
Proxied preview
To view your local changes on a proxied version of a page preview, first navigate to the page editor in HubSpot, then click Preview in the upper right, then Open in a new tab.
hslocal.net:3000 or localhost:3000 to the root domain. To view the page as a specific contact, you can add an email query parameter to the URL, followed by the contact’s email address, as shown below:
https://mydomain.hslocal.net:3000/private-page-path?hs_preview=[preview_key]&email=hi@hubspot.com
Storybook
The local development server includes a Storybook integration which allows you to start a Storybook instance alongside the development server. Include a--storybook option when running the command to start the local development server.
Once started, you can add .stories.jsx files alongside your components to build stories for testing or development. At the root http://hslocal.net:3000 page, a link will be included to the Storybook UI for your project. To make building stories for HubSpot modules easier, cms-dev-server provides helpers to auto-generate argTypes based on module fields. See the GraphQL and Storybook example project for usage of moduleStory().
Storybook is built with client components in mind, so components that cross island boundaries can have unexpected lifecycle behavior when rendered in a story. Because server-only components never make it to the browser, they cannot be hot reloaded, so a full re-render is necessary to update the server response. To fully emulate hybrid rendering in Storybook at the cost of hot module reloading, you can use moduleStoryWithIsland() in your story in place of moduleStory().
Fields type generation
If you’re using TypeScript in your project, you can use the--generateFieldTypes argument when starting the development server. This command will watch for changes to the fields object that is exported from module files and create a .types.ts file inside of the module directory. You can then import this module directly in your module component and use it in the generate ModuleProps<T> type.
For example, suppose you have the following fields.jsx file:
hs-cms-dev-server /path/to/project --generateFieldsTypes will generate a modules/MyModule/fields.types.ts file with a default exported type MyModuleFieldsType. The above fields.tsx will generate the following file:
fields.types.ts file will be overwritten every time there is an update made to the fields object. To disable this behavior, remove the THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY comment at the top of the file.
Secrets in local development
To make secrets available for local development via@hubspot/cms-dev-server, create a .env file to define secret values for local use only. Keys in this file need to be prefixed with HS_ for the development server to recognize it as a secret.
HS_ prefix you included in your .env file. In the example above, the secret can be accessed with getSecret('TEST_SECRET').