Files
twenty/packages/twenty-sdk/src/application/objects/extend-object.ts
T
martmull d55b83375d Allow app to extend object (#17116)
- exports `extendObject` utils from `twenty-sdk`
- define `*.object-extension.ts` filename pattern to extend and object
- handle object-extension configs in manifest
2026-01-13 15:07:14 +00:00

80 lines
2.2 KiB
TypeScript

import { type ObjectExtensionManifest } from 'twenty-shared/application';
import { isNonEmptyString } from '@sniptt/guards';
import { validateFieldsOrThrow } from './validate-fields';
/**
* Extend an existing object with additional fields.
*
* @example
* ```typescript
* import { extendObject, FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk';
*
* // Extend by object name
* export const companyExtension = extendObject({
* targetObject: {
* nameSingular: 'company',
* },
* fields: [
* {
* universalIdentifier: '...',
* name: 'healthScore',
* type: FieldType.NUMBER,
* label: 'Health Score',
* description: 'Calculated customer health metric',
* },
* {
* universalIdentifier: '...',
* name: 'churnRisk',
* type: FieldType.SELECT,
* label: 'Churn Risk',
* options: [
* { label: 'Low', value: 'low', color: 'green' },
* { label: 'Medium', value: 'medium', color: 'yellow' },
* { label: 'High', value: 'high', color: 'red' },
* ],
* },
* ],
* });
*
* // Or extend by universal identifier
* export const companyExtensionById = extendObject({
* targetObject: {
* universalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company,
* },
* fields: [...],
* });
* ```
*/
export const extendObject = <T extends ObjectExtensionManifest>(
config: T,
): T => {
if (!config.targetObject) {
throw new Error('Object extension must have a targetObject');
}
const { nameSingular, universalIdentifier } = config.targetObject;
if (
!isNonEmptyString(nameSingular) &&
!isNonEmptyString(universalIdentifier)
) {
throw new Error(
'targetObject must have either nameSingular or universalIdentifier',
);
}
if (isNonEmptyString(nameSingular) && isNonEmptyString(universalIdentifier)) {
throw new Error(
'targetObject cannot have both nameSingular and universalIdentifier - they are mutually exclusive',
);
}
if (!config.fields || config.fields.length === 0) {
throw new Error('Object extension must have at least one field');
}
validateFieldsOrThrow(config.fields);
return config;
};