From 305cd475a8b8953b85a48647545665d9ffe614d9 Mon Sep 17 00:00:00 2001 From: Bas Ben Zineb Date: Thu, 5 Feb 2026 12:54:10 +0100 Subject: [PATCH 1/2] Add external_id support for User and Organization entities Add external_id field to User and Organization structs and implement new API endpoints to fetch entities by their external ID: - GET /user_management/users/external_id/:external_id - GET /organizations/external_id/:external_id New functions: - WorkOS.UserManagement.get_user_by_external_id/1,2 - WorkOS.Organizations.get_organization_by_external_id/1,2 This enables looking up users and organizations by their external identifier, which is useful when integrating WorkOS with existing user management systems. --- lib/workos/organizations.ex | 14 +++++++++ lib/workos/organizations/organization.ex | 3 ++ lib/workos/user_management.ex | 13 ++++++++ lib/workos/user_management/user.ex | 3 ++ test/support/organizations_client_mock.ex | 33 +++++++++++++++++++++ test/support/user_management_client_mock.ex | 21 ++++++++++++- test/workos/organizations_test.exs | 13 ++++++++ test/workos/user_management_test.exs | 13 ++++++++ 8 files changed, 112 insertions(+), 1 deletion(-) diff --git a/lib/workos/organizations.ex b/lib/workos/organizations.ex index 3334d249..d3b3ba13 100644 --- a/lib/workos/organizations.ex +++ b/lib/workos/organizations.ex @@ -75,6 +75,20 @@ defmodule WorkOS.Organizations do ) end + @doc """ + Gets an organization by external ID. + """ + @spec get_organization_by_external_id(String.t()) :: WorkOS.Client.response(Organization.t()) + @spec get_organization_by_external_id(WorkOS.Client.t(), String.t()) :: + WorkOS.Client.response(Organization.t()) + def get_organization_by_external_id(client \\ WorkOS.client(), external_id) do + WorkOS.Client.get(client, Organization, "/organizations/external_id/:external_id", + opts: [ + path_params: [external_id: external_id] + ] + ) + end + @doc """ Creates an organization. diff --git a/lib/workos/organizations/organization.ex b/lib/workos/organizations/organization.ex index 17473925..07711f09 100644 --- a/lib/workos/organizations/organization.ex +++ b/lib/workos/organizations/organization.ex @@ -13,6 +13,7 @@ defmodule WorkOS.Organizations.Organization do object: String.t(), name: String.t(), allow_profiles_outside_organization: boolean(), + external_id: String.t() | nil, domains: list(Domain.t()) | nil, updated_at: String.t(), created_at: String.t() @@ -32,6 +33,7 @@ defmodule WorkOS.Organizations.Organization do :object, :name, :allow_profiles_outside_organization, + :external_id, :domains, :updated_at, :created_at @@ -45,6 +47,7 @@ defmodule WorkOS.Organizations.Organization do name: map["name"], domains: Castable.cast_list(Domain, map["domains"]), allow_profiles_outside_organization: map["allow_profiles_outside_organization"], + external_id: map["external_id"], updated_at: map["updated_at"], created_at: map["created_at"] } diff --git a/lib/workos/user_management.ex b/lib/workos/user_management.ex index c96aa2ea..941aa842 100644 --- a/lib/workos/user_management.ex +++ b/lib/workos/user_management.ex @@ -85,6 +85,19 @@ defmodule WorkOS.UserManagement do ) end + @doc """ + Gets a user by external ID. + """ + @spec get_user_by_external_id(String.t()) :: WorkOS.Client.response(User.t()) + @spec get_user_by_external_id(WorkOS.Client.t(), String.t()) :: WorkOS.Client.response(User.t()) + def get_user_by_external_id(client \\ WorkOS.client(), external_id) do + WorkOS.Client.get(client, User, "/user_management/users/external_id/:external_id", + opts: [ + path_params: [external_id: external_id] + ] + ) + end + @doc """ Lists all users. diff --git a/lib/workos/user_management/user.ex b/lib/workos/user_management/user.ex index ece7d3e0..8476e2e5 100644 --- a/lib/workos/user_management/user.ex +++ b/lib/workos/user_management/user.ex @@ -11,6 +11,7 @@ defmodule WorkOS.UserManagement.User do email_verified: boolean(), first_name: String.t() | nil, last_name: String.t() | nil, + external_id: String.t() | nil, updated_at: String.t(), created_at: String.t() } @@ -28,6 +29,7 @@ defmodule WorkOS.UserManagement.User do :email_verified, :first_name, :last_name, + :external_id, :updated_at, :created_at ] @@ -40,6 +42,7 @@ defmodule WorkOS.UserManagement.User do email_verified: map["email_verified"], first_name: map["first_name"], last_name: map["last_name"], + external_id: map["external_id"], updated_at: map["updated_at"], created_at: map["created_at"] } diff --git a/test/support/organizations_client_mock.ex b/test/support/organizations_client_mock.ex index 71a5cc12..3ebf7a7e 100644 --- a/test/support/organizations_client_mock.ex +++ b/test/support/organizations_client_mock.ex @@ -105,6 +105,39 @@ defmodule WorkOS.Organizations.ClientMock do end) end + def get_organization_by_external_id(context, opts \\ []) do + Tesla.Mock.mock(fn request -> + %{api_key: api_key} = context + + external_id = opts |> Keyword.get(:assert_fields) |> Keyword.get(:external_id) + assert request.method == :get + assert request.url == "#{WorkOS.base_url()}/organizations/external_id/#{external_id}" + + assert Enum.find(request.headers, &(elem(&1, 0) == "Authorization")) == + {"Authorization", "Bearer #{api_key}"} + + success_body = %{ + "id" => "org_01EHT88Z8J8795GZNQ4ZP1J81T", + "object" => "organization", + "name" => "Test Organization", + "allow_profiles_outside_organization" => false, + "external_id" => external_id, + "domains" => [ + %{ + "domain" => "example.com", + "object" => "organization_domain", + "id" => "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8" + } + ], + "created_at" => "2023-07-17T20:07:20.055Z", + "updated_at" => "2023-07-17T20:07:20.055Z" + } + + {status, body} = Keyword.get(opts, :respond_with, {200, success_body}) + %Tesla.Env{status: status, body: body} + end) + end + def create_organization(context, opts \\ []) do Tesla.Mock.mock(fn request -> %{api_key: api_key} = context diff --git a/test/support/user_management_client_mock.ex b/test/support/user_management_client_mock.ex index bdd7dece..3f657f4d 100644 --- a/test/support/user_management_client_mock.ex +++ b/test/support/user_management_client_mock.ex @@ -11,7 +11,8 @@ defmodule WorkOS.UserManagement.ClientMock do "last_name" => "User", "created_at" => "2023-07-18T02:07:19.911Z", "updated_at" => "2023-07-18T02:07:19.911Z", - "email_verified" => true + "email_verified" => true, + "external_id" => "ext_user_123" } @invitation_mock %{ @@ -90,6 +91,24 @@ defmodule WorkOS.UserManagement.ClientMock do end) end + def get_user_by_external_id(context, opts \\ []) do + Tesla.Mock.mock(fn request -> + %{api_key: api_key} = context + + external_id = opts |> Keyword.get(:assert_fields) |> Keyword.get(:external_id) + assert request.method == :get + assert request.url == "#{WorkOS.base_url()}/user_management/users/external_id/#{external_id}" + + assert Enum.find(request.headers, &(elem(&1, 0) == "Authorization")) == + {"Authorization", "Bearer #{api_key}"} + + success_body = @user_mock + + {status, body} = Keyword.get(opts, :respond_with, {200, success_body}) + %Tesla.Env{status: status, body: body} + end) + end + def list_users(context, opts \\ []) do Tesla.Mock.mock(fn request -> %{api_key: api_key} = context diff --git a/test/workos/organizations_test.exs b/test/workos/organizations_test.exs index 56648226..9d4d5b62 100644 --- a/test/workos/organizations_test.exs +++ b/test/workos/organizations_test.exs @@ -55,6 +55,19 @@ defmodule WorkOS.OrganizationsTest do end end + describe "get_organization_by_external_id" do + test "requests an organization by external_id", context do + opts = [external_id: "ext_org_123"] + context |> ClientMock.get_organization_by_external_id(assert_fields: opts) + + assert {:ok, %WorkOS.Organizations.Organization{id: id, external_id: external_id}} = + WorkOS.Organizations.get_organization_by_external_id(opts |> Keyword.get(:external_id)) + + refute is_nil(id) + assert external_id == "ext_org_123" + end + end + describe "create_organization" do test "with an idempotency key, includes an idempotency key with request", context do opts = [ diff --git a/test/workos/user_management_test.exs b/test/workos/user_management_test.exs index 1b778405..ae120763 100644 --- a/test/workos/user_management_test.exs +++ b/test/workos/user_management_test.exs @@ -136,6 +136,19 @@ defmodule WorkOS.UserManagementTest do end end + describe "get_user_by_external_id" do + test "requests a user by external_id", context do + opts = [external_id: "ext_user_123"] + context |> ClientMock.get_user_by_external_id(assert_fields: opts) + + assert {:ok, %WorkOS.UserManagement.User{id: id, external_id: external_id}} = + WorkOS.UserManagement.get_user_by_external_id(opts |> Keyword.get(:external_id)) + + refute is_nil(id) + assert external_id == "ext_user_123" + end + end + describe "list_users" do test "without any options, returns users and metadata", context do context From 19b000940c3d4ae4812e2490143719035c4ec3eb Mon Sep 17 00:00:00 2001 From: Bas Ben Zineb Date: Mon, 9 Feb 2026 18:59:02 +0100 Subject: [PATCH 2/2] Formatting --- test/support/user_management_client_mock.ex | 4 +++- test/workos/organizations_test.exs | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/support/user_management_client_mock.ex b/test/support/user_management_client_mock.ex index 3f657f4d..ce328e29 100644 --- a/test/support/user_management_client_mock.ex +++ b/test/support/user_management_client_mock.ex @@ -97,7 +97,9 @@ defmodule WorkOS.UserManagement.ClientMock do external_id = opts |> Keyword.get(:assert_fields) |> Keyword.get(:external_id) assert request.method == :get - assert request.url == "#{WorkOS.base_url()}/user_management/users/external_id/#{external_id}" + + assert request.url == + "#{WorkOS.base_url()}/user_management/users/external_id/#{external_id}" assert Enum.find(request.headers, &(elem(&1, 0) == "Authorization")) == {"Authorization", "Bearer #{api_key}"} diff --git a/test/workos/organizations_test.exs b/test/workos/organizations_test.exs index 9d4d5b62..a51efb9d 100644 --- a/test/workos/organizations_test.exs +++ b/test/workos/organizations_test.exs @@ -61,7 +61,10 @@ defmodule WorkOS.OrganizationsTest do context |> ClientMock.get_organization_by_external_id(assert_fields: opts) assert {:ok, %WorkOS.Organizations.Organization{id: id, external_id: external_id}} = - WorkOS.Organizations.get_organization_by_external_id(opts |> Keyword.get(:external_id)) + WorkOS.Organizations.get_organization_by_external_id( + opts + |> Keyword.get(:external_id) + ) refute is_nil(id) assert external_id == "ext_org_123"