# How Instatic’s Super Import Feature Converts HTML/CSS to Editable Nodes

> Learn how Instatic's super import converts HTML CSS to editable nodes. It parses markup into a DOM tree and maps elements to the native PageNode format for easy editing.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-28

---

**Instatic’s super import feature converts raw HTML and CSS into editable nodes by parsing markup into a sanitized DOM tree, walking it with `walkAndMap`, and mapping every element to the native `PageNode` format used by the visual editor.**

The `CoreBunch/Instatic` repository implements this importer as a deterministic pipeline that can **convert HTML/CSS to editable nodes** when users paste or upload static markup. Because the output matches the editor’s internal tree structure, imported content requires no manual reconfiguration. The entire workflow is orchestrated by the `importHtml` function inside [`src/core/htmlImport/walkAndMap.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/htmlImport/walkAndMap.ts).

## The Pipeline to Convert HTML/CSS to Editable Nodes

The importer processes every input through discrete stages. Each stage is implemented as a dedicated routine in [`walkAndMap.ts`](https://github.com/CoreBunch/Instatic/blob/main/walkAndMap.ts), ensuring the final result is safe, complete, and editable.

### Parse the Raw Markup

First, `importHtml` feeds the source string to `parseHtml`. This lightweight parser produces a DOM-like tree that can be traversed without a browser environment. All subsequent operations depend on this normalized representation.

### Strip Unsafe Content

Next, `stripUnsafe` removes disallowed elements and attributes before they reach the editor. It eliminates `<script>` tags, `on*` event handlers, and dangerous URLs. If anything is removed, the resulting `ImportResult` sets the `stripped` flag to `true`, signaling that the input was sanitized.

### Walk and Map to Page Nodes

The core conversion happens in `walkAndMap`. It recursively traverses the safe DOM tree and instantiates a **`PageNode`** for every element. The walker extracts:

- **HTML attributes** → stored on `props.htmlAttributes`
- **Inline styles** → stored on `inlineStyles` as camel-cased CSS properties
- **Class names** → converted to `classIds` for the selector registry
- **Text nodes** → become `base.text` nodes
- **Media and interactive elements** → mapped to built-in modules such as `base.image`, `base.link`, and form components

Children are processed recursively, preserving the original nesting order in the final tree.

### Collect Page-Scoped CSS

During the walk, the importer gathers all CSS from inline `<style>` blocks and `<link rel="stylesheet">` references. These rules are concatenated into the `styleCss` field of the `ImportResult`. Once persisted, this CSS appears in the editor’s **Style** panel as a page-scoped stylesheet.

### Return the Import Result

`importHtml` returns an **`ImportResult`** containing:

- `nodes`: a `Map<string, PageNode>` of all created nodes
- `rootIds`: identifiers for top-level roots, usually the `<body>` element
- `styleCss`: the concatenated stylesheet string
- `stripped`: boolean flag indicating whether unsafe content was removed

The caller can then commit these nodes through the standard editor mutation API.

## Importing HTML Programmatically

You can invoke the importer directly from client or admin code. The snippet below demonstrates parsing a raw HTML string, inserting the resulting nodes into the active page tree, and persisting the extracted CSS.

```typescript
import { importHtml } from '@core/htmlImport'
import { mutateActiveTree } from '@core/page-tree'
import { insertNode, addPageStyle } from 'src/admin/pages/site/store/slices/site/helpers'

const rawHtml = `
  <html>
    <head>
      <style>
        .hero { background: #f0f0f0; }
      </style>
    </head>
    <body class="hero">
      <h1>Hello world</h1>
      <p data-note="example">A paragraph with <a href="/page">a link</a>.</p>
    </body>
  </html>
`

const { nodes, rootIds, styleCss, stripped } = importHtml(rawHtml)

// Insert each root node into the current page
rootIds.forEach((rootId) => {
  const node = nodes[rootId]
  mutateActiveTree((s) => {
    insertNode({ parentId: s.activePageId, node })
  })
})

// Persist the collected CSS as a page-scoped stylesheet
mutateActiveTree((s) => {
  addPageStyle({ pageId: s.activePageId, css: styleCss })
})

```

After execution, the `<body>` becomes an editable **`base.body`** node, the `<h1>` becomes a **`base.text`** node, and the `<a>` becomes a **`base.link`** module. The `.hero` rule is stored as a page-scoped stylesheet, while any unsafe tags are automatically excluded.

## Key Source Files

Understanding the import architecture requires familiarity with these modules:

- **[`src/core/htmlImport/walkAndMap.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/htmlImport/walkAndMap.ts)** — Implements `importHtml`, `parseHtml`, `stripUnsafe`, and `walkAndMap`. This is the core engine that transforms static markup into the editor’s node tree.

- **[`src/core/siteImport/htmlPagePlan.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/siteImport/htmlPagePlan.ts)** — Houses the site-import pipeline that invokes `importHtml` and orchestrates broader asset handling during page ingestion.

- **[`src/admin/spotlight/commands/importHtml.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/spotlight/commands/importHtml.ts)** — Defines the admin UI command that opens the **Import HTML** modal and triggers the importer from the visual editor.

- **[`src/core/page-tree/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/types.ts)** — Declares `PageNode` and related tree structures that the importer populates.

- **[`src/core/publisher/cssCollector.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/cssCollector.ts)** — Manages the `styleCss` collection returned by the importer and coordinates how extracted styles are published.

## Summary

- Instatic’s super import feature centers on the **`importHtml`** function exported from [`src/core/htmlImport/walkAndMap.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/htmlImport/walkAndMap.ts).
- The pipeline runs through **parse**, **sanitize**, **walk/map**, and **CSS collection** stages in strict order.
- Every HTML element is mapped to a **`PageNode`** with preserved attributes, inline styles, and child nesting.
- Unsafe content is removed by **`stripUnsafe`**, and a `stripped` flag reports when sanitization occurs.
- Extracted CSS is returned as **`styleCss`** and later surfaced in the editor’s Style panel as a page-scoped stylesheet.
- Because the output uses the same node shape as the native editor tree, imported content is **immediately editable**.

## Frequently Asked Questions

### What is the entry point for Instatic’s HTML importer?

The public API is the **`importHtml`** function, which is the main export of [`src/core/htmlImport/walkAndMap.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/htmlImport/walkAndMap.ts). External callers, including the site-import pipeline in [`src/core/siteImport/htmlPagePlan.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/siteImport/htmlPagePlan.ts), invoke this single function to transform a raw HTML string into editable nodes and extracted CSS.

### How does Instatic prevent XSS or unsafe code during import?

Before any DOM traversal occurs, the **`stripUnsafe`** routine scrubs `<script>` tags, `on*` event handlers, and dangerous URLs from the parsed tree. If sanitization is required, the resulting `ImportResult` sets `stripped` to `true`, guaranteeing that the editor surface remains free of executable code.

### What happens to CSS styles embedded in imported HTML?

The walker collects all rules from `<style>` tags and external stylesheet links into a single **`styleCss`** string. This CSS is returned alongside the node tree and is typically persisted as a page-scoped stylesheet, making it available in the editor’s **Style** panel for further customization.

### Can imported nodes be edited right away without extra conversion?

Yes. Because `walkAndMap` emits nodes that conform exactly to the **`PageNode`** structure defined in [`src/core/page-tree/types.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/types.ts), the visual editor treats them identically to natively created content. Users can edit text, tweak attributes, and apply styles immediately after insertion.