Bug Days
Developer guide

gRPC-Web vs Native gRPC: How Browser Testing Actually Works

Choose the correct browser transport for a gRPC service, understand when gRPC-Web works directly, and test native HTTP/2 gRPC through a local bridge.

7 minute read gRPC and browsers
Browser gRPC client offering native gRPC through a local bridge and direct gRPC-Web transport

A failed browser call does not necessarily mean a gRPC server is down. “gRPC” and “gRPC-Web” are related transports, but they are not interchangeable endpoints. Choosing the wrong one can produce an HTTP error, a CORS message, undecodable bytes, or no useful response at all.

Start with one question: does the address accept gRPC-Web requests, or is it a native gRPC endpoint intended for SDKs and command-line clients?

Native gRPC and gRPC-Web compared

CapabilityNative gRPCgRPC-Web
Typical clientGenerated SDK, grpcurl, desktop client, service-to-service codeBrowser application using a gRPC-Web client
Browser web APIsCannot initiate the complete native protocol directlyDesigned for browser-compatible HTTP requests
Backend exposureServer’s native HTTP/2 gRPC listenerA gRPC-Web-capable server or gateway
CORSNot relevant to a native SDK; relevant to any browser-facing bridgeThe browser-facing endpoint must allow the web origin, methods, and headers
StreamingUnary, server, client, and bidirectional streaming depend on the methodBrowser/client/gateway support varies; verify the exact mode

The official gRPC-Web tutorial demonstrates a browser client calling a browser-facing endpoint that forwards to a native gRPC backend, including the need for CORS configuration.

Why a normal fetch request is not a native gRPC client

Native gRPC is more than “protobuf over HTTP.” A call has a fully qualified method path, framed protobuf messages, gRPC-specific headers, HTTP/2 behavior, and a final status with optional trailing metadata. Browser fetch does not expose the controls needed to behave like a general native gRPC client.

That is why a direct browser option should target gRPC-Web. A native endpoint needs a component outside the browser tab—a service gateway, application backend, desktop client, CLI, or a local bridge—to own the native gRPC connection.

Choose direct gRPC-Web when the service already supports it

Use direct mode when the public address is intentionally configured for gRPC-Web. Verify:

  • the gRPC-Web URL, which may differ from the native host and port;
  • allowed origins and preflight behavior;
  • allowed request metadata such as authorization;
  • which response and trailer headers the browser may expose;
  • whether your gateway supports the RPC’s streaming shape.

A CORS error happens in the browser security layer. It does not tell you whether the backend method exists or whether the protobuf message was accepted. Test the public gRPC-Web address and the native backend as two separate hops when debugging a gateway.

Choose a local bridge for an existing native gRPC endpoint

A local bridge is useful when you control or are authorized to test a native gRPC service but do not want to install and configure a heavyweight API workspace. The browser can still provide the contract browser, JSON editor, history, sharing, and decoded results; the bridge performs the native HTTP/2 call on your machine.

With Bug Days, the flow is:

  1. Import or paste the service’s proto files in the browser.
  2. Select Native gRPC (bridge).
  3. Run Holy CORS locally using Homebrew, Docker, or the standalone binary.
  4. Enter the native target such as localhost:50051.
  5. Send the request and inspect decoded messages, response headers, trailers, and gRPC status.
docker run --rm \
  -p 127.0.0.1:2345:2345 \
  ghcr.io/bugdays-com/holy-cors:latest

Binding to 127.0.0.1 keeps that bridge port on the local computer. If the target service also runs on the host while the bridge runs in Docker, use host.docker.internal as the service hostname; Linux Docker may need an explicit host-gateway mapping.

Diagnose the layer that actually failed

SymptomLikely layerUseful check
Browser preflight or CORS errorBrowser-facing gRPC-Web endpoint or local-access permissionOrigin, OPTIONS response, allowed metadata headers, and browser site permission
Bridge cannot connectLocal process, Docker networking, DNS, TCP, or TLSBridge status, target hostname/port, container-to-host address, and certificate trust
UNIMPLEMENTEDgRPC routing or method selectionPackage/service/method path and whether the gateway routes reflection separately
UNAUTHENTICATEDRequest metadataAuthorization key, token format, audience, and expiry
Messages followed by an errorStreaming RPC completionFinal status and trailing metadata, not only received messages

What should stay local or be redacted

Proto files can reveal internal package names and capabilities. Metadata can contain bearer tokens, trace identifiers, tenant IDs, and routing keys. Request bodies may contain customer or production data. Use synthetic values where possible and inspect any request before creating a share link.

Bug Days omits authentication secrets from shares, exports, saved requests, and history. The page parses proto definitions and protobuf messages in the browser. When native mode is selected, the request goes through Holy CORS on your machine to the target you supplied; it is not sent through a hosted Bug Days relay.

The practical decision

  • The service exposes gRPC-Web: connect directly and fix CORS on that browser-facing endpoint.
  • The service exposes native gRPC: use a native SDK, grpcurl, desktop client, internal gateway, or a local bridge.
  • You are unsure: ask the service owner which transport and address are supported; a proto file describes methods, not deployment topology.

Continue reading