All HubSpot CLI commands and settings, strategies for managing multiple portals, how to proceed with local development, CI/CD pipelines with GitHub Actions, and best practices for team development. We will organize a system to operate the project safely and quickly.
The HubSpot CLI (command line interface) A bidirectional sync tool between your local file system and the HubSpot portal. Rather than operating the design manager on a browser, Fast development with editor and terminalis the biggest advantage.
60 minutesMinimum Node.js version increased to Node 20.
Node 18 will end of support for both HubSpot serverless functions and CLI on October 1, 2025.
If you are using nvm (Node Version Manager) nvm install 20 && nvm use 20 Please switch.
Also in v8 Old command (hs upload/hs watch etc.) are completely deleted.is,
hs cms upload/hs cms watch (see Section 9-2).
# Requires Node.js 20 or higher (required for v8.0.0 or later). Check version $ node --version v20.x.x ← Must be 20 or more (18.x is NG) # Globally install HubSpot CLI with npm $ npm install -g @hubspot/cli # Confirm installation (v8.0.0 or higher recommended) $ hs --version 8.x.x # or run each time with npx (no installation required) $ npx @hubspot/cli --version
# initialize in project root $ hs init# Setup proceeds interactively ? Enter a name for this account (e.g. "my-sandbox"): my-company-dev ? Enter your HubSpot portal ID: 12345678 ? How would you like to authenticate your account? ❯ Personal Access Key (recommended) OAuth2 # Select Personal Access Key and your browser will open. # You can issue a Personal Access Key from the HubSpot admin screen # Obtained from Developer Accounts → Account → Personal Access Keys ? Enter your personal access key: eu1-xxxx-xxxx-xxxx-xxxx ✔ Authenticated account: my-company-dev (12345678) ✔ Config file created: ~/.config/@hubspot/cli/config.yml
hs init When you run
hubspot.config.yml will be generated.
This file is the starting point for CLI configuration.
defaultPortal: my-company-dev portals: - name: my-company-dev portalId: 12345678 authType: personalaccesskey personalAccessKey: eu1-xxxx-xxxx-xxxx-xxxx auth: tokenInfo: accessToken: CJa... expiresAt: '2024-12-31T00:00:00.000Z'
hubspot.config.yml contains your Personal Access Key (access token).
Never commit to a Git repository.
Immediately after creating the project .gitignore and add it to
Each team member has their own hs init I'd like you to do this.
In the production CI/CD environment, we will design the token to be passed via environment variables (described later).
From October 2025, CMS related commands will be added to hs cms namespace Moved to.
CLI v8.0.0 uses the old command (hs upload、hs watch etc.) isComplete deletionIt has been.
CI/CD pipeline and package.json If any old commands remain in your scripts, you need to update them immediately.
Please check the compatibility between the old and new versions in the table below.
| Old command (removed in v8) | New command (current official command) | Purpose |
|---|---|---|
| hs upload | hs cms upload | Upload files/directories to HubSpot |
| hs watch | hs cms watch | Monitor changes and automatically upload (constantly used during development) |
| hs fetch | hs cms fetch | Download files locally from HubSpot |
| hs lint | hs cms lint | HubL syntax check |
| hs list | hs cms list | File list display on HubSpot |
| hs mv | hs cms mv | Moving/renaming files on HubSpot |
| hs remove | hs cms delete | Delete files and folders on HubSpot |
| hs create theme | hs cms app / npx create-cms-theme | Generation of theme template |
| hs create module | hs cms module | Custom module template generation |
| hs theme preview | hs cms theme preview | Launch theme preview |
| hs theme marketplace-validate | hs cms theme marketplace-validate | Validation for marketplaces |
| hs function ... | hs cms function ... | General operations for serverless functions |
| hs logs | hs cms function logs | Check logs of serverless functions |
# ===== Upload the entire theme ===== $ hs cms upload ./src/my-theme @my-theme --portal=my-company-dev # ===== Start watch mode during development ===== $ hs cms watch ./src/my-theme @my-theme --portal=my-company-dev Watcher started, watching "./src/my-theme" Uploaded "templates/page.html" to "@my-theme/templates/page.html" Uploaded "modules/hero-banner.module/module.html" # ===== Get the latest from HubSpot (before checking the difference) ===== $ hs cms fetch @my-theme ./src/my-theme-remote --portal=my-company-prod --overwrite # ===== Lint check before deployment ===== $ hs cms lint ./src/my-theme --portal=my-company-dev ✔ No lint errors found # ===== Upload only specific files ===== $ hs cms upload ./src/my-theme/modules/hero-banner.module @my-theme/modules/hero-banner.module # ===== Generate new module template ===== $ hs cms module pricing-table --path=./src/my-theme/modules ✔ Created "pricing-table.module" at ./src/my-theme/modules/pricing-table.module # ===== Check portal file list ===== $ hs cms list --portal=my-company-prod @my-theme/ ├── templates/ ├── modules/ ├── css/ ├── js/ └── theme.json
$ hs cms lint ./src/my-theme ✖ Error in "templates/blog-post.html" line 42: Unexpected tag: endmodule (did you mean end_module?) ⚠ Warning in "modules/card-grid.module/module.html" line 18: Variable "module.subtitle" used but not defined in fields.json Lint complete: 1 error, 1 warning # Rerun after fixing the error $ hs cms lint ./src/my-theme ✔ No lint errors found (1 warning)
Development in practice hs cms watch Use the editor with .
Every time you save a file, it's automatically uploaded to HubSpot.
You can check it immediately by reloading your browser.
my-project/ ├── src/ # ← Files to be synchronized using CLI │ └── my-theme/ # Theme body │ ├── theme.json │ ├── templates/ │ ├── modules/ │ ├── css/ │ ├── js/ │ └── images/ ├── .gitignore ├── .hsignore # define files that CLI ignores ├── hubspot.config.yml # Authentication settings (add to .gitignore) ├── package.json # Define development commands with npm scripts └── README.md # Setup instructions documentation
{
"name": "my-company-hubspot",
"version": "1.0.0",
"scripts": {
// ===== Development commands =====
"dev": "hs cms watch src/my-theme @my-theme --portal=dev",
// Start watch mode with npm run dev (sync to dev portal)
// ===== Deploy command =====
"deploy:dev" : "hs cms upload src/my-theme @my-theme --portal=dev --overwrite",
"deploy:prod" : "hs cms upload src/my-theme @my-theme --portal=prod --overwrite",
// ===== Check command =====
"lint" : "hs cms lint src/my-theme --portal=dev",
"fetch:prod" : "hs cms fetch @my-theme src/my-theme-remote --portal=prod --overwrite",
// ===== Compound command =====
"predeploy:prod": "npm run lint"
// Automatically run lint before deploy:prod (aborts deployment if there is an error)
},
"devDependencies": {
"@hubspot/cli": "^8.0.0"
// Specify CLI v8 or higher (Node.js 20 required)
}
}
# === At the start of daily development === $ npm run devWatcher started, watching "src/my-theme" # Edit the file in the editor → Save → Automatically upload → Confirm reload in the browser # === Stop watch with Ctrl+C when finished === # === Checks before production deployment === $ npm run lint✔ No lint errors found # === Production deployment (executed only if lint passes) === $ npm run deploy:prodUploading "src/my-theme" to "@my-theme" on portal "prod"... ✔ Done uploading 48 files
In the actual caseDevelopment portal (Sandbox/Developer Account)and User privilege managementseparate and manage. Watching the production environment directly is strictly prohibited as it may cause accidents.
# Default portal (used when --portal is omitted in hs cms upload) defaultPortal: dev portals: # ===== Development portal (for daily development) ===== - name: dev portalId: 11111111 authType: personalaccesskey personalAccessKey: eu1-dev-key-xxxxxxxxxx # ===== Production portal (only when deploying) ===== - name: prod portalId: 22222222 authType: personalaccesskey personalAccessKey: eu1-prod-key-xxxxxxxxxx # ===== For client verification (optional) ===== - name: staging portalId: 33333333 authType: personalaccesskey personalAccessKey: eu1-staging-key-xxxxxxxxxx
| portal | Usage scene | Using watch | Edit management screen |
|---|---|---|---|
| Development portal (dev) | Daily development and operation confirmation | ✅ Recommended | freely possible |
| staging | As a general rule, approval should be obtained in writing (email/Slack) rather than orally. Just saying "No problem" is OK. | ⚠️ Be careful | limitedly |
| Production portal (prod) | Final deployment only | 🚫 Forbidden | Prohibited in principle |
hs cms watch When I launch it towards the production portal,
Unfinished code under development is immediately reflected in production.
Be sure to upload to production hs cms upload Run it manually with the command,
Please consciously check the contents of your deployment before proceeding.
package.json dev Make it a habit to regularly check that your scripts are always pointing to the development portal.
.hsignore The file is .gitignore In the same format as
hs cms upload or hs cms watch Specify the files and directories that you do not want to synchronize.
*.md of .hsignore Command namespace migrated package.json or node_modules/ etc.
hubspot.config.yml In addition to authentication information,
There are options to further customize CLI behavior.
defaultPortal: dev # ===== CLI global settings ===== httpTimeout: 30000 # API request timeout (ms) # Increase this value when uploading large themes allowUsageTracking: false Move under cms subcommand portals: - name: dev portalId: 11111111 authType: personalaccesskey personalAccessKey: eu1-dev-key # ===== Portal individual settings ===== defaultMode: write # write (upload priority) / read (download priority) - name: prod portalId: 22222222 authType: personalaccesskey personalAccessKey: eu1-prod-key defaultMode: write
By linking Git repositories and GitHub Actions, "Automatically deploy to production once merged into main branch" You can build a CI/CD pipeline called
Register your Personal Access Key to Secrets in your GitHub repository. Configure from the GitHub repository page → Settings → Secrets and variables → Actions.
| Secret name | value |
|---|---|
| HUBSPOT_PORTAL_ID_DEV | Portal ID of the development portal |
| HUBSPOT_ACCESS_KEY_DEV | Development Portal Personal Access Key |
| HUBSPOT_PORTAL_ID_PROD | Portal ID of the production portal |
| HUBSPOT_ACCESS_KEY_PROD | Personal Access Key for the production portal |
name: Deploy to Dev Portalon: push: branches: - develop jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' - name: Install HubSpot CLI run: npm install -g @hubspot/cli - name: Create HubSpot config # Dynamically generate hubspot.config.yml from Secrets run: | cat > hubspot.config.yml << EOF defaultPortal: dev portals: - name: dev portalId: ${{ secrets.HUBSPOT_PORTAL_ID_DEV }} authType: personalaccesskey personalAccessKey: ${{ secrets.HUBSPOT_ACCESS_KEY_DEV }} EOF - name: Lint check run: hs cms lint src/my-theme --portal=dev - name: Deploy to Dev run: hs cms upload src/my-theme @my-theme --portal=dev --overwrite - name: Notify Slack on success if: success() uses: slackapi/slack-github-action@v1 with: payload: | { "text": "✅ 開発環境へのデプロイ完了: ${{ github.event.head_commit.message }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
name: Deploy to Productionon: push: branches: - main jobs: lint: name: Lint Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm install -g @hubspot/cli - name: Create config run: | cat > hubspot.config.yml << EOF defaultPortal: dev portals: - name: dev portalId: ${{ secrets.HUBSPOT_PORTAL_ID_DEV }} authType: personalaccesskey personalAccessKey: ${{ secrets.HUBSPOT_ACCESS_KEY_DEV }} EOF - run: hs cms lint src/my-theme --portal=dev deploy: name: Deploy to Production needs: lint # Execute only if lint job is successful runs-on: ubuntu-latest environment: production New creation of the old API key (hapikey parameter) will be discontinued after November 2022. Whenever you update an existing integration, steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: '20' } - run: npm install -g @hubspot/cli - name: Create production config run: | cat > hubspot.config.yml << EOF defaultPortal: prod portals: - name: prod portalId: ${{ secrets.HUBSPOT_PORTAL_ID_PROD }} authType: personalaccesskey personalAccessKey: ${{ secrets.HUBSPOT_ACCESS_KEY_PROD }} EOF - name: Deploy to Production run: hs cms upload src/my-theme @my-theme --portal=prod --overwrite - name: Notify on success if: success() uses: slackapi/slack-github-action@v1 with: payload: | { "text": "🚀 本番デプロイ完了!\ncommit: ${{ github.event.head_commit.message }}\nby: ${{ github.actor }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Notify on failure if: failure() uses: slackapi/slack-github-action@v1 with: payload: | { "text": "❌ 本番デプロイ失敗!確認が必要です。\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
On GitHub, go to Settings → Environments production Create an environment,
Setting Required reviewers
Approval from specified members is required after merging to main and before deployingIt will be.
Workflow environment: production is linked to this.
It is very effective as a safety device to prevent unintended production deployment.
February 19, 2026HubSpot Developer MCP Server is officially GA (publicly available)It was done. MCP (Model Context Protocol) is a standard for AI tools to collaborate with external services. By installing HubSpot's Developer MCP Server locally, AI editors such as VS Code, Claude Code, and Cursor Generate, deploy, and debug CMS assets using natural language while referring to HubSpot's developer documentation.It will look like this.
A server that installs through the CLI and runs locally. Connect to HubSpot's developer platform to scaffold projects, manage CMS assets, and debug serverless functions in natural language.Requires CLI v8.0.0 or higher and Platform v2025.2.
A remote server connecting to mcp.hubspot.com. Provide real-time access to HubSpot CRM data to answer queries like "Get this contact's latest activity." Uses OAuth 2.0 authentication.
# Prerequisite: CLI v8.0.0 or higher has been installed $ hs --version 8.x.x # Set up MCP Server (interactive) $ hs mcp setup# ↓ Select MCP client to connect interactively ? Which clients would you like to add the MCP server to? ❯ ◉ VS Code ◉ Claude Code ◯ Cursor ◯ OpenAI Codex (Use arrow keys and spacebar to select, Enter to proceed) ✔ HubSpotDev MCP server has been added to: VS Code, Claude Code Restart your client to apply the changes.
| task | Example prompt to AI |
|---|---|
| module generation | "Create a React module called pricing-table. It has a grid layout that displays 3 columns of pricing plans, and a field design that can display the title, price, and feature list in each column." |
| template generation | "Create a new page template called Product Landing. It consists of 3 blocks: hero section, feature grid, and CTA section, and all of them are compatible with dnd_area." |
| error debug | "An 'Unable to initialize extension' error occurs during local development in NewCard.tsx. Please check the file and suggest a fix." |
| Check how to use CLI | How to migrate: |
| Deployment | "Once you have confirmed your changes, please upload them to the dev portal" |
| serverless functions | "Create a serverless function with a POST /api/submit-form endpoint and include the logic to register the form data with HubSpot CRM." |
The most effective usage is "initial scaffolding of module templates".
Field design template generation, serverless function boilerplate generation, etc.
Delegating repetitive tasks to AI greatly speeds up development.
In addition, the HubSpot documentation says "The most stable results are obtained using Claude Sonnet 4 or higher." It is stated.
The combination of MCP server and Claude Code is currently the recommended configuration.
Points to note:MCP Server requires Developer Platform v2025.2.
old project is hs project migrate # Help list
HubSpot design managerVersion history featureThere is, You can revert to previous versions from the change history of each file. However, bulk upload via CLI overwrites all files, so Version control using “Git tag + hs cms upload” is a safe rollback methodIt will be.
# === Always use Git tag during production deployment === $ git tag v1.2.0 -m "Hero Banner Renewal" $ git push origin v1.2.0 # === Rollback if problems are discovered after deployment === # ① Check out the tag of the version you want to rollback $ git checkout v1.1.0 HEAD is now at abc1234 v1.1.0 # ② Upload old version file to production $ hs upload src/my-theme @my-theme --portal=prod --overwrite ✔ Rollback complete: v1.1.0 deployed to production # ③ Return to working branch $ git checkout main # ④ Correct the problematic changes and redeploy
# my-company-hubspot ## Prerequisites - Node.js 20 or higher (CLI v8.0.0 or later required) - @hubspot/cli global installation - Access rights to the development portal (portal ID is shared separately) ## Setup steps ### 1. Clone the repository git clone https://github.com/mycompany/hubspot-theme.git cd hubspot-theme ### 2. HubSpot CLI authentication settings hs init # Development portal ID: 12345678 (separately shared on Slack) # Personal Access Key: Issued individually from the HubSpot admin screen # https://app.hubspot.com/personal-access-key/12345678 ◎ Can be set in detail # ✅ CMS - Design Manager # ✅ Content ### 4. Start development npm run dev # Open the development portal in your browser and check your edits # https://app.hubspot.com/website-pages/12345678 ## Branch strategy # feature/xxx → develop (automatically deploy to DEV portal) # develop → main (automatically deploy to production portal)
| scope | What you need to do |
|---|---|
| CMS - Design Manager | Upload/download templates/modules/themes (required) |
| Content | Viewing/editing pages/blog articles (required for template confirmation) |
| HubDB | HubDB table operations (when using HubDB) |
| Forms | Form confirmation/test (required for form development) |
When multiple developers and marketers are working at the same time,
Editing directly from the admin screen and uploading from the CLI may conflict.
What to do:
① Always deploy from CLI --overwrite Run with flag.
② Create operational rules that prohibit direct editing on the management screen and require changes to be made through local code.
③ If important changes have been made on the management screen,hs cms fetch , import it locally, update the code, and then upload it.
④ Say "I'm going to deploy now" in chat (Slack, etc.) to prevent simultaneous deployments.
.src, the link field is .url Are you checking for existence?
get_asset_url() Are you passing it through?
loading="eager", and others loading="lazy" Is it attached?
{{ standard_footer_includes }} Is it included?
Personal Access Keys do not have an expiration date, but For security reasons, we recommend regular rotation every 6 months to 1 year. If a team member leaves the company, please deactivate it immediately. Don't forget to update your GitHub Secrets.
CLI v8.0.0 is effective from February 9, 2026. Node.js 20 or higher is required.hs uploadOld commands such as etc. have been completely deleted. all hs cms upload / hs cms watch Shift to a new system such as
February 2026 GA's HubSpot Developer MCP Server allows you to generate, deploy, and debug modules using natural language from VS Code and Claude Code. Requires CLI v8 + Platform v2025.2.
Basically, the two portals for development and production are separated.hs cms watch is for development portal only. Uploading to production hs cms upload Execute it explicitly with GitHub Actions and automate it with GitHub Actions.
Be sure to include your Personal Access Key. .gitignore Added to. In CI/CD, pass the token via environment variable and dynamically generate yml.
develop push → Automatically deploy to the development portal, main push → Automatically deploy to the production portal. before deployment hs cms lint Be sure to pass. GitHub Deploy Action uses v1.7 or higher.
Direct editing on the management screen is prohibited, and changes must be made through local code. Before deployment, let's talk on Slack etc. If a conflict occurs hs cms fetch Get the remote with and then resolve.