logo

Migration Guide

Migrate from the original svelte-markdown (by Pablo Berganza) to @humanspeak/svelte-markdown — the actively maintained Svelte 5 successor.

Why Migrate?

The original svelte-markdown package is no longer actively maintained and does not support Svelte 5. @humanspeak/svelte-markdown is a drop-in replacement that adds:

  • Svelte 5 support — built with runes ($props, $derived, $state)
  • Full TypeScript support — complete type definitions for all exports
  • Token caching — LRU cache for 50-200x faster re-renders
  • 69+ HTML tag renderers — dedicated components for HTML within markdown
  • Allow/deny filtering — fine-grained control over rendered elements
  • Snippet overrides — customize rendering inline with Svelte 5 snippets
  • Marked extensions — KaTeX, Mermaid, and any marked extension via the extensions prop
  • Async parsing — support for async marked extensions
  • Active maintenance — regular updates, bug fixes, and new features

Step-by-Step Migration

1. Update Dependencies

Remove the old package and install the new one:

# Remove old package
npm uninstall svelte-markdown

# Install new package
npm install @humanspeak/svelte-markdown
# Remove old package
npm uninstall svelte-markdown

# Install new package
npm install @humanspeak/svelte-markdown

2. Update Imports

Find and replace all imports. Change this:

import SvelteMarkdown from 'svelte-markdown'
import SvelteMarkdown from 'svelte-markdown'

To this:

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import SvelteMarkdown from '@humanspeak/svelte-markdown'

3. Update Svelte to Version 5

@humanspeak/svelte-markdown requires Svelte 5. If you haven’t upgraded yet:

npm install svelte@5
npm install svelte@5

See the official Svelte 5 migration guide for framework-level changes.

4. Update Custom Renderers

If you have custom renderer components, update them to use Svelte 5 runes:

Before (Svelte 4):

<script>
    export let href = ''
    export let title = ''
</script>

<a {href} {title}>
    <slot />
</a>
<script>
    export let href = ''
    export let title = ''
</script>

<a {href} {title}>
    <slot />
</a>

After (Svelte 5):

<script>
    const { href = '', title = '', children } = $props()
</script>

<a {href} {title}>
    {@render children?.()}
</a>
<script>
    const { href = '', title = '', children } = $props()
</script>

<a {href} {title}>
    {@render children?.()}
</a>

5. Update Component Usage (if needed)

The core API is the same. The source, renderers, options, isInline, and parsed props work identically:

<script>
    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    const source = '# Hello World'
</script>

<!-- Same API as before -->
<SvelteMarkdown {source} />
<script>
    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    const source = '# Hello World'
</script>

<!-- Same API as before -->
<SvelteMarkdown {source} />

New Features Available After Migration

Once migrated, you can immediately use these new capabilities:

Token Caching

Automatic LRU caching for parsed markdown tokens — no code changes needed:

<!-- Caching is enabled by default -->
<SvelteMarkdown {source} />
<!-- Caching is enabled by default -->
<SvelteMarkdown {source} />

Allow/Deny Filtering

Control exactly which elements are rendered:

<script>
    import SvelteMarkdown, { allowHtmlOnly, excludeRenderersOnly } from '@humanspeak/svelte-markdown'

    const htmlRenderers = allowHtmlOnly(['strong', 'em', 'a'])
    const renderers = excludeRenderersOnly(['html', 'image'])
</script>

<SvelteMarkdown {source} {renderers} {htmlRenderers} />
<script>
    import SvelteMarkdown, { allowHtmlOnly, excludeRenderersOnly } from '@humanspeak/svelte-markdown'

    const htmlRenderers = allowHtmlOnly(['strong', 'em', 'a'])
    const renderers = excludeRenderersOnly(['html', 'image'])
</script>

<SvelteMarkdown {source} {renderers} {htmlRenderers} />

Snippet Overrides

Customize rendering inline without creating separate component files:

{#snippet heading(props)}
    <h1 class="custom-heading">{@render props.children?.()}</h1>
{/snippet}

<SvelteMarkdown {source} snippetOverrides={{ heading }} />
{#snippet heading(props)}
    <h1 class="custom-heading">{@render props.children?.()}</h1>
{/snippet}

<SvelteMarkdown {source} snippetOverrides={{ heading }} />

Marked Extensions

Use any marked extension directly:

<script>
    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    import markedKatex from 'marked-katex-extension'

    const extensions = [markedKatex({ throwOnError: false })]
</script>

<SvelteMarkdown source="Euler's identity: $e^{'{'}i\\pi{'}'} + 1 = 0$" {extensions} />
<script>
    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    import markedKatex from 'marked-katex-extension'

    const extensions = [markedKatex({ throwOnError: false })]
</script>

<SvelteMarkdown source="Euler's identity: $e^{'{'}i\\pi{'}'} + 1 = 0$" {extensions} />

API Compatibility

Featuresvelte-markdown@humanspeak/svelte-markdown
source propstringstring | Token[]
renderers propobjectPartial<Renderers> (typed)
options propobjectPartial<SvelteMarkdownOptions> (typed)
isInline propbooleanboolean
parsed callbackfunctionfunction
extensions propMarkedExtension[]
snippetOverrides propSnippetOverrides
Custom renderersSvelte 4 componentsSvelte 5 components
TypeScriptPartialFull type coverage
HTML tag renderers69+ built-in
Token cachingBuilt-in LRU
Allow/deny filtering6 utility functions

Related