API reference¶
Python¶
TipTapWidget(forms.Textarea)¶
django_tiptap_editor.widgets.tiptap_widget.TipTapWidget
Renders a <textarea> carrying data-tiptap-config. Config resolution (last wins):
get_default_config() → the per-instance config= → subclass overrides
(get_config(self, attrs)). class Media emits the committed bundle. storage is
"html" (default) or "json"; when None it resolves from
settings.TIPTAP_STORAGE_FORMAT. See Storage format.
AdminTipTapWidget(TipTapWidget)¶
django_tiptap_editor.widgets.admin_tiptap.AdminTipTapWidget — admin-sized variant
(taller default via admin_defaults).
TipTapModelAdminMixin¶
django_tiptap_editor.admin.mixin.TipTapModelAdminMixin — mix in before
admin.ModelAdmin. Swaps AdminTipTapWidget onto TextFields.
tiptap_fields = "__all__" (default) or an explicit list of field names.
TipTapFormField(forms.CharField)¶
django_tiptap_editor.forms.fields.TipTapFormField — a CharField whose default widget
is TipTapWidget.
TipTapJSONField(models.JSONField)¶
django_tiptap_editor.fields.tiptap_json_field.TipTapJSONField — a model field storing a
{doc, html} envelope. The Python value is a TipTapValue; the doc is protocol-allowlisted
on save. Optional link_protocols / image_protocols tuples override the allowlists. Its form
field is TipTapJSONFormField (a forms.Field whose widget is TipTapWidget(storage="json")).
See Storage format.
TipTapValue¶
django_tiptap_editor.types.tiptap_value.TipTapValue — frozen value with .doc (canonical
ProseMirror JSON) and .html (the safe HTML mirror, re-derived from doc on save). str(value)
/ {{ value }} render the mirror. TipTapValue.from_stored({...}) builds one from a {doc, html}
mapping.
render_doc¶
django_tiptap_editor.utils.render_doc.render_doc(doc, *, link_protocols=…, image_protocols=…)
— renders a ProseMirror doc dict to a safe HTML string in pure Python (no Node), covering the
package's node/mark set with protocol allowlisting + CSS validation. The
{{ value|tiptap_html }} filter wraps it (and uses a TipTapValue's mirror when given one). See
Storage format.
BaseImageUploadView / ImageUploadError¶
django_tiptap_editor.views — subclass BaseImageUploadView and implement
save(self, file) -> str to wire storage; see Contracts.
Template tags¶
{% load tiptap %}
{% tiptap_media %} {# editor assets for the active asset mode #}
{% tiptap_config %} {# the default config as a JSON string #}
{{ value|tiptap_html }} {# render a TipTapValue / doc to safe HTML #}
All of TipTapWidget, AdminTipTapWidget, TipTapModelAdminMixin, TipTapFormField,
TipTapJSONField, TipTapJSONFormField, TipTapValue, BaseImageUploadView,
ImageUploadError, get_default_config, render_doc, and validate_config are re-exported from
the package root.
JavaScript — window.DjangoTipTap¶
DjangoTipTap.init(element, config) // explicit mount; returns the editor
DjangoTipTap.get(id) // editor handle | null
DjangoTipTap.destroy(id) // unmount + tear down the shell
DjangoTipTap.autoMount(root?) // idempotent; mounts every config-carrying textarea (incl. manualMount)
DjangoTipTap.registerExtension(name, factory)
DjangoTipTap.registerLocale(code, strings)
DjangoTipTap.renderHTML(doc) // ProseMirror JSON -> HTML string
DjangoTipTap.htmlToJSON(html) // HTML -> ProseMirror JSON
DjangoTipTap.htmlToStored(html) // HTML -> { doc, html } envelope (migration)
DjangoTipTap.ui.registerButton(key, spec)
DjangoTipTap.ui.setTokens(tokens)
DjangoTipTap.ui.setRenderer(region, fn) // "toolbar" | "statusbar" | "bubbleMenu" | "floatingMenu"
DjangoTipTap.ui.setShellRenderer(fn) // replace the whole shell (experimental)
DjangoTipTap.tiptap // re-exported primitives for authors
DjangoTipTap.version // the django-tiptap-editor package version
DjangoTipTap.supportedTipTapVersion // the validated TipTap version
Auto-mount (Path A)¶
Textareas rendered by the widget mount automatically. A MutationObserver watches the
DOM, so editors mount when inserted and tear down when removed — by htmx, Turbo, Unpoly,
Livewire, Alpine, Django admin inlines, or any script — with no framework-specific wiring.
The textarea is hidden and kept in sync; destroy is also exposed for manual teardown.
A destructive swap re-mounts cleanly: when a textarea is replaced by a fresh one with
the same id (an outerHTML swap), the stale editor is destroyed and the new node mounted
— never a duplicate or an orphaned shell. Place {{ form.media }} in the page <head>,
not inside a swapped partial (see
Quickstart).
Idempotency is keyed to the live editor, not the data-tiptap-bound attribute, so two
more swap shapes are handled:
- htmx history (Back/Forward). With
hx-boost/hx-push-url/hx-history, htmx caches a static snapshot of the page — including the rendered shell and the hidden textarea — and restores it on Back (firinghtmx:historyRestore, notafterSwap). The restored field re-mounts a live editor and the dead snapshot shell is removed. For pages where you'd rather skip the snapshot entirely, sethx-history="false"on the editor page. - Morphing swaps (
hx-swap="morph"/ idiomorph). A morph reconciles the live textarea's attributes back to the server markup, strippingdisplay:none/data-tiptap-bound; a per-editor observer re-asserts the hidden state in place (no remount). Mark the editor regionhx-preservefor pure-attribute morphs. Note: once an editor is live it owns the content — a server-side value change delivered via morph is overwritten on the next edit.
Opting a field out of auto-mount — set manualMount: true (per field, or in
TIPTAP_DEFAULT_CONFIG) when renderers/extensions must be registered before the editor
builds. The automatic triggers (initial scan + the MutationObserver) then skip it; mount it
yourself once you're ready with DjangoTipTap.autoMount() (mounts every field, including
manualMount ones) or DjangoTipTap.init(el, config). See Theming → Load order.
Explicit init (Path B)¶
For full control — and for the onChange callback, which can't travel through the JSON
data-tiptap-config attribute:
<textarea id="ed"></textarea>
<script>
const editor = DjangoTipTap.init(document.getElementById("ed"), {
height: "400px",
imageUploadUrl: "/editor/upload/",
mergeTags: [{ label: "First name", value: "{{ first_name }}" }],
onChange(html) { /* live preview, autosave, … */ },
});
</script>
See Extending for registerExtension / registerButton and the load
order rules, and Theming for setTokens, setRenderer, and setShellRenderer.