Open-source project
danirus/django-comments-xtd avatar
danirus/django-comments-xtd

django-comments-xtd: threaded comments for Django with notifications

A pluggable Django comments application with thread support, follow-up notifications, mail confirmation, like/dislike flags, moderation, a ReactJS plugin and Bootstrap 5.3.

621 stars159 forksHTMLBSD-2-Clause

At a glance

What is it?
django-comments-xtd is a pluggable Django application that takes the framework's own comments app and adds the things a modern comment section needs: nesting, follow-up notifications by email, confirmation before anything is written, votes, flags, moderation, several themes, a plain JavaScript plugin, and an API so you can build your own front end. Version three is in development, with a rewritten front end and an end-to-end project.
Who is it for?
django-comments-xtd fits a Django project that already uses the framework's comments application and needs threads and notifications without writing them, especially a site with registered readers who discuss articles in long threads. It does not fit a project that wants a JavaScript framework to own the comment UI, unless you are prepared to use the API and build the interface yourself.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What it adds, and what it replaces

django-comments-xtd is a Django pluggable application that adds comments to your project, and the key word in that sentence is pluggable. It is not a replacement for the framework's comments application; it extends it.

The parent application is the Django Comments Framework, and the readme is explicit that this project adds to it rather than replacing it. That decision has consequences for adoption. The models, the admin, the permission model, the moderation queue, and the management commands are the ones you already know, and the migration path from the stock app is a matter of swapping the application in the settings and running the migrations. If you are starting a new project and do not care, the difference is smaller.

The feature list is the substance. Thread support, so comments can be nested. A customisable maximum thread level, on a per model basis, which is the setting that decides whether a discussion collapses at three replies or runs to fifty. Optional notifications on follow-up comments by email. Mute links, so a reader can cancel the notifications they subscribed to. Comment confirmation by email when the user is not authenticated. Comments that hit the database only after they have been confirmed. Comment reactions, votes, and flagging, each of which can be enabled per model. Reactions that the visitors choose are customisable, and the default is a thumb up and a thumb down. Several themes. A plain vanilla JavaScript plugin. And an API to support your own front end.

That last item and the one before it are related, and together they are the project's most unusual feature. A comment system that ships a JavaScript plugin is a comment system that owns the browser side of the interaction. A comment system that also ships an API is a comment system that will let you replace that plugin with a single-page application, a React component, or whatever your team prefers, without reimplementing the threading, the notifications, or the moderation.

The readme ends the list with an ellipsis, which is a nice touch of honesty about a growing feature set.

The supported matrix is the first thing to check. The example sites and the test suite run under the officially supported Django versions, which are 6.0, 5.1, 5.0, and 4.2, and under Python 3.14, 3.13, 3.12, and 3.11. The additional dependencies are the parent comments application and the REST framework, with a version range on the latter.

The project is BSD 2 clause licensed, and the documentation is published on Read the Docs with a tutorial and examples.

Threads, follow-ups, and the mute link

Threading is the feature the name is about, and the detail that makes it usable is the configurable depth.

A nested comment system needs a rule about how deep a thread goes, and the rule is per model rather than global. A blog with long analytical posts might allow a dozen levels, because a reply to a reply to a reply is still on topic. A page with a product listing might want two, because anything deeper is noise. Since the model is the unit that a comment is attached to, and the readme says the maximum level is set on a per model basis, the same installation can host both.

The follow-up notification system is the other half of the threading story, and its design is worth reading because it solves a problem that is easy to get wrong.

The problem is that a notification system without an escape hatch is a system people mute by leaving the site. So the package includes mute links, which are per subscription rather than global, and a reader who no longer wants the mail can cancel it from the mail itself.

That is a small piece of design with a large effect on how the feature is perceived. A notification that says how to stop it is a notification people tolerate; one that does not is a notification people route to a folder they never read, and a folder nobody reads is the same as no notification but with the cost of the mail delivery.

And the notifications are optional, which is a separate decision. A comment system for a site with registered readers may want them; one for a site with anonymous comments almost certainly does not, and the package lets you choose per installation rather than making it a global switch on the framework's own commenting behaviour.

The combination of these three is what makes threading practical for a site that cares about its readers' attention. The depth limit keeps the page readable, the mute link keeps the feature acceptable, and the opt-in keeps it from being imposed.

And there is a fourth piece that belongs with them, in the form of a one-line reason for every decision, which is the same idea that appears in the design of the agent project in the same batch of work: the documentation is the memory of the project, and a decision without a recorded reason is one that the next change will quietly undo.

Confirmation before the database

The confirmation feature is the one that changes the shape of the data flow, and it is worth understanding exactly what it does.

Comment confirmation by email when the user is not authenticated, and then the consequence: comments hit the database only after they have been confirmed.

That second half is the part that matters. A confirmation flow where the comment is written first and deleted if the mail is not answered leaves the moderation queue full of spam, and it means the comment counts in the page views and the search index before anybody has vouched for it. Writing the comment only after the confirmation means the queue stays clean and the content never appears until it is legitimate.

The cost is a round trip through email, and that is a real cost. The comment is not visible when the reader submits it, it appears a minute later, and if the mail does not arrive the comment simply does not exist. For a site with anonymous comments that is a reasonable price for not having a comment section full of links. For a site where comments are logged in, it is pointless, and the feature is presumably for the anonymous case only, which is what the readme says.

The thing that makes this feature pleasant rather than merely defensible is the second half of the design: the mute link for follow-up notifications and the confirmation mail are the same mechanism applied to two different things. A reader gets one mail asking them to confirm a comment, and a reader who subscribes to a thread gets a mail with a cancel link in it. Both are the same idea, which is that a mail the reader did not ask for has to be answerable by mail.

The three related settings that can be enabled per model are the reactions, the votes, and the flagging, and the per-model switch is the same switch as the thread depth, for the same reason. Different kinds of content attract different behaviour. A blog post that is worth arguing about gets votes and flags; a page with a comment form in the footer does not need them, and turning them off there keeps the database smaller and the templates simpler.

The reactions deserve one more note, because the default is a thumb up and a thumb down and that default is configurable. The set of reactions a visitor can choose is a project decision rather than a framework decision, and a community that wants something other than approval and disapproval can have it.

The API, the themes, and the front end

The front end of the package is built with a specific set of tools, and the manifest is a good summary of the approach.

The front end assets live in a directory of static files, and the compiled JavaScript is a single minified file, with a Sass source directory at the root of the package manifest. The manifest itself is marked private, because it is not published to the package index: it describes the build of the assets rather than the distribution of the Python package, and the two versions are separate, with the front end at a beta version and the Python package at its own release line.

That separation is the point. The Python package is published with its static assets already built, and the front end is built with a toolchain that has nothing to do with the Python one, so the two can move at their own speeds.

The styling pipeline is the interesting part of the manifest, because it is a complete pipeline written out as scripts. There is a clean step that removes the generated files, with separate targets for the package's own styles and the example site's styles, so the two do not interfere. There is a compile step that runs the Sass compiler with expanded output, source maps, embedded sources, and a flag that tells it not to fail on CSS errors. There is a lint step that runs the linter with a cache, and a separate step that checks the variables in the Sass source, which is a different check from checking the compiled output: it catches a variable that is defined but never used, or used but never defined, which the CSS linter cannot see. And there is a minification step with a specific optimisation level and a source map.

The flag that tells the compiler not to fail on CSS errors is worth a moment's thought, because it is the sort of thing that looks like a mistake. It is a deliberate choice in a build where the styles are compiled as part of a larger process: a stylesheet warning should not stop a release, and the errors that matter are caught by the linter step, which does fail the build.

The themes are a separate set of stylesheets rather than a single one, and the readme calls them simply several themes available. Since the styling is a Sass source with a variable lint step, the themes are almost certainly variations on that source, which means adding one is a matter of a variables file rather than a new template.

And the JavaScript plugin is described as plain vanilla JavaScript, with a Babel configuration file at the repository root and an ESLint flat configuration. The word plain is doing a lot of work in that sentence: it means no framework, which means the plugin works on a page that has no build step of its own, and it means the React front end in the project description is an alternative rather than a requirement.

So there are two ways to use the front end. Take the plugin as it is, or take the API and write your own. The readme lists both, and the existence of the example site's separate styles and separate JavaScript shows that the second path is a supported one rather than a theoretical one.

The build, the two test suites, and the work on version three

The make file is small, and the two test targets are the interesting part.

The Python tests run under coverage, with the source pinned to the package directory, and the report is generated with per line detail. The coverage configuration in the package manifest uses a Django-specific coverage plugin rather than the plain source filter, which is what lets the measurement understand the framework's URL dispatch and avoid reporting a view as uncovered because it is reached through a route that the plugin cannot see.

The JavaScript tests run through the package manager, so the front end has its own suite, separate from the Python one.

Running two suites is the right call for a package that ships both, and the cost is that a contributor has to have both toolchains available. The make file's help target lists the tasks with descriptions pulled out of the comments, which is the convention that makes a make file readable without opening it.

The build target chains the steps that have to happen for a release: clean, compile the styles, build the JavaScript, and then build the Python package. The styles and the scripts come first because their output is what gets packaged, so a stale asset would otherwise be shipped.

The documentation has its own targets, with a separate clean and build and a serve command that runs a plain static file server on a specific port over the built directory.

Now the status of version three, which the readme puts at the top. The work on version three is taking place in the main branch, and the readme points anybody curious at the end-to-end project directory. There is still a list of points to be done before it is ready, kept in a file at the root of the repository.

And the repository layout supports that story. There is the Python package, the documentation, the example sites, the end-to-end project, a directory of JavaScript sources, the Sass source, the end-to-end test configuration, a file for the end-to-end test runner, a Grunt file, a Babel configuration, an ESLint configuration, a Stylelint configuration, a pre-commit configuration, a tox configuration for running the tests against several environments, and a file listing the authors.

The examples directory is the part that tells you how the package is meant to be evaluated. There is a simple example, a compound one, a custom one, a shared directory with the common frontend, a project of quotes, and a compressed tutorial. Six example sites rather than one, each presumably showing a different configuration, which is the right way to demonstrate a pluggable application: the point of a pluggable application is that it is configured differently in every project, and examples are the only honest way to show that.

Editorial conclusion

django-comments-xtd fits a Django project that already uses the framework's comments application and needs threads and notifications without writing them, especially a site with registered readers who discuss articles in long threads. It does not fit a project that wants a JavaScript framework to own the comment UI, unless you are prepared to use the API and build the interface yourself. Verify first that the Django version you are on is in the supported list, since the package pins a range, that the notifications and confirmation flows are what you want, since they add a round trip through email that the plain framework does not have, and that the theme you pick matches your site's design, because the styling is a separate layer from the application.

Frequently asked questions

What does django-comments-xtd add to the Django comments framework?

Thread support with a configurable maximum depth per model, optional follow-up notifications by email with mute links, email confirmation for unauthenticated comments, reactions, votes and flagging that can be enabled per model, several themes, a plain JavaScript plugin, and a REST API so you can supply your own front end.

What do the mute links in django-comments-xtd do?

They let a reader cancel a follow-up notification subscription from the notification itself, rather than having to find a settings page. It is the same mechanism the comment confirmation mail uses, and it is what makes the notification feature tolerable for a reader who does not want it.

How does django-comments-xtd handle comments from users who are not logged in?

The comment is confirmed by email before it is written to the database, so an unconfirmed comment never reaches the moderation queue, the page view counts, or the search index. The trade is a round trip through email, so the comment appears a minute later rather than immediately.

Which Django and Python versions does django-comments-xtd support?

The example sites and the test suite run under the officially supported Django versions, which are 6.0, 5.1, 5.0 and 4.2, and under Python 3.14, 3.13, 3.12 and 3.11. The package itself requires Python 3.12 or newer and pins a range of Django versions.

Can I use a React front end with django-comments-xtd?

Yes, through the REST API it provides, which exists to support your own front end. The bundled JavaScript plugin is deliberately plain and framework-free, so the two are alternatives: take the plugin as it is, or take the API and build the interface yourself, as the example sites do.

Official sources

  1. danirus/django-comments-xtd on GitHub
  2. License: BSD-2-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/danirus-django-comments-xtd.svg)](https://hysenlabs.com/projects/danirus-django-comments-xtd)

Was this useful?