From what HubSpot CMS is, how it differs from other CMSs, and how to choose a plan, to acquiring the development portal, setting up the CLI, designing the theme directory, and defining design tokens using theme.json. This chapter provides everything you need to start developing.
HubSpot CMS (Content Management System) A cloud-based website construction and management platform provided by HubSpot. The biggest difference from other CMS is thatExists on the same platform as CRM (customer management), marketing, and sales toolsThat's it. A contact record is created the moment someone visits your site and submits a form. The workflow moves and notifications are sent to the sales person in charge—this all-in-one system is its greatest strength.
① HubL is the only template language:Front-end frameworks such as React, Vue, and Next.js cannot be used directly. HubL + raw HTML/CSS/JS is the basics.
② Server-side custom processing not possible:Backend code such as PHP/Node.js cannot be run. Dynamic processing will be replaced by HubL, HubDB, and JavaScript API.
③ Direct access to the file system is not possible:Files can only be manipulated through the CLI or admin interface.
The features available with HubSpot CMS vary greatly depending on the usage plan. Check the client's plan before development,What can and cannot be usedUnderstanding this is the starting point for design.
| function | Starter | Professional | Enterprise |
|---|---|---|---|
| Custom theme/module development | ✅ | ✅ | ✅ |
| Blog function (articles/tags) | ✅ 1 blog | ✅ Multiple | ✅ Multiple |
| HubDB (Cloud DB) | ❌ | ✅ | ✅ |
| Smart content (personalized) | ❌ | ✅ | ✅ |
| Dynamic Pages (HubDB linked pages) | ❌ | ✅ | ✅ |
| A/B test | ❌ | ✅ | ✅ |
| Multilingual content | ❌ | ✅ | ✅ |
| Custom Object (CRM) | ❌ | ❌ | ✅ |
| sandbox environment | ❌ | ⚠️ Standard | ✅ For developers |
| content partitioning | ❌ | ❌ | ✅ |
| design elements | Alternatives in Starter | Professional or above |
|---|---|---|
| Content type management | Manage types with 1 blog + tag prefix (type:blog / type:seminar, etc.) | Set up independent blogs for each type |
| List of products/case studies | Manage with alternative/static HTML pages using blog tag function | Dynamic management with HubDB tables |
| Change content with user attributes | Basically not possible (same content for all visitors) | Display by life cycle and industry with smart content |
Even if there is a requirement that “I want to create a staff introduction page using HubDB”, HubDB cannot be used if the client is on the Starter plan. Be sure to check the plan immediately after receiving the order and before starting the design. If there are features that cannot be used, we will suggest alternatives or consider upgrading your plan.Please. You can check your plan from Accounts & Subscriptions in your HubSpot admin screen.
At HubSpotFree Developer Accountcan be created. The Developer Account is a sandbox environment where you can use almost all the functions equivalent to production. This is an essential environment for development and verification before touching the client's portal.
app.hubspot.com/signup-hubspot/developers Create an account from.
Register as a "developer account" separate from your regular HubSpot account.
If you have an existing HubSpot account, you may not be able to create one using the same email address.
We recommend that you prepare an email address for development purposes.
| item | Content |
|---|---|
| cost | Free (no fee for the Developer Account itself) |
| Main functions that can be used | Theme/module development/HubDB/form/blog/page creation |
| limit | Email sending and some marketing functions are limited. Actual operation as a public site is not possible. |
| Number of portals | Can create up to 10 test portals |
| data retention | Access it regularly as it may be deleted if there is no activity for 90 days |
A Personal Access Key is required to access the portal via the CLI. After creating a Developer Account, issue it using the following steps.
A Personal Access Key has full access to the portal. Pasting to Slack, email, or GitHub is strictly prohibited. Manage with password managers such as 1Password and Bitwarden. If you want to share it with your team, please use the SecretSharing tool (e.g. Keybase). If it is leaked, we will immediately invalidate it and reissue it.
HubSpot's admin screen is multi-functional, so it's easy to get lost at first. We will organize the screens that you should at least understand as a developer.
| screen name | menu location | When used by developers |
|---|---|---|
| design manager | Marketing → Files and Templates → Design Manager | Check templates, modules, CSS, edit and preview on browser |
| file manager | Marketing → Files and Templates → File Manager | Upload image/PDF/confirm file URL |
| website page | Marketing → Website → Website Page | Check the operation of the created template and page settings (canonical, OGP, etc.) |
| Blog | Marketing → Website → Blog | Blog settings, linking with templates, tag management |
| HubDB | Marketing → Files and Templates → HubDB | Creating tables, designing schemas, and testing data |
| form | Marketing → Form | Confirm form ID, set hidden field, set post-send action |
| Settings → Domains and URLs | ⚙️ Settings → Website → Domains and URLs | Check domain connection/www settings/page URL |
| Settings → Tracking code | ⚙️ Settings → Tracking and Analytics | GA4/GTM ID settings/HubSpot tracking confirmation |
| tool | Version requirements | Purpose |
|---|---|---|
| Node.js | 20.x or higher (required) | Node 20 is a minimum requirement for HubSpot CLI v8.0.0 and later. Management with nvm is recommended. Support for Node 18 ends in October 2025. |
| @hubspot/cli | v8.0.0 or higher | v8 will be effective from February 2026. The command system hs cms upload Changed to etc. The old v5 series is not recommended for new projects. |
| VS Code | any | Recommended editor. Includes HubL extensions (described later). |
| Git | 2.x or higher | Version control. Required for CI/CD with GitHub Actions. |
# ===== Step 1: Check Node.js version (requires 20 or higher) ===== $ node --version v20.11.0 ← Confirm that it is 20 or higher (18.x is NG) # Version control using nvm (recommended) $ nvm install 20$ nvm use 20# ===== Step 2: HubSpot CLI installation ===== $ npm install -g @hubspot/cli $ hs --version 5.x.x ← Installation confirmation # ===== Step 3: Create project folder ===== $ mkdir my-company-hubspot $ cd my-company-hubspot $ git init$ npm init -y # ===== Step 4: HubSpot CLI authentication ===== $ hs init? Enter a name for this account: my-company-dev ? Enter your HubSpot portal ID: 12345678 ? How would you like to authenticate? ❯ Personal Access Key (recommended) ? Enter your personal access key: eu1-xxxx-xxxx-xxxx ✔ Authenticated! Config saved to hubspot.config.yml # ===== Step 5: Create .gitignore ===== $ echo "hubspot.config.yml" >> .gitignore$ echo "node_modules/" >> .gitignore$ echo ".DS_Store" >> .gitignore# ===== Step 6: Generate theme template ===== $ mkdir src$ hs create theme my-theme --path=./src ✔ Theme created at ./src/my-theme
{
"recommendations": [
"HubSpot.hubl", // HubL syntax highlighting/completion
"esbenp.prettier-vscode", // code formatter
"dbaeumer.vscode-eslint", // JavaScript lint
"bradlc.vscode-tailwindcss", // When using Tailwind
"streetsidesoftware.code-spell-checker" // spell check
]
}
{
// Recognize .html files as HubL
"files.associations": {
"*.html": "hubl"
},
// Format on save
"editor.formatOnSave": true,
// Indentation is 2 spaces
"editor.tabSize": 2,
"editor.insertSpaces": true
}
From codebase analysis of an actual project (a large-scale project with 741 modules and 964 templates), We have derived the optimal directory structure that works in practice. The following is the recommended standard configuration.
From an analysis of a large project with 741 modules and 964 templates,
Designed for easy management even as the number of modules increasesI found the following to be important for this purpose.
① The module is .module directoryManage by unit (5 files 1 set)
② CSS is managed in a layered structure similar to ITCSS, and module-specific styles are separated into module.css.
③ Templates are clearly separated by page type (general purpose/LP/blog/error page)
theme.json is the HubSpot theme's configuration file.
The values defined here can be changed with no code on the HubSpot admin screen.
It can be referenced as a CSS variable in all templates and all modules.
“The starting point for unified management of design”This is the most important file.
{
"label": "My Company Theme",
"preview_path": "./templates/page.html",
"screenshot_path": "./images/screenshot.png",
"enable_domain_stylesheets": false,
"hide_all_default_modules": true,
// ↑ Do not display HubSpot default module in page editor
// Set to true if you want to use only custom modules
"fields": [
// ===== Color =====
{
"type": "group",
"name": "colors",
"label": "Color settings",
"children": [
{
"type": "color",
"name": "primary_color",
"label": "Primary color",
"default": { "color": "#0052CC", "opacity": 100 }
},
{
"type": "color",
"name": "secondary_color",
"label": "Secondary Color",
"default": { "color": "#FF5630", "opacity": 100 }
},
{
"type": "color",
"name": "text_color",
"label": "Text Color",
"default": { "color": "#2d3748", "opacity": 100 }
},
{
"type": "color",
"name": "bg_color",
"label": "Page background color",
"default": { "color": "#ffffff", "opacity": 100 }
}
]
},
// ===== Typography =====
{
"type": "group",
"name": "typography",
"label": "typography",
"children": [
{
"type": "font",
"name": "heading_font",
"label": "Heading font",
"default": {
"font": "Noto Sans JP",
"font_set": "GOOGLE",
"variant": "700",
"size": "36px",
"color": "#1a202c"
}
},
{
"type": "font",
"name": "body_font",
"label": "Body font",
"default": {
"font": "Noto Sans JP",
"font_set": "GOOGLE",
"variant": "400",
"size": "16px",
"color": "#2d3748"
}
}
]
},
// ===== Spacing Layout =====
{
"type": "group",
"name": "layout",
"label": "Layout settings",
"children": [
{
"type": "number",
"name": "container_max_width",
"label": "Container maximum width (px)",
"default": 1200
},
{
"type": "number",
"name": "section_padding_v",
"label": "Section top and bottom margins (px)",
"default": 80
},
{
"type": "number",
"name": "border_radius",
"label": "Rounded corners (px)",
"default": 8
}
]
}
]
}
theme.json The values defined in are expanded as CSS variables using HubL.
All templates <head> Define it within or at the beginning of your global CSS.
/* Expand theme.json values as CSS variables Access with HubL's theme variable */ :root { /* ===== Color ===== */ --color-primary : {{ theme.colors.primary_color.color }}; --color-secondary : {{ theme.colors.secondary_color.color }}; --color-text : {{ theme.colors.text_color.color }}; --color-bg : {{ theme.colors.bg_color.color }}; /* ===== Typography ===== */ --font-heading : {{ theme.typography.heading_font.font }}, sans-serif; --font-body : {{ theme.typography.body_font.font }}, sans-serif; --font-size-base : {{ theme.typography.body_font.size }}; /* ===== Layout ===== */ --container-max : {{ theme.layout.container_max_width }}px; --section-padding : {{ theme.layout.section_padding_v }}px; --border-radius : {{ theme.layout.border_radius }}px; /* ===== Breakpoint (fixed value/CSS variable cannot be used in a media query, so for reference only) ===== */ --bp-sm : 480px; --bp-md : 768px; --bp-lg : 1024px; --bp-xl : 1280px; }
In code base analysis of actual projects, global CSS is close to ITCSS (Inverted Triangle CSS). It was confirmed that it was designed with a layered structure. ITCSS isDefine CSS in the order of "abstract → concrete" and "wide range → local"With design philosophy, Prevents style override issues and level of detail conflicts.
/* ===== Objects: Layout pattern ===== It does not have a design (color/font). Define only the structure. ===================================================== */ /* container */ .container { width: 100%; max-width: var(--container-max); margin-inline: auto; padding-inline: 20px; } @media (min-width: 768px) { .container { padding-inline: 32px; } } @media (min-width: 1280px) { .container { padding-inline: 40px; } }/* section */ .section { padding-block: var(--section-padding); } .section--sm { padding-block: calc(var(--section-padding) * 0.5); } .section--lg { padding-block: calc(var(--section-padding) * 1.5); }/* Grid */ .grid { display: grid; gap: var(--gap, 24px); } .grid-2 { grid-template-columns: repeat(2, 1fr); } .grid-3 { grid-template-columns: repeat(3, 1fr); } .grid-4 { grid-template-columns: repeat(4, 1fr); } @media (max-width: 768px) { .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; } } @media (min-width: 480px) and (max-width: 1024px) { .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); } }
Once you're set up, upload your theme to HubSpot and see it working. Follow the steps below to complete the process from initial upload to confirmation.
# ===== First time: Upload all files ===== $ hs upload src/my-theme @my-theme --portal=dev Uploading "src/my-theme" to "@my-theme" on portal "dev"... ✔ Done! Uploaded 12 files. # ===== Under development: Automatic synchronization in watch mode ===== $ hs watch src/my-theme @my-theme --portal=dev Watcher started, watching "src/my-theme" # Automatically uploads every time you save a file # ===== Confirmation after upload ===== # HubSpot admin → Marketing → Design Manager # Success if @my-theme is displayed in the left pane # ===== Apply template to page and check ===== # Website page → Create new page # → Select "page.html" as template # → Check the display in preview
{
"name": "my-company-hubspot",
"scripts": {
"dev" : "hs cms watch src/my-theme @my-theme --portal=dev",
"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",
"lint" : "hs lint src/my-theme --portal=dev",
"predeploy:prod": "npm run lint"
},
"devDependencies": {
"@hubspot/cli": "^8.0.0" // CLI v8 or higher (Node 20 required)
}
}
SaaS type CMS integrated with CRM/MA. No infrastructure management required, global CDN installed as standard. Design after understanding the constraints (HubL only/backend processing not possible).
HubDB, smart content, and multiple blogs are for Professional or higher. We will check the plan immediately after receiving your order, and if there are any features that cannot be used, we will suggest alternatives in advance.
Developer Account (free) / Personal Access Key (scope setting required) / hubspot.config.yml (must be added to .gitignore). These three things will prepare you for development.
Separation of templates / layouts / partials / modules / css / js is basic. Modules are managed in .module directory units (1 set of 5 files).
Define color, font, and spacing in theme.json → Expand as CSS variables → Reference in all files. Hard codes are prohibited. Changes must be made through theme.json.
Define in the order of Settings → Generic → Elements → Objects → Components → Utilities. Module-specific styles are separated into module.css. Preserving layers prevents level-of-detail collisions.