Bug: Union type exported from a shared package is not recognized as a union in API schema validation
Encore’s api() schema validation fails when a union type is imported from a shared package (e.g., a @shared/ workspace), even when the type is equivalent to an inline union of string literals.
This causes issues when attempting to define reusable types like OAuthProviderKey across backend services.
Reproduction
Shared package
// @shared/oauth/config.ts
export const OAUTH_PROVIDERS = {
google: {
name: 'Google',
authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
baseScope: ['openid', 'email', 'profile'] as const,
responseType: 'code',
},
facebook: {
name: 'Facebook',
authUrl: 'https://www.facebook.com/v15.0/dialog/oauth',
baseScope: ['email', 'public_profile'] as const,
responseType: 'code',
},
discord: {
name: 'Discord',
authUrl: 'https://discord.com/api/oauth2/authorize',
baseScope: ['identify', 'email'] as const,
responseType: 'code',
},
twitter: {
name: 'Twitter',
authUrl: 'https://twitter.com/i/oauth2/authorize',
baseScope: ['tweet.read', 'users.read', 'offline.access'] as const,
responseType: 'code',
},
} as const;
export type OAuthProviderKey = keyof typeof OAUTH_PROVIDERS;
✅ Working Example
// backend/auth.ts
import { api } from "encore.dev/api";
interface WorkingParams {
provider: "google" | "facebook" | "discord" | "twitter"; // ✅ works
}
export const testWorking = api(
{ expose: true, method: "POST" },
async (params: WorkingParams) => {
return { ok: true };
}
);
❌ Failing Example
// backend/auth.ts
import type { OAuthProviderKey } from "@shared/oauth/config";
interface BrokenParams {
provider: OAuthProviderKey; // ❌ validation fails at runtime
}
export const testBroken = api(
{ expose: true, method: "POST" },
async (params: BrokenParams) => {
return { ok: true };
}
);
Even though OAuthProviderKey is resolved as type OAuthProviderKey = "google" | "facebook" | "discord" | "twitter", Encore fails to recognize the enum for request validation, when the type is imported from a shared package.
Also, using a default-exported shared type, such as:
// @shared/oauth/config.ts
export type OAuthProviderKey = "google" | "facebook" | "discord" | "twitter"
will not work and will result in the following error in both scenarios:
> [email protected] dev C:\Users\xxx\Desktop\xxx\backend
> encore run --watch
⠋ Building Encore application graph...
⠋ Analyzing service topology...
error: object not found: OAuthProviderKey
--> C:\Users\xxx\Desktop\xxx\backend\services\auth\social-auth.ts:7:10
|
7 | import { OAuthProviderKey } from "@shared/oauth/config";
| ^^^^^^^^^^^^^^^^^^^^
error: unknown identifier
--> C:\Users\xxx\Desktop\xxx\backend\services\auth\social-auth.ts:13:13
|
13 | provider: OAuthProviderKey;
| ^^^^^^^^^^^^^^^^^^^^
Expected Behavior
Encore should treat imported union types the same as inline union literals when generating and validating the API schema.
This would enable better reuse of types across multiple services and packages in a monorepo.
Workaround
Manually repeat the union type inline in every service using it — but this defeats the purpose of having shared types in a monorepo.
Environment
- Encore version: [latest]
- TypeScript: 5.x
- Shared package built with the latest version of Tsup
- Platform: Windows (also tested on Linux)
Thanks. This is a known limitation and we are working on extending the types support. Generally, the workaround described -- declaring your own types can be more robust than relying on types defined in external packages.
Hi Marcus, is there any ETA on when this might be prioritised for a release? We have multiple occurrences across our current backend routes where we've now been forced to re-define types already defined elsewhere. We've already faced our first bugs that slipped past our code review as a result of this
We've made a bunch of improvements to the type resolution logic already. It's possible this issue has been fixed in the latest release. If you can verify and let us know whether it's still an issue that would be helpful. Thanks!