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("/docs/{path:path}")
def docs(request: Request) -> Response:
    theme = "dark" if request.cookies.get("theme") == "dark" else "light"
    return ContinueResponse(request_headers={"x-theme": theme})


@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.1

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.1
File Size Uploaded Provenance
vercel_proxy-0.1.1.tar.gz 12.2 kB Without provenance Details

Built distribution (wheel)

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

Total release size: 23.3 kB

Release files / vercel_proxy-0.1.1.tar.gz

Download URL vercel_proxy-0.1.1.tar.gz
Size 12.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e5534230b5c291914193a5751757cff0240faa023332259e79e7b6a73c74dc0a
BLAKE2b-256 checksum
How to use checksums
8622cb7fc23a4000133319a86a1b4c3b99f644d564baca76c3ea93f41345cc8a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.1-py3-none-any.whl

Download URL vercel_proxy-0.1.1-py3-none-any.whl
Size 11.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4467de534bfe18399e486db7114c44c4ecbf7404b72376267d88468e40daee01
BLAKE2b-256 checksum
How to use checksums
cfc7848d86ee93009de58f9e8889a8ee55d4e05791af56a1d6dbd792004a51bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","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.1

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.1 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