Troubleshooting
ASGI & WebSockets

Fix Django SynchronousOnlyOperation in Async Code

Fix Django SynchronousOnlyOperation You cannot call this from an async context: use async ORM methods, sync_to_async for blocking code, avoid lazy relation access in async views and consumers, and never disable the check in production.

Problem statement

An async view, a Channels consumer, or code run under Uvicorn raises:

django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async
context - use a thread or sync_to_async.

Django's ORM and many other parts of Django are synchronous: they block while waiting on the database. Calling them directly inside an async def function would block the event loop and freeze every other request on that worker, so Django detects it and raises this error instead. The fix is to use the async ORM API where it exists and to run remaining blocking code in a thread with sync_to_async.

Why Django raises thisasync def runson the event loopSync ORM callwould block the loopDjango detects itasync-unsafe guardException raisedbefore blockingDjango refuses to block the event loop; move the call to async APIs or a thread.
  1. Use async ORM methods: await Model.objects.aget(...), acreate, asave, adelete, async for obj in qs.
  2. Wrap blocking code that has no async version: await sync_to_async(func)(args).
  3. Avoid lazy access to related objects in async code (order.customer.name); use select_related or aget the relation.
  4. In Channels, use database_sync_to_async for ORM work in consumers.
  5. Never set DJANGO_ALLOW_ASYNC_UNSAFE in production.

Step-by-step solution

1. Read the traceback to find the sync call

The last frames show which call triggered the guard: QuerySet.__iter__, Model.save, connection.cursor, django.contrib.auth.get_user, or a template rendering a queryset. That call must become async or move to a thread.

2. Use the async ORM

Since Django 4.1, querysets and model instances have async versions of their I/O methods:

async def order_detail(request, pk):
    order = await Order.objects.select_related("customer").aget(pk=pk)
    lines = [line async for line in order.lines.all()]
    count = await Order.objects.filter(customer=order.customer).acount()
    return JsonResponse({"customer": order.customer.name, "lines": len(lines), "orders": count})

Building a queryset is fine in async code because it does not touch the database; only evaluating it does. aget, afirst, acount, aexists, acreate, aupdate, adelete, async for, and instance.asave() evaluate safely.

3. Watch for lazy relation access

This looks harmless and raises the error:

order = await Order.objects.aget(pk=pk)
name = order.customer.name        # lazy foreign key fetch: sync query

Either fetch the relation up front with select_related("customer"), or fetch it asynchronously: customer = await Customer.objects.aget(pk=order.customer_id). The same applies to reverse relations and ManyToMany managers, and to request.user in async views: use await request.auser() (Django 5.0+).

same view, two waysRaises the error✕Order.objects.get(pk=pk)✕order.customer.name (lazy fetch)✕for line in order.lines.all()Async-safe✓await ...select_related().aget()✓related object already loaded✓async for line in order.lines.all()Evaluating a queryset is the moment it must be asyncdjango-deployment.com
Lazy relation access is the most common hidden source of this error.

4. Wrap code without async support

Some things have no async API: Model.save() overrides with custom logic, third-party libraries, send_mail, transactions, and much of django.contrib. Run them in a thread:

from asgiref.sync import sync_to_async
from django.db import transaction

@sync_to_async
def place_order(cart_id):
    with transaction.atomic():
        ...
        return order.pk

async def checkout(request):
    order_id = await place_order(request.POST["cart"])
    return redirect("order-done", order_id)

sync_to_async runs the function in a thread with thread_sensitive=True by default, which keeps Django's database connection handling correct. Group several ORM operations into one wrapped function rather than wrapping each call; it is faster and keeps transactions together.

5. Channels consumers

from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncJsonWebsocketConsumer

class ChatConsumer(AsyncJsonWebsocketConsumer):
    async def receive_json(self, content):
        msg = await self.save_message(content["text"])
        await self.channel_layer.group_send(self.room, {"type": "chat.message", "id": msg})

    @database_sync_to_async
    def save_message(self, text):
        return Message.objects.create(room=self.room, text=text).pk

database_sync_to_async also closes stale connections around the call, which matters for long-lived WebSocket connections.

what is the blocking call?What does the traceback point at?simple ORM queryAsync ORM methodaget, acount, async fortransaction or librarysync_to_asyncwrap the whole unitChannels consumerdatabase_sync_to_asyncalso closes old connsPrefer native async APIs, then thread the rest in larger chunksdjango-deployment.com
One wrapped function per unit of work beats wrapping every call separately.

Verification check: run the view or consumer under the ASGI server you deploy with (uvicorn config.asgi:application) and exercise it; the error should not appear. Add an async test with django.test.AsyncClient that calls the view, so regressions fail in CI.

Explanation

An async server runs many requests on one thread, switching between them whenever one awaits I/O. A synchronous database call does not await; it holds the thread until the query returns, stalling every other request. Django marks async-unsafe operations with a decorator that checks whether an event loop is running in the current thread and raises SynchronousOnlyOperation if so. The async ORM methods currently run the underlying sync code in a thread pool for you; native async database drivers are being added gradually.

Edge cases / notes

  • DJANGO_ALLOW_ASYNC_UNSAFE disables the check. It exists for notebooks and shells that run an event loop, not for servers.
  • Jupyter and IPython run an event loop, so the ORM raises this in notebooks; setting the variable there is acceptable.
  • Sync views under ASGI are fine: Django runs them in a thread automatically. You only need async care inside async def.
  • Performance. An async view that wraps everything in sync_to_async is slower than a plain sync view. Use async where it gives concurrency benefits, such as calling external APIs or streaming.

Next steps

FAQ

Can I use the Django ORM in async views?

Yes, through the async methods (aget, acreate, async for) or by wrapping sync code with sync_to_async.

Is it safe to set DJANGO_ALLOW_ASYNC_UNSAFE?

Not in production. It lets blocking calls freeze the event loop for every request on that worker.

Why does accessing a foreign key raise the error?

Accessing an unloaded relation runs a synchronous query. Load it with select_related or fetch it asynchronously.

Do sync views break under Uvicorn?

No. Django runs sync views in a thread under ASGI. The error only affects code inside async functions.

Keep going