# Downcast conversion – model to view

<a id="introduction">

## Introduction

The process of converting the **model** to the **view** is called a **downcast**.

The downcast process happens every time a model node or attribute needs to be converted into a view node or attribute.

The editor engine runs the conversion process and uses converters registered by the plugins.

<a id="registering-a-converter">

## Registering a converter

To tell the engine how to convert a specific model element into a view element, you need to register a **downcast converter** by using the `editor.conversion.for( 'downcast' )` method, listing the elements that should be converted in the process:

```js
editor.conversion
	.for( 'downcast' )
	.elementToElement( {
		model: 'paragraph',
		view: 'p'
	} );
```

The above converter will handle the conversion of every `<paragraph>` model element into a `<p>` view element. You can see the input and output in the snippet below.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

> **Note**
>
> This is just an example. In fact, paragraph support is already provided by the [paragraph plugin](../../../api/paragraph.md) so you do not really need to write your own `<paragraph>` element to `<p>` element conversion.

You just learned about the [`elementToElement()` **downcast** conversion helper method](helpers/downcast.md#element-to-element-conversion-helper)! More helpers are documented in the following chapters.

<a id="downcast-pipelines">

## Downcast pipelines

The CKEditor 5 engine uses two different views: the **data view** and the **editing view**.

The **data view** is used when generating the editor output. This process is controlled by the data pipeline.

The **editing view**, on the other hand, is what you see when you open the editor. This is controlled by the editing pipeline.

The simple code example presented before registers a converter for both of these pipelines at once. It means that a `<paragraph>` model element will be converted to a `<p>` view element in both the **data view** and the **editing view** all the same.

Sometimes you may want to alter the converter logic for a specific pipeline. For example, in the editing view you may want to add some additional class to the view element. This kind of operation requires setting separate converters for both views, unlike the previous example where one, general downcast was set up.

```js
// dataDowncast for data pipeline
editor.conversion
	.for( 'dataDowncast' )
	.elementToElement( {
		model: 'paragraph',
		view: 'p'
	} );

// editingDowncast for editing pipeline
editor.conversion
	.for( 'editingDowncast' )
	.elementToElement( {
		model: 'paragraph',
		view: {
			name: 'p',
			classes: 'paragraph-in-editing-view'
		}
	} );
```

<a id="converting-text-attributes">

## Converting text attributes

As you should already know from the [chapter about the model](../../architecture/editing-engine.md#model), an **attribute** may be applied to a model text node.

Such text node attributes may be converted into view elements.

To do so, you can register a converter by using the [`attributeToElement()` conversion helper](helpers/downcast.md#attribute-to-element-conversion-helper):

```js
editor.conversion
	.for( 'downcast' )
	.attributeToElement( {
		model: 'bold',
		view: 'strong'
	} );
```

The above converter will handle the conversion of every `bold` model text node attribute to a `<strong>` view element, as shown in the snippet below.

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

> **Note**
>
> Again, this is just an example for the sake of simplicity. Bold support is actually provided by the [basic styles](../../../features/basic-styles.md) plugin so you do not have to write your own bold attribute to strong element conversion.

<a id="converting-element-to-element">

## Converting element to element

Similar to the previous example, you can convert a `<heading>` model element into an `<h1>` view element with the use of the [`elementToElement()` conversion helper](helpers/downcast.md#element-to-element-conversion-helper). A code to achieve it would look like this:

```js
editor.conversion
	.for( 'downcast' )
	.elementToElement( {
		model: 'heading',
		view: 'h1'
	} );
```

Which is equivalent to:

```js
editor.conversion
	.for( 'downcast' )
	.elementToElement( {
		model: 'heading',
		view: ( modelElement, { writer } ) => {
			return writer.createContainerElement(
				'h1'
			);
		}
	} );
```

You have previously learned that the `view` property can be a simple string or an object. The example above shows it is also possible to define a custom callback function to return the created element instead. The effect for this kind of conversion can be observed in the snippet below:

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

The `<heading>` element makes the most sense if you can set the heading level in the view.

In the previous chapter you have learned that you can apply attributes to text nodes. It is also possible to add attributes to elements, like in this example:

```js
editor.conversion
	.for( 'downcast' )
	.elementToElement( {
		model: {
			name: 'heading',
			attributes: [ 'level' ]
		},
		view: ( modelElement, { writer } ) => {
			return writer.createContainerElement(
				'h' + modelElement.getAttribute( 'level' )
			);
		}
	} );
```

From now on, every time a level attribute updates, the whole `<heading>` element will be converted to the `<h[level]>` element (for example `<h1>`, `<h2>`, etc).

You can check this in action by using the example below. Use the dropdown to change heading level in the model and see how view elements are updated by the converter:

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

> **Note**
>
> This, again, is just an example. Heading support is actually provided by the [headings feature](../../../features/headings.md) so you do not have to write your own `<heading level="1">` to `<h1>` element conversion.

<a id="converting-element-to-structure">

## Converting element to structure

Sometimes you may want to convert a **single model element** into a more complex view structure consisting of a **single view element with children**.

You can use the [`elementToStructure()` conversion helper](helpers/downcast.md#element-to-structure-conversion-helper) for this purpose:

```js
editor.conversion
	.for( 'downcast' ).elementToStructure( {
		model: 'myElement',
		view: ( modelElement, { writer } ) => {
			return writer.createContainerElement( 'div', { class: 'wrapper' }, [
				writer.createContainerElement( 'div', { class: 'inner-wrapper' }, [
					writer.createSlot()
				] )
			] );
		}
	} );
```

The above converter will convert all `<myElement>` model elements to `<div class="wrapper"><div class="inner-wrapper"><p>...</p></div></div>` structures, as shown below:

<!-- AI-AGENT-NOTE: An interactive demo is embedded here but is not represented in this Markdown file. If you need to see it in action, open this page in a browser (e.g. via a browser-automation MCP like Chrome DevTools or Playwright), or let the user know a live demo is available on this page. -->

> **Note**
>
> Using your own custom model element requires defining it in the [schema](../schema.md) first.

> **Note**
>
> For editor users, the best way to interact with complex structures is to act as independent entities and stay intact, for instance, when copied, pasted, and edited. CKEditor 5 allows that through the [widget API](../../../api/module_widget_utils.md#function-toWidget). If you want to learn how to use it on top of `elementToStructure()`, be sure to check out the [Implementing a block widget](../../tutorials/widgets/implementing-a-block-widget.md) tutorial.

<a id="downcasting-overlapping-markers">

## Downcasting overlapping markers

Multiple markers can share or overlap the same position in the model - for example, a delete marker and an insert marker can meet at the same boundary when text is replaced. The downcast dispatcher converts markers one at a time, so the conversion result would be non-deterministic if markers were processed in their [insertion order](../../../api/module_engine_model_markercollection-MarkerCollection.md). To prevent this, the engine always sorts markers into a stable **reverse DOM order** before downcasting, using [`compareMarkersForDowncast`](../../../api/module_engine_conversion_comparemarkers.md#function-compareMarkersForDowncast).

<a id="why-reverse-dom-order-matters">

### Why reverse DOM order matters?

Consider replacing the word “old” with “new” - this creates two adjacent markers (a delete range and an insert range) that share a boundary point. With `markerToElement`, each boundary becomes a self-closing tag, so the processing order controls where those tags land:

```html
Sorted (reverse DOM order):  <DEL-START/>old<DEL-END/><INS-START/>new<INS-END/>
Insertion order (legacy):	<DEL-START/>old<INS-START/><DEL-END/>new<INS-END/>
```

The sorted output is correct and stable. The insertion-order output is wrong: `<DEL-END/>` ends up inside the insert range.

<a id="sort-rules">

### Sort rules

The sort is applied to every pair of markers using the following cases (positions shown as `0123456789`, sorted result listed top-to-bottom):

1. Non-overlapping ranges - sorted by position, last range first:

   ```
   a: [--]               →   c, b, a
   b:     [--]
   c:        [--]
   ```

2. Adjacent ranges (end === start) - treated as non-overlapping:

   ```
   first:  [---]         →   third, second, first
   second:    [---]
   third:        [---]
   ```

3. Nested ranges (same start, different ends) - inner first, outer last:

   ```
   shorter: [-]          →   shorter, longer
   longer:  [---]
   ```

4. Partially overlapping ranges - sorted by start position, later start first:

   ```
   earlier: [---]        →   later, earlier
   later:     [---]
   ```

5. Identical ranges - fall back to reverse name comparison:

   ```
   alpha:   [---]        →   charlie, bravo, alpha
   bravo:   [---]
   charlie: [---]
   ```

Name comparison is **only a tie-breaker for identical ranges** (case 5 above). For all other cases - including non-overlapping ranges, adjacent ranges, and collapsed markers at the same position - the sort is based on range positions, and name order plays no role.

> **Important**
>
> This is especially important for features that use collapsed markers with random UID-based names (such as comments or HTML comments). The boundary walker in `markerToElement` intentionally does **not** sort collapsed markers at the same position by name, because `compareMarkersForDowncast` treats two collapsed markers at the same position as non-overlapping and preserves insertion order. Sorting by name in that case would make the output non-deterministic between runs.

Plugin developers **must not** rely on name-based ordering for anything other than co-located markers with identical, non-collapsed ranges.

This deterministic sort shipped in CKEditor 5 v48.1.0 and affects any feature that relies on markers, including comments, suggestions, mentions, find and replace, and restricted editing - see the [v48 update guide](../../../updating/guides/update-to-48.md) for release notes.

<a id="further-reading">

## Further reading

If you want to learn more about downcast helpers mentioned in this guide, we have [rounded them up](helpers/downcast.md) for you with complete descriptions and examples. We also recommend you to check out the [upcast conversion](upcast.md) guide and learn how to convert raw data on the editor input into a live model state.

---

Full index of the CKEditor 5 documentation: [llms.txt](../../../../llms.txt)
