Skip to content

Conversation

@nicknisi
Copy link
Member

@nicknisi nicknisi commented Oct 13, 2025

Overview

Version 8 is a major release focused on universal runtime compatibility, PKCE authentication support, and API modernization. The SDK now works seamlessly across Node.js, Deno, Bun, and Cloudflare Workers while removing long-deprecated APIs.


BREAKING CHANGES

Runtime & Build System

1. Node.js Version Requirement (BREAKING)

  • Changed: Minimum Node.js version: 1620
  • Reason: Node 18 reached end-of-life in April 2025
  • Files: package.json (engines.node)
  • Migration: Update to Node.js 20 or higher

2. Package Type Change to ESM-First (BREAKING)

  • Changed: package.json now includes "type": "module"
  • Impact: The package is now ESM-first with dual CJS/ESM exports
  • Files: package.json
  • Migration: Most users won't need changes due to conditional exports, but projects with custom build configurations may need adjustments

3. Build System Migration (Internal)

  • Changed: Migrated from tsc to tsdown (Rolldown/Oxc-based) for building
  • Added: tsdown.config.ts configuration
  • Removed: tslint.json, tsup.config.ts
  • Impact: Faster builds, lower memory usage, improved tree-shaking

Removed Internal Classes (BREAKING)

4. HTTP Client Removal (BREAKING)

  • Removed: NodeHttpClient class and createHttpClient() method
  • Files: src/common/net/node-client.ts (deleted)
  • Impact: Only affects deep imports into internal modules (never part of public API)
  • Migration: Use WorkOS instance instead - HTTP is handled automatically

5. Crypto Provider Removal (BREAKING)

  • Removed: NodeCryptoProvider, IronSessionProvider, EdgeIronSessionProvider, WebIronSessionProvider
  • Changed: Now using iron-webcrypto v2 directly with lightweight wrapper
  • Files: src/common/crypto/node-crypto-provider.ts (deleted), src/common/iron-session/* (deleted), src/common/crypto/seal.ts (added)
  • Impact: Only affects deep imports into internal modules
  • Migration: Use WorkOS instance methods - crypto is handled automatically

Directory Sync (BREAKING)

6. DirectoryUser Interface Changes (BREAKING)

  • Removed fields:
    • emails → Use customAttributes.emails
    • username → Use customAttributes.username
    • jobTitle → Use customAttributes.jobTitle
  • Removed utility: getPrimaryEmail() function
  • Files: src/directory-sync/interfaces/directory-user.interface.ts, src/directory-sync/utils/get-primary-email.ts (deleted)
  • Migration:
    // v7
    user.emails
    user.username
    user.jobTitle
    
    // v8
    user.customAttributes?.emails
    user.customAttributes?.username
    user.customAttributes?.jobTitle

User Management (BREAKING)

7. AuthorizationURLOptions Changes (BREAKING)

  • Removed: context field (no longer supported)
  • Files: src/user-management/interfaces/authorization-url-options.interface.ts
  • Migration: Remove the context parameter from authorization URL calls

8. Removed Deprecated Methods (BREAKING)

  • Removed:
    • sendMagicAuthCode() → Use userManagement.sendMagicCode() instead
    • sendPasswordResetEmail() → Use userManagement.sendPasswordResetEmail() instead
    • refreshAndSealSessionData() → Use new session helper methods instead
  • Files: src/user-management/user-management.ts, serializers removed

9. listOrganizationMemberships Requires userId or organizationId (BREAKING)

  • Changed: listOrganizationMemberships() now requires either userId or organizationId
  • Files: src/user-management/user-management.ts
  • Migration:
    // v7 - Could call without parameters
    workos.userManagement.listOrganizationMemberships();
    
    // v8 - Must specify userId or organizationId
    workos.userManagement.listOrganizationMemberships({ userId: 'user_123' });
    // OR
    workos.userManagement.listOrganizationMemberships({ organizationId: 'org_456' });

SSO (BREAKING)

10. SSOAuthorizationURLOptions Type Changes (BREAKING)

  • Changed: Converted to discriminated union for type safety
  • Removed: domain field (deprecated)
  • Files: src/sso/interfaces/authorization-url-options.interface.ts
  • Impact: Stricter TypeScript types - must now specify exactly one of: connection, organization, or provider
  • Migration:
    // v7 - Multiple options allowed
    { connection: 'conn_123', organization: 'org_456' } // Both accepted
    
    // v8 - Mutually exclusive (enforced by types)
    { connection: 'conn_123' } // OR
    { organization: 'org_456' } // OR
    { provider: 'GoogleOAuth' }

11. Connection Interface (BREAKING)

  • Removed: Several deprecated internal fields
  • Files: src/sso/interfaces/connection.interface.ts
  • Migration: Use only documented fields

MFA (BREAKING)

12. Method Removal (BREAKING)

  • Removed: verifyFactor() method
  • Files: src/mfa/mfa.ts, src/mfa/interfaces/verify-factor-options.ts (deleted)
  • Migration: Use verifyChallenge() instead (same functionality)
    // v7
    await workos.mfa.verifyFactor({ authenticationFactorId, code });
    
    // v8
    await workos.mfa.verifyChallenge({ authenticationFactorId, code });

Organizations (BREAKING)

13. Organization Options Changes (BREAKING)

  • Removed from CreateOrganizationOptions and UpdateOrganizationOptions:
    • allowProfilesOutsideOrganization
    • domains (use domainData instead)
  • Files: src/organizations/interfaces/*.interface.ts
  • Migration: Remove these fields from organization creation/update calls

14. Organization Domain Enum (BREAKING)

  • Removed: LegacyVerified from OrganizationDomainState enum
  • Files: src/organizations/interfaces/organization-domain.interface.ts
  • Migration: Use Verified instead

Events (BREAKING)

15. Event Type Removals (BREAKING)

  • Removed event interfaces:
    • DsyncDeactivatedEvent → Use dsync.deleted instead
    • OrganizationMembershipAdded → Not applicable in v8
    • OrganizationMembershipRemoved → Not applicable in v8
  • Files: src/common/interfaces/event.interface.ts
  • Migration:
    // v7
    if (event.event === 'dsync.deactivated') { }
    
    // v8
    if (event.event === 'dsync.deleted') { }

Vault (BREAKING)

16. Removed Deprecated Method Aliases (BREAKING)

  • Removed methods:
    • createSecret() → Use createObject() instead
    • listSecrets() → Use listObjects() instead
    • listSecretVersions() → Use listObjectVersions() instead
    • readSecret() → Use readObject() instead
    • describeSecret() → Use describeObject() instead
    • updateSecret() → Use updateObject() instead
    • deleteSecret() → Use deleteObject() instead
  • Files: src/vault/vault.ts
  • Migration: Replace all *Secret methods with *Object equivalents

Webhooks (NON-BREAKING)

17. constructEvent Payload Typing (NON-BREAKING)

  • Changed: constructEvent payload parameter typed as Record<string, unknown> for better type safety
  • Files: src/webhooks/webhooks.ts
  • Impact: Stricter typing, but backwards compatible

NEW FEATURES

18. PKCE Authentication Support (NON-BREAKING)

  • Added: Full PKCE (Proof Key for Code Exchange) support for public and confidential clients
  • API key now optional: Initialize with just clientId for PKCE-only mode
  • New methods:
    • userManagement.getAuthorizationUrlWithPKCE() - generates PKCE internally, returns { url, state, codeVerifier }
    • userManagement.authenticateWithCodeAndVerifier() - explicit PKCE token exchange
    • workos.pkce.generate() - manual PKCE generation
  • Enhanced: authenticateWithCode() auto-detects client mode based on credentials
  • Example:
    import { WorkOS } from '@workos-inc/node';
    
    // Public client (no API key)
    const workos = new WorkOS({ clientId: 'client_123' });
    
    // Generate authorization URL with PKCE
    const { url, state, codeVerifier } = await workos.userManagement.getAuthorizationUrlWithPKCE({
      redirectUri: 'myapp://callback',
      provider: 'authkit',
    });
    
    // Exchange code for tokens
    const { accessToken, refreshToken, user } = await workos.userManagement.authenticateWithCode({
      code: authCode,
      codeVerifier,
    });

19. createWorkOS Factory Function (NON-BREAKING)

  • Added: createWorkOS() factory with compile-time type safety for public vs confidential clients
  • Files: src/workos.ts
  • Benefit: TypeScript will error at compile time if you try to use methods unavailable to public clients
  • Example:
    import { createWorkOS } from '@workos-inc/node';
    
    // Public client - only PKCE methods available
    const publicClient = createWorkOS({ clientId: 'client_123' });
    publicClient.userManagement.getAuthorizationUrlWithPKCE({ ... }); // ✅ Works
    publicClient.userManagement.listUsers(); // ❌ TypeScript error
    
    // Confidential client - full access
    const serverClient = createWorkOS({ apiKey: 'sk_...', clientId: 'client_123' });
    serverClient.userManagement.listUsers(); // ✅ Works

20. Universal Runtime Support (NON-BREAKING)

  • Improved: Better support for Deno, Bun, and Cloudflare Workers
  • Added: Conditional exports in package.json for runtime-specific entry points
  • Files: package.json (exports), src/index.worker.ts
  • Example:
    // Cloudflare Workers
    import { WorkOS } from '@workos-inc/node/worker';
    
    // Deno
    import { WorkOS } from 'npm:@workos-inc/node';
    
    // Bun (same as Node.js)
    import { WorkOS } from '@workos-inc/node';

21. Environment Variable Helper (NON-BREAKING)

  • Added: Internal getEnv() helper for better cross-runtime environment variable access
  • Files: src/common/utils/env.ts
  • Benefit: Works consistently across Node.js, Deno, Cloudflare Workers, etc.

22. Pagination Improvements (NON-BREAKING)

  • Changed: AutoPaginatable now properly defaults PaginationOptions generic parameter
  • Files: src/common/utils/pagination.ts
  • Benefit: Better TypeScript inference and type safety

23. Runtime Analytics (NON-BREAKING)

  • Added: Runtime information included in User-Agent string for better debugging
  • Files: src/workos.ts
  • Impact: Helps identify runtime-specific issues

INTERNAL IMPROVEMENTS

24. Linting Migration (Internal)

  • Changed: Migrated from TSLint to ESLint with flat config
  • Added: eslint.config.mjs
  • Removed: tslint.json
  • Impact: Better code quality checks, modern linting setup

25. Runtime Testing (Internal)

  • Added: New GitHub Actions workflow for testing across multiple runtimes
  • Added: Ecosystem check script (scripts/ecosystem-check.ts)
  • Files: .github/workflows/runtime-tests.yml
  • Benefit: Ensures SDK works correctly in all supported runtimes

26. Dependency Updates (Internal)

  • Updated: All dependencies to latest compatible versions
  • Updated: Development tooling (Jest 30, TypeScript 5.9, etc.)
  • Removed: External leb and qs packages - replaced with internal vanilla implementations
  • Files: package.json, package-lock.json

27. Test Infrastructure (Internal)

  • Updated: Jest configuration migrated to CommonJS
  • Added: Jest ESM transform
  • Files: jest.config.cjs, jest-transform-esm.cjs
  • Updated: Worker test environment with latest Miniflare

28. Dynamic jose Import (Internal)

  • Added: Dynamic import wrapper for jose to support Node.js 20.15-20.18 compatibility
  • Files: src/common/utils/jose.ts
  • Benefit: Wider Node.js version compatibility

29. Runtime-Agnostic UUIDs (Internal)

  • Changed: Now uses globalThis.crypto.randomUUID instead of Node-specific crypto
  • Files: Various
  • Benefit: Better cross-runtime compatibility

REMOVED (Previously Announced)

30. /client Entry Point Removed (BREAKING)

  • Removed: The @workos-inc/node/client entry point
  • Migration: Use the main SDK without an API key instead:
    // Old approach (v8 beta)
    import { userManagement } from '@workos-inc/node/client';
    const url = userManagement.getAuthorizationUrl({ ... });
    
    // New approach (v8 final)
    import { WorkOS } from '@workos-inc/node';
    const workos = new WorkOS({ clientId: 'client_...' });
    const url = workos.userManagement.getAuthorizationUrl({ ... });

Files Changed Summary

  • Major additions: PKCE support, createWorkOS factory, runtime-specific exports, environment helper
  • Major deletions: Internal HTTP/crypto providers, deprecated methods, legacy event types, /client entry point
  • Configuration: New build system (tsdown), new linting (ESLint), updated Jest

Migration Guide

See V8_MIGRATION_GUIDE.md for detailed migration instructions.


Testing

  • All existing tests updated and passing
  • Runtime compatibility tests added
  • Worker environment tests updated
  • Ecosystem check script validates multi-runtime support

@nicknisi nicknisi requested a review from a team as a code owner October 13, 2025 17:50
@nicknisi nicknisi requested a review from amygdalama October 13, 2025 17:50
@nicknisi nicknisi marked this pull request as draft October 17, 2025 13:34
@benasher44
Copy link

Since you're going for dual ESM/CJS, you might want to check "are the types wrong". For example: https://arethetypeswrong.github.io/?p=%40workos-inc%2Fnode%408.0.0-rc.2

@socket-security
Copy link

socket-security bot commented Dec 4, 2025

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

4 similar comments
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@nicknisi nicknisi marked this pull request as ready for review January 8, 2026 21:33
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

3 similar comments
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 8, 2026

Too many files changed for review.

nicknisi and others added 29 commits January 9, 2026 16:49
## Description

This pull request removes several deprecated fields and enum values from
user management and directory sync interfaces, as well as from
organization domain state definitions. These changes help clean up the
codebase and ensure that only current, supported attributes are exposed
in the interfaces.

### Deprecated field removals

**User management authorization options:**
* Removed the deprecated `context` field from both
`AuthorizationURLOptions` and `UserManagementAuthorizationURLOptions`
interfaces, as it is no longer required for getting authorization URLs.
[[1]](diffhunk://#diff-b5a04503adce4aaadee02b4511ee9bd11ec26a46927bde7c07d85ad31786e4bbL9-L13)
[[2]](diffhunk://#diff-22f5a89986b17dd45c06dbbba9f624690b22d5ede6e7455dade9d3fb7924f131L6-L10)

**Directory user interfaces:**
* Removed deprecated fields (`emails`, `username`, and `jobTitle`) from
both `DirectoryUser` and `DirectoryUserResponse` interfaces, as these
should now be accessed through custom attributes.
[[1]](diffhunk://#diff-2151436e8d7d3a67c1165760fdfa80802fd72276034e54114c3ddfcdfe03e7fcL22-L41)
[[2]](diffhunk://#diff-2151436e8d7d3a67c1165760fdfa80802fd72276034e54114c3ddfcdfe03e7fcL61-L80)

### Enum cleanup

**Organization domain state:**
* Removed the deprecated `LegacyVerified` value from the
`OrganizationDomainState` enum.

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
This pull request updates the project's Node.js version requirements and
related configuration files to use Node.js 20 or newer, and removes
support for Node.js 18. These changes help ensure compatibility with the
latest Node.js features and security updates.

**Node.js version updates:**

* Updated the required Node.js engine in `package.json` from `>=18` to
`>=20`.
* Changed the `@types/node` development dependency in `package.json`
from version `~18` to `~20`.

**CI/CD workflow updates:**

* Modified the matrix in `.github/workflows/ci.yml` to run tests only on
Node.js versions 20, 22, and 24, removing Node.js 18.
* Updated the Node.js version used in
`.github/workflows/fix-latest.yml`, `.github/workflows/release.yml`, and
`.github/workflows/runtime-tests.yml` from 18 to 22.
[[1]](diffhunk://#diff-942585ab102b61d7fc2b3ebd901f78b627f006e4fe9ed539189fce8bfc503740L23-R23)
[[2]](diffhunk://#diff-87db21a973eed4fef5f32b267aa60fcee5cbdf03c67fafdc2a9b553bb0b15f34L22-R22)
[[3]](diffhunk://#diff-8240caaafb609c069828d284a282ebeb4f57b1ce1668ab60c97ad12412dfeb6dL12-R12)

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
This pull request primarily updates dependencies and refactors test
suites to improve consistency and compatibility with newer libraries.
The most significant changes are grouped below:

**Dependency Upgrades:**

* Updated several dependencies in `package.json`, including major
upgrades to `jose`, `eslint`, `jest`, `babel-jest`, `miniflare`, `nock`,
`supertest`, `ts-jest`, `tsx`, `typescript`, and related type packages.
These upgrades ensure compatibility with the latest features and
security patches.
[[1]](diffhunk://#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519L44-R44)
[[2]](diffhunk://#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519L53-R78)

**Testing Consistency & Modernization:**

* Replaced all usages of `.toThrowError` in Jest test assertions with
`.toThrow` for error checking across multiple test files, aligning with
best practices for newer Jest versions.
[[1]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L129-R135)
[[2]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L284-R286)
[[3]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L346-R344)
[[4]](diffhunk://#diff-cd36b281a0abacada2f46ce13c54911e6c24a720de104792fec5f1b529f10f74L183-R185)
[[5]](diffhunk://#diff-cd36b281a0abacada2f46ce13c54911e6c24a720de104792fec5f1b529f10f74L203-R205)
[[6]](diffhunk://#diff-6b41d79b4db8c635487ebe8aa7e41eae6e10899ed0876c1de5f81e3b8a97b529L205-R205)
[[7]](diffhunk://#diff-6b41d79b4db8c635487ebe8aa7e41eae6e10899ed0876c1de5f81e3b8a97b529L357-R357)
[[8]](diffhunk://#diff-f5f65d907d4110a523dd2249817af8ee20f4f9e2f8259fd0594f0ba909237b47L154-R154)
[[9]](diffhunk://#diff-f5f65d907d4110a523dd2249817af8ee20f4f9e2f8259fd0594f0ba909237b47L178-R178)
[[10]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400L2201-R2203)
[[11]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL102-R104)
[[12]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL113-R115)
[[13]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL124-R126)
[[14]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL136-R138)
[[15]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL148-R150)

* Updated Jest snapshot file headers to reference the current Jest
documentation URL instead of the deprecated link.
[[1]](diffhunk://#diff-51e4a0fec533fa983dab0091ee91e95a2de52e2c42836ff8de594153df44bbecL1-R1)
[[2]](diffhunk://#diff-e3850ae39761cff3333b5cf46beb64fb7171cc18c9a91eaa3693ba3c6a4985dcL1-R1)

**Mocking Improvements for JWT Handling:**

* Refactored mocking of the `jose` library in tests, switching from
`jest.spyOn` to `jest.mocked` for `jwtVerify` and `createRemoteJWKSet`.
This approach is more robust and compatible with newer Jest and
TypeScript versions.
[[1]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327R9-R13)
[[2]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327L69-R74)
[[3]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327L102-R107)
[[4]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327L250-R254)
[[5]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327L338-R342)
[[6]](diffhunk://#diff-19c19386dcd41859de57e28e16189fe3fa794ae5bb588b15ddde89eee5c9b327L384-R387)
[[7]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400R25-R30)
[[8]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400L888-R900)
[[9]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400L974-R978)
[[10]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400L1001-R1005)
[[11]](diffhunk://#diff-59f7ea4f069122023e354579e47852aa960f15a06c522ceedde35f7c66db0400L1043-R1046)

**Error Handling Standardization:**

* Standardized error throwing in tests by using direct error classes
(e.g., `UnauthorizedException`, `SignatureVerificationException`)
instead of custom error objects, simplifying the test logic and
improving clarity.
[[1]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L129-R135)
[[2]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L284-R286)
[[3]](diffhunk://#diff-674b12b55633a96dd5a8eb3177311ff4d2253741edc2a281166a829225aba040L346-R344)
[[4]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL102-R104)
[[5]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL113-R115)
[[6]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL124-R126)
[[7]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL136-R138)
[[8]](diffhunk://#diff-6e2a0e50ff73fa9faf5240a7705aabf4e870e3c3126c877fafc136cc723d346bL148-R150)

**Minor Test Maintenance:**

* Removed unused imports and made minor code cleanups in test files to
reduce noise and improve maintainability.

These changes collectively modernize the codebase, improve test
reliability, and ensure compatibility with the latest tooling.

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
## Description

This pull request introduces a minor improvement to the
`AutoPaginatable` class in `src/common/utils/pagination.ts`. The change
sets a default type for the `ParametersType` generic parameter, making
the class easier to use when the default `PaginationOptions` are
sufficient.

* TypeScript Improvement:
* Set a default value for the `ParametersType` generic parameter to
`PaginationOptions` in the `AutoPaginatable` class, simplifying
instantiation when custom options are not needed.

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
## Summary

Add runtime and version detection to the WorkOS Node SDK User-Agent
string to gather analytics about JavaScript runtime environments and
versions being used by customers.

## Changes

### Core Implementation
- **New utility**: `src/common/utils/runtime-info.ts` - Detects runtime
type and version across Node.js, Deno, Bun, Cloudflare Workers, and
other environments
- **User-Agent enhancement**: Modified `createUserAgent()` method in
`src/workos.ts` to include runtime information in standard format
- **Error handling**: Graceful fallbacks when runtime version detection
fails

### User-Agent Format
**Before**: `workos-node/8.0.0-beta.1 MyApp: 1.0.0`
**After**: `workos-node/8.0.0-beta.1 (node/v20.5.0) MyApp: 1.0.0`

The new format follows standard User-Agent conventions with runtime
information in parentheses, maintaining backward compatibility with
existing analytics parsing.

### Examples
- Node.js: `workos-node/8.0.0-beta.1 (node/v20.5.0)`
- Node.js with appInfo: `workos-node/8.0.0-beta.1 (node/v20.5.0) MyApp:
1.0.0`
- Deno: `workos-node/8.0.0-beta.1 (deno/1.36.4)`
- Edge runtime: `workos-node/8.0.0-beta.1 (edge-light)`

## Testing

### Unit Tests
- **17 comprehensive test cases** for `getRuntimeInfo()` utility
covering all runtime scenarios
- **Mock testing** for different environments (Node.js, Deno, Bun, edge
runtimes)
- **Error handling verification** with graceful degradation
- **Real-world testing** with actual Node.js environment

### Integration Tests
- **Updated `workos.spec.ts`** with runtime info mocking
- **Jest snapshots updated** to reflect new User-Agent format
- **Backward compatibility verified** for existing appInfo functionality

## Benefits

### For WorkOS Analytics
- **Node.js adoption tracking**: Monitor which Node.js versions are most
popular
- **Runtime diversity insights**: Understand usage across Node.js, Deno,
Bun, and edge environments
- **Support optimization**: Prioritize support for popular runtime
versions
- **Migration patterns**: Track adoption of new JavaScript runtime
versions

### For Customer Support
- **Environment debugging**: Quickly identify runtime-specific issues
from API logs
- **Performance optimization**: Focus optimization efforts on popular
runtime combinations
- **Compatibility planning**: Make informed decisions about runtime
support

## Technical Details

### Runtime Detection
- **Cached detection**: Uses existing `detectRuntime()` from `env.ts`
for performance
- **Version extraction**: Safely extracts version numbers from
runtime-specific globals
- **Fallback mechanisms**: Multiple strategies for version detection
(e.g., Bun version from user-agent)
- **Error resilience**: Never crashes, gracefully degrades to runtime
name only

### Implementation Approach
- **No configuration needed**: Runtime info is always included (internal
analytics feature)
- **Zero breaking changes**: Existing functionality unchanged
- **Standards compliant**: Follows established User-Agent format
conventions
- **Minimal overhead**: One-time detection per process with cached
results

## Risk Mitigation

- **Version detection failures**: Graceful fallback to runtime name only
- **Performance impact**: Minimal (cached detection, ~1ms overhead)
- **User-Agent length**: Reasonable increase (~20 characters)
- **Privacy concerns**: No personally identifiable information collected
…18 (#1371)

The jose library is ESM-only and cannot be loaded via require() in
Node.js versions before 20.19.0. This adds a dynamic import wrapper that
works across all Node.js 20+ versions using import() which is supported
in both ESM and CJS.

Breaking changes:
- UserManagement.jwks getter changed to async UserManagement.getJWKS()
method
- CookieSession.jwks property removed (uses UserManagement.getJWKS()
instead)

The wrapper enables:
- Lazy loading of jose (only when JWT methods are called)
- Support for all Node.js 20.x versions
- Smaller bundle size (no jose bundling needed)
- Clean migration path when Node 20 reaches EOL (April 2026)

Also updates:
- Minimum Node version to 20.15.0 (conservative choice within 20.x)
- tsup config: removes redundant external arrays (not needed with
bundle: false)

## Description

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
This pull request removes the dependency on the external `leb` and `qs`
packages by introducing an in-house LEB128 encoding/decoding utility and
a custom query string serializer. It updates all relevant imports to use
these new utilities, ensuring compatibility and maintainability.
Comprehensive unit tests for the new LEB128 implementation are also
included. This is to improve cross-runtime compatibility support.

**Dependency Removal and Internal Utility Replacement:**

* Removed the `leb` and `qs` packages from `package.json` and replaced
their usage with internal implementations. (`package.json`,
[package.jsonL45-R45](diffhunk://#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519L45-R45))
* Replaced all imports of `leb`'s `encodeUInt32`/`decodeUInt32` with
internal versions in `src/vault/vault.ts`.
* Removed the old `toQueryString` implementation from
`src/client/utils.ts` and updated all imports in `src/client/sso.ts` and
`src/client/user-management.ts` to use the new internal utility.
[[1]](diffhunk://#diff-3973d52ad7d2360214857fd42a183273a2e3904458c1eb573c34b3ec6151ab02L1-L20)
[[2]](diffhunk://#diff-aba556dc64a77e993f9ce2de8ffd20b276128d1f6f4ba69bf2967e05dc1f7676L1-R1)
[[3]](diffhunk://#diff-b5a04503adce4aaadee02b4511ee9bd11ec26a46927bde7c07d85ad31786e4bbL1-R1)

**New Utility Implementations:**

* Added `src/common/utils/leb128.ts`: Implements `encodeUInt32` and
`decodeUInt32` for LEB128 encoding/decoding of unsigned 32-bit integers,
with input validation and error handling.
* Added `src/common/utils/query-string.ts`: Implements `toQueryString`,
matching the old behavior (RFC1738 encoding, key sorting, array/object
handling) without external dependencies.

**Testing and Compatibility:**

* Added comprehensive unit tests for the new LEB128 implementation in
`src/common/utils/leb128.spec.ts`, including boundary values, invalid
input handling, and compatibility with the previous `leb` package's
output.

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
## Description

pretty self explanatory. the current path doesn't exist and gives an
error when trying to deploy
<img width="1620" height="682" alt="CleanShot 2025-11-15 at 19 38 29@2x"
src="https://github.com/user-attachments/assets/dadf41ee-5e53-49a6-a895-860840f2c404"
/>


## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

- [ ] Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
Replaces Node.js-specific crypto import with globalThis.crypto to ensure
compatibility across Node.js 20 and other runtimes.

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
## Summary
- Use Jest fake timers instead of real timers in audit log retry tests
- Tests now run instantly instead of waiting for actual retry delays
- Removes timeout extensions that were needed for slow tests
…#1413)

Migrate from tsup to tsdown to fix CI out-of-memory errors during
TypeScript declaration generation.

The build was hitting `ERR_WORKER_OUT_OF_MEMORY` errors in CI on Node 20
and 22. With 297 source files and tsup's worker-based DTS generation,
the memory requirements were too high.

Replace tsup with [tsdown](https://tsdown.dev/), a modern bundler
powered by Rolldown (Rust-based) and Oxc. Key benefits:

- **Faster builds**: Rolldown is significantly faster than esbuild for
bundling
- **Lower memory usage**: Oxc-based TypeScript declaration generation is
more memory-efficient
- **Same output structure**: Maintains the existing `lib/esm`,
`lib/cjs`, and `lib/types` directory structure

- Replaced `tsup` with `tsdown`
- Removed `esbuild-fix-imports-plugin` (no longer needed)
- Created `tsdown.config.ts` with equivalent configuration
- Updated build scripts in `package.json`

- 297 ESM files (`.js`) in `lib/esm/`
- 297 CJS files (`.cjs`) in `lib/cjs/`
- 296 declaration files (`.d.ts`) in `lib/types/`
- All tests pass (427 passed)

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
## Description

This pull request contains a minor version bump for the package,
updating all references from `8.0.0-rc.3` to `8.0.0-rc.4`.

* Updated the `version` field in `package.json` to `8.0.0-rc.4`.
* Updated the `VERSION` constant in `src/workos.ts` to `8.0.0-rc.4`.

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
- Replaces `iron-session` dependency with direct `iron-webcrypto` v2.0.0
- Creates lightweight `seal.ts` wrapper providing iron-session
compatible API
- Reduces dependency footprint while maintaining backwards compatibility

- Add `src/common/crypto/seal.ts` with `sealData` and `unsealData`
functions
- Update `package.json` to use `iron-webcrypto` ^2.0.0
- Update `jest.config.cjs` to transform ESM-only `uint8array-extras`
dependency
- Update imports in session and user-management modules
## Description

This pull request updates the package version from `8.0.0-rc.5` to
`8.0.0-rc.6` throughout the codebase to ensure consistency.

Version bump:

* Updated the `version` field in `package.json` to `8.0.0-rc.6`.
* Updated the `VERSION` constant in `src/workos.ts` to `8.0.0-rc.6`.

## Documentation

Does this require changes to the WorkOS Docs? E.g. the [API
Reference](https://workos.com/docs/reference) or code snippets need
updates.

```
[ ] Yes
```

If yes, link a related docs PR and add a docs maintainer as a reviewer.
Their approval is required.
Bundle `iron-webcrypto` and `uint8array-extras` into the build output
using `noExternal`.

These packages are ESM-only and cause `ERR_REQUIRE_ESM` errors for CJS
consumers without special Jest/bundler configuration.
…point (#1435)

Enable PKCE authentication for both public and confidential clients.

- **API key now optional**: Initialize with just `clientId` for PKCE
mode: `new WorkOS({ clientId: 'client_...' })`
- **New helper method**: `getAuthorizationUrlWithPKCE()` - generates
PKCE internally, returns `{ url, state, codeVerifier }`
- **Enhanced exchange**: `authenticateWithCode()` auto-detects client
mode based on available credentials
- **Manual PKCE option**: `workos.pkce.generate()` +
`getAuthorizationUrl()` for advanced use cases
- **Non-breaking**: Existing `getAuthorizationUrl()` unchanged, still
returns URL string

Server-side apps can use PKCE alongside the client secret for defense in
depth:

```ts
const workos = new WorkOS('sk_...'); // With API key

const { url, codeVerifier } = await workos.userManagement.getAuthorizationUrlWithPKCE({
  provider: 'authkit',
  redirectUri: 'https://example.com/callback',
  clientId: 'client_...',
});

// Both client_secret AND code_verifier will be sent
const { accessToken } = await workos.userManagement.authenticateWithCode({
  code: authorizationCode,
  codeVerifier,
  clientId: 'client_...',
});
```

The auto-detection logic:
| API Key | codeVerifier | Behavior |
|---------|--------------|----------|
| ✅ | ✅ | Send both `client_secret` AND `code_verifier` (confidential +
PKCE) |
| ✅ | ❌ | Send `client_secret` only (traditional confidential client) |
| ❌ | ✅ | Send `code_verifier` only (public client) |
| ❌ | ❌ | Error |

The separate `/client` entry point has been removed. Instead of:

```ts
// Old approach - standalone functions
import { userManagement } from '@workos-inc/node/client';
const url = userManagement.getAuthorizationUrl({ ... });
```

Use the standard SDK without an API key:

```ts
// New approach - consistent with rest of SDK
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS({ clientId: 'client_...' });
const url = workos.userManagement.getAuthorizationUrl({ ... });
```

This provides a single, consistent API surface rather than two parallel
approaches.

```ts
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS({ clientId: 'client_...' });

// Step 1: Get authorization URL with auto-generated PKCE
const { url, state, codeVerifier } = await workos.userManagement.getAuthorizationUrlWithPKCE({
  redirectUri: 'myapp://callback',
  provider: 'authkit',
});

// Store codeVerifier securely, then redirect user to url

// Step 2: Exchange code for tokens
const { accessToken, refreshToken, user } = await workos.userManagement.authenticateWithCode({
  code: authCode,
  codeVerifier,
});
```

| Method | Description |
|--------|-------------|
| `userManagement.getAuthorizationUrlWithPKCE()` | Build OAuth URL with
auto-generated PKCE |
| `userManagement.getAuthorizationUrl()` | Build OAuth URL (with manual
PKCE params) |
| `userManagement.authenticateWithCode()` | Exchange code + verifier for
tokens |
| `userManagement.authenticateWithCodeAndVerifier()` | Exchange code +
verifier for tokens (explicit) |
| `userManagement.authenticateWithRefreshToken()` | Refresh tokens |
| `userManagement.getLogoutUrl()` | Build logout redirect URL |
| `userManagement.getJwksUrl()` | Get JWKS URL for local JWT validation
|
| `workos.pkce.generate()` | Generate PKCE code verifier and challenge |
## Summary

- Remove unused devDependencies: `@types/cookie`, `@types/glob`,
`@types/qs`, `baseline-browser-mapping`, `glob`, `nock`, `supertest`
- Remove deprecated `/* eslint-env node */` comments (globals already
defined in eslint config for `.cjs` files)
- Fix formatting/linting issues across interface files and serializers
…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
## Summary
- Splits tsdown build config into separate ESM and CJS configurations
- ESM: unbundled with external deps (ESM consumers import ESM deps
directly)
- CJS: bundled with `iron-webcrypto` and `uint8array-extras` inlined

## Problem
Previous single config with `noExternal` created `lib/node_modules/`
structure that broke Electron asar packaging and pnpm symlink
resolution.

## Why CJS inlining is required
`iron-webcrypto` and `uint8array-extras` are ESM-only packages—they
don't ship CJS builds. CJS code can't `require()` ESM modules, so these
deps must be inlined/bundled into the CJS output for compatibility.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

8 participants