twentyhq / twentyhq/core-team-issues
Fix type sharing between front/back for union type inputs
@FelixMalfait is already working on this.
Since May 27, 2026.
- Dominant language
- No language data
- Stars
- 7
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Problem Statement
Currently, field settings suffer from type safety issues and inconsistencies across our GraphQL API and TypeScript codebase.
The problem here comes from the lack of support for union input type in GraphQL: https://github.com/graphql/graphql-spec/issues/488
# ❌ NOT POSSIBLE in GraphQL
input FieldSettingsInput =
| NumberSettingsInput
| DateSettingsInput
| CurrencySettingsInput
Current State
GraphQL Schema:
type Field {
settings: JSON # ❌ Completely untyped
}
input CreateFieldInput {
settings: JSON # ❌ Completely untyped
}
Generated TypeScript (Frontend):
settings?: InputMaybe<Scalars['JSON']>; // Resolves to 'any'
Backend TypeScript:
// Discriminated union types in twenty-shared
type FieldMetadataSettings<T extends FieldMetadataType> =
T extends FieldMetadataType.NUMBER ? FieldMetadataNumberSettings :
T extends FieldMetadataType.DATE ? FieldMetadataDateSettings :
T extends FieldMetadataType.CURRENCY ? FieldMetadataCurrencySettings :
// ... etc
Backend Validation:
Custom way to validate settings inputs in FieldMetadataValidationService
Solutions
Twenty-shared
I initially tried to move all settings types to twenty-shared. But this felt inconsistent as we usually rely on the codegen to pass types, not twenty-shared.
Flat Input Type
input FieldSettingsInput {
# Number settings
decimals: Int
numberType: NumberVariant
dataType: NumberDataType
# Date settings
displayFormat: DateDisplayFormat
customUnicodeDateFormat: String
# Currency settings
format: CurrencyFormat
# Multiple values
maxNumberOfValues: Int
# Text settings
displayedMaxRows: Int
# Address settings
subFields: [String!]
# ... all other settings
}
This is nice as it's simple and allow passing things like DateDisplayFormat to the codegen. But it would still require runtime validation for typespecific constraints (if field type is X then Y has to be empty or cannot be empty).
Nested Types (with @OneOf)
input FieldSettingsInput @oneOf {
numberSettings: NumberSettingsInput
dateSettings: DateSettingsInput
currencySettings: CurrencySettingsInput
textSettings: TextSettingsInput
addressSettings: AddressSettingsInput
...
}
Pros:
- ✅ Enforces "exactly one settings object" at GraphQL level
- ✅ Cleaner separation of concerns
- ✅ Type-specific settings grouped together
Cons:
- ❌ Database migration work!
- ❌ Requires extra nesting layer
- ❌ Breaking change to API
- ❌ Limited tooling support for OneOf? Todo: check
- ❌ Still can't enforce "numberSettings only when type=NUMBER" at graphql-level (do it at runtime)
- ❌ Maybe more complex frontend code? (wrap/unwrap nested objects)
Many mutations / field types
Implementation:
input CreateNumberFieldInput {
name: String!
label: String!
settings: NumberSettingsInput
}
input CreateDateFieldInput {
name: String!
label: String!
settings: DateSettingsInput
}
# Separate mutation for each type
mutation {
createNumberField(input: CreateNumberFieldInput!)
createDateField(input: CreateDateFieldInput!)
# ... 20+ mutations
}
Pros:
- ✅ Perfect type safety at GraphQL level
- ✅ No runtime validation needed for structure
Cons:
- ❌ Massive breaking change
- ❌ 20+ separate mutations instead of 1
- ❌ Big API surface area
- ❌ Difficult to extend (new field type = new mutation)
- ❌ Makes generic field management UI extremely complex
Recommended solution
I think I would go with the Nested Objects solution but curious to get opinions.
Beyond
We should look at all JSON input and try to always take a more structured approach for better typesafety and unified validation mechanism (maybe ban JSON inputs except special cases?)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.