NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #4967 most downloaded on PyPI
Library to convert rich text from Draft.js raw ContentState to HTML
Last release 1 months ago
13 Aug 2026
Release timing varies
gaps range from 3 weeks to 2.9 years
Nearly every release is documented
notes for 41 of 41 stable releases
Nothing withdrawn
no release was ever pulled
10 years old
41 releases · first in 2016
Re-export code_block and render_children from the package root.
code_block and render_children from the package root.HTMLExporter as a compatibility alias for HTML (alongside Exporter).md_ prefix (for example md_link, md_image, md_mark_safe, md_prefixed_block).md_ prefix (for example link → md_link, mark_safe → md_mark_safe). Import them from draftjs_exporter under their new names.Add experimental Markdown importer: MarkdownImporter converts Markdown to Draft.js ContentState, with configurable entity resolvers (scheme_resolver f
MarkdownImporter converts Markdown to Draft.js ContentState, with configurable entity resolvers (scheme_resolver for internal URL schemes), an inline HTML style whitelist, and ContentStateFilter for content policy.One column per quarter.
For breaking changes and upgrade steps, see the migration guide.
DOM.MARKDOWN, with MARKDOWN_CONFIG and build_markdown_config().draftjs_exporter package root (Exporter, HTML_CONFIG, types, constants).TypedDict definitions for configuration and ContentState.lxml dependency to >=4.6.5.wagtail/draftjs_exporter (same maintainers, new home).HTML instances. Each exporter now uses its own engine via a context variable (#122).For breaking changes and upgrade steps, see the migration guide.
DOM.MARKDOWN, with MARKDOWN_CONFIG and build_markdown_config().draftjs_exporter package root (Exporter, HTML_CONFIG, types, constants).TypedDict definitions for configuration and ContentState.SECURITY.md.lxml dependency to >=4.6.5.wagtail/draftjs_exporter (same maintainers, new home).HTML instances. Each exporter now uses its own engine via a context variable (#122).For breaking changes and upgrade steps, see the migration guide.
Formalize support for Python 3.14, with tentative support for Python 3.15.
draftjs_exporter[lxml] extra.Formalize support for Python 3.12 and 3.13, with tentative support for Python 3.14.
draftjs_exporter[lxml] extra.Add tentative support for Python 3.11.
engine property to 'engine': DOM.STRING_COMPAT (#138).Python 3.6 is no longer supported, as it has reached its end of life. For projects needing Python 3.6, please keep using v4.1.2 of the exporter.
engine property to 'engine': DOM.STRING_COMPAT, (#138).For breaking changes and upgrade steps, see the migration guide.
Add tentative support for Python 3.10.
extras_require for development-only dependencies.Add support for Python 3.9 (#134).
Publish the package as a wheel (#132, #133). Thanks to Stormheg.
This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
Do not upgrade to this version if you are using the exporter in Python 3.5. Please keep using v3.0.1 of the exporter.
The default string engine no longer sorts attributes alphabetically by name in its output HTML. This makes it possible to control the order as needed, wherever attributes can be specified:
def image(props):
return DOM.create_element('img', {
'src': props.get('src'),
'width': props.get('width'),
'height': props.get('height'),
'alt': props.get('alt'),
})
If you relied on this behavior, you can either reorder your props / wrapper_props / create_element calls as needed, or subclass the built-in string engine and override its render_attrs method to add back the attrs.sort:
@staticmethod
def render_attrs(attr: Attr) -> str:
attrs = [f' {k}="{escape(v)}"' for k, v in attr.items()]
attrs.sort()
return "".join(attrs)
The default string engine no longer escapes single and double quotes in HTML content (it still escapes quotes inside attributes). If you relied on this behavior, subclass the built-in string engine and override its render_children method to add back quote=True:
@staticmethod
def render_children(children: Sequence[Union[HTML, Elt]]) -> HTML:
return "".join(
[
DOMString.render(c)
if isinstance(c, Elt)
else escape(c, quote=True)
for c in children
]
)
The exporter supports passing the style attribute as a dictionary with JS attributes for style properties, and will automatically convert it to a string. The properties are no longer sorted alphabetically – it’s now possible to reorder the dictionary’s keys to change the order.
If you relied on this behavior, either reorder the keys as needed, or pass the style as a string (with CSS properties syntax).
Add Typing :: Typed trove classifier to the package.
Typing :: Typed trove classifier to the package.\n -> <br/> composite decorators. (#127)This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
This release contains breaking changes. Be sure to check out the "how to upgrade" section below.
.sort() instead of sorted(), which is a bit faster. (±2% faster) (#120).block, blocks list, mutability, and key as entity_range.key (#91, #124).Do not upgrade to this version if you are using the exporter in Python 2.7 or 3.4. Please keep using v2.1.7 of the exporter.
If you are using the exporter in a codebase using type annotations and a type checker, there is a chance the annotations added in this release will create conflicts with your project’s annotations – if there are discrepancies between the expected input/output of the exporter, or in the configuration. In this case you may need to update your project’s type annotations or stubs to match the expected types of the exporter’s public API.
If you believe there is a problem with how the public API is typed, please open a new issue.
This release contains breaking changes. Be sure to check out the migration guide.
.sort() instead of sorted(), which is a bit faster. (±2% faster) (#120).block, blocks list, mutability, and key as entity_range.key (#91, #124).For breaking changes and upgrade steps, see the migration guide.
Minor performance improvements (10% speed-up, 30% lower memory consumption) by adding Python `__slots__` and implementing other optimisations.
__slots__ and implementing other optimisations.Assume same block defaults as Draft.js would when attributes are missing: depth = 0, type = unstyled, no entities, no styles (#110, thanks to @tpict).
Minor performance improvements (8% speed-up, 20% lower memory consumption)
Attempt to fix project description formatting on PyPI, broken in the last release (#103).
Increase lower bound of optional lxml dependency to v4.2.0 to guarantee Python 3.7 support (#88).
Use io.open with utf-8 encoding in setup.py. Fix #98
Add upper bound to lxml dependency, now defined as lxml>=3.6.0,<5 (#75).
lxml>=3.6.0,<5 (#75).html5lib>=0.999,<=1.0.1.Give block rendering components access to the current block, when the component is rendered for a block, and the blocks list (#90).
block, when the component is rendered for a block, and the blocks list (#90).block and blocks list (#90).block, blocks list, and current style type as inline_style_range.style (#87, #90).This release contains breaking changes that will require updating the exporter's configurations. Be sure to check out the "how to upgrade" section bel…
This release contains breaking changes that will require updating the exporter's configurations. Be sure to check out the "how to upgrade" section below.
DOMString (#79, #85).strategy and component attributes.ImportError when loading an engine fails, not ConfigException.DOM.use must use a valid engine, there is no default value anymore.engine option, or to DOM.use.The specificities of the new engine are described in the documentation. To start using the new default,
engine property from the exporter configuration, or do 'engine': DOM.STRING,.html5lib and beautifulsoup4 dependencies from your project if they aren't used anywhere else.To keep using the previous default, html5lib:
engine property to 'engine': DOM.HTML5LIB,.pip install draftjs_exporter[html5lib].Decorator components now require the function syntax (see the relevant documentation).
# Before:
class OrderedList:
def render(self, props):
depth = props['block']['depth']
return DOM.create_element('ol', {
'class': 'list--depth-{0}'.format(depth)
}, props['children'])
# After:
def ordered_list(props):
depth = props['block']['depth']
return DOM.create_element('ol', {
'class': 'list--depth-{0}'.format(depth)
}, props['children'])
If you were relying on the configuration capabilities of the class API, switch to composing components instead:
# Before:
class Link:
def __init__(self, use_new_window=False):
self.use_new_window = use_new_window
def render(self, props):
link_props = {
'href': props['url'],
}
if self.use_new_window:
link_props['target'] = '_blank'
link_props['rel'] = 'noreferrer noopener'
return DOM.create_element('a', link_props, props['children'])
# In the config:
ENTITY_TYPES.LINK: Link(use_new_window=True)
# After:
def link(props):
return DOM.create_element('a', props, props['children'])
def same_window_link(props):
return DOM.create_element(link, {
'href': props['url'],
}, props['children'])
})
def new_window_link(props):
return DOM.create_element(link, {
'href': props['url'],
'target': '_blank',
'rel': 'noreferrer noopener',
}, props['children'])
})
The composite decorators API now looks closer to that of other decorators, and to Draft.js:
# Before:
class BR:
SEARCH_RE = re.compile(r'\n')
def render(self, props):
if props['block']['type'] == BLOCK_TYPES.CODE:
return props['children']
return DOM.create_element('br')
'composite_decorators': [
BR,
]
# After:
def br(props):
if props['block']['type'] == BLOCK_TYPES.CODE:
return props['children']
return DOM.create_element('br')
# In the config:
'composite_decorators': [
{
'strategy': re.compile(r'\n'),
'component': br,
},
],
# The `engine` field in the exporter config now has to be a dotted path string pointing to a valid engine.
- 'engine': 'html5lib',
+ 'engine': 'draftjs_exporter.engines.html5lib.DOM_HTML5LIB',
# Or, using the shorthand.
+ 'engine': DOM.HTML5LIB,
# It's not possible either to directly provide an engine implementation - use a dotted path instead.
- DOM.use(DOMTestImpl)
+ DOM.use('tests.test_dom.DOMTestImpl')
Fix string engine incorrectly skipping identical elements at the same depth level (#83).
Add new string-based dependency-free DOM backing engine, with much better performance, thanks to the expertise of @BertrandBordage (#77).
There is no need to make any changes to keep using the previous engines (html5lib, lxml). To switch to the new string engine, opt-in via the config:
exporter = HTML({
+ # Specify which DOM backing engine to use.
+ 'engine': 'string',
})
For breaking changes and upgrade steps, see the migration guide.
The project has reached a high-enough level of stability to be used in production, and breaking changes will now be reflected via major version change…
This release is functionally identical to the previous one,
v0.9.0.
The project has reached a high-enough level of stability to be used in production, and breaking changes will now be reflected via major version changes.
Add configuration options to determine handling of missing blocks #52.
props['block']['type'].props['entity']['type'].props['block']['depth'], props['block']['data'].None in render.lxml as a DOM backing engine, with pip install draftjs_exporter[lxml] pip extra.props['block_type'] to props['block']['type'].ConfigException to draftjs_exporter.error.DOM.get_children method.DOM.pretty_print method.className prop to class.# Change composite decorators block type access
- props['block_type']
+ props['block']['type']
# Stop using DOM.get_children directly.
- DOM.get_children()
# Stop using DOM.pretty_print directly.
- DOM.pretty_print()
# Move `ConfigException` to `draftjs_exporter.error`.
- from draftjs_exporter.options import ConfigException
+ from draftjs_exporter.error import ConfigException
# Remove automatic conversion from `className` prop to `class` attribute.
- BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'className': 'c-pullquote'}]
+ BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'class': 'c-pullquote'}]
Fix KeyError when the content state is empty.
Add simplified block mapping format: BLOCK_TYPES.HEADER_TWO: 'h2'.
BLOCK_TYPES.HEADER_TWO: 'h2'.style_map does not define an element for the style.style_map.style prop from a dict of camelCase properties to a string, on all elements (if style is already a string, it will be output as is).render function returning create_element nodes) in style_map.BOLD = 'strong'
CODE = 'code'
ITALIC = 'em'
UNDERLINE = 'u'
STRIKETHROUGH = 's'
SUPERSCRIPT = 'sup'
SUBSCRIPT = 'sub'
MARK = 'mark'
QUOTATION = 'q'
SMALL = 'small'
SAMPLE = 'samp'
INSERT = 'ins'
DELETE = 'del'
KEYBOARD = 'kbd'
pre block type.render function returning create_element nodes) in block_map, for both element and wrapper.['ul'], ['ul', {'class': 'bullet-list'}]).DOM.create_text_node method.props and wrapper_props attributes (dictionaries of props).BlockException to ConfigException.style_map config format to the one of the block_map.camel_to_dash method to DOM for official use.STRIKETHROUGH styles in default style map now map to s tag.UNDERLINE styles in default style map now map to u tag.code-block blocks are now rendered inside a combination of pre and code tags.data dict as props instead of whole entity map declaration.# Change element-only block declarations:
- BLOCK_TYPES.HEADER_TWO: {'element': 'h2'},
+ BLOCK_TYPES.HEADER_TWO: 'h2',
# Change array-style block declarations:
- BLOCK_TYPES.BLOCKQUOTE: ['blockquote', {'class': 'c-pullquote'}]
+ BLOCK_TYPES.BLOCKQUOTE: {'element': 'blockquote', 'props': {'class': 'c-pullquote'}}
# Change block wrapper declarations:
- 'wrapper': ['ul', {'class': 'bullet-list'}],
+ 'wrapper': 'ul',
+ 'wrapper_props': {'class': 'bullet-list'},
# Change location and name of exceptions:
- from draftjs_exporter.wrapper_state import BlockException
+ from draftjs_exporter.options import ConfigException
# Change element-only style declarations:
- 'KBD': {'element': 'kbd'},
+ 'KBD': 'kbd',
# Change object-style style declarations:
- 'HIGHLIGHT': {'element': 'strong', 'textDecoration': 'underline'},
+ 'HIGHLIGHT': {'element': 'strong', 'props': {'style': {'textDecoration': 'underline'}}},
# Create custom STRIKETHROUGH styles:
+ 'STRIKETHROUGH': {'element': 'span', 'props': {'style': {'textDecoration': 'line-through'}}},
# Create custom UNDERLINE styles:
+ 'UNDERLINE': {'element': 'span', 'props': {'style': {'textDecoration': 'underline'}}},
# New camel_to_dash location:
- from draftjs_exporter.style_state import camel_to_dash
- camel_to_dash()
+ from draftjs_exporter.dom import DOM
+ DOM.camel_to_dash()
# New default rendering for code-block:
- BLOCK_TYPES.CODE: 'pre',
+ BLOCK_TYPES.CODE: lambda props: DOM.create_element('pre', {}, DOM.create_element('code', {}, props['children'])),
# Use the new pre block to produce the previous result, or override the default for code-block.
+ BLOCK_TYPES.PRE: 'pre',
# Entities now receive the content of `data` directly, instead of the whole entity:
def render(self, props):
- data = props.get('data', {})
link_props = {
- 'href': data['url'],
+ 'href': props['url'],
}
# Remove wrapping around text items.
- DOM.create_text_node(text)
+ text
# Remove fragment calls.
- DOM.create_document_fragment()
+ DOM.create_element()
# Remove text getters and setters. This is not supported anymore.
- DOM.get_text_content(elt)
- DOM.set_text_content(elt, text)
Add support for decorators thanks to @su27 (#16, #17).
*ngFor will now be exported as *ngFor.Add profiling tooling thanks to @su27 (#31).
Automatically convert line breaks to br elements.
br elements.This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
hr rendering to be done with entities instead of block types. Instead of having a TOKEN entity rendering as Null inside a horizontal-rule block rendering as hr, we now have a HORIZONTAL_RULE entitiy rendering as HR inside an atomic block rendering as fragment.pullquoteFix state being kept between exports, causing blocks to be duplicated in re-runs.
### Fixed - Fix broken link in README
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
children prop in a similar fashion to React's.This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
This release is likely to be a breaking change. It is not released as such because the exporter has not reached 1.0 yet.
draftjs_exporter.entities instead of draftjs_exporter.entities.<entity>wrapper options definition: {'unordered-list-item' : { 'element': 'li', 'wrapper': 'ul'}}{'header-two' : { 'element': ['h2', {'class': 'c-amazing-heading'}]}}DOM.create_element, for conditional rendering like what React does.DOM.create_element.Last release before switching to BeautifulSoup4 / html5lib. If we ever need to switch back to lxml, it should be as simple as looking at the code at v
Last release before switching to BeautifulSoup4 / html5lib. If we ever need to switch back to lxml, it should be as simple as looking at the code at v0.3.3.
Fix exporter crashing on empty blocks (renders empty string instead)
Use HTML parser instead of XML for DOM API
(Breaking change) Exporter API changed to be closer to React's
class to class).Support for tag / TOKEN entities
<hr/> tag / TOKEN entitiesFirst usable release! ---
First usable release!
Your coding agent can read these notes before it upgrades. Set up the MCP server →