Skip to main content

vercel-proxy

vercel.proxy lets you write Vercel middleware in Python. It runs before each request reaches your app and decides whether to let it continue, rewrite it, redirect it or answer it directly.

from urllib.parse import quote

from vercel.proxy import (
    ContinueResponse,
    Proxy,
    RedirectResponse,
    Request,
    Response,
    RewriteResponse,
)

proxy = Proxy()


@proxy.route("/old-blog/{slug}")
def old_blog(request: Request) -> Response:
    slug = quote(request.path_params["slug"])
    return RedirectResponse(f"/blog/{slug}", status_code=308)


@proxy.route("/dashboard/{path:path}")
def dashboard(request: Request) -> Response:
    if "session" not in request.cookies:
        return RedirectResponse("/login")
    return ContinueResponse(request_headers={"x-user": request.cookies["session"]})


@proxy.route("/api/{path:path}", host="{tenant}.example.com")
async def tenant_api(request: Request) -> Response:
    tenant = request.path_params["tenant"]
    path = quote(request.path_params["path"])
    return RewriteResponse(f"https://{tenant}.api.example.com/{path}")

Setup

Create a proxy module, such as proxy.py, and set the proxy in vercel.json as module:attribute. A module in a package works too, such as app.proxy:proxy.

{
  "proxy": { "entrypoint": "proxy:proxy" }
}

By default the proxy runs for every request. To run it only for some paths, add a matcher with one path or a list of paths.

{
  "proxy": { "entrypoint": "proxy:proxy", "matcher": ["/api/:path*", "/admin"] }
}

The proxy runs separately from your app and gets only the packages in the vercel.proxy dependency group of pyproject.toml.

[dependency-groups]
"vercel.proxy" = ["vercel-proxy"]

Routes

@proxy.route(path) registers a handler for requests whose path matches. Each request goes to the first matching route, in the order the routes were registered.

A path can capture parts of the URL with placeholders. The captured values are passed to the handler in request.path_params. They are decoded, so quote them with urllib.parse.quote before building a URL from them, and check any value that chooses the destination, such as a host or a path containing ...

@proxy.route("/users/{user_id:int}")
def user(request: Request) -> Response:
    return RewriteResponse(f"/profiles/{request.path_params['user_id']}")

The following placeholders are available:

  • {name} captures one path segment as a string.
  • {name:type} also converts the value. The type can be str, int, float or uuid.
  • {name:path} captures any part of the path, including /.

Trailing slashes are optional by default. Each request is first matched against every route as sent, and only then with the trailing slash added or removed. So with /users/{id} registered before /{path:path}, a request for /users/42/ goes to /{path:path}, which matches it exactly. Pass strict=True to Proxy() to require exact matches.

A route can also be limited to certain methods or hosts.

@proxy.route("/admin", methods=["GET", "POST"], host="admin.example.com")
def admin(request: Request) -> Response:
    return RewriteResponse("/internal/admin")
  • methods lists the HTTP methods the route accepts. Omit it to accept every method.
  • host is the hostname the route accepts. It can capture values like the path. Omit it to accept every host.

Handlers can be sync or async. Sync handlers run in a worker thread.

Fallback

Requests that match no route go to the fallback. By default they continue unchanged.

Pass a fallback to the constructor to change it. It can be a response, which is returned for every unmatched request, or a handler.

proxy = Proxy(fallback=JSONResponse({"error": "not found"}, status_code=404))

The fallback can also be set with the @proxy.fallback decorator. It replaces any fallback passed to the constructor.

@proxy.fallback
def fallback(request: Request) -> Response:
    return RewriteResponse("/404")

Requests

Request is a Starlette Request. Use method, url, headers, query_params, cookies, path_params and client to decide how to route it.

WebSocket upgrades are routed like GET requests, and the destination accepts the socket. Check request.headers.get("upgrade") to tell them apart.

Responses

Every handler returns a Response. Responses are based on Starlette's responses, so headers, set_cookie and delete_cookie work as in Starlette.

response = ContinueResponse()
response.set_cookie("bucket", "b", max_age=86400)
return response

Header names starting with x-middleware- are reserved. Background tasks are not supported.

Continuing the request

ContinueResponse() lets the request continue to the original URL. RewriteResponse(url) serves it from url without changing the URL the client sees.

For both, request_headers is a dictionary of headers to replace on the forwarded request. A None value removes the header.

return ContinueResponse(request_headers={"x-region": "eu", "cookie": None})

Answering the request

Response, JSONResponse, PlainTextResponse, HTMLResponse and RedirectResponse answer the request directly, like the Starlette classes of the same name. The request does not reach your app.

Metadata

Release files for vercel-proxy 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vercel-proxy 0.1.0
File Size Uploaded Provenance
vercel_proxy-0.1.0.tar.gz 12.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for vercel-proxy 0.1.0
File Interpreter ABI Platform
vercel_proxy-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 23.3 kB

Release files / vercel_proxy-0.1.0.tar.gz

Download URL vercel_proxy-0.1.0.tar.gz
Size 12.2 kB
Tags Source
SHA-256 checksum
How to use checksums
02451c091e336ee33f286550c7595045d663e410a0cbdd43af457ecbc044a3a8
BLAKE2b-256 checksum
How to use checksums
534767dc4904809e81397ce4ac1ba37385c25db7e25ef8cbdb11bc95c3973efe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vercel_proxy-0.1.0-py3-none-any.whl

Download URL vercel_proxy-0.1.0-py3-none-any.whl
Size 11.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8c4d0165f9246c7b0f06766a42efd09fb0c52c1ef660497fb32b27b479befd1f
BLAKE2b-256 checksum
How to use checksums
5b73d5fa06825d1bdc3b72b3a88477889bb234934392d2db70241a9186f5e646
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for vercel-proxy 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page