feat: add createWorkOS factory for type-safe public/confidential clients [v8]#1440
Merged
feat: add createWorkOS factory for type-safe public/confidential clients [v8]#1440
Conversation
Adds a factory function with overloaded signatures that returns different
types based on input credentials:
- `createWorkOS({ clientId })` returns `PublicWorkOS` (PKCE methods only)
- `createWorkOS({ apiKey })` returns full `WorkOS` type
This provides compile-time safety for public clients (Electron, mobile, CLI)
that cannot use API-key-required methods.
These are server-side concerns (receiving callbacks from WorkOS), not relevant to public client use cases (PKCE auth flows).
imkesin
approved these changes
Jan 8, 2026
imkesin
reviewed
Jan 8, 2026
Comment on lines
+10
to
+15
| beforeEach(() => { | ||
| jest.resetModules(); | ||
| process.env = { ...OLD_ENV }; | ||
| delete process.env.WORKOS_API_KEY; | ||
| delete process.env.WORKOS_CLIENT_ID; | ||
| }); |
There was a problem hiding this comment.
If we want to be sure that createWorkOS doesn't rely on these, there could be some approach with a Proxy to throw an error when we try to get certain variables.
(Obviously would be more work + gross)
Member
Author
There was a problem hiding this comment.
Yeah IMO it's not worth it. This give you the compile-time safety but if you have the env var set it'll still work but at that point you're ignoring TS errors or using JS and not worried about the factory function's return type, anyway.
db7be12 to
b3222f4
Compare
- Remove string apiKey overload for cleaner, unambiguous signature - Add @ts-expect-error tests proving PublicWorkOS type narrowing works - Add test verifying factory types based on explicit options, not env vars - Document apiKey?: never discriminant pattern
nicknisi
added a commit
that referenced
this pull request
Jan 9, 2026
…nts [v8] (#1440) ## Summary Adds a `createWorkOS()` factory function that provides **compile-time type safety** for public vs confidential clients. When the SDK is instantiated with only a `clientId` (no API key), most methods throw `ApiKeyRequiredException` at runtime. However, TypeScript can't warn you at compile time because the `WorkOS` class type is static—all methods appear available regardless of how it was constructed. The factory solves this by using function overloads that return different types based on input: - `createWorkOS({ clientId })` → returns `PublicWorkOS` (narrow type exposing only PKCE-compatible methods) - `createWorkOS({ apiKey, ... })` → returns full `WorkOS` type ## Example ```typescript import { createWorkOS } from '@workos-inc/node'; // Public client (Electron, mobile, CLI) - only PKCE methods available const publicClient = createWorkOS({ clientId: 'client_123' }); // ✅ These work - available on PublicWorkOS const { url, codeVerifier } = await publicClient.userManagement.getAuthorizationUrlWithPKCE({ provider: 'authkit', redirectUri: 'myapp://callback', }); const auth = await publicClient.userManagement.authenticateWithCodeAndVerifier({ code: authCode, codeVerifier, }); // ❌ TypeScript error - not available on PublicWorkOS publicClient.userManagement.listUsers(); publicClient.organizations.list(); // Confidential client (server) - full access const serverClient = createWorkOS({ apiKey: process.env.WORKOS_API_KEY!, clientId: 'client_123', }); // ✅ All methods available await serverClient.userManagement.listUsers(); await serverClient.organizations.list(); ``` ## Design - **No runtime changes** - The factory returns a standard `WorkOS` instance; type narrowing is purely compile-time - **Uses `Pick<>` for maintainability** - Public method names are listed once as a string union; types are derived automatically - **Ignores env vars** - Factory uses only explicit input for predictable types. Users who want env var convenience can use `new WorkOS()` or pass `process.env.WORKOS_API_KEY` explicitly - **Backward compatible** - `new WorkOS()` continues to work unchanged
nicknisi
added a commit
that referenced
this pull request
Jan 12, 2026
…nts [v8] (#1440) ## Summary Adds a `createWorkOS()` factory function that provides **compile-time type safety** for public vs confidential clients. When the SDK is instantiated with only a `clientId` (no API key), most methods throw `ApiKeyRequiredException` at runtime. However, TypeScript can't warn you at compile time because the `WorkOS` class type is static—all methods appear available regardless of how it was constructed. The factory solves this by using function overloads that return different types based on input: - `createWorkOS({ clientId })` → returns `PublicWorkOS` (narrow type exposing only PKCE-compatible methods) - `createWorkOS({ apiKey, ... })` → returns full `WorkOS` type ## Example ```typescript import { createWorkOS } from '@workos-inc/node'; // Public client (Electron, mobile, CLI) - only PKCE methods available const publicClient = createWorkOS({ clientId: 'client_123' }); // ✅ These work - available on PublicWorkOS const { url, codeVerifier } = await publicClient.userManagement.getAuthorizationUrlWithPKCE({ provider: 'authkit', redirectUri: 'myapp://callback', }); const auth = await publicClient.userManagement.authenticateWithCodeAndVerifier({ code: authCode, codeVerifier, }); // ❌ TypeScript error - not available on PublicWorkOS publicClient.userManagement.listUsers(); publicClient.organizations.list(); // Confidential client (server) - full access const serverClient = createWorkOS({ apiKey: process.env.WORKOS_API_KEY!, clientId: 'client_123', }); // ✅ All methods available await serverClient.userManagement.listUsers(); await serverClient.organizations.list(); ``` ## Design - **No runtime changes** - The factory returns a standard `WorkOS` instance; type narrowing is purely compile-time - **Uses `Pick<>` for maintainability** - Public method names are listed once as a string union; types are derived automatically - **Ignores env vars** - Factory uses only explicit input for predictable types. Users who want env var convenience can use `new WorkOS()` or pass `process.env.WORKOS_API_KEY` explicitly - **Backward compatible** - `new WorkOS()` continues to work unchanged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
createWorkOS()factory function that provides compile-time type safety for public vs confidential clients.When the SDK is instantiated with only a
clientId(no API key), most methods throwApiKeyRequiredExceptionat runtime. However, TypeScript can't warn you at compile time because theWorkOSclass type is static—all methods appear available regardless of how it was constructed.The factory solves this by using function overloads that return different types based on input:
createWorkOS({ clientId })→ returnsPublicWorkOS(narrow type exposing only PKCE-compatible methods)createWorkOS({ apiKey, ... })→ returns fullWorkOStypeExample
Design
WorkOSinstance; type narrowing is purely compile-timePick<>for maintainability - Public method names are listed once as a string union; types are derived automaticallynew WorkOS()or passprocess.env.WORKOS_API_KEYexplicitlynew WorkOS()continues to work unchanged