Generate framework-agnostic TypeScript services and models from Protobuf definitions using the native Fetch API.
gRPC/Connect transport. Runs on Node, browsers, Vite, SvelteKit, Next.js, Bun, Deno, and any TS runtime that ships fetch. No runtime dependencies in the generated code.
- NPM Package: @metaengine/protobuf-fetch
- NuGet Package: MetaEngine.TypeScript.Protobuf.Fetch
- Website: metaengine.eu/packages/protobuf-fetch
- ✅ Framework-agnostic — works in any TS runtime with
fetch - ✅ Native Fetch API — zero runtime dependencies in generated code
- ✅ gRPC/Connect transport — services, messages, and enums generated straight from
.proto - ✅ Production-ready — Bearer/Basic auth, timeouts, custom headers, middleware, all via CLI flags
- ✅ gRPC-native error handling — opt into a status-code taxonomy (
NOT_FOUND,UNAVAILABLE, …) instead of HTTP-status guessing - ✅ TypeScript — fully typed clients and models
- ✅ Result pattern — opt into
ApiResult<T>for structured error handling without exceptions - ✅ Middleware hooks —
onRequest/onResponse/onErrorcomposable around the request pipeline - ✅ Vite + SvelteKit —
--import-meta-envforimport.meta.envaccess - ✅ Tree-shakeable — separate file per model and per service
npm install --save-dev @metaengine/protobuf-fetchOr use directly with npx:
npx @metaengine/protobuf-fetch <input> <output>- Node.js 18.0 or later
- .NET 8.0 or later runtime (Download)
- A TS runtime that supports
fetch(Node 18+, all modern browsers, Bun, Deno)
npx @metaengine/protobuf-fetch service.proto ./src/api \
--documentation \
--result-patternnpx @metaengine/protobuf-fetch service.proto ./src/api \
--bearer-auth API_TOKEN \
--timeout 30 \
--custom-header X-Tenant-ID=TENANT_ID \
--error-handlingAdd to your package.json:
{
"scripts": {
"generate:api": "metaengine-protobuf-fetch service.proto ./src/api --bearer-auth API_TOKEN --timeout 30 --error-handling"
}
}Then run:
npm run generate:apiVite / SvelteKit (uses import.meta.env):
npx @metaengine/protobuf-fetch service.proto ./src/api \
--import-meta-env \
--base-url-env VITE_API_URLNext.js:
npx @metaengine/protobuf-fetch service.proto ./src/api \
--base-url-env NEXT_PUBLIC_API_URLPlain Node / Bun / Deno:
npx @metaengine/protobuf-fetch service.proto ./src/api \
--base-url-env API_BASE_URL# Multiple custom headers from env vars
npx @metaengine/protobuf-fetch service.proto ./src/api \
--custom-header X-Tenant-ID=TENANT_ID \
--custom-header X-App-Id=APP_ID
# Map well-known timestamp/duration types to strings
npx @metaengine/protobuf-fetch service.proto ./src/api \
--type-mapping google.protobuf.Timestamp=string \
--type-mapping google.protobuf.Duration=string| Option | Description | Default |
|---|---|---|
--base-url-env <name> |
Environment variable name for base URL | API_BASE_URL |
--service-suffix <suffix> |
Service naming suffix | Service |
--options-threshold <n> |
Parameter count for options object | 4 |
--documentation |
Generate JSDoc comments | false |
--import-meta-env |
Use import.meta.env for env access (Vite, SvelteKit) |
false |
--result-pattern |
Return ApiResult<T> instead of T for structured error handling |
false |
--middleware |
Emit middleware hooks (onRequest, onResponse, onError) in client |
false |
--error-handling |
gRPC-native error handling keyed on gRPC status codes | false |
--bearer-auth <env-var-name> |
Bearer token authentication (runtime token provider) | - |
--basic-auth <userEnv:passEnv> |
Basic auth from env vars (e.g. API_USER:API_PASS) |
- |
--timeout <seconds> |
Request timeout in seconds for all operations | - |
--custom-header <header=envVarName> |
Static header from env var. Repeatable. | - |
--date-transformation |
Convert google.protobuf.Timestamp fields in responses to Date objects |
false |
--types-barrel |
Emit an index.ts barrel per folder plus a root index.ts re-exporting everything |
false |
--clean |
Clean output directory (remove files not in generation) | false |
--verbose |
Enable verbose logging | false |
--type-mapping <name=target> |
Override TS type for a protobuf type. Repeatable. See Type mapping overrides | - |
--help, -h |
Show help message | - |
output/
├── models/ # One file per message/enum
│ ├── entity.ts # export interface Entity { ... }
│ ├── create-entity-request.ts
│ └── ...
├── services/ # One file per proto service
│ ├── entity.service.ts # All EntityService RPCs
│ └── ...
├── client.ts # ClientConfig, createClient, getDefaultClient
└── errors.ts # ApiResult / error helpers
With --error-handling, two extra models are emitted: models/grpc-status-code.ts and models/grpc-error.ts.
npx @metaengine/protobuf-fetch service.proto ./src/api --bearer-auth API_TOKENThe generated client.ts exposes an auth config field accepting a token string or an async token provider (() => string | Promise<string>), and adds Authorization: Bearer <token> to every request. This suits gRPC/Connect clients where tokens are refreshed at runtime rather than read once from the environment.
npx @metaengine/protobuf-fetch service.proto ./src/api --basic-auth API_USER:API_PASSReads the username and password from the two named environment variables and sends Authorization: Basic <base64(user:password)>.
npx @metaengine/protobuf-fetch service.proto ./src/api --timeout 30Uses AbortSignal.timeout(seconds * 1000) and composes correctly with consumer-supplied AbortSignal.
npx @metaengine/protobuf-fetch service.proto ./src/api \
--custom-header X-Tenant-ID=TENANT_ID \
--custom-header X-App-Id=APP_IDRepeatable. Each header value comes from a separate env var.
npx @metaengine/protobuf-fetch service.proto ./src/api --error-handlingEmits a GrpcError type and a GrpcStatusCode taxonomy. Errors are classified by gRPC status code rather than HTTP status: NOT_FOUND / PERMISSION_DENIED return null; INVALID_ARGUMENT / ALREADY_EXISTS / FAILED_PRECONDITION return the error body; UNAUTHENTICATED / INTERNAL / UNAVAILABLE throw.
npx @metaengine/protobuf-fetch service.proto ./src/api --result-patternOperations return ApiResult<T> instead of throwing — useful when you want to handle errors as values rather than exceptions:
const result = await entityService.getEntity({ id: '123' });
if (result.ok) {
console.log(result.data.name);
} else {
console.error(result.error.code, result.error.message);
}npx @metaengine/protobuf-fetch service.proto ./src/api --middlewareGenerated client emits a Middleware interface and accepts an optional middleware array on ClientConfig:
const client = createClient({
baseUrl: 'https://api.example.com',
middleware: [
{
onRequest: (req) => { console.log('→', req.url); return req; },
onResponse: (res) => { console.log('←', res.status); return res; },
onError: (err) => { console.error('✗', err); throw err; },
},
],
});Middleware composes with --bearer-auth, --timeout, and --error-handling.
Use --type-mapping to override the TS type emitted for a protobuf type, keyed by its fully-qualified name. Repeatable. Unsupported targets are hard errors.
| Target | Emitted TS type |
|---|---|
string |
string |
number |
number |
Date |
Date |
boolean |
boolean |
npx @metaengine/protobuf-fetch service.proto ./src/api \
--type-mapping google.protobuf.Timestamp=string \
--type-mapping google.protobuf.Duration=stringInteger scalars (including
int64/uint64) map tonumberby design so request bodies surviveJSON.stringifyand parsed responses match the wire shape. Abiginttarget is therefore not offered.
Try the generator with your own spec at https://www.metaengine.eu/converters.
The NuGet package allows programmatic use in .NET projects. See the website documentation for full C# API reference.
- Issues: GitHub Issues
- Email: info@metaengine.eu
- Website: metaengine.eu
MIT License - see LICENSE file for details.
This is the documentation and issue tracking repository for MetaEngine Protobuf Fetch. The compiled NPM package is available at @metaengine/protobuf-fetch.
Source code is proprietary, but the package is free to use under MIT license.