Closes [1744](https://github.com/twentyhq/core-team-issues/issues/1744). This PR migrates attachments to morph relations behind a feature flag, following the TimelineActivity pattern. it introduces the `IS_ATTACHMENT_MIGRATED` flag, updates standard field metadata and indexes to use morph relations, adds a workspace migration that renames `attachment.*Id` columns to `target*Id` and converts the corresponding field metadata to `MORPH_RELATION` with a shared `morphId`. On the frontend, attachment read/write paths now switch to `target*Id` when the flag is enabled. It also fixes optimistic filtering for morph join columns. The metadata API deduplicates morph fields, so attachments now expose a single target field of type `MORPH_RELATION` plus a `morphRelations` array listing each target object. Because only one `settings.joinColumnName` is returned (e.g. `targetRocketId`), filters like `targetCompanyId` don’t map to any field and the optimistic cache code throws. `doesMorphRelationJoinColumnMatch` resolves this by computing all valid join column names from `morphRelations` using `computeMorphRelationFieldName` and comparing them to the filter key. That makes filters like `targetCompanyId` resolvable even with a single target field, so attachment uploads and list matching no longer crash. <img width="477" height="474" alt="image" src="https://github.com/user-attachments/assets/50e19418-3438-4d1e-9f1f-1bc1a03174a9" /> <br /> <br /> Today the metadata API returns one morph field called `target` and a list of possible targets (`morphRelations`), but it does not tell us the join column for each target. That’s why the Frontend had to compute join column names. If we want to fix this at the API level, there are two options: - Add join column names to each target in `morphRelations` (e.g. company → `targetCompanyId`). This is additive and low‑risk. - Return each target as its own field (`targetCompany`, `targetPerson`, etc.) instead of a single target. This is a larger change because it changes the shape of metadata and would require more UI updates. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduces morph relations for attachments behind `IS_ATTACHMENT_MIGRATED`, aligning server schema/metadata and frontend behavior. > > - Adds `IS_ATTACHMENT_MIGRATED` flag (frontend/server) and seeds/defaults; updates generated GraphQL enums > - New workspace upgrade `1.17` command migrates data: renames `attachment.*Id` → `target*Id` and converts related fields to `MORPH_RELATION` with shared `morphId` > - Updates standard field metadata and indexes to `target*` (attachment + related objects), dev seeds, snapshots, and workspace entity types > - Frontend: switches attachment read/write filters via `getActivityTargetObjectFieldIdName` using the feature flag; updates hooks/components (`useAttachments`, `useUploadAttachmentFile`, editors); expands `Attachment` type > - Fixes optimistic cache filtering to recognize morph join columns in `isRecordMatchingFilter` by computing valid join-column keys from `morphRelations` > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit f208fa23b1135af114f484f6d65041a816d6e718. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix@twenty.com>
The #1 Open-Source CRM
🌐 Website · 📚 Documentation · Roadmap ·
Discord ·
Figma
Installation
See: 🚀 Self-hosting 🖥️ Local Setup
Does the world need another CRM?
We built Twenty for three reasons:
CRMs are too expensive, and users are trapped. Companies use locked-in customer data to hike prices. It shouldn't be that way.
A fresh start is required to build a better experience. We can learn from past mistakes and craft a cohesive experience inspired by new UX patterns from tools like Notion, Airtable or Linear.
We believe in Open-source and community. Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
What You Can Do With Twenty
Please feel free to flag any specific needs you have by creating an issue.
Below are a few features we have implemented to date:
- Personalize layouts with filters, sort, group by, kanban and table views
- Customize your objects and fields
- Create and manage permissions with custom roles
- Automate workflow with triggers and actions
- Emails, calendar events, files, and more
Personalize layouts with filters, sort, group by, kanban and table views
Customize your objects and fields
Create and manage permissions with custom roles
Automate workflow with triggers and actions
Emails, calendar events, files, and more
Stack
- TypeScript
- Nx
- NestJS, with BullMQ, PostgreSQL, Redis
- React, with Recoil, Emotion and Lingui
Thanks
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
- Star the repo
- Subscribe to releases (watch -> custom -> releases)
- Follow us on Twitter or LinkedIn
- Join our Discord
- Improve translations on Crowdin
- Contributions are, of course, most welcome!




