Horilla CRM for Developers: Five Coding Features Worth Building On
Horilla CRM for Developers: Five Coding Features Worth Building On
You’ve seen the product pitch. This post is narrower: what Horilla CRM gives you as a Django developer when you need to extend, integrate, or ship an internal module — without turning every change into a core fork.
Repo: github.com/horilla/horilla-crm
We’ve already written in depth about AppLauncher and app startup wiring. If you need those primers, start here and come back:
-
Plugin architecture with
AppLauncher -
registration.py,menu.py, and what loads at startup - Product overview: Django CRM for modern businesses · self-hosted CRM
Below is the coding surface you’ll actually touch day to day.
1. Plugin-based architecture (apps that join the platform)
Horilla treats CRM and contrib modules as installable units: declare URLs, assets, menus, signals, and schedules on the app config, and the platform wires them in. You don’t maintain a growing checklist of root urls.py / ready() edits for every feature.
Benefit: new work stays inside your app package. Upgrades stay rebases, not merge conflicts in core wiring.
Practical use: a private AppLauncher app for a client’s custom approval flow, lead intake, or reporting widget — installed beside horilla_crm like any other Django app.
For the how, use the AppLauncher posts linked above; this post won’t re-walk that design.
2. Open-source customization that stays upstream-friendly
Open source only helps if customization has stable seams. Horilla’s are deliberate:
| Seam | What you use it for |
|---|---|
horilla.web / db / views.generic / shortcuts / urls
|
One import map instead of mixing Django and Horilla helpers |
Generics (HorillaListView, detail tabs, kanban, forms) |
CRUD and CRM chrome without rewriting shells |
| Feature registries | Opt models into Custom Fields, layouts, requirements — no hard-coding Lead into platform apps |
horilla.extension (_inherit_*) |
Extend shared forms/views/lists from your app instead of monkey-patching theirs |
Benefit: you customize at the edges; core stays mergeable.
Practical use: add fields or validation on Lead create via a Form/View extension in your package, not a patched copy of horilla.contrib.generics.
(We care about developer tooling outside the CRM too — e.g. cutting Claude Code token costs — but the CRM’s own extension seams are what keep custom work maintainable.)
3. Developer-friendly API integrations
Horilla ships a DRF API layer. Apps contribute routes through the same plugin lifecycle (get_api_paths() and friends). Auth patterns (JWT / session) match what Django API teams already run.
What that buys you:
- Module endpoints for CRM entities (leads, accounts, opportunities, …)
- Company-scoped serializers and permissions so multi-tenant rules aren’t reinvented per client
- Shared mixins for search / bulk-style payloads when you need sync jobs
Benefit: integrations speak JSON against the same tenancy and permission story as the UI.
Practical use: a nightly job that upserts opportunities into a warehouse, or a lightweight mobile client that never scrapes HTMX HTML.
4. UX built for Django developers (HTMX, not a second SPA)
The UI is server-rendered + HTMX: list/detail shells, modals, tab swaps, OOB fragments. You debug with Django templates and responses — including helpers like ScriptResponse — instead of owning a separate frontend build for every CRM screen.
Benefit: product polish (kanban, activities, settings) and your custom screens share one interaction model.
Practical use: a new related list or settings page inherits search, empty states, and HTMX create/edit from generics; you fill in columns and permissions.
5. Performance as patterns you can copy
Recent platform work isn’t only “make it faster” — it’s query discipline you should reuse in custom code:
- Resolve ContentTypes / parents once; reuse in history, related lists, tabs
- Prefetch what calendars and assignees need before serializing
- Pass already-loaded related objects through kanban-style pipelines
- Build public links with a shared absolute-URL helper (
SITE_URL) so Celery emails don’t invent relative paths
Benefit: custom features don’t reintroduce the N+1s the core just removed.
Practical use: when you add a detail tab that lists related records, take the parent from the view context instead of Model.objects.get(pk=…) again.
Closing
Horilla CRM is meant to be extended like a platform: plug apps in, customize at documented seams, integrate over DRF, ship UI in the HTMX shell, and write new code with the same performance habits as core.
Clone it, star it if it’s useful, and build on the seams — not on a fork of generics:
https://github.com/horilla/horilla-crm
What still forces you to patch core today? Drop it in the comments — that’s the highest-signal feedback for the extension roadmap.