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.json
file (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/page
http://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/page
https://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
?hsLocalQueryKey
query parameter. hublDataTemplate
is not supported at this route.Icon
,CTA
, and other@hubspot/cms-component
field 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.
hublDataTemplate
is supported.Icon
,CTA
, and other@hubspot/cms-component
field 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')
.