twentyhq / twentyhq/core-team-issues

Fix type sharing between front/back for union type inputs

Open
#1,663 1 comment 0 reactions 1 assignee View on GitHub

@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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.