## The Issue
When CALENDAR_BOOKING_PAGE_ID env variable is not configured, the
onboarding flow still sets the booking step as pending in the database.
This causes users to get stuck on a broken booking page after
logout/login, as the Cal.com iframe tries to load with an empty calendar
link.
## The Fix
Made the booking step handling idempotent across the stack:
Backend:
- setOnboardingBookOnboardingPending now checks if calendar is actually
configured before setting the step as pending
- getOnboardingStatus auto-cleans invalid booking states when detected
(booking pending but no calendar configured)
- Empty strings in env are now treated as undefined in client config
Frontend:
- Added navigation protection to redirect away from booking pages when
calendar isn't configured
- Existing defensive logic in useSetNextOnboardingStatus already skips
booking when no calendar ID
Result
- New users won't get invalid booking states
- Existing bad data self-heals when users interact with the system
- Backend and frontend stay in sync about when booking should be shown
Fixes the issue Felix reported where users saw a broken booking page in
production.
I think we should keep the old CAL_LINK constant for now - while we
could remove the booking onboarding step entirely, it would break the
plan/pricing modal which uses it as a fallback when no calendar is
configured. Open for discussion! -- May be we dont show the `Book a
Call` button if the env is not set -- but we should keep it as it is if
we want two different behaviors :)
closes https://github.com/twentyhq/core-team-issues/issues/1558
Closes
https://github.com/twentyhq/core-team-issues/issues/248?issue=twentyhq%7Ccore-team-issues%7C1543
In this PR
- Implementation of groupBy resolver, without pagination, without viewId
parameter
- introduction of new GqlInputTypeDefinitionKind `OrderByWithGroupBy`
which differs from `OrderBy` as it should use aggregations as order by
conditions (e.g.: OrderByWithGroupBy should use avgEmployees and not
employees which does not make sense)
- Minor refacto of all orderBy expressions to use orderBy signature with
`OrderByCondition` which is usable for both groupBy queries and
non-groupBy queries. groupBy queries differ because their orderBy
conditions are based on aggregatedFields (such as avgEmployees) for
which we can't use the same orderBy signature
<img width="728" height="216" alt="Capture d’écran 2025-09-24 à 15 07
14"
src="https://github.com/user-attachments/assets/aa5c0875-cf38-4d4b-be70-f4d64da77a41"
/>
- Minor refacto to add table name in aggregates expressions
`(AVG("employees") -> AVG("company"."employees")` to keep having the
table names in the orderBy expression as we already had. Since we use
common utils for orderBy and aggregates on findMany this change also
impacts findMany queries with aggregates. It also impacts field
permission check where we extract the name of the selected fields to
check permission on, so I also updated that (see
`extractColumnNamesFromAggregateExpression`)
Workspace cleaning jobs slow down the db each hours when running. We
suspect the object metadata deleting query with all cascade/depending
entities (field, index, ..) to be the cause.
## Context
-
packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts
Fixing targetFlatFieldMetadata not being accurate for default relation
during object creation (was not used yet)
-
packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/services/workspace-schema-foreign-key-manager.service.ts
Simplifying API + Removing unused methods and the ones that were
querying pg schema as we want to avoid those as much as possible
-
packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/field/services/create-field-action-handler.service.ts
Adding FK creation when Join column is created
This PR should fix optimistic rendering issues on step updates:
- compute a diff for trigger and steps on mutations
- build an util that applies that diff (built a more robust version from
https://github.com/AsyncBanana/micropatch)
- apply diff in cache
Add a new graphql mutation in the pageLayout resolver to handle page
layout update with tabs and widgets.
Later we will add validation inside the service to check if the page
layout tab structure is correct (for instance check widgets don't
overlap or aren't out of the grid, or to check that they have a correct
configuration ...).
This mutation will be plugged to the save action of the dashboards in
the frontend.
# Introduction
Honestly this implem is a mess, discussing a potential side effect
handler with @weiko before the build and run that would handle each side
effect per entity and operation
Handling:
- [ ] unique index is generated when a field is updated with the
`isUnique`
- [x] an index is generated when a relation is created
- [x] search vector index creation on custom object creation
- [x] renaming a field metadata or an object should re-create all
related indexes which are composed by their namings
- [x] delete object should remove any related indexes
- [x] delete field should update related indexes ( if index ends up
empty it should be removed )
- [ ] on object renaming that contains morph fields -> triggers update
field -> trigger index recompute
- [x] on update name renaming should recompute all related indexes
## TODO
- [x] Integration testing
- [ ] Refactor the index maps cache to be storing a
`idsByObjectMetadataId`
- [x] Refactor deterministic name to use order sorting
- [x] Remove flat index from flat object
## What's next
Will handle morph indexes in a new dedicated PR for the moment will
stick to this
Same for the cache improvement and uniqueness
## Context
We are not exactly sure how we want to handle side effects with
workspace migration v2, in the meantime I'm re-introducing this part in
the caller (object v2 service)
## Summary
Implements the foundation for dynamic search field configuration from
[issue #1428](https://github.com/twentyhq/core-team-issues/issues/1428).
## Changes
- Add `SearchFieldMetadataEntity` junction table for storing searchable
field configurations
- Add `SearchFieldMetadataService` with core CRUD operations
- Add `SearchFieldMetadataModule` following existing patterns
- Add `IS_DYNAMIC_SEARCH_FIELDS_ENABLED` feature flag (defaults to
`false`)
- Database migration with proper indexes and foreign keys
## Architecture
Uses a junction table where **record existence = field is searchable**:
**Table: `searchFieldMetadata(objectMetadataId, fieldMetadataId,
workspaceId)`**
- Unique constraint on `(objectMetadataId, fieldMetadataId)`
- `ON DELETE CASCADE` on foreign keys
## Testing
- [x] Migration runs successfully
- [x] Table created with correct schema
- [x] Feature flag seeded properly
- [x] Database reset works correctly
## Next Steps
Future PRs will handle:
- Data migration from existing hardcoded search field configs
- Integration with search services
✅ No breaking changes — fully backward compatible.
## Context
1. We are debugging the experience on messaging and calendar sync. I'm
adding two scripts to relaunch messaging and calendar channels
2. We are seeing errors in Sentry regarding microsoft drivers errors. I
was about to fix them but I'm refactoring / simplifying a bit the logic
first. It will be a betters starting point
Fixes https://github.com/twentyhq/twenty/issues/14442
Issues were
1. Table headers always used label metadata identifier (or record text)
as first column, while table body followed viewFields positions
2. Label metadata identifier should always be visible and in first
position for the table views to work as expected, while when updating
the label metadata identifier for an object, no changes were brought to
the viewFields
To fix that
1. In the BE:
- a new logic is implemented to i) update label identifier's viewFields
position and visibility when an object's label identifier is updated to
a new field; ii) add validation on the update of a viewField's position
and visibility to make sure the label identifier's viewField always has
the lowest position + is visible
- a command was added to check all existing views and viewfields
3. In the FE: at first I tried to replicate the logic of the headers
(based on the label identifier rather than the positions) on the body,
but it was too complex and error-prone as in multiple places we are
based on the positions. It also feels more right to have only one source
of truth which is the viewField position. @lucasbordeau if that does not
suit you, we can throw an error if the field with the lowest position is
not the label metadata identifier, as you said the table view will be
very buggy / wont work if the label identifier is not in the first
position. but now it should never be the case thanks to the validation
implemented in the BE
This should be migrated to viewFieldService V2 when relevant @Weiko
@prastoin
Fixes:
- relation one to many cannot be set (we have deprecated limit: keyword
in graphql at it was not use, we should only use first or last)
- https://github.com/twentyhq/twenty/issues/14556
## Introduction
https://github.com/twentyhq/twenty/pull/14363
In this PR we're creating builder and runner for index metadata
Removing its previous integration within object builder
## Next:
- implem index impact on field and object metadata api
- Add integration testing coverage add integration test on index
- Implement uniqueness toggle in field metadata api
- add integration test on uniqueness
related to https://github.com/twentyhq/core-team-issues/issues/1344
Tested :
- workspace needs to flush cache after activating feature flag
- workspace does not need to flush cache if ff false and coming from
previous version (before gql schema gen updates)
## ✨ Add accent-insensitive search functionality
### 🎯 Overview
Implements accent-insensitive search across all searchable fields in
Twenty CRM.
Users can now search for "jose" to find "José", "muller" to find
"Müller", "cafe" to find "café", etc.
### 🔍 Problem
Twenty's search functionality was accent-sensitive, requiring users to
type exact accented characters to find records.
This created a poor user experience, especially for international names
and content.
### 💡 Solution
Added PostgreSQL `unaccent` extension with a custom immutable wrapper
function to enable accent-insensitive full-text search across all
searchable field types.
### 📋 Changes Made
**Modified Files:**
- `packages/twenty-server/scripts/setup-db.ts`
-
`packages/twenty-server/src/engine/api/graphql/graphql-query-runner/utils/compute-where-condition-parts.ts`
-
`packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts`
### 🗄️ Database Setup (`setup-db.ts`)
```sql
-- Added unaccent extension
CREATE EXTENSION IF NOT EXISTS "unaccent";
-- Created immutable wrapper function
CREATE OR REPLACE FUNCTION unaccent_immutable(text) RETURNS text AS $$
SELECT public.unaccent($1)
$$ LANGUAGE sql IMMUTABLE;
```
### 🔍 Search Vector Generation
(`get-ts-vector-column-expression.util.ts`)
Applied `public.unaccent_immutable()` to all searchable field types:
- TEXT fields (job titles, names, etc.)
- FULL_NAME fields (first/last names)
- EMAILS fields (both email address and domain)
- ADDRESS fields
- LINKS fields
- RICH_TEXT and RICH_TEXT_V2 fields
### 🔎 Query Processing (`compute-where-condition-parts.ts`)
Enhanced search queries to use `public.unaccent_immutable()` for both:
- Full-text search (`@@` operator with `to_tsquery`)
- Pattern matching (`ILIKE` operator)
### 🧠 Technical Rationale: Why the Wrapper Function?
**The Challenge:**
PostgreSQL's built-in `unaccent()` is marked as **STABLE**, but
`GENERATED ALWAYS AS` expressions (used for search vector columns)
require **IMMUTABLE** functions.
**The Solution:**
Created an IMMUTABLE wrapper function that calls the underlying
`unaccent()` function:
- ✅ Satisfies PostgreSQL's immutability requirements for generated
columns
- ✅ Maintains the exact same functionality as the original `unaccent()`
- ✅ Uses fully qualified `public.unaccent_immutable()` to ensure
function resolution from workspace schemas
**Alternative Approaches Considered:**
- ❌ Modifying `search_path`: would affect workspace isolation
- ❌ Computing unaccent at query time: would hurt performance
- ❌ Using triggers: would complicate data consistency
### 🎯 Impact
For **Person** records, accent-insensitive search now works on:
- Name (first/last name): `"jose garcia"` finds `"José García"`
- Email: `"jose@cafe.com"` finds `"josé@café.com"`
- Job Title: `"manager"` finds `"Managér"` or `"Gerente de Café"`
Applies to all searchable standard objects:
- Companies, People, Opportunities, Notes, Tasks, etc.
- Any custom fields of searchable types (TEXT, EMAILS, etc.)
### ✅ Testing
- Database reset completes successfully
- Workspace seeding works without errors
- Search vectors generate with unaccent functionality
- All searchable field types properly handle accented characters
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
# Introduction
Initially this PR was about introducing integration coverage for view
field v2 tests
But feature itself wasn't finished, so ended up in a TDD style
devlopment
## The new orchestrator
Orchestrator will organize each flat entity diffing inferring either
validation errors or actions sequentially.
A new builder has been introduced when extended requires to define flat
entity validation utils and action generation for `delete` `update`
`create` operations. It's highly typed using generics
## View field integration testing
Introduce coverage on failing basic tests cases for view field
operations, successfull create is also covered. We could still add more
coverage, will be done later in following PRs
## Remaining tasks for upcoming PRs:
- [ ] rename builder methods names to add generate action in their names
- [x] refactor view validation to handle field and view uniqueness index
combination
- [ ] implem strict update validator on view and view field
- [ ] dynamic cache invalidation post run
- [ ] Add coverage to successful and failing view field operations
- [ ] error formatting summary computation
- [ ] extract types out of the workspace entity builder
## Some vision
Currently flat field metadata maps is nested in the flat object metadata
maps, making its build nested too. ( not extending the new generic
builder )
We will refactor this part for both to be stored extra flat such as the
other existing flat entities.
It will require a small refactor to the generic builder that will not
only have impact on the currently built flat entity optimistic cache but
to an other one ( for example when building a create object action it
will also have an impact flat field maps )