Skip to main content

djangocms-text

pypi coverage pre-commit.ci status PyPI - Python Version PyPI - Django Versions from Framework Classifiers PyPI - django CMS Versions from Framework Classifiers

djangocms-text is a flexible and extensible rich text editing solution for Django CMS. This package is designed as a replacement for djangocms-text-ckeditor, introducing a swappable rich text editor interface and supporting enhanced data storage in both HTML and JSON formats.

Features

  • Swappable editors — switch between TipTap (default), CKEditor 4, and CKEditor 5 frontends.

  • HTML and JSON storage — store content in either format, depending on your use case.

  • Inline editing — click a text plugin to edit it directly in edit mode.

  • Text-enabled plugins — embed CMS plugins inline within rich text.

  • Drop-in for djangocms-text-ckeditor — automatic data migration on install.

Installation

  1. Install: pip install djangocms-text

  2. Add to INSTALLED_APPS:

    INSTALLED_APPS = [..., "djangocms_text", ...]
  3. Run migrations: python manage.py migrate djangocms_text

  4. Start your server and add a Text plugin.

The default editor is TipTap and is included in the base package. To use a different editor, install its frontend package and set TEXT_EDITOR:

INSTALLED_APPS = [..., "djangocms_text.contrib.text_ckeditor4", ...]
TEXT_EDITOR = "djangocms_text.contrib.text_ckeditor4.ckeditor4"

Editors

djangocms-text ships with several editor frontends that can be swapped via the TEXT_EDITOR setting:

  • TipTap (default) — A modern, modular rich text editor. Supports text-enabled plugins, dynamic links, inline editing, and on-the-fly Markdown conversion. Does not allow direct HTML source editing.

  • CKEditor 4 — Compatible with djangocms-text-ckeditor and usable as a drop-in replacement. Supports inline editing and text-enabled plugins.

  • CKEditor 5 — Available as a separate package (djangocms-text-ckeditor5) to keep licenses separated. Fully supports text-enabled CMS plugins and dynamic links.

Configuration

Selecting an editor

Add the editor’s package to INSTALLED_APPS and point TEXT_EDITOR at its RTEConfig path:

INSTALLED_APPS = [
    ...,
    "djangocms_text.contrib.text_ckeditor4",
    ...,
]
TEXT_EDITOR = "djangocms_text.contrib.text_ckeditor4.ckeditor4"

Global editor configuration

The TEXT_EDITOR setting points to a RTEConfig object. The following attributes are available:

  • name (str) — The name of the RTE configuration.

  • config (str) — The configuration string.

  • js (Iterable[str]) — JavaScript files to include.

  • css (dict) — CSS files to include.

  • admin_css (Iterable[str]) — CSS files for the admin interface only.

  • inline_editing (bool) — Whether to enable inline editing.

  • child_plugin_support (bool) — Whether to support child plugins.

  • configuration (dict) — Frontend-specific options.

  • additional_context (dict) — Additional context to pass to the editor.

The default configuration is:

DEFAULT_EDITOR = RTEConfig(
    name="tiptap",
    config="TIPTAP",
    js=("djangocms_text/bundles/bundle.tiptap.min.js",),
    css={"all": ("djangocms_text/css/bundle.tiptap.min.css",)},
    admin_css=("djangocms_text/css/tiptap.admin.css",),
    inline_editing=True,
    child_plugin_support=True,
    configuration={},  # see below
)

Use admin_css to include CSS files loaded into the dialog window, e.g. to declare custom colors or other styles.

Frontend-specific configuration

Frontend-specific options live in the configuration property of the RTEConfig. The contents depend on the rich text editor frontend (TipTap, CKEditor 4, etc.).

The preferred way to set them is via TEXT_EDITOR_SETTINGS, which mirrors RTEConfig.configuration. For backwards compatibility with djangocms-text-ckeditor, CKEDITOR_SETTINGS is also forwarded to the frontend (even when the frontend is not CKEditor 4).

Example TipTap configuration (matches the defaults):

DEFAULT_EDITOR.configuration = {
    "inlineStyles": [  # Styles menu, by default contains some rarer styles
        { "name": 'Small', "element": 'small' },
        { "name": 'Kbd', "element": 'kbd' },
        { "name": 'Var', "element": 'var' },
        { "name": 'Samp', "element": 'samp' },
    ],
    "blockStyles": [],
    # Block styles menu, e.g., for paragraphs; empty by default.
    # Example: [{"name": "Lead", "element": "p", "attributes": {"class": "lead"}}]
    # Styles for p and h1-h6 set the attributes on the element itself;
    # styles for container elements (div, section, ...) wrap the selected blocks.
    "textColors": {  # Colors offered for the text color menu - the keys are CSS classes
        'text-primary': {"name": "Primary"},
        'text-secondary': {"name": "Secondary"},
        'text-success': {"name": "Success"},
        'text-danger': {"name": "Danger"},
        'text-warning': {"name": "Warning"},
        'text-info': {"name": "Info"},
        'text-light': {"name": "Light"},
        'text-dark': {"name": "Dark"},
        'text-body': {"name": "Body"},
        'text-muted': {"name": "Muted"},
    },
    "tableClasses": "table",  # classes added to new tables
}

Three ways to configure the classes added to new tables:

# Option 1: modify the default editor configuration in place
from djangocms_text.editors import DEFAULT_EDITOR
DEFAULT_EDITOR.configuration["tableClasses"] = "table ui"

# Option 2: offer a list of named choices
from djangocms_text.editors import DEFAULT_EDITOR
DEFAULT_EDITOR.configuration["tableClasses"] = [
    ["table", _("Default")],
    ["table table-striped", _("Striped")],
]

# Option 3: use TEXT_EDITOR_SETTINGS in settings.py
TEXT_EDITOR_SETTINGS = {
    "tableClasses": "table ui",
}

Security and resource limits

The link picker resolves django CMS pages by default. Additional model types must be explicitly allowlisted, registered with Django’s admin, implement get_absolute_url(), and grant the requesting user view permission:

TEXT_LINKABLE_MODELS = ("cms.page", "news.article")

Embedded base64 images are decoded with Pillow and re-encoded before the configured TEXT_SAVE_IMAGE_FUNCTION receives them. The default limits are 5 MiB of decoded data and 25 million pixels; deployments can lower them:

TEXT_SAVE_IMAGE_MAX_BYTES = 2 * 1024 * 1024
TEXT_SAVE_IMAGE_MAX_PIXELS = 12_000_000

Inline editing

Inline editing lets editors click a text plugin and change its contents directly in django CMS edit mode. The editor appears around the text field, and changes are saved as soon as the field loses focus.

Inline editing wraps the HTML in a <div> in edit mode, which may interact with site CSS that uses direct child selectors.

Inline editing is enabled by default. To disable it:

TEXT_INLINE_EDITING = False

When enabled, a toolbar toggle lets users switch inline editing on and off for the current session. If only text changes, editing continues seamlessly. If a text-enabled plugin was added, changed, or removed, the page refreshes to update the page tree and re-render the affected plugins.

For security, opening a new Text plugin form no longer creates a temporary database record. Save the new Text plugin once before adding inline child plugins such as images or links.

Custom plugin templates

If you override the default cms/plugins/text.html and wrap the plugin output in container elements (e.g. <section>, <article>, <div>), the innermost container must have the class cms-content-start:

<!-- cms/plugins/text.html -->
<section class="my-text-plugin">
    <div class="cms-content-start">
        {{ body|safe }}
    </div>
</section>

This tells the inline editor which element to use as the editable area. Without it, the editor will treat the outermost container as the editable region, which may include non-editable markup.

Text-enabled plugins

djangocms-text supports text-enabled plugins (note: not all editor frontends do — see Editors).

To make a plugin available inside Text plugins, set text_enabled = True on its plugin class:

class MyTextPlugin(TextPlugin):
    name = "My text plugin"
    model = MyTextModel
    text_enabled = True

The plugin then appears in the CMS Plugins dropdown (puzzle icon) in the editor and is previewed inline.

Pro-tip: Provide an icon_alt method on the plugin so that, when many text_enabled plugins are available, users get a useful tooltip — for example, the name of the product whose price the plugin shows.

Text-enabled plugins can also have their own icons. Add a text_icon property containing SVG source code:

class MyTextPlugin(TextPlugin):
    name = "My text plugin"
    model = MyTextModel
    text_enabled = True
    text_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/></svg>'

The icon shows in the CMS plugin pulldown menu and toolbar. To make a text-enabled plugin directly accessible from the editor toolbar, add its name (e.g. "LinkPlugin") to the toolbar configuration.

For more on extending the CMS with plugins, see the django-cms doc.

Default content in placeholders

You can use TextPlugin in default_plugins (see the CMS_PLACEHOLDER_CONF setting docs). TextPlugin requires a single value: body for the default HTML content. To attach default children (e.g. a LinkPlugin), reference them in the body using "%(_tag_child_<order>)s" placeholders:

CMS_PLACEHOLDER_CONF = {
    'content': {
        'name' : _('Content'),
        'plugins': ['TextPlugin', 'LinkPlugin'],
        'default_plugins':[
            {
                'plugin_type':'TextPlugin',
                'values':{
                    'body':'<p>Great websites : %(_tag_child_1)s and %(_tag_child_2)s</p>'
                },
                'children':[
                    {
                        'plugin_type':'LinkPlugin',
                        'values':{
                            'name':'django',
                            'url':'https://www.djangoproject.com/'
                        },
                    },
                    {
                        'plugin_type':'LinkPlugin',
                        'values':{
                            'name':'django-cms',
                            'url':'https://www.django-cms.org'
                        },
                    },
                ]
            },
        ]
    }
}

HTML sanitizer

djangocms-text uses nh3 to sanitize HTML, both for security and to enforce well-formed markup. Sanitization may strip tags useful for some use cases (e.g. iframe).

HTML is sanitized when it is written through Django’s ORM. It is not sanitized again when read from the database; stored values are treated as trusted HTML. Code that writes directly to the database, bypassing the ORM, must therefore sanitize HTML before storing it.

Customize allowed tags and attributes via TEXT_ADDITIONAL_ATTRIBUTES:

TEXT_ADDITIONAL_ATTRIBUTES = {
    'iframe': {'scrolling', 'allowfullscreen', 'frameborder'},
}

The dictionary maps tag names to sets of allowed attribute names.

If you have settings in the older djangocms-text-ckeditor style using both TEXT_ADDITIONAL_TAGS and TEXT_ADDITIONAL_ATTRIBUTES, those will be translated automatically — with a warning from the Django checks framework at server startup.

Note: Some frontend editors pre-sanitize content before sending it to the server, which can render the above settings ineffective.

To disable sanitization entirely, set TEXT_HTML_SANITIZE = False.

Markdown support

The TipTap frontend includes (minimal) Markdown support:

  • Markdown is converted to HTML when pasting. (To prevent XSS attacks, pasted content is not converted if it contains JavaScript.)

  • When typing, Markdown syntax is converted on the fly.

Supported syntax:

  • Headings: # Heading 1, ## Heading 2, ### Heading 3, etc.

  • Bold: **bold text** or __bold text__

  • Italic: *italic text* or _italic text_

  • Strikethrough: ~~strikethrough~~

  • Links: [link text](http://example.com)

  • Lists: - Item or * Item for unordered, 1. Item for ordered.

  • Blockquotes: > Quote

  • Code: `inline code` for inline code, triple backticks for code blocks.

  • Tables (pasting only): use | to separate columns:

    | Header 1 | Header 2 |
    |----------|----------|
    | Row 1    | Row 2    |
  • Horizontal rules: ---

Optional contrib extensions

Three small contrib packages ship with djangocms-text as demos of how to extend the TipTap editor dynamically — at runtime, through the window.CMS_Editor.tiptap registry, without forking djangocms-text or pulling a second copy of TipTap into the page. The full registry contract is documented in tiptap-extensions.md.

Each is opt-in: add the relevant app to INSTALLED_APPS. The READMEs linked below cover what each package adds, the configuration knobs it exposes, and the integration pattern it illustrates.

Migrating from djangocms-text-ckeditor

djangocms-text’s migrations automatically migrate existing text plugins from djangocms-text-ckeditor and clean up old tables. To migrate:

  1. Uninstall djangocms-text-ckeditor.

  2. Remove djangocms_text_ckeditor from INSTALLED_APPS.

  3. Add djangocms_text to INSTALLED_APPS (see Installation).

  4. Run python -m manage migrate djangocms_text.

Attention: The migration also deletes djangocms-text-ckeditor’s tables from the database (to avoid referential integrity issues). Make a backup beforehand to be safe.

Switching from CKEditor 4 to TipTap

When transitioning from CKEditor 4 to TipTap (the new default), keep these points in mind:

  • No HTML source editing — TipTap does not support direct HTML source editing. This simplifies the editor for most users but may be a drawback for advanced users or developers who manually edit HTML.

  • Loss of non-standard formatting — TipTap stores content in an abstract JSON format and regenerates HTML on edit. Formatting created via CKEditor 4 plugins or manually added HTML classes may not be preserved. This only happens on first edit after migration.

  • Keep using CKEditor 4 if needed — If retaining CKEditor 4 behavior is essential, use the CKEditor 4 backend that ships with djangocms-text.

You can continue to use CKEditor 4 for migration compatibility. CKEditor 4 is end-of-life, so TipTap is recommended for new deployments. The bundled integration disables source editing and other unsafe legacy plugins, and its content-filter restrictions cannot be relaxed through editor settings. Compared to djangocms-text-ckeditor, the CKEditor 4 sources have moved to static/djangocms_text/vendor/ckeditor4. Update any custom CKEditor 4 plugins accordingly.

Usage outside django CMS

django CMS Text can be used without django CMS installed. It provides HTMLField, HTMLFormField, and the TextEditorWidget class for use with any Django model or form.

When django CMS is not installed alongside django CMS Text, add this to your MIGRATION_MODULES setting to skip creation of the CMS plugin model:

MIGRATION_MODULES = {
    ...,
    "djangocms_text": None,
    ...,
}

Contributing

Contributions to djangocms-text are welcome! See the contributing guidelines to get started.

pre-commit hooks

The repo uses pre-commit git hooks to ensure code quality. Install with:

pip install pre-commit
pre-commit install

Building the JavaScript

djangocms-text distributes a JavaScript bundle containing the editor frontends and CMS integration. To rebuild the bundle:

nvm use
npm install
npm run build

Other build scripts:

  • npm run build:dev — unminified development build

  • npm run watch — rebuild on file changes

Acknowledgments

Special thanks to the django CMS community and to all contributors to the djangocms-text-ckeditor project.

License

This project is licensed under the BSD-3-Clause License — see the LICENSE file for details.

Metadata

Release files for djangocms-text 1.0.2

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

Source distribution (sdist)

Source distribution for djangocms-text 1.0.2
File Size Uploaded
djangocms_text-1.0.2.tar.gz 2.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for djangocms-text 1.0.2
File Interpreter ABI Platform
djangocms_text-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 5.0 MB

Release files / djangocms_text-1.0.2.tar.gz

Download URL djangocms_text-1.0.2.tar.gz
Size 2.2 MB
Tags Source
SHA-256 checksum
How to use checksums
26f8cef53f7b1396ed76ccf1ec1b479d3ad160276a5b72202282a5407a743638
BLAKE2b-256 checksum
How to use checksums
72cff0cafb90c23092d1e950ae3eb9739464e501677b36243d6a506f8be7bfcd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / djangocms_text-1.0.2-py3-none-any.whl

Download URL djangocms_text-1.0.2-py3-none-any.whl
Size 2.8 MB
Tags Python 3
SHA-256 checksum
How to use checksums
9bbd1fef41ca280fc5e0c506875cf1ddbd084dafb6d1a4dd7f15ff85635fbe68
BLAKE2b-256 checksum
How to use checksums
68bf1f5bb3039be5a507fda9a775dc6061970dde9366385a4395a0cb8642b884
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.2 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

0.9.11

2 release files

0.9.9

2 release files

0.9.8

2 release files

0.9.7

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.3

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

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