HubSpot's Marketplace review process has evolved significantly. What used to be a slow, manual cycle taking one to two weeks per round is now substantially faster — with what feels like meaningful automation behind the scenes. The internal mechanics aren't public, but the result is the same: your submission either meets the requirements or it doesn't.
What hasn't changed are the requirements themselves. The technical standards for upload validity, the quality bar for editor experience, the accessibility baseline — these apply whether a human or an automated system does the checking. This guide covers all of them, based on direct experience publishing multiple modules and themes on the Marketplace.
After submitting a listing, your asset goes through a review pass. The timeline has compressed noticeably in recent iterations — turnaround that previously took the better part of two weeks now often comes back much faster. Rejection notifications arrive by email, listing the issues found. They're brief — usually a single sentence per item — and there's no direct communication channel with whoever or whatever flagged them.
Two things remain true regardless of how automated the process becomes:
Rejections are not exhaustive. A rejection lists issues found in that specific pass. A second submission after fixing the first round can surface new issues. Keep a running log across all rounds.
The demo page is the primary review surface. Everything that gets evaluated is evaluated on your published demo URL. Source files matter for upload validity — but the live demo is what gets approved or rejected.
These issues prevent the asset from reaching review at all. The CLI may or may not surface them as hard errors — silent failures are common.
Every module needs a valid meta.json. The field that causes the most silent upload failures is host_template_types.
Common mistake:
Neither BLOG nor LANDING_PAGE are valid enum values. The CLI may not throw a hard error, but the module will fail internal HubSpot validation and may not appear correctly in the editor.
Correct structure:
is_available_for_new_content: true is required for the module to show in the editor's module panel. Setting it to false causes the module to upload successfully but be completely invisible to editors — a confusing failure mode because the CLI reports success and no error is thrown anywhere.
The CLI validates fields.json syntax on upload, but certain issues pass CLI validation and fail silently at runtime.
Hyphens in field id or name values. HubSpot silently rejects field names containing hyphens, even though the CLI accepts the upload without complaint. The module uploads, but the affected fields simply don't render in the editor.
JSON syntax issues that pass standard linting. If fields stop rendering after an upload, validate the file directly:
This catches trailing commas, stray characters, and encoding issues. If the file parses cleanly but fields still don't appear, check for a BOM (byte order mark) at the start of the file — some editors insert it invisibly, and HubSpot's parser rejects it without any visible error.
id and name divergence on published modules. The id is what HubSpot uses to map saved editor data. The name is what you reference in HubL as module.field_name. They should always be identical. If you rename the id on a module that's already live on pages, every instance loses the field's saved value — permanently and without warning. Treat id as immutable from the moment any page uses the module.
For a complete breakdown of fields.json field types, defaults, and non-obvious behaviors, see HubSpot fields.json Deep Dive.
A global partial is a template-level file shared structurally across pages — headers, footers, navigation. It lives in templates/ and is referenced via {% include %}.
A module lives in modules/ as a .module directory, is editor-placeable, and is referenced via {% module %} or placed via drag-and-drop in the page editor.
The failure mode: placing a partial-style file in modules/, or using {% include %} paths that resolve incorrectly after a CLI upload. The result is a rendering error that only appears on the live page — not in CLI output, not in Design Manager preview.
The rule: if an editor needs to place it on a page, it's a module. If it's structural scaffolding shared at the developer level across templates, it's a partial.
The following patterns account for the majority of rejections across module and theme submissions.
The module is placed on a blank page with zero configuration and evaluated as-is.
The standard: a developer who installs your module and places it without touching any editor fields should see a complete, presentable component — not empty boxes, not layout without content, not placeholder strings like "Your title here."
Every visible output element needs a meaningful default in fields.json:
Image field defaults must include a real src and descriptive alt:
For repeater groups: the default array must contain at least one complete item with real content. Note that repeater defaults have reliability nuances — the safest approach combines fields.json defaults with a server-side demo fallback in HubL. Full mechanics covered in Why You Can't Prefill Complex HubSpot Modules.
The inverse of the above. If your module renders a headline, button label, color, icon, or URL — and there's no corresponding field in fields.json — it gets flagged.
The test: imagine your module deployed in a client portal with no developer available. Can they change everything they might reasonably need to change, without touching code?
Hardcoded values are acceptable only for genuinely structural properties: CSS class names, schema markup, semantic HTML attributes, ARIA roles. Everything visible and content-bearing should map to an editable field.
Accessibility failures are the hardest to anticipate because they require active testing, not just reading source. The three most common items:
Missing alt text on images. Every <img> must have an alt attribute. Decorative images use alt="". Content-bearing images need descriptive text. The template must output the alt value from the field — not hardcode it, not omit it:
Interactive elements not reachable by keyboard. Tabs, accordions, carousels, dropdowns — all must respond to Tab, Enter, Space, and where applicable, Arrow keys. Using native <button> elements instead of <div> with click handlers solves most of this automatically. A <div> with an onclick attribute is not keyboard accessible by default and will be flagged.
Missing ARIA on stateful components. Components that change visible state require ARIA attributes that reflect that state in real time:
When the panel opens, aria-expanded must update to "true" and hidden must be removed via JavaScript. Without this, screen reader users receive no indication that the component changed state.
Full ARIA implementation patterns for tabs and accordions: Mastering ARIA: Enhancing Accessibility for Interactive Interfaces.
Open the browser DevTools console on your published demo URL before every submission. JavaScript errors are caught during review. Common sources:
querySelector returning null when no editor content is configured:
Undefined property access on HubL variables. Guard against fields that may be empty:
Missing external script dependencies. Any library your module relies on must either be loaded by the module itself or clearly documented as a prerequisite. Don't assume the portal has jQuery, Alpine, or any other library present.
The demo is evaluated at multiple viewport widths. Common failure modes:
max-width: 100% that break out of their containersTest at 375px (mobile), 768px (tablet), and 1280px (desktop) before every submission. Horizontal scroll on mobile is an immediate rejection.
Every field in fields.json must have a human-readable label. Not "field_1", not the raw name value. The editor sidebar experience is evaluated, not just the front-end output.
Fields with non-obvious behavior must have help_text:
For theme submissions, the template set must cover the minimum required types. Missing any of the following results in rejection regardless of visual quality:
Every template must reference only CSS and JS files that actually exist within the theme directory. Broken asset references — even in development-only files that weren't cleaned up — cause failures.
Run this before every submission, against the live demo page — not a local environment.
host_template_types uses only valid values: PAGE, BLOG_POST, BLOG_LISTING, EMAIL, KNOWLEDGE_ARTICLE, QUOTE_TEMPLATEis_available_for_new_content: true on every editor-placeable modulefields.json passes python3 -m json.tool fields.json with no errorsid or nameid and name are identical for every fieldlabelhelp_textdefault array contains at least one complete, real-content itemsrc URL and descriptive alt text<img> tags output the alt attribute from the image field<button> or are fully keyboard-accessiblearia-expanded, aria-selected, or equivalent on interactionRead all listed items before touching anything. Some fixes address multiple rejection items at once. Understanding the full list before acting prevents redundant work and missed connections between issues.
Fix every listed item before resubmitting. Partial fixes waste a round. The next pass will find the unfixed items, and potentially surface new ones on top.
Republish the demo page after fixing. HubSpot caches published content aggressively. Uploading a fixed module source does not automatically update live pages. Republish every affected page, verify the fix is live in the browser, then resubmit.
Test for regressions. A fix in one area can introduce a problem in another. After every change, re-run the relevant checklist sections — not only the section covering what you fixed.
The checklist above, applied before every first submission, consistently reduces rejection rounds. The requirements themselves are stable — what changed is how fast you find out whether you've met them.