Skip to content

Service bindings

A service binding lets server-side code in one service call another service directly, without exposing the target service to public traffic.

Bindings are how services find each other internally. The binding simultaneously grants access to the caller and generates the URL the caller's code uses, so there is no public route or hardcoded hostname involved.

You declare a binding on the caller service. The caller names the target service and the environment variable that receives the generated URL.

vercel.json
{
  "services": {
    "orders": {
      "root": "services/orders/",
      "framework": "express",
      "bindings": [
        {
          "type": "service",
          "service": "inventory",
          "format": "url",
          "env": "INVENTORY_URL"
        }
      ]
    },
    "inventory": {
      "root": "services/inventory/",
      "framework": "fastapi",
      "entrypoint": "main:app"
    }
  },
  "rewrites": [{ "source": "/api/(.*)", "destination": { "service": "orders" } }]
}

The top-level rewrite is the only public entry point. A request from the public internet can reach orders, but Vercel never routes public traffic to inventory: it has no rewrite of its own, so it stays unreachable from the outside.

orders can still call inventory because it declares a binding to it. The binding grants that access and injects the target URL as INVENTORY_URL, which server-side code in orders reads to build request URLs:

await fetch(new URL('items/123', process.env.INVENTORY_URL));

Service bindings aren't available yet for services that use the Go or Rust runtime. If your service uses one of these runtimes and declares bindings, build it as a container image instead. A container image service can use bindings. Runtimes such as Node.js and Python are not affected.

FieldRequiredDescription
typeYesMust be "service" for a service-to-service binding.
serviceYesTarget service name from services.
formatYesMust be "url". The generated value is an absolute URL base for the target service.
envYesEnvironment variable name that receives the generated URL. Vercel generates this value and injects into a service, you do not set it.

The binding is the reachability grant. If orders declares a binding to inventory, it can call inventory. A service without a matching binding cannot derive the URL and fails to connect. This grant is independent from public ingress. Adding a binding gives a service internal access to another service. It does not create a public route to the target, and a public rewrite to a service does not grant another service internal access to it.

Vercel injects the binding as a URL base, and the value is deployment-aware. This way, a preview deployment's orders service reaches that same preview deployment's inventory service, so you never reference a fixed hostname.

Internal calls also skip the public request pipeline. Firewall, Deployment Protection, the project's top-level middleware, and CDN request accounting do not apply to a service-to-service call.

A call over a binding is billed as a single service request, with no separate Edge Request or Fast Data Transfer charge. See Pricing and Limits for how billing works across services.

A binding grants internal access only. It does not authenticate or authorize the call. If you need application-level authorization between services, handle it in your service code.

Bindings are available to a service's functions at runtime only. They do not resolve during builds, and code running in middleware cannot call another service over a binding. Make service-to-service calls from a function instead.

Last updated July 15, 2026

Was this helpful?