| title | Frequently Asked Questions |
|---|
The request object is accessible via the get_request method.
from strawberry_django.utils.requests import get_request
from strawberry.types import Info
def resolver(root, info: Info):
request = get_request(info)
# Access request properties
user = request.user
headers = request.headersThe current user object is accessible via the get_current_user method.
from strawberry_django.auth.utils import get_current_user
from strawberry.types import Info
def resolver(root, info: Info):
current_user = get_current_user(info)
if current_user.is_authenticated:
# Do something with authenticated user
passCheck out the examples directory in the GitHub repository for complete Django project examples.
If your type checker (PyLance, mypy) shows errors with strawberry.auto, it's because you're returning a Django model instance while the annotation expects a GraphQL type. Use type casts to inform the type checker that the return value is compatible:
from typing import cast
@strawberry_django.mutation
def create_fruit(self, name: str) -> Fruit: # Fruit is the GraphQL type
fruit = models.Fruit.objects.create(name=name) # Returns Django model
return cast(
Fruit, fruit
) # Tell type checker: model is compatible with GraphQL typeThis is only needed when the annotation is a GraphQL type but you're returning a Django model instance. The cast is purely for type checking and has no runtime effect.
Use the Query Optimizer (recommended for most cases):
- Automatic optimization
- Works with Django ORM
- Less code to maintain
- Handles most N+1 scenarios
from strawberry_django.optimizer import DjangoOptimizerExtension
schema = strawberry.Schema(
query=Query,
extensions=[DjangoOptimizerExtension],
)Use DataLoaders when:
- You need custom batching logic
- Fetching from external APIs
- The optimizer doesn't handle your use case
- You need fine-grained caching control
See DataLoaders guide for details.
Three main approaches:
- Enable the Query Optimizer (easiest):
schema = strawberry.Schema(
query=Query,
extensions=[DjangoOptimizerExtension],
)- Add optimization hints to custom fields:
@strawberry_django.field(select_related=["author"], prefetch_related=["tags"])
def custom_field(self, root) -> str:
return f"{root.author.name}: {root.tags.count()}"- Use DataLoaders for complex scenarios (see above).
Define nested filter types:
@strawberry_django.filter_type(models.Author)
class AuthorFilter:
name: auto
@strawberry_django.filter_type(models.Book)
class BookFilter:
title: auto
author: AuthorFilter | NoneQuery:
query {
books(filters: { author: { name: "John" } }) {
title
author {
name
}
}
}Yes, use custom filter/order methods:
from django.db.models import Count, Q, QuerySet
@strawberry_django.filter_type(models.Author)
class AuthorFilter:
name: auto
@strawberry_django.filter_field
def book_count(
self, queryset: QuerySet, value: int, prefix: str
) -> tuple[QuerySet, Q]:
queryset = queryset.alias(_book_count=Count(f"{prefix}books"))
return queryset, Q(**{f"{prefix}_book_count": value})Recommended: Use the automatic mutation generators which handle nested relationships automatically:
import strawberry
import strawberry_django
from strawberry_django import mutations
from . import models
@strawberry_django.input(models.Author)
class AuthorInput:
name: auto
books: auto # Automatically handles nested book creation
@strawberry.type
class Mutation:
create_author: Author = mutations.create(AuthorInput)
update_author: Author = mutations.update(AuthorInput)
delete_author: Author = mutations.delete()These mutations automatically:
- Generate appropriate input types for nested relationships
- Handle create, update, and delete operations on related objects
- Validate data using Django's validation system
See the tests in the repository for complete examples of automatic mutations with nested relationships.
Manual approach (when you need custom logic):
from django.db import transaction
@strawberry_django.input(models.Author)
class AuthorInputWithBooks:
name: auto
email: auto
books: list[BookInput] | None = None
@strawberry_django.mutation(handle_django_errors=True)
@transaction.atomic
def create_author_with_books(self, data: AuthorInputWithBooks) -> Author:
author = models.Author.objects.create(name=data.name, email=data.email)
if data.books:
for book_data in data.books:
models.Book.objects.create(
author=author,
title=book_data.title,
)
return models.Author.objects.get(pk=author.pk)See Nested Mutations guide for more details.
Use ListInput with set, add, or remove operations:
from strawberry_django import ListInput, NodeInput
@strawberry_django.partial(models.Article)
class ArticleInputPartial(NodeInput):
title: auto
tags: ListInput[strawberry.ID] | None = None
@strawberry_django.mutation
def update_article(self, data: ArticleInputPartial) -> Article:
article = models.Article.objects.get(pk=data.id)
if data.tags is not strawberry.UNSET and data.tags is not None:
if data.tags.set is not None:
article.tags.set(data.tags.set)
if data.tags.add is not None:
article.tags.add(*data.tags.add)
if data.tags.remove is not None:
article.tags.remove(*data.tags.remove)
article.save()
return articleDjango caches related managers. Refresh or refetch the object:
@strawberry_django.mutation
@transaction.atomic
def create_with_relations(self, data: Input) -> Model:
obj = models.Model.objects.create(...)
# Create related objects...
# ✅ Option 1: Refresh
obj.refresh_from_db()
# ✅ Option 2: Refetch (better for optimizer)
return models.Model.objects.get(pk=obj.pk)Use dict-style ValidationError for field-specific errors:
from django.core.exceptions import ValidationError
@strawberry_django.mutation(handle_django_errors=True)
def create_user(self, email: str, age: int) -> User:
errors = {}
if not email or "@" not in email:
errors["email"] = "Invalid email address"
if age < 18:
errors["age"] = "Must be at least 18 years old"
if errors:
raise ValidationError(errors)
return models.User.objects.create(email=email, age=age)See Error Handling guide for details.
Use permission extensions:
from strawberry_django.permissions import IsAuthenticated, HasPerm
@strawberry_django.type(models.Document)
class Document:
title: auto
@strawberry_django.field(extensions=[IsAuthenticated()])
def content(self) -> str:
return self.content
@strawberry_django.field(extensions=[HasPerm("documents.view_secret")])
def secret_data(self) -> str:
return self.secretSee Permissions guide for more options.
Yes, with django-guardian:
from strawberry_django.permissions import HasRetvalPerm
@strawberry_django.type(models.Document)
class Document:
@strawberry_django.field(extensions=[HasRetvalPerm("documents.view_document")])
def content(self) -> str:
# Permission checked against this specific document instance
return self.contentSee Guardian integration.
Map them in the field_type_map:
from strawberry_django.fields.types import field_type_map
from django.db import models
import strawberry
# For django-money
from djmoney.models.fields import MoneyField
MoneyScalar = strawberry.scalar(...)
field_type_map.update({
MoneyField: MoneyScalar,
models.SlugField: str,
})Three options:
- Model property (recommended):
from decimal import Decimal
from strawberry_django.descriptors import model_property
class Order(models.Model):
price = models.DecimalField(...)
quantity = models.IntegerField(...)
@model_property(only=["price", "quantity"])
def total(self) -> Decimal:
return self.price * self.quantity- Custom resolver:
from decimal import Decimal
@strawberry_django.type(models.Order)
class Order:
price: auto
quantity: auto
@strawberry_django.field(only=["price", "quantity"])
def total(self) -> Decimal:
return self.price * self.quantity- Annotated field:
from django.db.models import F
@strawberry_django.type(models.Order)
class Order:
price: auto
quantity: auto
total: auto = strawberry_django.field(
annotate={"total": F("price") * F("quantity")}
)See Model Properties guide for details.
Use django-choices-field:
from django_choices_field import TextChoicesField
class Status(models.TextChoices):
ACTIVE = "active", "Active"
INACTIVE = "inactive", "Inactive"
class Company(models.Model):
status = TextChoicesField(choices_enum=Status)This automatically generates GraphQL enums.
import strawberry
from strawberry import relay
import strawberry_django
@strawberry_django.type(models.Fruit)
class Fruit(relay.Node):
name: auto
@strawberry.type
class Query:
fruits: relay.Connection[Fruit] = strawberry_django.connection()See Relay guide.
Offset pagination (default):
- Straightforward to implement
- Allows jumping to any page
- Works well for small to medium datasets
Cursor pagination:
- Better performance for large datasets
- Prevents missing/duplicate items during pagination
- Required for Relay compliance
Choose based on your use case and dataset size.
Use the test client:
from strawberry_django.test.client import TestClient
def test_create_fruit(db):
client = TestClient("/graphql")
res = client.query("""
mutation {
createFruit(data: { name: "Apple" }) {
id
name
}
}
""")
assert res.errors is None
assert res.data["createFruit"]["name"] == "Apple"For authenticated tests:
def test_authenticated_query(db):
user = User.objects.create_user(username="test")
client = TestClient("/graphql")
with client.login(user):
res = client.query("query { me { username } }")
assert res.data["me"]["username"] == "test"See Unit Testing guide.
Use pytest-asyncio:
import pytest
@pytest.mark.django_db
@pytest.mark.asyncio
async def test_async_resolver():
result = await my_async_resolver()
assert result is not NoneYes, but it requires additional setup. Check the Federation guide in Strawberry's documentation.
Use Strawberry's Upload scalar:
from strawberry.file_uploads import Upload
@strawberry.type
class Mutation:
@strawberry_django.mutation
def upload_file(self, file: Upload) -> bool:
content = file.read()
# Handle the file...
return TrueYou're likely returning a non-model object. Ensure you're returning actual Django model instances:
# ❌ Returns dict
return {"id": obj.id, "name": obj.name}
# ✅ Returns model instance
return objThis happens when using interfaces with handle_django_errors=True. Either:
- Return concrete types:
update_project: WebProject | ExternalProject = mutations.update_project(...)- Or set error handling to false:
update_project: Project = mutations.update_project(handle_django_errors=False)This occurs when importing Django models before Django is fully initialized. In your asgi.py:
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")
# Initialize Django FIRST
django_asgi_app = get_asgi_application()
# THEN import your schema (which imports models)
from myproject.schema import schemaCommon causes:
- Field not exposed: Add the field to your type definition
- Wrong field name: GraphQL uses camelCase by default (
firstNamenotfirst_name) - Type mismatch: Ensure the field is defined on the correct type
Usually caused by circular type references. Use string annotations:
@strawberry_django.type(models.Author)
class AuthorType:
books: list["BookType"] # String annotation for forward reference
@strawberry_django.type(models.Book)
class BookType:
author: "AuthorType" # String annotation for back reference- Ensure you're using ASGI, not WSGI
- Check that Daphne is installed and configured
- Verify
ASGI_APPLICATIONis set in settings - Import schema after
get_asgi_application()
See Subscriptions guide for setup details.
DataLoaders require async execution. They don't work with:
- WSGI servers (use ASGI instead)
runserverwithout Daphne- Sync resolvers
Use the Query Optimizer for sync contexts, or switch to ASGI.
- Documentation: Check the full guides
- Troubleshooting: See Troubleshooting guide
- GitHub Issues: Search existing issues
- Discussions: GitHub Discussions
- Discord: Join the Strawberry Discord