← All playbooks · Raw API
task-create-html-component
# Capability: Create from designer HTML (HtmlComponent)
## Intent phrases
- build from designer HTML
- HTML dump
- create from HTML prototype
- import HTML landing page
- HtmlComponent from HTML
- custom HTML page
- load HtmlComponent external scripts
## Requires capabilities
`pages` or `articles`
## Prerequisites
- Read `cms-edit://customer/pages` (or `articles`) and `cms-edit://customer/components-index`
- `cms_edit ["schema", "htmlComponent"]` — use this space’s field IDs (`rawHtml` vs `htmlContent`, `customJs` vs `jsContent`). **`externalScripts` is the same field id on every space.** If `schema` does **not** list `externalScripts`, **stop** and ask for Contentful migration 28 — do not stuff vendor/long JS into `customJs` (Contentful Text is ~50k characters).
- Designer HTML / prototype available (file, paste, or Drive). **Do not** paste a multi-megabyte dump with `data:` URIs into Contentful.
- For ordinary Google Doc / Figma-to-registered-stack briefs with no custom CSS/JS, use `task-create-from-document` instead.
## When to use this playbook
Use this when the source is a **designer HTML dump**, interactive prototype, or a block that needs a **third-party JS library**. Do not flatten custom CSS/JS/scroll into registered cards because the copy looks like a card grid.
## Per-section decision
For **each** source section, ask: do **layout and interaction** match a registered type?
| Fit | Action |
|-----|--------|
| Strong (same pattern) | Use the registered type. |
| Medium (copy matches, chrome differs) | Ask the human: site chrome vs keep HtmlComponent. |
| Weak / none | HtmlComponent. Custom sticky scroll, diagrams, widgets, vendor JS. |
Mock **nav and footer** in the HTML: strip. The page **template** supplies chrome.
## Split HtmlComponents (mixed pages)
Mixed pages are expected: registered types **plus** one or more `htmlComponent` entries.
**Do split** when a section matches a registered type, remaining HTML would exceed field limits, or adjacent custom blocks **do not share** JS/CSS/sticky state.
**Do not split** when one behaviour spans the markup (sticky rail + stacked cards) or several sections share one stylesheet / SVG sprite / library.
Rules:
1. Registered match → registered type.
2. Merge consecutive custom sections that share CSS/JS into **one** HtmlComponent.
3. Start a new HtmlComponent only at a clean boundary (no shared script, no sticky parent).
4. Never one HtmlComponent per `<section>` by default.
## External scripts (non-negotiable)
Put third-party libraries in **`externalScripts`**, not `<script src>` in HTML and not `createElement('script')` inside `customJs`.
Allowed values:
- Same-origin paths under `/html-components/` that end in `.js` (no `..`, no query, no hash). Prefer files in the site repo at `public/html-components/<feature>/….js`.
- HTTPS URLs whose hostname is already listed on the site’s `CmsRendererConfig.htmlComponentExternalScriptHosts`. If the site sets CSP, that host must also be in `script-src`.
If the host is **not** allowlisted, **stop** — do not invent a CDN load and do not patch renderer config without a human.
Rejected (renderer drops these; preview/dev may warn): `http:`, `javascript:`, protocol-relative `//…`, path `..`, query/hash on same-origin paths.
Max **10** URLs per entry. Duplicate URLs on one page share one `next/script` id. Externals load with `scriptStrategy` (default `afterInteractive`). **`customJs` runs only after every listed src is ready**; if any src fails, inline JS is not injected.
### How to set `externalScripts`
It is an **Array of Symbol**, not a string. Scalar `set` is rejected.
```text
cms_edit ["set", "@cN", "externalScripts", "--json", "[\"/html-components/feature/lib.js\"]"]
```
On `create from-json`, put the same array under `fields.externalScripts`. Run `schema htmlComponent` first — unknown keys fail (`html` vs `htmlContent`).
Then set HTML/CSS/JS. Hosted MCP **rejects** `--content` / `--content-base64` / `--json` over **8192 bytes**. Do not stub-create then patch. Write files on disk, then:
```
cms_edit_request_staged_upload kind=text mimeType=text/html|text/css|text/javascript
# run curlCommand
cms_edit ["set", "@cN", "rawHtml", "--staged", "<id>"]
```
Contentful Text fields themselves cap around **50k characters**. Put vendor/long JS in `externalScripts` (`/html-components/….js` in the site repo), not `customJs`.
`diff` prints bytes + sha256 + a 20-line head for long Text — not the full body. Confirm integrity with `["read", "@cN", "--hash"]` (full body only on `read` without `--hash`).
## Media first (mandatory)
1. Extract binary media from `data:` URIs and local files. Upload via `task-media-reuse-and-upload` (search/reuse first).
2. Rewrite HTML `src` / `poster` to Contentful or CDN URLs. Never leave base64 in the field.
3. `markdownContent` = user-visible copy and structure. Omit layout chrome and JS behaviour.
## HtmlComponent authoring
1. Follow the site HTML style guide (`docs/cms-guidelines/html-component-style-guide.md` when available).
2. Scope all JS queries to injected `hcRoot`.
3. `isHero` — `true` if this block contains the page `<h1>`. `fullWidth` — as needed for full-bleed sections.
4. Set `cmsLabel` immediately (never leave “New Generic”).
5. Dual-write legacy + core field IDs when `schema htmlComponent` shows both.
6. Draft only — never publish unless the human explicitly asks.
## Steps
1. Inventory sections. For each: registered / ask / HtmlComponent. Merge custom blocks that share CSS/JS.
2. List vendor libraries. Copy same-origin files into `public/html-components/<feature>/` or confirm the HTTPS host is already allowlisted.
3. Post a soft-proof (ordered stack + `externalScripts` URLs) and get a light human **yes**.
4. Media pipeline: extract, upload, rewrite URLs.
5. Create or open the page/article. Add registered components where decided.
6. For each HtmlComponent: `add htmlComponent` (or `create from-json --staged` / `--dry-run --strict` with this space’s field keys). Do not put HTML/CSS/JS over 8k in `--json`.
7. Set `externalScripts` with `--json`. Set HTML/CSS/JS via `--staged` when over 8k. Set `cmsLabel`.
8. `diff` → `read --hash` → `save` (draft).
9. `cms_edit ["preview", "urls", "<slug>"]` — follow `task-preview-verify`. Confirm libraries load (no `createElement('script')`, no dropped-URL warnings).
## Confirmation gates
1. Soft-proof (mixed stack + script URLs) + human yes
2. Media extracted (no `data:` in saved HTML)
3. `schema htmlComponent` field IDs used
4. Libraries only via `externalScripts`
5. `diff` before `save`
## Out of scope
- Publish
- Flattening custom interaction into cards/lists because copy looks similar
- One HtmlComponent per `<section>` by default
- Rewriting source copy
- Pasting the raw designer file into HTML
- Adding a new CDN host to `htmlComponentExternalScriptHosts` without a human
- Scalar `set` of a single URL string onto `externalScripts`
## Related resources
- `cms-edit://customer/pages`
- `cms-edit://customer/task-create-from-document`
- `cms-edit://customer/task-create-page`
- `cms-edit://customer/task-media-reuse-and-upload`
- `cms-edit://customer/task-preview-verify`
- `cms-edit://customer/task-publish-handoff`
- `cms_edit ["help", "fields-html-component"]`