## Summary
UNLISTED views are personal views tied to a specific user, so API keys
should not be able to create them.
## Changes
- Added check in `canUserCreateView` to block API keys from creating
UNLISTED views
- Refactored the service to use smaller functions with early returns (no
nested if/else)
## Behavior Matrix
### Creating Views
| Caller | Visibility | Has VIEWS Permission | Result |
|--------|------------|---------------------|--------|
| User | UNLISTED | (not checked) | ✅ Allow |
| User | WORKSPACE | Yes | ✅ Allow |
| User | WORKSPACE | No | ❌ Denied |
| **API Key** | **UNLISTED** | (not checked) | **❌ Denied** |
| API Key | WORKSPACE | Yes | ✅ Allow |
| API Key | WORKSPACE | No | ❌ Denied |
Fixes#16739
- Remove empty string coercion in createCoreView that caused PostgreSQL
UUID errors for API keys
- Add permission check allowing API keys with VIEWS permission to manage
workspace views they created
API keys with 'Manage Views' permission can now create, update, and
delete workspace views via both GraphQL and REST APIs.
PR for #15313
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Enables importing timestamp fields that were previously filtered out.
>
> - Removes explicit exclusions of `createdAt` and `updatedAt` in
`spreadsheetImportFilterAvailableFieldMetadataItems`
> - Keeps existing constraints: only active fields, system fields
limited (except `id`), exclude `deletedAt`, and only allow `RELATION`
fields that are `MANY_TO_ONE`; sorting unchanged
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
a2b36e4cc0. 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>
Solves #16015
- Added a check in `useTimelineActivities.ts` to see if the system
object page we're viewing has timelineActivity being tracked before
querying to get the activity history.
- Removed @WorkspaceIsObjectUIReadOnly decorator from system objects and
added @WorkspaceIsFieldUIReadOnly to standard and system fields to allow
custom field edits as requested in the issue.
- Did not add calendarEvents or other system objects to timelineActivity
just yet since keeping track of timeline activity for every one of them
felt counter-intuitive and bloated. In order to determine which objects
need timelineActivity, I think we need to fix the broken views of system
objects first, such as the one in the attached screenshots - a good
number of them are broken. The check I added to
`useTimelineActivities.ts` hook displays timeline activity as empty for
the time - error would not be shown as suggested by Thomas.
<img width="1062" height="858" alt="image"
src="https://github.com/user-attachments/assets/e877e0fe-b665-46e3-b785-e84f2af7f833"
/>
<br />
<img width="1061" height="858" alt="image"
src="https://github.com/user-attachments/assets/0eba8c1c-444a-4b13-beda-64b95cf39077"
/>
- Editing custom fields on calendar events (and other objects without
position fields) crashed with TypeError: Cannot convert undefined or
null to object in sortCachedObjectEdges. This happened because some
cached queries had empty orderBy arrays ([]), and the optimistic effect
tried to sort with them. Objects without position fields returned
empty orderBy arrays when there were no sorts, while objects with
position fields automatically got [{ position: 'AscNullsFirst' }].
The backend always adds { id: 'AscNullsFirst' } as a fallback, but
the frontend didn't match this. So, I added { id: 'AscNullsFirst' } to
the Frontend as default orderBy when there are no sorts and no position
field.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Apply per-field UI read-only to system/standard fields and skip
timeline activity fetching when the target object isn’t related; refine
record-table cell open/navigation behavior.
>
> - Server: Replace object-level UI read-only with per-field
`isUIReadOnly` across many standard objects (e.g., `calendarEvent`,
`workspaceMember`, messaging, calendar, favorites, attachments,
workflows, etc.), and mirror this via `@WorkspaceIsFieldUIReadOnly` on
workspace entities.
> - Frontend: In `useTimelineActivities.ts`, check object metadata for a
relation to `timelineActivity` and `skip` the query when absent,
preventing errors on system object pages.
> - Frontend: Simplify/adjust record-table cell logic—remove unused
args, allow navigation from first column when non-empty, block editing
for read-only records (while still allowing navigation), and update
button handlers/hover styles accordingly.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
dac1262d70. 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>
- added an image, one article
- updated the 2 files under the Navigation folder but not the docs.json
- no need to redirect the links given this is a new article
## Summary
Adds comprehensive documentation for the `IS_MULTIWORKSPACE_ENABLED`
configuration variable, which was previously undocumented despite being
a fundamental configuration option that significantly changes how Twenty
behaves.
## Changes
### Self-Host Setup Guide (`setup.mdx`)
Added a new **Multi-Workspace Mode** section that explains:
- **Single-workspace mode (default)**: Only one workspace allowed, first
user gets admin privileges, signups disabled after first workspace
- **Multi-workspace mode**: Multiple workspaces with subdomain-based
URLs (e.g., `sales.your-domain.com`)
- **Related config variables**: `DEFAULT_SUBDOMAIN` and
`IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS`
- **DNS configuration**: Wildcard DNS setup for dynamic subdomains
- **Workspace creation restrictions**: How to limit workspace creation
to server admins
### Local Setup Guide (`local-setup.mdx`)
Added an info callout in the environment variables section to make
contributors aware of multi-workspace mode, useful when testing
subdomain-based features.
## Why This Matters
The `IS_MULTIWORKSPACE_ENABLED` variable controls:
- Whether multiple workspaces can exist on a single instance
- URL structure (plain domain vs subdomains)
- First user privileges
- Sign-up behavior after initial setup
- SSO workspace resolution logic
This is critical knowledge for self-hosters who want to run Twenty as a
multi-tenant SaaS.
Removing the feature flag to enable read on replica for all workspaces.
It will still be possible to toggle off the feature by removing the env
variable `PG_DATABASE_REPLICA_URL`.
## Summary
The merge conflict resolution from PR #16705 incorrectly discarded the
new documentation structure changes. This PR updates the navigation JSON
files (the correct approach) to restore the intended changes.
## Changes restored
- New 'Capabilities' and 'How-Tos' subgroups organization
- Renamed sections (e.g., 'Getting Started' → 'Discover Twenty')
- New sections: Data Migration, Calendar & Emails, AI, Views &
Pipelines, Dashboards, Permissions & Access, Billing
- Reorganized Developers section with Extend, Self-Host, and Contribute
groups
## Files updated
- `navigation/base-structure.json`
- `navigation/navigation-schema.json`
- `navigation/navigation.template.json`
## Context
PR #16705 was merged but the merge conflict was incorrectly resolved,
causing all the structural changes to be lost. The previous fix (PR
#16741) updated docs.json directly, but the correct approach is to
update the navigation JSON files instead. This PR properly restores
those changes from the final commit of PR #16705
(`c856e0d598a0056c2bdaf528502e08261daf7c7c`).
---------
Co-authored-by: github-actions <github-actions@twenty.com>
## Summary
The merge conflict resolution from PR #16705 incorrectly discarded the
new documentation structure changes. This PR restores the intended
changes.
## Changes restored
- New 'Capabilities' and 'How-Tos' subgroups organization
- Renamed sections (e.g., 'Getting Started' → 'Discover Twenty')
- New sections: Data Migration, Calendar & Emails, AI, Views &
Pipelines, Dashboards, Permissions & Access, Billing
- Reorganized Developers section with API subsection
- URL redirects for SEO and user experience continuity
## Context
PR #16705 was merged but the merge conflict was incorrectly resolved,
causing all the structural changes to be lost. This PR brings back those
changes from the final commit of PR #16705
(`c856e0d598a0056c2bdaf528502e08261daf7c7c`).
Reorganizing by Feature sections
Capabilities folders to give an overview of each feature
How-Tos folders to give guidance for advanced customizations
Reorganized the Developers section as well, moving the API sub section
there
added some new visuals and videos to illustrate the How-Tos articles
checked the typos, the links and added a section at the end of the
doc.json file to redirect existing links to the new ones (SEO purpose +
continuity of the user experience)
What I have not updated is the "l" folder that, per my understanding,
contains the translation of the User Guide - that I only edited in
English
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
5301502a32. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Abdul Rahman <ar5438376@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Doing an upsert on existing value, composite field not updated properly.
```
Input: {
id: "08ca34fe-fc39-474f-adac-4d89f844e922",
name: "tom",
linkedinLink: {
primaryLinkUrl: "https://www.linkedin.com/in/etienneyaouni1982",
primaryLinkLabel: "etienne",
secondaryLinks: null,
},
}
```
Building `overwrites` for upsert forgets `linkedinLink` because column
names are not flattened yet.
We don't want to call formatData yet on the input, because this is
heavy.
Overriding `overwrites` on execute.
- Replaced direct instance checks for GaxiosError with a utility
function isGmailApiError for better error handling consistency across
services.
- Debug logs
- Added new `useUpdateManyRecords` hook for batch record updates with
optimistic cache updates
- Added `updateMessageFoldersSyncStatus` hook for managing message
folder sync state
- Redesigned Message Folders List with BreadCrumb and Animations
- Batch Gmail API calls using `googleapis-batcher` for folder processing
- Add concurrency limit for Microsoft Graph folder processing
- Skip IMAP folder sync when no new messages (checks UIDVALIDITY/MODSEQ)
- Refactored `syncMessageFolders` to return folder state directly,
avoiding extra DB round-trips
- Refactored `processPendingFolderActions` to reuse state instead of
querying DB again
- Add unique index on message folders entity
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
- Rename page title from 'Releases' to 'Updates'
- Rename navigation item label from 'Releases' to 'Updates'
- Remove tabbed interface (Changelog/Lab tabs)
- Add 'Releases' section with external link to changelog
- Add 'Early access' section with lab features
- Add IconTransform to twenty-ui exports
- Delete unused tab-related components and constants
Screenshot:
<img width="2158" height="1698" alt="CleanShot 2025-12-17 at 17 53
46@2x"
src="https://github.com/user-attachments/assets/87ead041-24a7-4afb-9dfc-71e5c20324d6"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Fixes https://github.com/twentyhq/twenty/issues/16624
Original issue:
- while persisting a field (calling useUpdateOne), the response from the
backend is missing the taskTargets many to many (same for note). As we
optimistically update the cache, we lose the "Relations" in the UI
- I'm changing the behavior of useUpsertInRecordStore to accept
recordGqlFields to only update the fields we want in the record store
(this way, we are not losing the targets information in our case)