Official ParadeDB integration for Django
Project description
Simple, Elastic-quality search for Postgres
Website • Docs • Community • Blog • Changelog
django-paradedb
The official Python client for ParadeDB — Elastic-quality full-text, similarity, and hybrid search inside Postgres — built for the Django ORM.
Features
- BM25 index management through Django migrations
- Full-text search with
Match,Term,FuzzyTerm,Regex,PhrasePrefix, and more - Faceted search and aggregations (
TopK,TopKWithCount,Percentile,Stats, and customAgg) - Relevance scoring with
Score()annotation - Hybrid search via Reciprocal Rank Fusion (RRF)
- More Like This queries for document similarity
- Autocomplete with prefix matching and fuzzy tolerance
- Composable with Django's
Qobjects,filter(),exclude(), and custom managers - Diagnostic management commands for index health and verification
- Type-aware with a
py.typedpackage marker and typed public APIs
Requirements & Compatibility
| Component | Supported |
|---|---|
| Python | 3.10+ |
| Django | 4.2+ |
| ParadeDB | 0.21.0+ |
| PostgreSQL | 15+ (with ParadeDB extension) |
Installation
pip install django-paradedb
or with uv:
uv add django-paradedb
Quick Start
Prerequisites
This guide assumes you have installed pg_search, and have configured your Django project with
the Postgres database where pg_search is installed.
Create an Index
Add a BM25 index to your model and use ParadeDBManager:
from django.db import models
from django.contrib.postgres.fields import IntegerRangeField
from paradedb.indexes import BM25Index
from paradedb.queryset import ParadeDBManager
class MockItem(models.Model):
description = models.TextField(null=True, blank=True)
rating = models.IntegerField(null=True, blank=True)
category = models.CharField(max_length=255, null=True, blank=True)
in_stock = models.BooleanField(null=True, blank=True)
metadata = models.JSONField(null=True, blank=True)
created_at = models.DateTimeField(null=True, blank=True)
last_updated_date = models.DateField(null=True, blank=True)
latest_available_time = models.TimeField(null=True, blank=True)
weight_range = IntegerRangeField(null=True, blank=True)
objects = ParadeDBManager()
class Meta:
db_table = "mock_items_django"
indexes = [
BM25Index(
fields={
"id": {},
"description": {"tokenizer": "unicode_words"},
"category": {"tokenizer": "literal"},
"rating": {},
"in_stock": {},
"metadata": {"json_fields": {"fast": True}},
"created_at": {},
"last_updated_date": {},
"latest_available_time": {},
"weight_range": {},
},
key_field="id",
name="search_idx",
),
]
Run migrations to create the index:
python manage.py makemigrations
python manage.py migrate
The json_fields option enables native dotted-path access for JSON subfields
such as metadata.color in facets and aggregations.
Index Computed Expressions
You can index computed expressions using IndexExpression. This allows indexing
transformed values or combinations of fields:
from django.db.models import F
from django.db.models.functions import Lower
from paradedb.indexes import BM25Index, IndexExpression
class Article(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
views = models.IntegerField(default=0)
class Meta:
indexes = [
BM25Index(
fields={"id": {}, "title": {}, "body": {}},
expressions=[
# Text expression with tokenizer
IndexExpression(
Lower("title"),
alias="title_lower",
tokenizer="simple",
),
# Non-text expression with pdb.alias
IndexExpression(
F("views"),
alias="views_indexed",
),
],
key_field="id",
name="article_search_idx",
),
]
For text expressions, specify a tokenizer. For non-text expressions (integers,
timestamps, etc.), omit the tokenizer to use pdb.alias.
Generate Test Data
To demonstrate search, we need to populate the table we just created. First, open a Python shell:
python manage.py shell
And paste the following commands:
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
CALL paradedb.create_bm25_test_table(
schema_name => 'public',
table_name => 'mock_items'
);
""")
cursor.execute("""
INSERT INTO public.mock_items_django
SELECT * FROM public.mock_items;
""")
cursor.close()
Text Search
Search with a simple query:
from paradedb.search import ParadeDB, Match, Term
# Single term
MockItem.objects.filter(description=ParadeDB(Match('shoes', operator='AND')))
# Multiple terms (explicit AND)
MockItem.objects.filter(description=ParadeDB(Match('running', 'shoes', operator='AND')))
# OR across terms
MockItem.objects.filter(description=ParadeDB(Match('shoes', 'boots', operator='OR')))
# Fuzzy search (typo tolerance via distance)
MockItem.objects.filter(description=ParadeDB(Match('shoez', operator='OR', distance=1)))
# Fuzzy prefix (distance + prefix matching)
MockItem.objects.filter(description=ParadeDB(Term('runn', distance=1, prefix=True)))
# Fuzzy transposition-cost-one
MockItem.objects.filter(description=ParadeDB(Term('shose', distance=1, transposition_cost_one=True)))
Annotate with BM25 relevance score and sort by it:
from paradedb.functions import Score
MockItem.objects.filter(
description=ParadeDB(Match('shoes', operator='AND'))
).annotate(
score=Score()
).order_by('-score')
Django ORM Compatibility
django-paradedb works seamlessly with Django's ORM features:
from django.db.models import Q
from paradedb.search import ParadeDB, Match
# Combine with Q objects
MockItem.objects.filter(
Q(description=ParadeDB(Match('shoes', operator='AND'))) & Q(rating__gte=4)
)
# Chain with standard filters
MockItem.objects.filter(
description=ParadeDB(Match('shoes', operator='AND'))
).filter(
category='footwear'
).exclude(
rating__lt=4
)
Custom Manager
If you have a custom manager, compose it with ParadeDBQuerySet:
from paradedb.queryset import ParadeDBQuerySet
class CustomManager(models.Manager):
def active(self):
return self.filter(is_active=True)
CustomManagerWithParadeDB = CustomManager.from_queryset(ParadeDBQuerySet)
class MockItem(models.Model):
objects = CustomManagerWithParadeDB()
Diagnostics Helpers and Commands
django-paradedb includes helper functions for ParadeDB diagnostic table functions and
optional Django management commands:
paradedb_indexes()paradedb_index_segments()paradedb_verify_index()paradedb_verify_all_indexes()
Python helper example:
from paradedb.functions import paradedb_indexes, paradedb_verify_index
# Uses Django's default DB alias ("default")
rows = paradedb_indexes()
# Multi-DB: run against a specific database alias
checks = paradedb_verify_index("search_idx", using="search")
Management command examples:
# Uses Django's default DB alias ("default")
python manage.py paradedb_indexes
# Multi-DB: target a specific database alias
python manage.py paradedb_verify_index search_idx --database search
Notes:
- Management commands are discovered by Django only when
"paradedb"is inINSTALLED_APPS. - The selected database must have ParadeDB (
pg_search) installed, and the target BM25 index must exist there. - Some diagnostics functions may not be available on older
pg_searchversions.
Common Errors
"facets() requires a ParadeDB search condition in the WHERE clause"
# ❌ Missing ParadeDB filter
MockItem.objects.filter(rating__lt=4).order_by('id')[:10].facets('category')
# ✅ Add a ParadeDB search filter
MockItem.objects.filter(
rating__gte=4,
description=ParadeDB(Match('shoes', operator='AND'))
).order_by('id')[:10].facets('category')
"facets(include_rows=True) requires order_by() and a LIMIT"
# ❌ Missing ordering or limit
MockItem.objects.filter(description=ParadeDB(Match('shoes', operator='AND')))[:10].facets('category')
MockItem.objects.filter(description=ParadeDB(Match('shoes', operator='AND'))).order_by('id').facets('category')
# ✅ Both ordering and limit
MockItem.objects.filter(description=ParadeDB(Match('shoes', operator='AND'))).order_by('id')[:10].facets('category')
# ✅ Or skip rows entirely
MockItem.objects.filter(description=ParadeDB(Match('shoes', operator='AND'))).facets('category', include_rows=False)
Security
django-paradedb uses SQL literal escaping (rather than parameterized queries) for search terms. This is intentional: ParadeDB's full-text operators (&&&, |||, ===, @@@, etc.) require string literals that the query planner can inspect at parse time — parameterized placeholders are incompatible with this design. All user input is escaped via PostgreSQL's standard single-quote escaping (' → '') before being embedded in the query. The implementation is covered by 300+ tests including special-character and injection cases.
Examples
Documentation
- Package Documentation: https://paradedb.github.io/django-paradedb
- ParadeDB Official Docs: https://docs.paradedb.com
- ParadeDB Website: https://paradedb.com
Contributing
See CONTRIBUTING.md for development setup, running tests, linting, and the PR workflow.
Support
If you're missing a feature or have found a bug, please open a GitHub Issue.
To get community support, you can:
- Post a question in the ParadeDB Slack Community
- Ask for help on our GitHub Discussions
If you need commercial support, please contact the ParadeDB team.
Acknowledgments
We would like to thank the following members of the Django community for their valuable feedback and reviews during the development of this package:
- Timothy Allen - Principal Engineer at The Wharton School, PSF and DSF member
- Frank Wiles - President & Founder of REVSYS
License
django-paradedb is licensed under the MIT License.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_paradedb-0.5.0.tar.gz.
File metadata
- Download URL: django_paradedb-0.5.0.tar.gz
- Upload date:
- Size: 247.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f37c1287b02938032e1943feca491e53a9543e5b63431f2755ef845190abc5fa
|
|
| MD5 |
d82b6caf80fa6f9411e5385179775e28
|
|
| BLAKE2b-256 |
d4f39f65518d983c4b88b693498dcd0d7dd88c7df6c43e336c1100dd12044c70
|
File details
Details for the file django_paradedb-0.5.0-py3-none-any.whl.
File metadata
- Download URL: django_paradedb-0.5.0-py3-none-any.whl
- Upload date:
- Size: 35.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b68500769529e26b88f6eed6e06bb7948bbbfde8585fba97fff2bc90e393403f
|
|
| MD5 |
65a56ba172817fcfea18fc18622fcdd2
|
|
| BLAKE2b-256 |
6440fc88751bd17817d26a29163fcebbf617d62867b40bbfcc4da96715823813
|