From 7a7f36d38c3e89e0ed4f7306df0381b296e28796 Mon Sep 17 00:00:00 2001
From: Marie <51697796+ijreilly@users.noreply.github.com>
Date: Thu, 11 Dec 2025 00:53:48 +0100
Subject: [PATCH] [Fix] Forbid default value removal for non-nullable select
field (#16465)
Permanently fixes https://github.com/twentyhq/private-issues/issues/389
On a non-nullable select field, default value should not be removable,
otherwise users won't be able to create new records from the interface.
This PR enforces this in FE and BE.
https://github.com/user-attachments/assets/1dfa2bcc-b9df-4a00-9915-679d36ec1b25
---
.../SettingsDataModelFieldSelectForm.tsx | 6 ++
...tingsDataModelFieldSelectFormOptionRow.tsx | 80 +++++++++++--------
.../flat-field-metadata-validator.service.ts | 11 +++
3 files changed, 62 insertions(+), 35 deletions(-)
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx
index 69a4e43499..64f8afeb67 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectForm.tsx
@@ -17,6 +17,7 @@ import { moveArrayItem } from '~/utils/array/moveArrayItem';
import { toSpliced } from '~/utils/array/toSpliced';
import { applySimpleQuotesToString } from '~/utils/string/applySimpleQuotesToString';
+import { useFieldMetadataItemById } from '@/object-metadata/hooks/useFieldMetadataItemById';
import { AdvancedSettingsWrapper } from '@/settings/components/AdvancedSettingsWrapper';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useTheme } from '@emotion/react';
@@ -119,6 +120,10 @@ export const SettingsDataModelFieldSelectForm = ({
useSelectSettingsFormInitialValues({
fieldMetadataId: existingFieldMetadataId,
});
+ const { fieldMetadataItem } = useFieldMetadataItemById(
+ existingFieldMetadataId,
+ );
+ const isNullable = fieldMetadataItem?.isNullable;
const isAdvancedModeEnabled = useRecoilValue(isAdvancedModeEnabledState);
@@ -340,6 +345,7 @@ export const SettingsDataModelFieldSelectForm = ({
onChange(nextOptions);
}}
isDefault={isOptionDefaultValue(option.value)}
+ fieldIsNullable={!!isNullable}
onSetAsDefault={() => {
if (disabled) {
return;
diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx
index 8ca14e602a..e43b0b8337 100644
--- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx
+++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/select/components/SettingsDataModelFieldSelectFormOptionRow.tsx
@@ -32,6 +32,7 @@ type SettingsDataModelFieldSelectFormOptionRowProps = {
onInputEnter?: () => void;
option: FieldMetadataItemOption;
isNewRow?: boolean;
+ fieldIsNullable?: boolean;
};
const StyledRow = styled.div`
@@ -76,6 +77,7 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
onInputEnter,
option,
isNewRow,
+ fieldIsNullable,
}: SettingsDataModelFieldSelectFormOptionRowProps) => {
const theme = useTheme();
const SELECT_COLOR_DROPDOWN_ID = `select-color-dropdown-${option.id}`;
@@ -84,6 +86,8 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
const { closeDropdown: closeColorDropdown } = useCloseDropdown();
const { closeDropdown: closeActionsDropdown } = useCloseDropdown();
+ const shouldForbidRemoveAsDefault = isDefault && !fieldIsNullable;
+
const handleInputEnter = () => {
onInputEnter?.();
};
@@ -157,43 +161,49 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
dropdownPlacement="right-start"
clickableComponent={
-
+
}
dropdownComponents={
-
-
- {isDefault ? (
-
-
+ shouldForbidRemoveAsDefault ? null : (
+
+
+ {isDefault ? (
+ {
+ onRemoveAsDefault?.();
+ closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
+ }}
+ />
+ ) : (
+ {
+ onSetAsDefault?.();
+ closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
+ }}
+ />
+ )}
+ {!!onRemove && !isDefault && (
+ {
+ onRemove();
+ closeActionsDropdown(SELECT_ACTIONS_DROPDOWN_ID);
+ }}
+ />
+ )}
+
+
+ )
}
/>
diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
index 6023ca3d9b..66bae5fffa 100644
--- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service.ts
@@ -158,6 +158,17 @@ export class FlatFieldMetadataValidatorService {
});
}
+ if (
+ flatFieldMetadataToValidate.isNullable === false &&
+ flatFieldMetadataToValidate.defaultValue === null
+ ) {
+ validationResult.errors.push({
+ code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
+ message: 'Default value cannot be null for non-nullable fields',
+ userFriendlyMessage: msg`Default value cannot be null.`,
+ });
+ }
+
const fieldMetadataTypeValidationErrors =
this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
{