Skip to main content

django-markdown-widget

A GitHub-style markdown editor widget for Django forms and admin.

PyPI version Python versions Django versions License: MIT codecov Docs

Documentation | PyPI | GitHub

Screenshots

Dark (default) Light
Write Light
Split View Preview
Split Preview

Features

  • Three view modes -- Write, Split (side-by-side live preview), Preview
  • Syntax highlighting for code blocks via highlight.js (GitHub themes)
  • Rich toolbar -- text formatting, headings, lists, code, tables, media, embeds
  • Image, video, and document uploads with drag & drop support
  • Embed support -- paste YouTube, Vimeo, CodePen URLs or raw <iframe> code
  • Autosave -- drafts saved to browser localStorage, survives page reloads
  • Draggable split divider -- resize editor/preview panels
  • Temp upload system -- files upload to temp storage, finalize on form submit
  • Light, dark, and auto theme support
  • Django admin integration via one-line mixin
  • Template tag and filter for server-side markdown rendering
  • Pluggable renderer and upload handler architecture
  • Media cleanup -- automatic orphaned file deletion + management command
  • Custom undo/redo history stack (100 states)
  • HTML sanitization on both client and server side
  • Keyboard shortcuts -- Ctrl+B, Ctrl+I, Ctrl+K, Ctrl+Z, etc.
  • Zero hard dependencies beyond Django

Installation

pip install django-markdown-widget

Add to your INSTALLED_APPS and include the URLs:

# settings.py
INSTALLED_APPS = [
    ...
    "django_markdown_widget",
]

# urls.py
from django.urls import include, path

urlpatterns = [
    ...
    path("md-editor/", include("django_markdown_widget.urls")),
]

For server-side rendering (template tag/filter), install a markdown library:

pip install markdown

Quick Start

Form Widget

from django.forms import ModelForm
from django_markdown_widget import MarkdownEditorWidget

class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = ["title", "content"]
        widgets = {
            "content": MarkdownEditorWidget(),
        }

Django Admin

from django.contrib import admin
from django_markdown_widget import MarkdownEditorAdminMixin

@admin.register(Post)
class PostAdmin(MarkdownEditorAdminMixin, admin.ModelAdmin):
    list_display = ["title", "created_at"]
    markdown_fields = ["content"]  # omit to apply to all TextFields

Template Rendering

{% load markdown_widget %}

{# As a filter (recommended) #}
{{ post.content|markdown }}

{# As a tag #}
{% markdown post.content %}

Media Cleanup

from django_markdown_widget import MarkdownCleanupMixin

class Post(MarkdownCleanupMixin, models.Model):
    content = models.TextField()
    # markdown_cleanup_fields = ["content"]  # optional: limit to specific fields

Enable in settings:

MD_EDITOR = {
    "CLEANUP_MEDIA": True,
}

Bulk cleanup via management command:

python manage.py cleanup_markdown_media --dry-run
python manage.py cleanup_markdown_media

Toolbar

Default toolbar layout, grouped by purpose:

Group Buttons
Text formatting Bold, Italic, Strikethrough, Highlight
Structure Heading, Quote, Horizontal rule
Code Inline code, Code block
Lists Bullet, Numbered, Task list
Links & media Link, Image, Video, Document
Rich blocks Table, Collapsible section, Embed
Extras Superscript, Subscript
Actions Undo, Redo

Fullscreen toggle and autosave switch are in the top-right tabs bar.

Configuration

All settings are optional and go under MD_EDITOR in your Django settings:

MD_EDITOR = {
    # Pluggable backend classes
    "RENDERER_CLASS": "django_markdown_widget.renderers.DefaultRenderer",
    "UPLOAD_HANDLER_CLASS": "django_markdown_widget.uploads.DefaultUploadHandler",

    # Toolbar buttons (grouped by purpose)
    "TOOLBAR": [
        "bold", "italic", "strikethrough", "highlight", "separator",
        "heading", "quote", "horizontal-rule", "separator",
        "code", "code-block", "separator",
        "unordered-list", "ordered-list", "task-list", "separator",
        "link", "image", "video", "document", "separator",
        "table", "details", "embed", "separator",
        "superscript", "subscript", "separator",
        "undo", "redo",
    ],

    # Upload settings
    "ALLOWED_UPLOAD_TYPES": [
        "image/png", "image/jpeg", "image/gif", "image/webp",
        "video/mp4", "video/webm", "video/ogg",
        "application/pdf", "application/zip", "text/plain", "text/csv",
        "application/json",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    ],
    "MAX_UPLOAD_SIZE": 50 * 1024 * 1024,  # 50 MB
    "UPLOAD_PATH": "md-editor/uploads/%Y/%m/",
    "TEMP_UPLOAD_PATH": "md-editor/tmp/",
    "TEMP_MAX_AGE": 86400,  # 24 hours

    # Editor defaults
    "DEFAULT_HEIGHT": "300px",
    "PLACEHOLDER": "Add your comment here...",
    "THEME": "auto",  # "light", "dark", or "auto"

    # Security
    "REQUIRE_AUTH": True,

    # Media cleanup
    "CLEANUP_MEDIA": False,
}

Custom Renderer

from django_markdown_widget import BaseRenderer

class MyRenderer(BaseRenderer):
    def render(self, markdown_text: str) -> str:
        import markdown_it
        md = markdown_it.MarkdownIt()
        return md.render(markdown_text)
MD_EDITOR = {
    "RENDERER_CLASS": "myapp.renderers.MyRenderer",
}

Custom Upload Handler

from django_markdown_widget import BaseUploadHandler

class S3UploadHandler(BaseUploadHandler):
    def validate(self, file):
        # your validation logic
        pass

    def save(self, file) -> str:
        # save to S3 and return the URL
        return url
MD_EDITOR = {
    "UPLOAD_HANDLER_CLASS": "myapp.uploads.S3UploadHandler",
}

Development

git clone https://github.com/ganiyevuz/django-md-editor.git
cd django-md-editor
uv sync --group dev
make test
make lint

Run the example app:

cd example
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

License

MIT License. See LICENSE for details.

Metadata

Release files for django-markdown-widget 2.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-markdown-widget 2.0.1
File Size Uploaded Provenance Actions
django_markdown_widget-2.0.1.tar.gz 1.2 MB Without provenance Details for django_markdown_widget-2.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-markdown-widget 2.0.1
File Interpreter ABI Platform Actions
django_markdown_widget-2.0.1-py3-none-any.whl Python 3 none any Details for django_markdown_widget-2.0.1-py3-none-any.whl

Total release size: 1.4 MB

Release files / django_markdown_widget-2.0.1.tar.gz

Download URL django_markdown_widget-2.0.1.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
8c97d7739504320180360f2b26d8abb746741d8e2fc466a8760bdc860cb3c311
BLAKE2b-256 checksum
How to use checksums
abb9160416a55795f52b3162e9e259e505ed194dd33328c17948c06e4ca1a85d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / django_markdown_widget-2.0.1-py3-none-any.whl

Download URL django_markdown_widget-2.0.1-py3-none-any.whl
Size 126.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
debd92d23604d8da5f20e62a5969d59a59d8b08cd672979847aa1898c345b891
BLAKE2b-256 checksum
How to use checksums
263164719fd3d31f5e143765dd3973bfbd189f3d3c618e9bf2feab76501b07f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for django-markdown-widget 2.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page