Library / SDK
tomwojcik/starlette-context avatar
tomwojcik/starlette-context

starlette-context: per-request context data for Starlette

Middleware for Starlette that allows you to store and access the context data of a request. Can be used with logging so logs automatically use request headers such as x-request-id or x-correlation-id.

607 stars28 forksPythonMIT

At a glance

What is it?
starlette-context is a Starlette middleware that creates a per-request context, lets handlers read and write into it, and ships plugins that lift request id and correlation id headers out of the request and into your logs. It is a small package with one dependency, and its 0.5 line is marked beta.
Who is it for?
starlette-context fits a Starlette or FastAPI service whose logs need a request identifier on every line and where passing that identifier down through call arguments has become awkward. It is the wrong choice if your service already has a logging setup that injects context, or if you need the context to survive past the request, since it is request-scoped by design.
Can I use it commercially?
Yes. MIT 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 122 days ago.
What is it written in?
Mainly Python, 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

Request-scoped data that logging can reach

The README describes the package as middleware for Starlette that allows you to store and access the context data of a request, and says it can be used with logging so that logs automatically use request headers such as x-request-id or x-correlation-id.

That last sentence is the reason the package exists. In a request handling stack, the facts you most want in a log line, which request this is and which upstream call it belongs to, are also the facts most likely to be lost as a call goes deeper. Passing a request id down as a function argument through every layer is the kind of plumbing that decays, and a context object tied to the request removes the need for it.

The scope is small and worth stating clearly. The package is middleware, it operates on the request lifecycle, and the data it holds dies with the request. It is not a general context or configuration system, and it is not a background task or cache. The primary language is Python, the licence is MIT, and the documentation is on Read the Docs at starlette-context.readthedocs.io, with the changelog as a page within it.

The topics attached to the repository name fastapi as well as starlette, which tells you the intended consumer, since FastAPI is built on Starlette's middleware stack and a Starlette middleware drops into a FastAPI application unchanged.

The middleware creates the context, the plugins fill it

The example in the README is short enough to read in one pass, and it shows the whole mechanism.

A handler accesses and stores data in the context and returns the context data as the response:

python
async def index(request: Request):
    # Access and store data in context
    context["custom_value"] = "example"
    return JSONResponse(context.data)

The middleware is configured with a tuple of plugins:

python
# Configure middleware
middleware = [
    Middleware(
        ContextMiddleware,
        plugins=(
            plugins.RequestIdPlugin(),
            plugins.CorrelationIdPlugin()
        )
    )
]

So the division of labour is between the middleware and the plugins. `ContextMiddleware` owns the lifetime of the context and creates it early in the request, and the plugins are what put values into it. The two bundled plugins read the request id and the correlation id, and the response in the example shows the result, with the two headers and the value the handler added:

json
{
  "X-Correlation-ID": "5ca2f0b43115461bad07ccae5976a990",
  "X-Request-ID": "21f8d52208ec44948d152dc49a713fdd",
  "custom_value": "example"
}

The header names appear in the response because the plugins store them under the header name, which is why `context.data` is a plain mapping you can serialise straight into a response.

Context stays writable for the whole request

One sentence in the README carries most of the design, and it is easy to skim past. Context can be updated and accessed at anytime if it's created in the middleware.

That conditional is doing the work. The context is created by the middleware, before the handler runs, and it is a mutable mapping that later code can both read and write. The `context` object in the example is imported from the package and used as if it were a plain dictionary, with `context["custom_value"] = "example"` on the left and `context.data` on the right.

The practical consequence is that anything running during the request, a dependency, a helper deeper in the stack, an exception handler, can reach the same context without it being passed as an argument. That is the mechanism that makes the logging use case work, because a log call in a function three layers down can pull the request id out of the context rather than out of a parameter list.

The limit is the same as the benefit. The context is per request and it is gone when the request ends, so anything that outlives the request, a background task, a message handler, a queued job, has to capture what it needs before the request returns. The README does not document any mechanism for that, so it is worth knowing before you depend on it.

One dependency, and a wide Python range

The Dependencies section lists a single line, `starlette>=0.27.0`, and the installation is a pip install with Python 3.10 or newer:

bash
# Python 3.10+
pip install starlette-context

The packaging metadata confirms the shape. The project requires Python 3.10 or newer, declares classifiers for Python 3.10 through 3.14, lists starlette as the only runtime dependency, and carries the keywords middleware, starlette and fastapi.

A single dependency is the right shape for middleware, because a middleware is something you put in the path of every request, and every dependency it drags in is a dependency your application inherits. Starlette itself is a reasonable exception, since you already have it.

The Python range is also worth noting for a package that is going to sit in the request path of a service. The classifiers run to 3.14, and the development status is 4 - Beta. A middleware is exactly the kind of component where a subtle change in how the framework dispatches requests can turn into a debugging session, so a package with a stated version range and a beta label deserves a look at its changelog before you pin it across a fleet.

The example app is where the logging story is told

The repository root carries an `example/` directory, and its contents say more about the intended usage than the README does. There is an `app.py`, a `setup_logging.py`, a `requirements.txt`, a `mypy.ini`, a `Makefile` and a `README.md`.

A separate `setup_logging.py` is the tell. The point of the package is that logs pick up the request context, and that requires a logging setup that knows how to read it, so the example ships one rather than leaving you to guess. The dev dependency groups back this up, with an `example` group containing `structlog` and `uvicorn`, so the example is a structured logging setup rather than the standard library's.

Having the example as a directory rather than a snippet in the README means it can be run, type checked and linted like the rest of the project, with a mypy.ini of its own. For a package whose value is in how you wire it into a logging configuration, that is the part worth copying.

The README's own example is the minimal version of the same idea, with the app created by Starlette with routes and middleware, and run under uvicorn with `uvicorn.run(app, host="0.0.0.0")`.

uv, pre-commit and a small Makefile for the developer

The development tooling is a small Makefile over uv, and it shows the shape of the project. The `init` target runs `uv sync`, and the `test` target syncs the dev group and then runs the test script:

makefile
init:
	uv sync

test:
	uv sync --group dev
	sh scripts/test

The rest of the targets follow the same pattern. `run-hooks` syncs the code-quality group and runs pre-commit across all files, `clean` runs a script in `scripts/`, `docs` syncs the docs group and runs `make html` inside the docs directory, `update-deps` runs `uv lock --upgrade`, and `build` runs `uv build`.

The dependency groups are the interesting part of the manifest, because they are grouped by purpose rather than by kind. The dev group holds the test stack, with coverage, pytest, pytest-cov, pytest-sugar, pytest-xdist, pytest-asyncio, codecov and httpx. The docs group has sphinx, the sphinx-rtd-theme, toml and myst-parser. The example group has structlog and uvicorn. The code-quality group has ruff, mypy, docformatter, pyupgrade and pre-commit.

pytest-xdist for parallel runs and pytest-asyncio for the async tests are the two that tell you about the test suite, since both are only needed when the tests are numerous and the handlers are async, and the middleware under test is inherently async.

Beta, on a 0.5 line, with a sponsor behind it

The maintenance picture is a small package on a fast-moving version line. The recent releases are v0.5.1 and v0.5.0, both on 2026-02-28, and v0.4.0 on 2025-03-04, and the repository is not archived with the last push on 2026-06-24.

The version scheme is worth reading carefully. A 0.5 release under semantic versioning means the public API is not considered stable, and the project says so itself with the Development Status 4 - Beta classifier. For a middleware that sits in the path of every request, that label is a reasonable thing for the maintainer to state and a reasonable thing for you to check before rolling it out widely.

There is a sponsor behind the work, and the README names them, with a thank you to Adverity for sponsoring the development of this OSS library. That is a meaningful signal for a package this small, because it explains how the maintenance happens at all.

The repository also carries a .pre-commit-config.yaml, an AUTHORS file, a .python-version file, a uv.lock, and a .codecov.yml, with the test suite badge pointing at a test-suite workflow and a separate coverage badge, so the package is checked by CI rather than by hand.

The resources section points at the source repository, the documentation and the changelog, and the contribution guide lives in the documentation rather than in the repository, which is where you should look before opening a pull request.

Editorial conclusion

starlette-context fits a Starlette or FastAPI service whose logs need a request identifier on every line and where passing that identifier down through call arguments has become awkward. It is the wrong choice if your service already has a logging setup that injects context, or if you need the context to survive past the request, since it is request-scoped by design. Verify first which headers your upstream actually sends, since the bundled plugins are built around request id and correlation id, and check the version you install, because the package is on a 0.5 line marked beta with a changelog on the documentation site.

Frequently asked questions

What does starlette-context do?

It is middleware for Starlette that lets you store and access the context data of a request. The documented use is with logging, so that log records automatically pick up request headers such as x-request-id or x-correlation-id.

How do I install starlette-context?

With pip install starlette-context, on Python 3.10 or newer. The only runtime dependency is starlette at version 0.27.0 or above, and the package is published on PyPI as starlette-context.

How do I add the request id and correlation id to the context?

Add the ContextMiddleware with a tuple of plugins, for example plugins.RequestIdPlugin() and plugins.CorrelationIdPlugin(), which read the corresponding headers and store them in the context. The response in the README example then contains X-Request-ID and X-Correlation-ID alongside any values the handler added.

Can I change the context after the middleware has created it?

Yes. The README states that context can be updated and accessed at anytime if it is created in the middleware, so handlers and anything deeper in the stack can read from and write to the same per-request mapping.

Is starlette-context ready for production use?

The packaging metadata classifies it as Development Status 4 - Beta, and the recent releases are v0.5.1 and v0.5.0 from 2026-02-28 after v0.4.0 from 2025-03-04. The changelog is published on the documentation site, and the development of the library was sponsored by Adverity.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tomwojcik/starlette-context on GitHub
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/tomwojcik-starlette-context.svg)](https://hysenlabs.com/projects/tomwojcik-starlette-context)

Was this useful?