Files field - add files field display and input + filtering (#17637)

This PR introduces a new FILES field type for Twenty CRM, allowing users
to attach multiple files to any record.

- Files field display
- Files field preview
- Files field input
- Files field filtering

To test : 
1/ need to activate feature flag `IS_FILES_FIELD_ENABLED` + create a new
FILES field

- display in read only, edit, inline, table
- edit
- filter
- export


closes https://github.com/twentyhq/core-team-issues/issues/2154


<img width="354" height="134" alt="Screenshot 2026-02-02 at 19 11 01"
src="https://github.com/user-attachments/assets/3c3de89e-f6b6-4526-b710-e4c7ee1a6d30"
/>
<img width="1081" height="933" alt="Screenshot 2026-02-02 at 19 10 41"
src="https://github.com/user-attachments/assets/7c9a7278-edb7-4d4a-882c-f7404689d011"
/>
<img width="1073" height="719" alt="Screenshot 2026-02-02 at 19 10 33"
src="https://github.com/user-attachments/assets/79cc372b-56a2-4cbf-b4fe-023adc4753a8"
/>
This commit is contained in:
Etienne
2026-02-04 11:55:48 +01:00
committed by GitHub
parent 9b8eacb7f7
commit 4fcc424e24
73 changed files with 1872 additions and 455 deletions
@@ -1,6 +1,5 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { validateFilesFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/utils/validate-files-flat-field-metadata.util';
@@ -19,15 +18,9 @@ const createFlatEntityToValidate = (
const callValidator = (
flatEntityToValidate: FlatFieldMetadata<FieldMetadataType.FILES>,
featureFlagEnabled = true,
) =>
validateFilesFlatFieldMetadata({
flatEntityToValidate,
additionalCacheDataMaps: {
featureFlagsMap: {
[FeatureFlagKey.IS_FILES_FIELD_ENABLED]: featureFlagEnabled,
},
},
} as Parameters<typeof validateFilesFlatFieldMetadata>[0]);
describe('validateFilesFlatFieldMetadata', () => {
@@ -37,14 +30,6 @@ describe('validateFilesFlatFieldMetadata', () => {
expect(errors).toHaveLength(0);
});
it('should return error when feature flag is disabled', () => {
const errors = callValidator(createFlatEntityToValidate(), false);
expect(errors).toHaveLength(1);
expect(errors[0].code).toBe(FieldMetadataExceptionCode.INVALID_FIELD_INPUT);
expect(errors[0].message).toContain('Files field type is not supported');
});
it('should return error when isUnique is true', () => {
const errors = callValidator(
createFlatEntityToValidate({ isUnique: true }),
@@ -3,25 +3,14 @@ import { FILES_FIELD_MAX_NUMBER_OF_VALUES } from 'twenty-shared/constants';
import { type FieldMetadataType } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FieldMetadataExceptionCode } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FlatFieldMetadataTypeValidationArgs } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
import { type FlatFieldMetadataValidationError } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-validation-error.type';
export const validateFilesFlatFieldMetadata = ({
flatEntityToValidate,
additionalCacheDataMaps,
}: FlatFieldMetadataTypeValidationArgs<FieldMetadataType.FILES>): FlatFieldMetadataValidationError[] => {
const errors: FlatFieldMetadataValidationError[] = [];
const { featureFlagsMap } = additionalCacheDataMaps;
if (featureFlagsMap[FeatureFlagKey.IS_FILES_FIELD_ENABLED] !== true) {
errors.push({
code: FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
message: 'Files field type is not supported',
userFriendlyMessage: msg`Files field type is not supported`,
});
}
if (flatEntityToValidate.isUnique === true) {
errors.push({