> ## Documentation Index
> Fetch the complete documentation index at: https://docs.contentwrap.io/llms.txt
> Use this file to discover all available pages before exploring further.

# HTML to Portable Text Conversion

> Understand how MigrateKit intelligently converts Webflow's HTML rich text to Sanity's Portable Text format with semantic preservation.

## How Conversion Works

MigrateKit automatically converts HTML from Webflow's rich text fields into Sanity's Portable Text format during migration. This process:

1. **Parses HTML** - Reads your Webflow HTML structure
2. **Maps tags to blocks** - Converts HTML tags to Portable Text blocks and marks
3. **Preserves semantics** - Maintains meaning, not just visual appearance
4. **Handles edge cases** - Wraps unsupported content as code blocks

## Supported HTML Tags

MigrateKit handles most common HTML tags from Webflow:

### Headings

| HTML   | Portable Text | Usage               |
| ------ | ------------- | ------------------- |
| `<h1>` | `style: "h1"` | Top-level headings  |
| `<h2>` | `style: "h2"` | Section headings    |
| `<h3>` | `style: "h3"` | Subsection headings |
| `<h4>` | `style: "h4"` | Minor headings      |
| `<h5>` | `style: "h5"` | Rarely used         |
| `<h6>` | `style: "h6"` | Rarely used         |

### Paragraphs & Line Breaks

* `<p>` → `style: "normal"` block
* `<br>` → New line within block
* Multiple `<br>` → New blocks

### Text Formatting

| HTML           | Portable Text Mark          | Visual            |
| -------------- | --------------------------- | ----------------- |
| `<strong>`     | `marks: ["strong"]`         | **Bold**          |
| `<b>`          | `marks: ["strong"]`         | **Bold**          |
| `<em>`         | `marks: ["em"]`             | *Italic*          |
| `<i>`          | `marks: ["em"]`             | *Italic*          |
| `<u>`          | `marks: ["underline"]`      | Underlined        |
| `<s>`, `<del>` | `marks: ["strike-through"]` | ~~Strikethrough~~ |
| `<code>`       | `marks: ["code"]`           | `Inline code`     |

### Lists

**Unordered lists:**

```html theme={null}
<ul>
  <li>First item</li>
  <li>Second item</li>
</ul>
```

Becomes:

```json theme={null}
[
  {"_type": "block", "listItem": "bullet", "children": [...]},
  {"_type": "block", "listItem": "bullet", "children": [...]}
]
```

**Ordered lists:**

```html theme={null}
<ol>
  <li>First step</li>
  <li>Second step</li>
</ol>
```

Becomes:

```json theme={null}
[
  {"_type": "block", "listItem": "number", "children": [...]},
  {"_type": "block", "listItem": "number", "children": [...]}
]
```

**Nested lists** are supported with proper indentation levels.

### Links

```html theme={null}
<a href="https://example.com">Click here</a>
```

Becomes:

```json theme={null}
{
  "_type": "span",
  "text": "Click here",
  "marks": [{
    "_type": "link",
    "href": "https://example.com"
  }]
}
```

**Link attributes:**

* `href` - Preserved as `href`
* `target="_blank"` - Preserved as `blank: true`
* `rel` - Preserved if present

### Images (Inline)

Images within rich text are converted to image blocks:

```html theme={null}
<p>Here's an image: <img src="/image.jpg" alt="Description"></p>
```

Becomes separate blocks:

```json theme={null}
[
  {"_type": "block", "children": [{"text": "Here's an image:"}]},
  {"_type": "image", "asset": {...}, "alt": "Description"}
]
```

## Handled with Warnings

Some HTML requires special handling:

### Inline Styles

```html theme={null}
<span style="color: red;">Red text</span>
```

Inline `style` attributes are **stripped** during conversion. Portable Text doesn't preserve inline CSS.

**Why?** Portable Text separates content from presentation. You style content in your frontend, not in the CMS.

### Custom Classes

```html theme={null}
<div class="custom-webflow-class">Content</div>
```

Class names are **not preserved**. Portable Text has no concept of CSS classes.

**Solution:** Use custom Portable Text blocks in Sanity for special formatting needs.

## Unsupported Content → Code Blocks

Anything we can't map to a Portable Text block/mark is wrapped as a `code` block with `language: "html"`. Examples:

### Scripts & Embeds

```html theme={null}
<script>alert('hello');</script>
```

Becomes:

```json theme={null}
{
  "_type": "code",
  "language": "html",
  "code": "<script>alert('hello');</script>"
}
```

### Iframes

```html theme={null}
<iframe src="https://youtube.com/embed/abc123"></iframe>
```

Becomes:

```json theme={null}
{
  "_type": "code",
  "language": "html",
  "code": "<iframe src=\"https://youtube.com/embed/abc123\"></iframe>"
}
```

### Custom Webflow Elements & Unknown Tags

```html theme={null}
<custom-element data-foo="bar">Content</custom-element>
```

Becomes:

```json theme={null}
{
  "_type": "code",
  "language": "html",
  "code": "<custom-element data-foo=\"bar\">Content</custom-element>"
}
```

These appear as code in Sanity and won't execute. Replace with Sanity-native components (e.g., YouTube plugin or custom embed blocks) after import.

## Conversion Examples

<Tabs>
  <Tab title="Basic Formatting">
    **HTML:**

    ```html theme={null}
    <h2>My Blog Post</h2>
    <p>This is <strong>bold</strong> and <em>italic</em> text.</p>
    <p>Here's a <a href="/link">link</a>.</p>
    ```

    **Portable Text:**

    ```json theme={null}
    [
      {
        "_type": "block",
        "style": "h2",
        "children": [{"text": "My Blog Post"}]
      },
      {
        "_type": "block",
        "children": [
          {"text": "This is "},
          {"text": "bold", "marks": ["strong"]},
          {"text": " and "},
          {"text": "italic", "marks": ["em"]},
          {"text": " text."}
        ]
      },
      {
        "_type": "block",
        "children": [
          {"text": "Here's a "},
          {"text": "link", "marks": [{"_type": "link", "href": "/link"}]},
          {"text": "."}
        ]
      }
    ]
    ```
  </Tab>

  <Tab title="Lists">
    **HTML:**

    ```html theme={null}
    <ul>
      <li>First item</li>
      <li>Second item with <strong>bold</strong></li>
    </ul>
    ```

    **Portable Text:**

    ```json theme={null}
    [
      {
        "_type": "block",
        "listItem": "bullet",
        "children": [{"text": "First item"}]
      },
      {
        "_type": "block",
        "listItem": "bullet",
        "children": [
          {"text": "Second item with "},
          {"text": "bold", "marks": ["strong"]}
        ]
      }
    ]
    ```
  </Tab>

  <Tab title="Complex Nesting">
    **HTML:**

    ```html theme={null}
    <p>A paragraph with <a href="/link"><strong>bold link</strong></a>.</p>
    ```

    **Portable Text:**

    ```json theme={null}
    [
      {
        "_type": "block",
        "children": [
          {"text": "A paragraph with "},
          {
            "text": "bold link",
            "marks": ["strong", {"_type": "link", "href": "/link"}]
          },
          {"text": "."}
        ]
      }
    ]
    ```
  </Tab>
</Tabs>

## Best Practices

### Before Migration

<Steps>
  <Step title="Clean up Webflow content">
    Remove unnecessary HTML before exporting:

    * Delete empty `<div>` and `<span>` tags
    * Remove inline styles you don't need
    * Simplify complex nested structures
  </Step>

  <Step title="Identify custom elements">
    Note any Webflow-specific elements that will need replacement:

    * Custom interactions
    * Dynamic embeds
    * Complex grids or layouts
  </Step>

  <Step title="Test with small sample">
    Export a small collection first to see how your HTML converts
  </Step>
</Steps>

### After Migration

<Steps>
  <Step title="Review code blocks">
    Check all code blocks in Sanity Studio:

    * Replace scripts with Sanity-safe alternatives
    * Convert iframes to proper embeds
    * Remove unnecessary preserved HTML
  </Step>

  <Step title="Fix formatting issues">
    Adjust any conversion quirks:

    * Incorrect heading levels
    * Lost spacing
    * Misinterpreted formatting
  </Step>

  <Step title="Add semantic improvements">
    Enhance content with Portable Text features:

    * Custom block types
    * Better link handling
    * Responsive embeds
  </Step>
</Steps>

## Common Issues

<AccordionGroup>
  <Accordion title="Line breaks disappeared">
    **Cause:** Multiple `<br>` tags or `<p>` tags with empty content.

    **Fix:** Manually add paragraph breaks in Sanity Studio where needed.
  </Accordion>

  <Accordion title="Formatting looks different">
    **Cause:** Inline styles or classes were stripped during conversion.

    **Fix:** Portable Text separates content from styling. Style your content in your frontend rendering, not in the CMS.
  </Accordion>

  <Accordion title="Images are missing">
    **Cause:** Images didn't upload (network error, quota exceeded).

    **Fix:** Check the import report for asset failures and manually upload missing images.
  </Accordion>

  <Accordion title="Links are broken">
    **Cause:** Relative Webflow links may not work in new context.

    **Fix:** Update links in Sanity Studio to point to correct URLs.
  </Accordion>

  <Accordion title="Custom elements became code blocks">
    **Expected:** MigrateKit preserves unknown HTML as code blocks for safety.

    **Fix:** Review code blocks and replace with Sanity-compatible alternatives.
  </Accordion>
</AccordionGroup>
