Protorip
---
site: protos.rip
page: blog/why-protorip
---
           __                              __             _     
 _      __/ /_  __  __   ____  _________  / /_____  _____(_)___ 
| | /| / / __ \/ / / /  / __ \/ ___/ __ \/ __/ __ \/ ___/ / __ \
| |/ |/ / / / / /_/ /  / /_/ / /  / /_/ / /_/ /_/ / /  / / /_/ /
|__/|__/_/ /_/\__, /  / .___/_/   \____/\__/\____/_/  /_/ .___/ 
             /____/  /_/                               /_/      

# Why we built Protorip

I first started using the `buf` CLI when our team needed an RPC protocol and schema language for cross-microservice communication. At the time, we were building on serverless microservices, where traditional gRPC felt like operational overkill for transient function lifecycles. Connect was still in active development for TypeScript back then, so we settled on Twirp as a simpler, direct alternative.
What sold me on Protocol Buffers wasn’t high-throughput serialization benchmarks or binary compactness, but the Schema Definition Language itself. It gave us a clean, readable contract format that existed independently of application code.
Before settling on Protobuf, we had piloted GraphQL since our web frontend already used it. But its frontend-centric design (forced field-selection, normalized client caching, and query versus mutation splits) introduced unnecessary overhead for internal backend APIs. OpenAPI was the other obvious option, but its tooling typically forced schemas into decorator-heavy code pipelines, and raw OpenAPI JSON was nearly impossible to navigate in an editor without external documentation generators to resolve nested references.
Protocol Buffers gave us a direct, human-readable contract language, and pairing it with the `buf` CLI gave us clean `buf.yaml` configurations and fast local linting. Initially, all our services lived in a single monorepo, so generating code across service boundaries was straightforward. The real friction began when our code moved across repository boundaries.

## The cross-repository sync problem

When we first moved to multi-repo setups, we generated client libraries next to the server definitions and distributed them through the standard npm packaging lifecycle. That introduced a surprising amount of ceremony: bumping package versions, running publish steps, and updating consumer dependencies across pull requests, all just to keep a consumer in sync with a server definition living in another repository.
In my current work, we use Connect between a backend monolith and a separate frontend repository. Even without a microservices mesh, the boundary problem remains identical: keeping `.proto` definitions synchronized across repository lines.
We didn’t adopt the Buf Schema Registry because of its pricing, so we relied on an ad-hoc workaround: copying the `.proto` files between repositories and running local code generation as needed.
The ad-hoc approach works, but it feels clumsy. In modern software development, dependency resolution is a solved baseline across npm, Cargo, and Go modules. Regressing to manual file-copying across repositories because registry pricing is prohibitive feels like an unnecessary compromise.

## The friction of per-type pricing

I’ve never considered `buf.build`’s pricing model viable for our teams. I can’t speak for every organization, but for me, the core requirement of a schema registry (simply hosting and versioning private Protobuf modules) could never be justified at a per-type price tag.
On Buf’s published pricing, the free Community tier includes one private repository with up to 100 types. Beyond that, the Teams tier costs $0.50 per private type per month, and the Pro tier costs $5.00 per type per month with a $3,000 monthly minimum. Buf counts every `message`, `enum`, and `rpc` as a billable unit, averaged across the month.
In modern software development, package hosting is treated as basic infrastructure. GitHub doesn’t bill per line of code. Docker Hub doesn’t bill per container layer. An npm registry doesn’t bill per exported JavaScript function. Yet the Buf Schema Registry asks you to pay for the size of your schema.
The fundamental issue is that per-type pricing couples billing directly to everyday API design. Protobuf types don’t correspond to runtime infrastructure costs the way a persistent database, message queue, or cloud compute instance does. An abstract schema type consumes negligible storage and zero idle compute.
Yet under this model, standard API design practices actively drive up your bill. If you follow standard Protobuf conventions and Buf’s own recommended linting rules, you define a dedicated Request message, a Response message, and the RPC method itself for each endpoint. Following that basic hygiene means adding one well-designed RPC costs $1.50 every month in perpetuity.
Breaking a large, messy message into two cleaner domain objects or adding an enum variant directly increases your monthly invoice. Buf even publishes documentation on cost control and pruning types from your schemas, which highlights how an unnatural pricing unit can escape the billing layer and influence technical architecture.
The startups I’ve worked at were small teams of fewer than ten engineers. Whether evaluated from the perspective of a scrappy startup avoiding unnecessary monthly overhead, or a well-funded team willing to spend money to accelerate developer velocity, paying per type for schema storage never made sense.
I’ve never used a paid Buf plan, so I can’t speak to the quality of their enterprise tooling. But for teams like ours, whose primary need is simple, reliable module hosting and versioning, paying hundreds or thousands of dollars a month for developer convenience tooling just doesn’t add up.

## Cloudflare economics and the wire protocol

While dealing with cross-repo synchronization, I’d been following Cloudflare’s platform developments, particularly Workers, D1, and R2. Seeing how cheaply and reliably they operate as a primary application stack made me curious whether those economics could solve this problem.
A Protobuf schema registry is fundamentally versioned file storage paired with a relational metadata graph. Cloudflare R2 provides object storage with zero egress fees, meaning consumers can run `buf dep update` without accumulating bandwidth charges. Cloudflare D1 provides a serverless relational database with zero idle capacity costs, eliminating the need to pay for provisioned database instances. Cloudflare Workers provides a fast TypeScript runtime with native bindings to D1 and R2 and sub-millisecond cold starts.
Because the marginal cost of running this infrastructure is effectively zero if nobody uses it, a solo developer can build and operate a production registry sustainably. That completely alters the calculus, making flat pricing viable without venture subsidies or usage meters:
  • >Free ($0): Unlimited public modules and 1 private module for solo developers.
  • >Solo ($9/mo): Unlimited private modules for 1 user.
  • >Team ($49/mo flat): Unlimited private modules, unlimited types, and unlimited members for entire engineering teams.
  • >Open source ($0): Free team accounts for eligible public OSS projects.
The `buf` CLI is open source (Apache-2.0), and Buf published their protocol definitions in `bufbuild/registry-proto`. Implementing the server side, however, revealed subtle gaps between the published proto definitions and the real-world behavior of the `buf` CLI.
Achieving complete drop-in compatibility required auditing the CLI’s open-source Go codebase to account for undocumented behaviors, specific error-handling expectations, and endpoints marked unstable that the official CLI still calls in practice. It became clear that while Buf open-sourced the protocol, supporting alternative registries wasn’t an active priority for them.
From a business perspective, that makes sense. Buf has largely shifted its focus toward enterprise data infrastructure with Bufstream, their Kafka alternative. With enterprise contracts already structured around the escalating per-type model, they have little commercial incentive to cannibalize their revenue by offering a flat $49 tier for small teams. The developer mid-market was left without a dedicated option.

## Scope and boundaries

Protorip focuses strictly on the 90% developer loop.
A schema registry sits entirely off the production request path as development plumbing. The complex semantics (type validation, dependency graph resolution, and content-addressed digest hashing) are executed locally inside the open-source `buf` CLI. The registry’s job is simply to store, version, and resolve those modules reliably.
This design also aligns with how modern teams handle quality checks. While Buf offers server-side review gates that quarantine non-compliant pushes at the registry, most engineering teams already run `buf lint` and `buf breaking` directly on pull requests in GitHub Actions. Running checks in CI keeps feedback inside the developer’s normal code review flow, rather than failing a push command late in the process and requiring developers to debug it in a separate vendor dashboard.
Migrating to Protorip is a direct hostname replacement: you point your module name in `buf.yaml` to `protos.rip` and authenticate with `buf registry login protos.rip`.
```yaml
version: v2
modules:
- path: proto
name: protos.rip/acme/orderapis
deps:
- protos.rip/googleapis/googleapis
- protos.rip/bufbuild/protovalidate
```
```bash
buf registry login protos.rip
buf dep update
buf generate
buf push --label v1.0.0
```
We are currently rolling out mirrored community remote plugins (`protocolbuffers/go`, `connectrpc/es`, and others) so developers and CI runners can run `buf generate` without configuring local compiler toolchains. Automatic client SDK generation for package managers like npm or Maven is something we may explore in the future, while enterprise features like SAML SSO and dedicated instances remain well-served by Buf’s enterprise platform.
By building on modern edge primitives, Protorip provides what the `buf` CLI ecosystem was missing: a dependable, flat-rate schema registry that lets you focus entirely on your API design.