HubSpot Marketplace Submission: Technical Requirements & Rejection Checklist


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.

What the Submission Process Looks Like

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.

Pre-Upload: Technical Requirements That Block Submission

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.

meta.json: The Most Common Upload Failure

Every module needs a valid meta.json. The field that causes the most silent upload failures is host_template_types.

Common mistake:

{
    "host_template_types": ["BLOG", "LANDING_PAGE"]
}

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:

{
    "label": "My Module",
    "host_template_types": ["PAGE", "BLOG_POST"],
    "is_available_for_new_content": true,
    "global": false,
    "smart_type": "NOT_SMART",
    "tags": []
}

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.

fields.json Validation Gotchas

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.

// Will fail silently — avoid
{ "id": "button-label", "name": "button-label", "type": "text" }

// Correct
{ "id": "button_label", "name": "button_label", "type": "text" }

JSON syntax issues that pass standard linting. If fields stop rendering after an upload, validate the file directly:

python3 -m json.tool fields.json

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.

Global Partials vs. Modules

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.

What Gets Flagged During Review

The following patterns account for the majority of rejections across module and theme submissions.

1. Missing Default Content

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:

{
    "id": "headline",
    "name": "headline",
    "label": "Headline",
    "type": "text",
    "default": "Why Every Team Needs a Smarter Onboarding Process"
}

Image field defaults must include a real src and descriptive alt:

{
    "id": "hero_image",
    "name": "hero_image",
    "label": "Image",
    "type": "image",
    "default": {
        "src": "https://yourcdn.com/demo-image.jpg",
        "alt": "Team collaborating around a whiteboard",
        "width": 1200,
        "height": 630,
        "loading": "lazy"
    }
}

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.

2. Hardcoded Content That Should Be Editable

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.

3. Accessibility — The Quiet Dealbreaker

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:

Hubl
<img
    src="{{ module.hero_image.src }}"
    alt="{{ module.hero_image.alt }}"
    width="{{ module.hero_image.width }}"
    height="{{ module.hero_image.height }}"
    loading="{{ module.hero_image.loading }}"
>

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:

HTML
<button
    id="trigger-1"
    aria-expanded="false"
    aria-controls="panel-1"
    class="accordion__trigger"
>
    Section Title
</button>
<div
    id="panel-1"
    role="region"
    aria-labelledby="trigger-1"
    hidden
>
    Content here
</div>

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.

4. Console Errors on the Demo Page

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:

JavaScript
// Throws TypeError if element doesn't render without editor content
const slider = document.querySelector('.my-slider');
slider.addEventListener('input', handler);

// Safe
const slider = document.querySelector('.my-slider');
if (!slider) return;
slider.addEventListener('input', handler);

Undefined property access on HubL variables. Guard against fields that may be empty:

Hubl
{% if module.items and module.items|length > 0 %}
    ...
{% endif %}

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.

5. Responsive Layout

The demo is evaluated at multiple viewport widths. Common failure modes:

  • Fixed-width containers that overflow and cause horizontal scroll on mobile
  • Images without max-width: 100% that break out of their containers
  • Buttons or interactive elements with tap targets smaller than 44×44px
  • Text that truncates or wraps into unreadable layouts at narrow widths

Test at 375px (mobile), 768px (tablet), and 1280px (desktop) before every submission. Horizontal scroll on mobile is an immediate rejection.

6. Field Labels and Help Text

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:

{
    "id": "column_count",
    "name": "column_count",
    "label": "Columns per row",
    "type": "choice",
    "choices": [["2", "2 columns"], ["3", "3 columns"], ["4", "4 columns"]],
    "default": "3",
    "help_text": "Controls how many items appear per row on desktop. On mobile, content always stacks to a single column regardless of this setting."
}

7. Required Template Types for Themes

For theme submissions, the template set must cover the minimum required types. Missing any of the following results in rejection regardless of visual quality:

  • Home page template
  • Standard page template
  • Blog listing template
  • Blog post template
  • System error page template (404)

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.

Pre-Submission Checklist

Run this before every submission, against the live demo page — not a local environment.

Upload validity

  1. host_template_types uses only valid values: PAGE, BLOG_POST, BLOG_LISTING, EMAIL, KNOWLEDGE_ARTICLE, QUOTE_TEMPLATE
  2. is_available_for_new_content: true on every editor-placeable module
  3. fields.json passes python3 -m json.tool fields.json with no errors
  4. No hyphens in any field id or name
  5. No BOM character at the start of JSON files (check if edited on Windows)
  6. id and name are identical for every field

Content and editor experience

  1. Every visible output element has a corresponding editable field
  2. Every field has a clear, human-readable label
  3. Every non-obvious field has help_text
  4. Repeater default array contains at least one complete, real-content item
  5. Image field defaults include a real src URL and descriptive alt text
  6. Module renders as a complete, presentable component with zero editor configuration

Accessibility

  1. All <img> tags output the alt attribute from the image field
  2. All interactive elements use <button> or are fully keyboard-accessible
  3. Stateful components update aria-expanded, aria-selected, or equivalent on interaction
  4. Tab order is logical through the full component

Demo page

  1. Browser console is clear of JavaScript errors on the published demo URL
  2. Layout tested at 375px, 768px, and 1280px viewport widths
  3. No horizontal scroll at any viewport width
  4. All interactions function correctly without errors

Themes only

  1. Home, page, blog listing, blog post, and 404 error templates all present
  2. All templates reference only assets that exist within the theme directory

After a Rejection

Read 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.

Report an issue

Frequently Asked Questions

What are the most common reasons for HubSpot Marketplace rejection?
The most frequent rejection reasons are: module renders empty or broken without any editor configuration (missing meaningful defaults), hardcoded content that should be editable via fields, missing alt text on images, interactive components without keyboard navigation or ARIA state attributes, and JavaScript errors visible in the browser console on the demo page.
Why are my module fields not appearing in the HubSpot editor after a CLI upload?
The most common causes are: a syntax error in fields.json (validate with python3 -m json.tool fields.json), field id or name values containing hyphens (HubSpot rejects these silently), a BOM character at the start of the JSON file, or is_available_for_new_content set to false in meta.json — which hides the module from the editor panel entirely despite a successful upload.
What template types are required for a HubSpot theme to pass Marketplace review?
A valid HubSpot theme must include at minimum: a home page template, a standard page template, a blog listing template, a blog post template, and a system error page (404 template). Missing any of these results in rejection regardless of the quality of the rest of the submission.
Does HubSpot Marketplace review check accessibility?
Yes. Review checks for baseline accessibility compliance: alt attributes on all images, keyboard navigability of interactive elements (tabs, accordions, buttons), and ARIA state attributes on components that change their visible state. Modules with interactive elements built on div elements without keyboard support or ARIA are consistently rejected.
What is the difference between id and name in HubSpot fields.json?
The id is HubSpot's internal key for storing and retrieving saved editor data. The name is the variable you reference in HubL templates as module.field_name. They should always be identical. If you rename a field's id after the module is live on any page, all saved values for that field on existing pages are permanently lost. For Marketplace modules used across many portals, this is irreversible at scale.

Stay in touch

Notes on Web Development

New articles on web development, accessibility, and technical SEO.

Occasional deep dives into platform-specific topics like HubSpot CMS, based on real-world problems and solutions.