Skip to content

Commit fa59e52

Browse files
authored
Add metadata to organizations and users (#1241)
## Description Supports passing metadata on updating and creation of both as well. Right now the API doesn't always return metadata, but it will eventually always return empty object if there is none, and so I've made it so that users and organizations will always have a metadata object after deserialization. ## 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.
1 parent 4a71da8 commit fa59e52

19 files changed

+140
-2
lines changed

src/actions/actions.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ describe('Actions', () => {
127127
createdAt: '2024-10-22T17:12:50.746Z',
128128
updatedAt: '2024-10-22T17:12:50.746Z',
129129
externalId: null,
130+
metadata: {},
130131
},
131132
ipAddress: '50.141.123.10',
132133
userAgent: 'Mozilla/5.0',
@@ -142,6 +143,7 @@ describe('Actions', () => {
142143
createdAt: '2024-10-22T17:12:50.746Z',
143144
updatedAt: '2024-10-22T17:12:50.746Z',
144145
externalId: null,
146+
metadata: {},
145147
},
146148
organizationMembership: {
147149
object: 'organization_membership',

src/organizations/fixtures/get-organization.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
"verification_strategy": "dns",
1313
"verification_token": "xB8SeACdKJQP9DP4CahU4YuQZ"
1414
}
15-
]
15+
],
16+
"metadata": {}
1617
}

src/organizations/interfaces/create-organization-options.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export interface CreateOrganizationOptions {
55
name: string;
66
domainData?: DomainData[];
77
externalId?: string | null;
8+
metadata?: Record<string, string>;
89

910
/**
1011
* @deprecated If you need to allow sign-ins from any email domain, contact [email protected].
@@ -20,6 +21,7 @@ export interface SerializedCreateOrganizationOptions {
2021
name: string;
2122
domain_data?: DomainData[];
2223
external_id?: string | null;
24+
metadata?: Record<string, string>;
2325

2426
/**
2527
* @deprecated If you need to allow sign-ins from any email domain, contact [email protected].

src/organizations/interfaces/organization.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface Organization {
1313
createdAt: string;
1414
updatedAt: string;
1515
externalId: string | null;
16+
metadata: Record<string, string>;
1617
}
1718

1819
export interface OrganizationResponse {
@@ -25,4 +26,5 @@ export interface OrganizationResponse {
2526
created_at: string;
2627
updated_at: string;
2728
external_id?: string | null;
29+
metadata?: Record<string, string>;
2830
}

src/organizations/interfaces/update-organization-options.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export interface UpdateOrganizationOptions {
66
domainData?: DomainData[];
77
stripeCustomerId?: string | null;
88
externalId?: string | null;
9+
metadata?: Record<string, string>;
910

1011
/**
1112
* @deprecated If you need to allow sign-ins from any email domain, contact [email protected].
@@ -22,6 +23,7 @@ export interface SerializedUpdateOrganizationOptions {
2223
domain_data?: DomainData[];
2324
stripe_customer_id?: string | null;
2425
external_id?: string | null;
26+
metadata?: Record<string, string>;
2527

2628
/**
2729
* @deprecated If you need to allow sign-ins from any email domain, contact [email protected].

src/organizations/organizations.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,19 @@ describe('Organizations', () => {
187187
expect(subject.domains).toHaveLength(1);
188188
});
189189
});
190+
191+
it('adds metadata to the request', async () => {
192+
fetchOnce(createOrganization, { status: 201 });
193+
194+
await workos.organizations.createOrganization({
195+
name: 'My organization',
196+
metadata: { key: 'value' },
197+
});
198+
199+
expect(fetchBody()).toMatchObject({
200+
metadata: { key: 'value' },
201+
});
202+
});
190203
});
191204

192205
describe('with an invalid payload', () => {
@@ -316,6 +329,19 @@ describe('Organizations', () => {
316329
expect(subject.domains).toHaveLength(1);
317330
});
318331
});
332+
333+
it('adds metadata to the request', async () => {
334+
fetchOnce(updateOrganization, { status: 201 });
335+
336+
await workos.organizations.updateOrganization({
337+
organization: 'org_01EHT88Z8J8795GZNQ4ZP1J81T',
338+
metadata: { key: 'value' },
339+
});
340+
341+
expect(fetchBody()).toEqual({
342+
metadata: { key: 'value' },
343+
});
344+
});
319345
});
320346

321347
describe('when given `stripeCustomerId`', () => {

src/organizations/serializers/create-organization-options.serializer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export const serializeCreateOrganizationOptions = (
1111
domain_data: options.domainData,
1212
domains: options.domains,
1313
external_id: options.externalId,
14+
metadata: options.metadata,
1415
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { deserializeOrganization } from './organization.serializer';
2+
import organizationFixture from '../fixtures/get-organization.json';
3+
4+
const organizationResponse = {
5+
...organizationFixture,
6+
object: 'organization' as const,
7+
created_at: new Date().toISOString(),
8+
updated_at: new Date().toISOString(),
9+
domains: [],
10+
};
11+
12+
describe('deserializeOrganization', () => {
13+
it('includes metadata if present', () => {
14+
const metadata = { key: 'value' };
15+
16+
expect(
17+
deserializeOrganization({
18+
...organizationResponse,
19+
metadata,
20+
}),
21+
).toMatchObject({
22+
metadata,
23+
});
24+
});
25+
26+
it('coerces missing metadata to empty object', () => {
27+
const { metadata, ...organizationResponseWithoutMetadata } =
28+
organizationResponse;
29+
30+
expect(
31+
deserializeOrganization(organizationResponseWithoutMetadata),
32+
).toMatchObject({
33+
metadata: {},
34+
});
35+
});
36+
});

src/organizations/serializers/organization.serializer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export const deserializeOrganization = (
1616
createdAt: organization.created_at,
1717
updatedAt: organization.updated_at,
1818
externalId: organization.external_id ?? null,
19+
metadata: organization.metadata ?? {},
1920
});

src/organizations/serializers/update-organization-options.serializer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ export const serializeUpdateOrganizationOptions = (
1212
domains: options.domains,
1313
stripe_customer_id: options.stripeCustomerId,
1414
external_id: options.externalId,
15+
metadata: options.metadata,
1516
});

0 commit comments

Comments
 (0)