Migrating to v3
Background
Section titled “Background”html-to-markdown began as a Python library — a fork of markdownify — focused on converting HTML to Markdown. Over time, the core was rewritten in Rust for performance, and generated packages were added for 15 language surfaces: Python, TypeScript, Ruby, Go, PHP, Java, C#, Elixir, R, C, WebAssembly, Swift, Dart, Kotlin Android, and Zig.
v3.0.0 rationalises the library’s API surface. Instead of multiple
specialised functions, there is now a single convert() function that
returns a structured ConversionResult. This simplifies the API while
making all extraction capabilities available through one call.
hOCR support moved to Xberg
Section titled “hOCR support moved to Xberg”hOCR (OCR-generated HTML) processing was removed from html-to-markdown and moved into Xberg, the document extraction library in the Xberg ecosystem. hOCR is a document-level concern — it belongs in the extraction pipeline, not in an html-to-markdown converter.
API changes
Section titled “API changes”Before (v2)
Section titled “Before (v2)”v2 had multiple functions for different extraction needs:
convert(html)→Stringconvert_with_metadata(html, options, config)→(String, Metadata)convert_with_inline_images(html, options, config)→Extractionconvert_with_tables(html, options)→TablesResultconvert_with_visitor(html, options, visitor)→String
After (v3)
Section titled “After (v3)”v3 has one function:
convert(html, options?, visitor?)→ConversionResult
ConversionResult contains all data:
content— the converted text (Markdown, Djot, or plain text)metadata— HTML metadata (title, OG, links, images, structured data)tables— extracted tables with cell datadocument— structured document tree (when enabled)images— extracted inline images where the binding exposes them (Rust and WASM when built with inline image support; generated native bindings may omit the Rust-only image payload)warnings— non-fatal processing warnings
Migration table
Section titled “Migration table”| v2 | v3 |
|---|---|
convert(html) |
convert(html).content |
convert_with_metadata(html, opts, cfg) |
convert(html, opts).metadata |
convert_with_inline_images(html, opts, cfg) |
convert(html, opts).images (set extract_images: true) |
convert_with_tables(html, opts) |
convert(html, opts).tables |
convert_with_visitor(html, opts, v) |
convert(html, opts, v).content |
convert_to_string(html) |
convert(html).content |
hocr_spatial_tables: true |
Use Xberg |
ExtendedMetadata |
HtmlMetadata |
start_profiling() |
Removed |
markdownify(html) (Python) |
convert(html).content |
Examples by language
Section titled “Examples by language”// v2let markdown = convert(html, None)?;let (md, meta) = convert_with_metadata(html, None, cfg, None)?;
// v3let result = convert(html, None)?;let markdown = result.content.unwrap_or_default();let metadata = result.metadata; // always availablePython
Section titled “Python”# v2markdown = convert(html)markdown, metadata = convert_with_metadata(html)
# v3result = convert(html)markdown = result.contentmetadata = result.metadataTypeScript
Section titled “TypeScript”// v2const markdown = convert(html);const { markdown, metadata } = convertWithMetadata(html);
// v3const result = convert(html);const markdown = result.content;const metadata = result.metadata;// v2markdown, err := htmltomarkdown.Convert(html, nil)markdown, meta, err := htmltomarkdown.ConvertWithMetadata(html, nil)
// v3result, err := htmltomarkdown.Convert(html, nil)markdown := result.Contentmetadata := result.Metadata# v2markdown = HtmlToMarkdown.convert(html)markdown, metadata = HtmlToMarkdown.convert_with_metadata(html)
# v3result = HtmlToMarkdown.convert(html)markdown = result.contentmetadata = result.metadata// v2$markdown = HtmlToMarkdown\convert($html);[$markdown, $metadata] = HtmlToMarkdown\convert_with_metadata($html);
// v3$result = HtmlToMarkdown\HtmlToMarkdownApi::convert($html);$markdown = $result->content;$metadata = $result->getMetadata();// v2String markdown = HtmlToMarkdown.convert(html);ConversionPair pair = HtmlToMarkdown.convertWithMetadata(html);
// v3ConversionResult result = HtmlToMarkdown.convert(html);String markdown = result.content();HtmlMetadata metadata = result.metadata();C# (.NET)
Section titled “C# (.NET)”// v2string markdown = HtmlToMarkdown.Convert(html);(string md, Metadata meta) = HtmlToMarkdown.ConvertWithMetadata(html);
// v3ConversionResult result = HtmlToMarkdownConverter.Convert(html, null);string markdown = result.Content;HtmlMetadata metadata = result.Metadata;Elixir
Section titled “Elixir”# v2{:ok, markdown} = HtmlToMarkdown.convert(html){:ok, markdown, metadata} = HtmlToMarkdown.convert_with_metadata(html)
# v3{:ok, result} = HtmlToMarkdown.convert(html)markdown = result.contentmetadata = result.metadata# v2markdown <- html_to_markdown(html)result <- html_to_markdown_with_metadata(html)
# v3result <- convert(html)markdown <- result$contentmetadata <- result$metadataC (FFI)
Section titled “C (FFI)”/* v2 */char *markdown = htm_convert(html, NULL);htm_free(markdown);
/* v3 */HTMAlefHandle result = htm_convert(html, 0);char *markdown = htm_conversion_result_content(result);htm_free_string(markdown);htm_conversion_result_free(result);C ABI: handles replace pointers
Section titled “C ABI: handles replace pointers”Separate from the v2 to v3 API change above, the C ABI itself changed. Objects no longer cross the
boundary as opaque struct pointers. They are now HTMAlefHandle, a uint64_t handle into a
registry owned by the library. The null handle is 0, so comparisons against NULL on these types
become comparisons against 0.
Strings are unaffected: they are still char * and are still released with htm_free_string. The
visitor callback struct HTMHtmVisitorCallbacks and the HTM_VISIT_* status codes are unchanged.
Only code that includes html_to_markdown.h and links the native library directly needs to change.
The shipped language bindings are regenerated against the new ABI and their public APIs are the
same.
/* before */HTMConversionOptions *options = htm_conversion_options_from_json("{}");HTMConversionResult *result = htm_convert(html, options);if (result == NULL) { fprintf(stderr, "%s\n", htm_last_error_context()); return 1;}char *markdown = htm_conversion_result_content(result);htm_free_string(markdown);htm_conversion_result_free(result);htm_conversion_options_free(options);
/* after */HTMAlefHandle options = htm_conversion_options_from_json("{}");HTMAlefHandle result = htm_convert(html, options);if (result == 0) { fprintf(stderr, "%s\n", htm_last_error_context()); return 1;}char *markdown = htm_conversion_result_content(result); /* unchanged: still char* */htm_free_string(markdown);htm_conversion_result_free(result);htm_conversion_options_free(options);Where you declared T *, declare HTMAlefHandle. Where you compared against NULL, compare
against 0. A bare truth test such as if (result) keeps working, because the null handle is zero.
Found a bug or mistake on this page?
If something here is wrong or out of date, open an issue on GitHub or contribute a fix via pull request.