Feat/multivalue limit (#14961)

## Summary
1. This change introduces the ability to limit how many values a
multi-value field can contain (for example: maximum number of emails,
phone numbers, links, or array items).
2. It centralizes the limit as a shared constant and type, validates
settings on the server, updates front-end types and components to
consume the setting, and adds a small settings UI so workspace admins
can change the limit per field.
3. Default behavior is preserved: if no max is configured, the existing
default (10) is used.


## **Fixes Issue:** [#14740
](https://github.com/twentyhq/twenty/issues/14740)

## Manual Test Screenshot
<img width="383" height="451" alt="Screenshot 2025-10-08 002413"
src="https://github.com/user-attachments/assets/a7704af6-10ef-4d10-b8c9-a9eeca03bfd9"
/>


### Values in fields


https://github.com/user-attachments/assets/7f59c4f7-3aca-4f83-8c04-3d44988316e3


Let me know if any changes are needed

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Ashwin Temkar
2025-10-08 17:39:38 +05:30
committed by GitHub
parent fbd7e65111
commit 1d09cb949a
21 changed files with 330 additions and 60 deletions
@@ -1,5 +1,6 @@
import {
type AllowedAddressSubField,
type FieldMetadataMultiItemSettings,
type FieldMetadataType,
type IsExactly,
} from 'twenty-shared/types';
@@ -62,6 +63,10 @@ type FieldMetadataSettingsMapping = {
[FieldMetadataType.ADDRESS]: FieldMetadataAddressSettings | null;
[FieldMetadataType.MORPH_RELATION]: FieldMetadataRelationSettings;
[FieldMetadataType.TS_VECTOR]: FieldMetadataTsVectorSettings | null;
[FieldMetadataType.PHONES]: FieldMetadataMultiItemSettings | null;
[FieldMetadataType.EMAILS]: FieldMetadataMultiItemSettings | null;
[FieldMetadataType.LINKS]: FieldMetadataMultiItemSettings | null;
[FieldMetadataType.ARRAY]: FieldMetadataMultiItemSettings | null;
};
export type AllFieldMetadataSettings =
@@ -12,6 +12,7 @@ import {
type ValidationError,
validateOrReject,
} from 'class-validator';
import { MULTI_ITEM_FIELD_MIN_MAX_VALUES } from 'twenty-shared/constants';
import {
ALLOWED_ADDRESS_SUBFIELDS,
type AllowedAddressSubField,
@@ -72,6 +73,13 @@ class AddressSettingsValidation {
@IsEnum(ALLOWED_ADDRESS_SUBFIELDS, { each: true })
subFields?: AllowedAddressSubField[];
}
class MultipleValuesSettingsValidation {
@IsOptional()
@IsInt()
@Min(MULTI_ITEM_FIELD_MIN_MAX_VALUES)
maxNumberOfValues?: number;
}
@Injectable()
export class FieldMetadataValidationService {
constructor(
@@ -107,6 +115,16 @@ export class FieldMetadataValidationService {
settings,
});
break;
case FieldMetadataType.PHONES:
case FieldMetadataType.EMAILS:
case FieldMetadataType.LINKS:
case FieldMetadataType.ARRAY:
await this.validateSettings({
type: fieldType,
validator: MultipleValuesSettingsValidation,
settings,
});
break;
default:
break;
}
@@ -1,5 +1,6 @@
import { type Expect, type HasAllProperties } from 'twenty-shared/testing';
import {
type FieldMetadataMultiItemSettings,
type FieldMetadataType,
type NullablePartial,
} from 'twenty-shared/types';
@@ -133,10 +134,6 @@ type SettingsAssertions = [
Expect<HasAllProperties<RawJsonFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<RichTextFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<ActorFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<ArrayFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<PhonesFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<EmailsFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<LinksFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<UUIDFieldMetadata, NotDefinedSettings>>,
Expect<HasAllProperties<BooleanFieldMetadata, NotDefinedSettings>>,
@@ -164,6 +161,30 @@ type SettingsAssertions = [
{ settings: FieldMetadataDateTimeSettings | null }
>
>,
Expect<
HasAllProperties<
ArrayFieldMetadata,
{ settings: FieldMetadataMultiItemSettings | null }
>
>,
Expect<
HasAllProperties<
PhonesFieldMetadata,
{ settings: FieldMetadataMultiItemSettings | null }
>
>,
Expect<
HasAllProperties<
EmailsFieldMetadata,
{ settings: FieldMetadataMultiItemSettings | null }
>
>,
Expect<
HasAllProperties<
LinksFieldMetadata,
{ settings: FieldMetadataMultiItemSettings | null }
>
>,
Expect<
HasAllProperties<