vibespot 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +33 -0
- package/README.md +118 -0
- package/assets/content-guide.md +445 -0
- package/assets/conversion-guide.md +693 -0
- package/assets/design-guide.md +380 -0
- package/assets/hubspot-rules.md +560 -0
- package/assets/page-types.md +116 -0
- package/bin/vibespot.mjs +11 -0
- package/dist/index.js +6552 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/ui/chat.js +803 -0
- package/ui/dashboard.js +383 -0
- package/ui/dialog.js +117 -0
- package/ui/field-editor.js +292 -0
- package/ui/index.html +393 -0
- package/ui/preview.js +132 -0
- package/ui/settings.js +927 -0
- package/ui/setup.js +830 -0
- package/ui/styles.css +2552 -0
- package/ui/upload-panel.js +554 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# HUBSPOT_RULES.md — HubSpot CMS Design Manager Gotchas & Validation Rules
|
|
2
|
+
|
|
3
|
+
> These rules prevent the most common errors when generating HubSpot CMS templates, modules, and theme files. Every rule here comes from real validation errors, community forum pain, or undocumented behavior that breaks silently.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Module Field Naming Rules
|
|
8
|
+
|
|
9
|
+
### Reserved Field Names (will collide with dnd_module parameters)
|
|
10
|
+
These names are used by drag-and-drop tags as built-in parameters. If you name a module field any of these, it will conflict when used inside `dnd_module` tags. The Design Manager will block new fields with these names, but the error is cryptic.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
NEVER use these as field names:
|
|
14
|
+
- width
|
|
15
|
+
- offset
|
|
16
|
+
- label
|
|
17
|
+
- path
|
|
18
|
+
- module_id
|
|
19
|
+
- css_class
|
|
20
|
+
- css_id
|
|
21
|
+
- styles
|
|
22
|
+
- style
|
|
23
|
+
- flexbox_positioning
|
|
24
|
+
- definition_id
|
|
25
|
+
- smart_type
|
|
26
|
+
- smart_objects
|
|
27
|
+
- wrap
|
|
28
|
+
- extra_classes
|
|
29
|
+
|
|
30
|
+
If you inherit a legacy module that already uses one of these names,
|
|
31
|
+
use the `fields` parameter to pass values instead:
|
|
32
|
+
|
|
33
|
+
✅ {% dnd_module path="@hubspot/divider", fields={width: "90"} %}
|
|
34
|
+
❌ {% dnd_module path="@hubspot/divider", width="90" %}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Field Name Format Rules
|
|
38
|
+
```
|
|
39
|
+
- Use snake_case for field names: "hero_heading" not "heroHeading" or "Hero Heading"
|
|
40
|
+
- Field names must be unique within a module (including across groups)
|
|
41
|
+
- Field names cannot contain spaces, dashes, or special characters
|
|
42
|
+
- Field names are case-sensitive
|
|
43
|
+
- The "name" in fields.json is the HubL variable name (module.field_name)
|
|
44
|
+
- The "label" in fields.json is the human-readable name shown in the editor
|
|
45
|
+
- "name" and "label" should NEVER be identical short strings like "title" or "text"
|
|
46
|
+
→ Use descriptive names: "hero_title", "section_heading", "cta_button_text"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Field Label Rules (for Marketplace / quality)
|
|
50
|
+
```
|
|
51
|
+
- Labels must be descriptive: "Job Title" not just "Title"
|
|
52
|
+
- Labels must NOT contain numbers: "Hero Banner" not "Hero Banner 01"
|
|
53
|
+
- Labels must NOT contain underscores: "Hero Banner" not "Hero_Banner"
|
|
54
|
+
- Labels must NOT contain abbreviations: "Column" not "Col"
|
|
55
|
+
- Default field values must NOT contain Lorem Ipsum text
|
|
56
|
+
- Module labels must not duplicate default HubSpot module names
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 2. Module File Structure
|
|
62
|
+
|
|
63
|
+
### Local Development (.module folder)
|
|
64
|
+
```
|
|
65
|
+
my-module.module/
|
|
66
|
+
├── fields.json ← Field definitions (array of objects)
|
|
67
|
+
├── meta.json ← Module configuration (label, icon, categories)
|
|
68
|
+
├── module.html ← HubL + HTML template
|
|
69
|
+
├── module.css ← Scoped CSS (loaded once per page)
|
|
70
|
+
└── module.js ← JavaScript (loaded once per page, NOT per instance)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Critical: Module Folder Naming
|
|
74
|
+
```
|
|
75
|
+
✅ my-module.module/ ← MUST end with .module
|
|
76
|
+
❌ my-module-module/ ← WRONG — causes "text fields not supported in theme" errors
|
|
77
|
+
❌ my-module/ ← WRONG — not recognized as a module
|
|
78
|
+
|
|
79
|
+
This is a VERY common mistake. The folder MUST have the .module suffix.
|
|
80
|
+
The CLI error message when this is wrong is misleading — it says things like
|
|
81
|
+
"text fields are not supported in theme fields.json" because HubSpot thinks
|
|
82
|
+
your module's fields.json IS the theme's fields.json.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### meta.json Required Structure
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"label": "My Custom Module",
|
|
89
|
+
"css_assets": [],
|
|
90
|
+
"external_js": [],
|
|
91
|
+
"global": false,
|
|
92
|
+
"help_text": "",
|
|
93
|
+
"host_template_types": ["PAGE", "BLOG_POST", "BLOG_LISTING"],
|
|
94
|
+
"is_available_for_new_content": true,
|
|
95
|
+
"js_assets": [],
|
|
96
|
+
"other_assets": [],
|
|
97
|
+
"smart_type": "NOT_SMART",
|
|
98
|
+
"categories": [],
|
|
99
|
+
"content_tags": []
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 3. fields.json Rules
|
|
106
|
+
|
|
107
|
+
### Structure
|
|
108
|
+
```
|
|
109
|
+
- fields.json is a JSON ARRAY of objects: [ { ... }, { ... } ]
|
|
110
|
+
- NOT an object: { fields: [ ... ] } ← WRONG
|
|
111
|
+
- Every field needs at minimum: "name", "label", "type"
|
|
112
|
+
- "id" is optional in local dev (auto-generated on upload)
|
|
113
|
+
- "default" should always be provided — never leave fields without defaults
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Field Types Available in Modules vs Themes
|
|
117
|
+
```
|
|
118
|
+
MODULES can use ALL field types including:
|
|
119
|
+
text, richtext, image, link, url, number, boolean, choice, color,
|
|
120
|
+
font, alignment, spacing, backgroundimage, border, gradient, icon,
|
|
121
|
+
cta, form, blog, hubdbtable, hubdbrow, crmobject, menu, date,
|
|
122
|
+
datetime, payment, video, embed, audioplayer
|
|
123
|
+
|
|
124
|
+
THEMES can only use a LIMITED subset:
|
|
125
|
+
color, font, image, boolean, choice, number, spacing, border,
|
|
126
|
+
backgroundimage, gradient, alignment, group
|
|
127
|
+
|
|
128
|
+
❌ "text" fields are NOT supported in theme fields.json
|
|
129
|
+
❌ "richtext" fields are NOT supported in theme fields.json
|
|
130
|
+
❌ "url" / "link" fields are NOT supported in theme fields.json
|
|
131
|
+
|
|
132
|
+
If you see "text fields are not supported in theme fields.json"
|
|
133
|
+
you either:
|
|
134
|
+
1. Put a text field in your THEME'S fields.json (not allowed), OR
|
|
135
|
+
2. Your module folder is missing the .module suffix (see above)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Color Field Format (CRITICAL)
|
|
139
|
+
```
|
|
140
|
+
Color fields MUST use this exact default format:
|
|
141
|
+
{ "color": "#rrggbb", "opacity": 100 }
|
|
142
|
+
|
|
143
|
+
RULES:
|
|
144
|
+
- "color" MUST be a 6-digit hex string starting with "#" (e.g. "#ffffff")
|
|
145
|
+
- "opacity" MUST be an integer from 0 to 100 (NOT a float like 0.7)
|
|
146
|
+
- NEVER use rgba(), rgb(), hsl(), or named colors (e.g. "red", "white")
|
|
147
|
+
|
|
148
|
+
✅ { "color": "#ffffff", "opacity": 70 }
|
|
149
|
+
✅ { "color": "#1a2e0d", "opacity": 100 }
|
|
150
|
+
❌ { "color": "rgba(255,255,255,0.7)", "opacity": 100 } ← INVALID
|
|
151
|
+
❌ { "color": "white", "opacity": 100 } ← INVALID
|
|
152
|
+
❌ { "color": "#fff", "opacity": 100 } ← INVALID (3-digit)
|
|
153
|
+
❌ { "color": "#ffffff", "opacity": 0.7 } ← INVALID (float)
|
|
154
|
+
|
|
155
|
+
HubSpot error: "The format for the color value is invalid"
|
|
156
|
+
This error means you used a non-hex color format in a color field default.
|
|
157
|
+
Convert rgba to hex + opacity:
|
|
158
|
+
rgba(255,255,255,0.85) → { "color": "#ffffff", "opacity": 85 }
|
|
159
|
+
rgba(0,0,0,0.5) → { "color": "#000000", "opacity": 50 }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Style Fields (tab: "STYLE")
|
|
163
|
+
```
|
|
164
|
+
- Style fields MUST be wrapped in a group with "tab": "STYLE"
|
|
165
|
+
- The group must be named "styles"
|
|
166
|
+
- Style fields render in the Style tab of the page editor
|
|
167
|
+
- Only certain field types can be style fields:
|
|
168
|
+
color, font, alignment, spacing, backgroundimage, border, gradient,
|
|
169
|
+
number, boolean, choice
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
[
|
|
173
|
+
{
|
|
174
|
+
"type": "group",
|
|
175
|
+
"name": "styles",
|
|
176
|
+
"tab": "STYLE",
|
|
177
|
+
"children": [
|
|
178
|
+
{
|
|
179
|
+
"name": "background_color",
|
|
180
|
+
"label": "Background Color",
|
|
181
|
+
"type": "color",
|
|
182
|
+
"default": { "color": "#ffffff", "opacity": 100 }
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Field Groups
|
|
190
|
+
```
|
|
191
|
+
- Groups are fields with "type": "group" and a "children" array
|
|
192
|
+
- Groups CAN be nested (groups inside groups)
|
|
193
|
+
- When a module has at least one control in a group, ALL controls
|
|
194
|
+
should be organized into labeled groups (Marketplace requirement)
|
|
195
|
+
- Repeater fields need "occurrence" with "min" and optionally "max" / "default"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Visibility / Display Conditions
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"name": "headline_text",
|
|
202
|
+
"label": "Headline",
|
|
203
|
+
"type": "text",
|
|
204
|
+
"default": "Welcome",
|
|
205
|
+
"visibility": {
|
|
206
|
+
"controlling_field": "show_headline",
|
|
207
|
+
"controlling_value_regex": "true",
|
|
208
|
+
"operator": "EQUAL"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
- "controlling_field" references another field's "name" (not label)
|
|
213
|
+
- For nested fields, use "controlling_field_path": "group_name.field_name"
|
|
214
|
+
- Operators: "EQUAL", "NOT_EQUAL", "EMPTY", "NOT_EMPTY", "MATCHES_REGEX"
|
|
215
|
+
- controlling_value_regex for booleans: use "true" or "false" (as strings)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 4. HubL Template Rules
|
|
221
|
+
|
|
222
|
+
### Template Annotations (required at top of .html template files)
|
|
223
|
+
```html
|
|
224
|
+
<!--
|
|
225
|
+
templateType: page
|
|
226
|
+
label: My Landing Page
|
|
227
|
+
isAvailableForNewContent: true
|
|
228
|
+
screenshotPath: ../images/template-previews/landing-page.png
|
|
229
|
+
-->
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Valid templateType values:
|
|
233
|
+
```
|
|
234
|
+
page, blog_base, blog_listing, blog_post, email, section,
|
|
235
|
+
global_partial, search_results, membership_login,
|
|
236
|
+
membership_register, membership_reset, password_prompt, error_page
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Section Templates (the most error-prone area)
|
|
240
|
+
```
|
|
241
|
+
Section template annotations:
|
|
242
|
+
<!--
|
|
243
|
+
templateType: section
|
|
244
|
+
label: Hero Section
|
|
245
|
+
isAvailableForNewContent: true
|
|
246
|
+
screenshotPath: ../images/section-previews/hero.png
|
|
247
|
+
description: "A hero section with headline and CTA"
|
|
248
|
+
-->
|
|
249
|
+
|
|
250
|
+
CRITICAL RULES for section templates:
|
|
251
|
+
1. Multiple dnd_modules in a single dnd_section REQUIRE width and offset
|
|
252
|
+
2. Width + offset values use a 12-column grid
|
|
253
|
+
3. Missing width/offset causes: "Cannot resolve property [missing {{ token }} value]"
|
|
254
|
+
|
|
255
|
+
✅ CORRECT (two modules in one section):
|
|
256
|
+
{% dnd_section %}
|
|
257
|
+
{% dnd_column %}
|
|
258
|
+
{% dnd_row %}
|
|
259
|
+
{% dnd_module "rich_text_1" path="@hubspot/rich_text" width=6, offset=0 %}
|
|
260
|
+
{% end_dnd_module %}
|
|
261
|
+
{% dnd_module "rich_text_2" path="@hubspot/rich_text" width=6, offset=6 %}
|
|
262
|
+
{% end_dnd_module %}
|
|
263
|
+
{% end_dnd_row %}
|
|
264
|
+
{% end_dnd_column %}
|
|
265
|
+
{% end_dnd_section %}
|
|
266
|
+
|
|
267
|
+
❌ WRONG (missing width/offset):
|
|
268
|
+
{% dnd_section %}
|
|
269
|
+
{% dnd_module "rich_text_1" path="@hubspot/rich_text" %}{% end_dnd_module %}
|
|
270
|
+
{% dnd_module "rich_text_2" path="@hubspot/rich_text" %}{% end_dnd_module %}
|
|
271
|
+
{% end_dnd_section %}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Drag-and-Drop Nesting Order
|
|
275
|
+
```
|
|
276
|
+
The nesting MUST follow this exact hierarchy:
|
|
277
|
+
dnd_area → dnd_section → dnd_column → dnd_row → dnd_module
|
|
278
|
+
|
|
279
|
+
- dnd_area: top-level container, only ONE per template
|
|
280
|
+
- dnd_section: full-width horizontal band
|
|
281
|
+
- dnd_column: vertical division inside a section
|
|
282
|
+
- dnd_row: horizontal division inside a column
|
|
283
|
+
- dnd_module: the actual content module
|
|
284
|
+
|
|
285
|
+
Every opening tag MUST have a matching closing tag:
|
|
286
|
+
{% dnd_section %} ... {% end_dnd_section %}
|
|
287
|
+
{% dnd_column %} ... {% end_dnd_column %}
|
|
288
|
+
{% dnd_row %} ... {% end_dnd_row %}
|
|
289
|
+
{% dnd_module "name" path="..." %} {% end_dnd_module %}
|
|
290
|
+
|
|
291
|
+
Common mistake: forgetting {% end_dnd_column %} or mismatching nesting.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Module References in HubL
|
|
295
|
+
```
|
|
296
|
+
CURRENT (v2): {{ module.field_name }}
|
|
297
|
+
LEGACY (v1): {{ widget.field_name }} ← still works but deprecated
|
|
298
|
+
|
|
299
|
+
- URL fields return OBJECTS, not strings:
|
|
300
|
+
✅ {{ module.my_link.href }}
|
|
301
|
+
❌ {{ module.my_link }} ← prints the entire object as text
|
|
302
|
+
|
|
303
|
+
- Image fields:
|
|
304
|
+
✅ {{ module.my_image.src }}
|
|
305
|
+
✅ {{ module.my_image.alt }}
|
|
306
|
+
❌ {{ module.my_image }} ← prints object
|
|
307
|
+
|
|
308
|
+
- Icon fields have inconsistent naming:
|
|
309
|
+
The "style" property on {% icon %} tag reads from "type" in the field data
|
|
310
|
+
✅ {% icon name="{{ module.icon_field.name }}"
|
|
311
|
+
style="{{ module.icon_field.type }}"
|
|
312
|
+
unicode="{{ module.icon_field.unicode }}" %}
|
|
313
|
+
❌ {% icon style="{{ module.icon_field.style }}" %} ← "style" doesn't exist on the field
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Module Names (unique identifiers in templates)
|
|
317
|
+
```
|
|
318
|
+
- Must be in quotes: {% module "my_module" ... %}
|
|
319
|
+
- Must use underscores, not spaces or dashes: "my_module" not "my-module"
|
|
320
|
+
- Must be unique within the template
|
|
321
|
+
- If two modules share the same name, editing one edits both
|
|
322
|
+
- This is sometimes intentional (for synced content) but usually a bug
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 5. CSS Rules
|
|
328
|
+
|
|
329
|
+
### Do NOT Style HubSpot's Generated Classes
|
|
330
|
+
```
|
|
331
|
+
These classes are auto-generated and WILL change without notice:
|
|
332
|
+
|
|
333
|
+
/* IDs — never target these */
|
|
334
|
+
#hs_cos_wrapper_*
|
|
335
|
+
#hs_form_target_dnd*
|
|
336
|
+
|
|
337
|
+
/* Classes — never target these */
|
|
338
|
+
.heading-container-wrapper
|
|
339
|
+
.heading-container
|
|
340
|
+
.body-container-wrapper
|
|
341
|
+
.body-container
|
|
342
|
+
.footer-container-wrapper
|
|
343
|
+
.footer-container
|
|
344
|
+
.container-fluid
|
|
345
|
+
.row-fluid
|
|
346
|
+
.row-fluid-wrapper
|
|
347
|
+
.row-depth-*
|
|
348
|
+
.row-number-*
|
|
349
|
+
.span*
|
|
350
|
+
.hs-cos-wrapper
|
|
351
|
+
.hs-cos-wrapper-widget
|
|
352
|
+
.dnd-section
|
|
353
|
+
.dnd-column
|
|
354
|
+
.dnd-row
|
|
355
|
+
.dnd-module
|
|
356
|
+
.dnd_area*
|
|
357
|
+
|
|
358
|
+
Instead: use custom classes assigned via the class parameter in templates
|
|
359
|
+
or through field-driven dynamic classes in modules.
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### CSS Scoping in Modules
|
|
363
|
+
```
|
|
364
|
+
- module.css loads ONCE per page, even if the module appears 10 times
|
|
365
|
+
- Use {{ name }} in module.html to get a unique instance class
|
|
366
|
+
- Scope your CSS:
|
|
367
|
+
|
|
368
|
+
module.html:
|
|
369
|
+
<div class="{{ name }} my-custom-module">...</div>
|
|
370
|
+
|
|
371
|
+
module.css:
|
|
372
|
+
.my-custom-module { ... }
|
|
373
|
+
|
|
374
|
+
- For dynamic styles, use require_css block:
|
|
375
|
+
{% require_css %}
|
|
376
|
+
<style>
|
|
377
|
+
{% scope_css %}
|
|
378
|
+
.my-module { color: {{ module.styles.text_color.color }}; }
|
|
379
|
+
{% end_scope_css %}
|
|
380
|
+
</style>
|
|
381
|
+
{% end_require_css %}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Inline Styles
|
|
385
|
+
```
|
|
386
|
+
- Hardcoded inline styles are discouraged (Marketplace rejection)
|
|
387
|
+
- Use field-driven dynamic inline styles instead:
|
|
388
|
+
|
|
389
|
+
❌ <div style="background-color: #ff0000;">
|
|
390
|
+
✅ <div style="background-color: {{ module.styles.bg_color.color }};">
|
|
391
|
+
✅ <div style="{{ module.styles.bg_image.css }}"> ← .css property on backgroundimage fields
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## 6. JavaScript Rules
|
|
397
|
+
|
|
398
|
+
### Module JS Behavior
|
|
399
|
+
```
|
|
400
|
+
- module.js loads ONCE per page, regardless of how many instances exist
|
|
401
|
+
- You CANNOT assume your module appears only once on the page
|
|
402
|
+
- Use class-based selectors scoped to the module, not IDs
|
|
403
|
+
|
|
404
|
+
❌ document.getElementById('my-module')
|
|
405
|
+
✅ document.querySelectorAll('.my-module-class')
|
|
406
|
+
|
|
407
|
+
- For module-specific targeting, use the {{ name }} variable in module.html
|
|
408
|
+
to generate a unique wrapper class, then target that
|
|
409
|
+
|
|
410
|
+
- Scripts in module.js load BEFORE any require_js files
|
|
411
|
+
- Defer module.js execution:
|
|
412
|
+
var defined_name = (function() { /* your code */ })();
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### jQuery
|
|
416
|
+
```
|
|
417
|
+
- Prefer vanilla JS — adding jQuery to a site not using it causes conflicts
|
|
418
|
+
- If jQuery is needed, use require_js() to include it:
|
|
419
|
+
{{ require_js("https://code.jquery.com/jquery-3.x.min.js") }}
|
|
420
|
+
- This ensures it only loads if not already present
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## 7. Theme Inheritance Rules
|
|
426
|
+
|
|
427
|
+
### Font and Color Inheritance (Marketplace requirement)
|
|
428
|
+
```
|
|
429
|
+
Modules with font or color fields MUST inherit from theme settings.
|
|
430
|
+
Without this, you get: "Module needs to inherit from standard field names"
|
|
431
|
+
|
|
432
|
+
Use "inherited_value" with "default_value_path":
|
|
433
|
+
|
|
434
|
+
{
|
|
435
|
+
"name": "heading_font",
|
|
436
|
+
"label": "Heading Font",
|
|
437
|
+
"type": "font",
|
|
438
|
+
"inherited_value": {
|
|
439
|
+
"default_value_path": "theme.heading_font"
|
|
440
|
+
},
|
|
441
|
+
"default": {
|
|
442
|
+
"font": "Poppins",
|
|
443
|
+
"fallback": "sans-serif",
|
|
444
|
+
"variant": "600",
|
|
445
|
+
"font_set": "GOOGLE",
|
|
446
|
+
"size": 48,
|
|
447
|
+
"size_unit": "px"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
Standard theme field names to inherit from:
|
|
452
|
+
- theme.primary_color
|
|
453
|
+
- theme.secondary_color
|
|
454
|
+
- theme.heading_font
|
|
455
|
+
- theme.body_font
|
|
456
|
+
|
|
457
|
+
If your theme uses different names, use alternate_names in theme fields.json:
|
|
458
|
+
{
|
|
459
|
+
"name": "brand_color",
|
|
460
|
+
"type": "color",
|
|
461
|
+
"alternate_names": ["primary_color"],
|
|
462
|
+
"default": { "color": "#516747" }
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 8. Common Error Messages → Causes
|
|
469
|
+
|
|
470
|
+
| Error | Likely Cause |
|
|
471
|
+
|-------|-------------|
|
|
472
|
+
| `The format for the color value is invalid` | Color field default uses rgba/rgb/named color instead of hex `{ "color": "#rrggbb", "opacity": 100 }` |
|
|
473
|
+
| `Cannot resolve property "[missing {{ token }} value]"` | Multiple dnd_modules in a section without width/offset |
|
|
474
|
+
| `"text" fields are not supported in theme fields.json` | Module folder missing .module suffix, OR text field in theme fields.json |
|
|
475
|
+
| `Module inherits standard fields` | Font/color field missing `inherited_value` with `default_value_path` |
|
|
476
|
+
| `The template cannot contain "dnd_area" modules` | Using dnd_area in a blog post template (not supported) or wrong templateType |
|
|
477
|
+
| Field shows entire object instead of value | URL field: use `.href`, Image: use `.src`, not just `module.field_name` |
|
|
478
|
+
| Icon not displaying | Icon field "style" property reads from `.type` not `.style` (HubSpot inconsistency) |
|
|
479
|
+
| Nested module error | Modules cannot be nested inside other modules — use sections/groups instead |
|
|
480
|
+
| Module JS not working for multiple instances | module.js loads once — use class selectors, not ID selectors |
|
|
481
|
+
| Validation errors on upload but not in UI | Use `hs cms convert-fields` to get detailed errors from JS field format |
|
|
482
|
+
| Changes not appearing | Saving is NOT publishing — you must click Publish |
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## 9. HubL Syntax Quick Reference
|
|
487
|
+
|
|
488
|
+
### Variables and Expressions
|
|
489
|
+
```
|
|
490
|
+
{{ variable }} ← Output a value
|
|
491
|
+
{% statement %} ← Logic (if, for, set, etc.)
|
|
492
|
+
{# comment #} ← Comment (not rendered)
|
|
493
|
+
|
|
494
|
+
{{ module.field_name }} ← Access module field
|
|
495
|
+
{{ content.meta_description }} ← Access page settings
|
|
496
|
+
{{ request.path }} ← Access request info
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Useful Filters
|
|
500
|
+
```
|
|
501
|
+
{{ variable|pprint }} ← Debug: print variable type
|
|
502
|
+
{{ variable|tojson }} ← Debug: output as JSON
|
|
503
|
+
{{ module.field|escape }} ← HTML escape
|
|
504
|
+
{{ module.text|truncatewords(20) }} ← Truncate text
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Require CSS/JS (proper asset loading)
|
|
508
|
+
```
|
|
509
|
+
{# External CSS — loads in <head> #}
|
|
510
|
+
{{ require_css(get_asset_url("../css/module.css")) }}
|
|
511
|
+
|
|
512
|
+
{# Inline CSS — loads in <head> via <style> tag #}
|
|
513
|
+
{% require_css %}
|
|
514
|
+
<style>
|
|
515
|
+
.my-class { color: red; }
|
|
516
|
+
</style>
|
|
517
|
+
{% end_require_css %}
|
|
518
|
+
|
|
519
|
+
{# External JS — loads after CSS #}
|
|
520
|
+
{{ require_js(get_asset_url("../js/module.js")) }}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### CRM Functions Limits
|
|
524
|
+
```
|
|
525
|
+
- crm_object() and crm_objects(): max 10 calls per page
|
|
526
|
+
- crm_objects(): max 100 objects returned, default 10
|
|
527
|
+
- blog_popular_posts(): max 200 posts, max 10 calls per page
|
|
528
|
+
- Standard CRM objects (contacts, deals, etc.) require password-protected pages
|
|
529
|
+
- Only products and marketing_events can be shown on public pages
|
|
530
|
+
- Custom objects CAN be shown on public pages
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## 10. Pre-Upload Checklist
|
|
536
|
+
|
|
537
|
+
Before deploying any HubSpot CMS code, verify:
|
|
538
|
+
|
|
539
|
+
- [ ] All module folders end with `.module` suffix
|
|
540
|
+
- [ ] fields.json is a JSON array `[...]` not an object `{...}`
|
|
541
|
+
- [ ] No field names collide with dnd reserved parameters (width, offset, label, path, styles)
|
|
542
|
+
- [ ] Field names use snake_case, no spaces or dashes
|
|
543
|
+
- [ ] All fields have `default` values (no empty defaults for required fields)
|
|
544
|
+
- [ ] Color field defaults use hex format `{ "color": "#rrggbb", "opacity": 100 }` — no rgba/rgb/named colors
|
|
545
|
+
- [ ] No Lorem Ipsum in any default values
|
|
546
|
+
- [ ] URL fields accessed via `.href`, image fields via `.src`
|
|
547
|
+
- [ ] Multiple dnd_modules in sections have `width` and `offset` defined
|
|
548
|
+
- [ ] dnd nesting follows: area → section → column → row → module
|
|
549
|
+
- [ ] Every opening dnd tag has its closing `end_` counterpart
|
|
550
|
+
- [ ] Module unique names use underscores: `"my_module"` not `"my-module"`
|
|
551
|
+
- [ ] CSS does not target HubSpot generated classes (.dnd-section, .hs-cos-wrapper, etc.)
|
|
552
|
+
- [ ] module.js handles multiple instances (class selectors, not ID selectors)
|
|
553
|
+
- [ ] Font/color fields inherit from theme via `inherited_value` if in a theme
|
|
554
|
+
- [ ] No `text` or `richtext` fields in theme fields.json (module-only types)
|
|
555
|
+
- [ ] Template annotations present at top of .html files (templateType, label, etc.)
|
|
556
|
+
- [ ] No errors in Design Manager OR browser console before publishing
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
*These rules will prevent 90% of the cryptic errors that HubSpot CMS throws at you. The remaining 10% will be undocumented edge cases — when in doubt, build a minimal reproduction in a sandbox account and test before deploying.*
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# HubSpot Page Types — AI Context
|
|
2
|
+
|
|
3
|
+
Each page type has specific template requirements, HubL variables, and annotation rules.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Landing Page
|
|
8
|
+
|
|
9
|
+
Template annotation:
|
|
10
|
+
```
|
|
11
|
+
templateType: page
|
|
12
|
+
isAvailableForNewContent: true
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Rules:
|
|
16
|
+
- Self-contained single page — no navigation menu required
|
|
17
|
+
- Full creative freedom with modules (hero, features, testimonials, CTA, footer, etc.)
|
|
18
|
+
- Use `{% dnd_area %}` for drag-and-drop content areas
|
|
19
|
+
- All modules referenced via `{% dnd_module path="../modules/ModuleName.module" %}`
|
|
20
|
+
- Focus on conversion: clear CTAs, social proof, benefit-driven copy
|
|
21
|
+
- Anchor-link navigation is fine (link to #section-id within the same page)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Blog Post
|
|
26
|
+
|
|
27
|
+
Template annotation:
|
|
28
|
+
```
|
|
29
|
+
templateType: blog_post
|
|
30
|
+
isAvailableForNewContent: true
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Required HubL variables — these MUST be used in the template:
|
|
34
|
+
- `{{ content.name }}` — post title
|
|
35
|
+
- `{{ content.post_body }}` — post body content (the main article)
|
|
36
|
+
- `{{ content.featured_image }}` — featured image URL
|
|
37
|
+
- `{{ content.featured_image_alt_text }}` — featured image alt text
|
|
38
|
+
- `{{ content.publish_date }}` — publication date (use `|datetimeformat('%B %d, %Y')`)
|
|
39
|
+
- `{{ content.author.display_name }}` — author name
|
|
40
|
+
- `{{ content.author.avatar }}` — author avatar URL
|
|
41
|
+
- `{{ content.tag_list }}` — list of tags
|
|
42
|
+
- `{{ content.topic_list }}` — list of topics
|
|
43
|
+
- `{{ content.comment_count }}` — number of comments
|
|
44
|
+
- `{{ content.absolute_url }}` — canonical URL
|
|
45
|
+
|
|
46
|
+
Optional HubL:
|
|
47
|
+
- `{% blog_social_sharing %}` — social share buttons
|
|
48
|
+
- `{% blog_comments %}` — comment section
|
|
49
|
+
- `{% related_blog_posts %}` — related posts widget
|
|
50
|
+
|
|
51
|
+
Rules:
|
|
52
|
+
- The `{{ content.post_body }}` is where blog content goes — don't replace it with modules
|
|
53
|
+
- Modules are for surrounding layout: header, sidebar, author bio, related posts, share buttons
|
|
54
|
+
- Always include a blog listing template alongside (templateType: blog_listing)
|
|
55
|
+
- The listing template uses `{% for content in contents %}` to loop over posts
|
|
56
|
+
- Blog listing required: `{{ group.public_title }}`, `{{ next_page_url }}`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Website Page
|
|
61
|
+
|
|
62
|
+
Template annotation:
|
|
63
|
+
```
|
|
64
|
+
templateType: page
|
|
65
|
+
isAvailableForNewContent: true
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Rules:
|
|
69
|
+
- MUST include navigation using `{% menu "main_nav" %}` HubL tag
|
|
70
|
+
- The menu reads from the portal's menu settings — links are managed in HubSpot, not hardcoded
|
|
71
|
+
- Footer should include `{% menu "footer_nav" %}` for footer links
|
|
72
|
+
- Design the menu module visually (layout, colors, hover states, mobile hamburger)
|
|
73
|
+
- The user wires actual page links in the HubSpot portal after publishing pages
|
|
74
|
+
- Include consistent header and footer across all website page templates
|
|
75
|
+
|
|
76
|
+
Navigation module pattern:
|
|
77
|
+
```html
|
|
78
|
+
<nav class="nav">
|
|
79
|
+
{% menu "main_nav" %}
|
|
80
|
+
</nav>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
For a custom-styled menu (when {% menu %} styling is insufficient):
|
|
84
|
+
```html
|
|
85
|
+
<nav class="nav">
|
|
86
|
+
{% set menu = menu("main_nav") %}
|
|
87
|
+
{% for item in menu.children %}
|
|
88
|
+
<a href="{{ item.url }}" class="nav__link{% if item.active %} nav__link--active{% endif %}">
|
|
89
|
+
{{ item.label }}
|
|
90
|
+
</a>
|
|
91
|
+
{% endfor %}
|
|
92
|
+
</nav>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Menu field type for modules:
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"name": "navigation_menu",
|
|
99
|
+
"label": "Navigation Menu",
|
|
100
|
+
"type": "menu",
|
|
101
|
+
"default": "main_nav"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Module Only
|
|
108
|
+
|
|
109
|
+
No template annotation needed — no template is generated.
|
|
110
|
+
|
|
111
|
+
Rules:
|
|
112
|
+
- Only module files are generated: fields.json, meta.json, module.html, module.css, module.js
|
|
113
|
+
- Used for adding standalone modules to existing HubSpot themes
|
|
114
|
+
- The module can be used in any template via `{% dnd_module path="..." %}`
|
|
115
|
+
- Set `host_template_types: ["PAGE"]` in meta.json (or `["BLOG_POST"]`, `["PAGE", "BLOG_POST"]` as needed)
|
|
116
|
+
- Focus on making the module self-contained and reusable
|
package/bin/vibespot.mjs
ADDED