https://sonarly.com/issue/22112?type=bug
Contact auto-creation crashes with TypeError because connectedAccount.handleAliases can be a plain string after migration from workspace TEXT column to core varchar[] column, but the code assumes it is always an array.
Fix: Replaced `connectedAccount.handleAliases || []` with `Array.isArray(connectedAccount.handleAliases) ? connectedAccount.handleAliases : []` at both usage sites where handleAliases is consumed as an array.
The `|| []` fallback only activates for falsy values (null, undefined, empty string), but a non-empty string like "[email protected],[email protected]" is truthy, so it passes through and then fails on .map() or .includes() with array semantics. Array.isArray() correctly rejects strings and all non-array types, ensuring we always operate on a real array.
Two files changed:
1. filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts (line 23) — the crash site from the Sentry error
2. messaging-save-messages-and-enqueue-contact-creation.service.ts (line 52) — same pattern, would cause incorrect substring matching (not a crash, but wrong behavior) if handleAliases were a string
## Summary
- Introduces a pluggable `WebSearchDriver` abstraction (interface,
factory, service, module) so web search is no longer tied to native
provider tools (Anthropic/OpenAI)
- **Exa** is the first driver implementation with support for
category-filtered search (company, people, news, research paper, etc.) —
particularly useful for CRM workflows
- Per-query billing for both Exa ($0.007/query) and native provider
surcharges ($0.01/query for Anthropic/OpenAI) via the existing
`USAGE_RECORDED` pipeline
- New config variables: `WEB_SEARCH_DRIVER` (EXA/DISABLED),
`EXA_API_KEY`, `WEB_SEARCH_PREFER_NATIVE` (default false — prefers Exa
over native when both available)
- `WEB_SEARCH` operation type added for usage tracking and Stripe
metering
### Architecture
```
WebSearchDriver (interface)
├── ExaDriver — Exa neural search with category support
└── DisabledDriver — throws when search is disabled
WebSearchDriverFactory (extends DriverFactoryBase)
└── creates driver based on WEB_SEARCH_DRIVER config
WebSearchService (facade)
├── search(query, options?, billingContext?)
├── isEnabled()
└── emits USAGE_RECORDED events per query
WebSearchTool (Tool implementation)
└── registered in ActionToolProvider, available via tool catalog
```
### Native search billing gap fixed
Anthropic and OpenAI both charge $0.01/search on top of token costs. The
token costs were already billed, but the per-call surcharge was not.
Added `countNativeWebSearchCallsFromSteps` utility +
`billNativeWebSearchUsage` to `AiBillingService`, wired into both chat
and workflow agent paths.
## Test plan
- [ ] Set `WEB_SEARCH_DRIVER=EXA` + `EXA_API_KEY=...` and verify AI chat
can search the web
- [ ] Verify category parameter works (ask about a specific
company/person)
- [ ] Set `WEB_SEARCH_DRIVER=DISABLED` and verify search tool is not
exposed
- [ ] Set `WEB_SEARCH_PREFER_NATIVE=true` with Anthropic model and
verify native search is used
- [ ] Verify usage events are emitted in ClickHouse for both Exa and
native search paths
- [ ] Verify existing billing tests pass (`npx jest
ai-billing.service.spec.ts`)
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
## Summary
- Move email thread display from side panel to a dedicated record page
with a new `EMAIL_THREAD` widget type
- Add message thread as a standard object with page layout, subject
field, and backfill command
- Add reply-to-email command menu item for message thread records
- Remove old side panel message thread components in favor of the new
widget-based approach
## Type fixes
- Add `EMAIL_THREAD` to `WidgetConfigurationType`, `WidgetType`, and all
configuration/validator maps
- Create `EmailThreadConfigurationDTO` and shared
`EmailThreadConfiguration` type
- Register EMAIL_THREAD in widget type validators, configuration
resolvers, and standard widget mappings
## Test plan
- [ ] Verify message thread record pages render with the email thread
widget
- [ ] Verify email thread preview navigates to the record page instead
of opening side panel
- [ ] Verify reply-to-email command appears for message thread records
- [ ] Verify typecheck passes for both twenty-front and twenty-server
- [ ] Run existing test suites to check for regressions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
This PR migrates `messageFolder`.`parentFolderId` from an internal
`UUID` reference to external provider id.
Eliminates unnecessary lookup and complexity
---------
Co-authored-by: Charles Bochet <[email protected]>
This PR implements a whole lot of fixes and updates to the website. It
adds a release-notes page, terms and conditions page, privacy policy
page, while fixing HomeStepper, ProductStepper, and WhyTwentyStepper. 3D
models still need to be styled and their sizes need to be fixed.
# Introduction
Typeorm migration are now associated to a given twenty-version from the
`UPGRADE_COMMAND_SUPPORTED_VERSIONS` that the current twenty core engine
handles
This way when we upgrade we retrieve the migrations that need to be run,
this will be useful for the cross-version incremental upgrade so we
preserve sequentiality
## What's new
To generate
```sh
npx nx database:migrate:generate twenty-server -- --name add-index-to-users
```
To apply all
```sh
npx nx database:migrate twenty-server
```
## Next
Introduce slow and fast typeorm migration in order to get rid of the
save point pattern in our code base
Create a clean and dedicated `InstanceUpgradeService` abstraction
## Summary
- Replaces `npm pkg set version=X` with a direct `node -e` script in the
`set-local-version` Nx target
- Fixes `CI Create App E2E minimal` workflow failures on fork PRs where
`npm pkg set` fails with `EJSONPARSE: Unexpected end of JSON input while
parsing empty string`
The `npm` command has unreliable `package.json` resolution in certain CI
checkout contexts (shallow clones of fork PR merge refs). Using `node`
directly to read/write the JSON file avoids this entirely.
The minimalMetadata query was returning untranslated English labels for
standard objects (Companies, People, Opportunities, etc.) when users
requested zh-CN locale. This fix adds translation logic to
MinimalMetadataService using the existing I18nService.
Changes:
- Add translateLabel() method to translate standard object labels
- Pass locale from GraphQL context through resolver to service
- Only translate non-custom objects (custom objects use user-defined
labels)
Co-authored-by: Kevin Yu <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).
This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.
**Please review before merging** — verify no critical models were
incorrectly deprecated.
Co-authored-by: FelixMalfait <[email protected]>
## Summary
- **Remove `ExecuteToolResult` wrapper** — `execute_tool` is now a
transparent dispatcher that returns the raw `ToolOutput` from underlying
tools. No more `{ toolName, result }` envelope.
- **Type the entire execution chain as `Promise<ToolOutput>`** — from
`ToolExecutorService.dispatch()` through `resolveAndExecute()` to
`execute_tool.execute()`. Zero `Promise<unknown>` remaining in the tool
layer.
- **Use `Extract<ToolExecutionRef, ...>`** for dispatch methods,
enabling exhaustive switch checking and removing `as never` casts.
- **Relax `ToolOutput.result` to accept `null`** — removes `??
undefined` hacks at the boundary with logic function results.
- **Enforce 1-export-per-file** across tool type/interface files (split
`tool-descriptor.type.ts`, `tool-provider.interface.ts`, `tool.type.ts`,
`tool-output.type.ts`, `tool-executor.service.ts`).
- **Simplify error handling** — `wrapWithErrorHandler` and all
meta-errors (tool not found, tool excluded) now return consistent
`ToolOutput` shape with `error` as a plain string.
- **Frontend reads output directly** — removed `unwrapToolOutput`
utility; `ToolStepRenderer` and `ThinkingStepsDisplay` extract
`message`/`error` from the raw output with simple type guards.
- **Add permission error detection** for email tools via
`isInsufficientPermissionsError`, guiding the AI model to suggest
account reconnection instead of hallucinating about visibility settings.
## Test plan
- [ ] AI chat tool calls return visible output (not "null") in the UI
- [ ] Tool errors display correctly in the JSON tree
- [ ] Email draft/send tools return actionable permission errors
- [ ] Code interpreter output renders correctly
- [ ] Thinking steps display tool outputs properly
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Bug fixes exposed by always-on direct execution
1. GraphQL spec compliance — data[field] = null on resolver error
direct-execution.service.ts — Changed from Promise.allSettled (which
lost the responseKey on rejection) to Promise.all with per-field
try/catch; errors now set data[responseKey] = null per spec
2. Empty object arguments skipped (extractArgumentsFromAst)
extract-arguments-from-ast.util.ts — Removed isEmptyObject check;
filter: {}, data: {} now correctly passed to resolvers instead of
silently dropped (which caused permissions to never be checked)
3. orderBy: {} factory default treated as "no ordering"
direct-execution.service.ts — Before calling the resolver, strips
orderBy: {} and orderByForRecords: {} (empty-object factory defaults
that mean "no ordering")
assert-find-many-args.util.ts / assert-group-by-args.util.ts — Accept {}
for orderBy without throwing
4. orderBy: { field: '...' } object auto-coerced to [{ field: '...' }]
array
direct-execution.service.ts — Applies GraphQL list coercion: a
non-array, non-empty orderBy object is wrapped in an array before
assertion and resolver call
5. totalCount and aggregate fields returned as strings from PostgreSQL
graphql-format-result-from-selected-fields.util.ts — Added
coerceAggregateValue that parses numeric strings to numbers for
totalCount, sum*, avg*, min*, max*, count*, percentageOf* fields
Test updates
nested-relation-queries.integration-spec.ts — Updated expected error
message from Yoga schema-validation message to direct execution resolver
message
~30 snapshot files — Updated to reflect direct execution's error
messages (different from Yoga schema-validation messages for input type
errors)
- WorkflowCronTriggerCronJob was querying every active workspace (~700)
sequentially every minute to find cron triggers, causing regular CPU
spikes to 100% on worker pods
- Added a Redis hashset cache that stores cron triggers. On cache miss
(TTL expired, cold start, or explicit invalidation), a full scan
rebuilds the cache
- Creating/deleting a new cron trigger updates the cache, only if exists
Fixes https://github.com/twentyhq/twenty/issues/19309
In exclusion mode (select all), selectedRecords is always [] since
individual record IDs aren't tracked. The noneDefined()/everyDefined()
checks return false on empty arrays by design, which hides the delete,
restore, and destroy commands from the command menu.
- Wrap selectedRecords array checks with (isSelectAll or ...) to bypass
when in exclusion mode
- Remove the `numberOfSelectedRecords < 10000` limit
- Add `upgrade:1-21:fix-select-all-command-menu-items` command to
backfill existing workspaces
- Add tests
- simplify the base application template
- remove --exhaustive option and replace by a --example option like in
next.js https://nextjs.org/docs/app/api-reference/cli
- Fix some bugs and logs
- add a post-card app in twenty-apps/examples/
## Summary
Rebased version of #19051 with all review comments addressed. Clean
branch on latest main, lint/typecheck/tests passing.
### Changes from original PR
- AI can now create, update, and delete **view filters**
(`ViewFilterToolsFactory`) and **view sorts** (`ViewSortToolsFactory`)
- `create_view` now accepts `calendarFieldName`, `calendarLayout`, and
`fieldNames` to configure views at creation time
- Three new standard skills: `view-building`, `view-filters-and-sorts`,
`custom-objects-cleanup`
- `workspace-demo-seeding` skill reworked to keep standard objects and
enrich them with custom fields
- Cache invalidation for nav menu items when object `isActive` changes
- Dashboard tool descriptions improved (RECORD_TABLE widget workflow)
### Review comments addressed (all 10 from #19051)
1. **Sentry + Cubic**: Calendar field DATE/DATE_TIME validation — added
`resolveCalendarFieldMetadataId` using `isFieldMetadataDateKind`
2. **Cubic**: "navigate tool" → "navigate_app tool" in skill metadata
(all 7 occurrences)
3. **Copilot**: KANBAN views now require `mainGroupByFieldName` — throws
clear error if missing
4. **Copilot**: CALENDAR views now require both `calendarFieldName` and
`calendarLayout` — validated before DB call
5. **Copilot**: Mock field fixtures include `type` property (DATE_TIME,
TEXT, SELECT)
6. **Copilot**: `ViewFilterValue` type assertion instead of unsafe `as
string` casts (3 locations)
7. **FelixMalfait**: Removed
`NavigationMenuItemObjectDeactivationListener` — replaced with cache
invalidation
8. **FelixMalfait**: Consolidated `ViewFilterToolProvider` and
`ViewSortToolProvider` into single `ViewToolProvider`
9. Removed `VIEW_FILTER` and `VIEW_SORT` from `ToolCategory` enum
(merged into `VIEW`)
10. Removed stale `existingFeatureFlagsMap` param incompatible with
current main
## Test plan
- [x] `npx nx lint:diff-with-main twenty-server` — passes
- [x] `npx nx typecheck twenty-server` — passes
- [x] `view-tools.factory.spec.ts` — all 20 tests pass (including 3 new
validation tests)
Supersedes #19051https://claude.ai/code/session_01QPV74NU6vzmJb32e4i899E
---------
Co-authored-by: Claude <[email protected]>
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).
This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.
**Please review before merging** — verify no critical models were
incorrectly deprecated.
Co-authored-by: FelixMalfait <[email protected]>
## Summary
- Bumps `handlebars` from `^4.7.8` to `^4.7.9` in
`packages/twenty-shared`
## Why
- **CVE-2026-33937** — Prototype pollution via crafted template input
- **GHSA-2w6w-674q-4c4q** — Related handlebars security advisory
- Severity: **Critical** (CVSS 9.8)
- Detected by Trivy and Grype scanning `twentycrm/twenty:v1.20.0`
## What changed
- `packages/twenty-shared/package.json`: `"handlebars": "^4.7.8"` →
`"^4.7.9"`
- `yarn.lock`: updated accordingly
## Impact
`handlebars` is used in `packages/twenty-shared` for template
evaluation. The fix patches the prototype pollution vector without any
API changes.
## Test plan
- [ ] `yarn build` passes
- [ ] `yarn test` passes in twenty-shared
- [ ] Template evaluation works as before
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Abdullah <[email protected]>
**Summary**
- update the home hero navbar controls and surface styling to better
match the latest visual design
- simplify row hover actions by removing edit affordances and disabling
hover controls for `createdBy` and `accountOwner`
- tighten chip typography and spacing for more consistent hero table
rendering
- include the captured Playwright screenshot artifact for reference
**Testing**
- Not run (not requested)
## Summary
- **Make app-synced objects searchable**: `isSearchable` was hardcoded
to `false` and the `searchVector` field was missing the `GENERATED
ALWAYS AS (...)` expression, causing all records to have a `NULL` search
vector and be excluded from search results. Fixed by defaulting
`isSearchable` to `true` (configurable via the object manifest),
computing the `asExpression` from the label identifier field, and
allowing the update-field-action-handler to handle the `null` → defined
`asExpression` transition.
- **Make `isSearchable` updatable on an object**: The property had
`toCompare: false` in the entity properties configuration, so updates
via the API were silently ignored and never persisted. Fixed by setting
`toCompare: true`.
Auto merged passed to fast on
https://github.com/twentyhq/twenty/pull/19271
Try catch has been added while debugging an integration test suite
covering the workspace creation that would fail due to sdk generation
failure
As they're run synchronously in integration tests they would stop the
workspace creation process
If the proposed fix here isn't enough I'll wrapp the run sync test to be
catching any unexpected issue
## PR description
The previous approach used a detached flag to represent selection mode,
which could was not synced sync with actual UI selection state. This
change ties the edit mode's selection/fallback behavior directly to
whether records are truly selected in the record index.
- Removes `commandMenuItemEditSelectionModeState` (a standalone 'none' |
'selection' atom) and replaces it with
`mainContextStoreHasSelectedRecordsSelector`, which derives selection
state from the real record selection in the context store.
- Adds `useSelectFirstRecordForEditMode` to programmatically select the
first record (table row or kanban card) when toggling to selection mode,
and useResetRecordIndexSelection to clear selection when toggling off or
exiting edit mode.
- Ensures record selection is properly cleaned up when exiting layout
customization mode or closing the side panel.
## Video QA
### Table
https://github.com/user-attachments/assets/1d845809-8369-4872-b3a9-a0281b8e464b
### Board
https://github.com/user-attachments/assets/325c2d58-4ca0-408a-ab4a-66b97d9d70de
## Summary
- Adds `IS_AI_ENABLED` to `PUBLIC_FEATURE_FLAGS` so it appears in
**Settings > Lab** as a self-serve toggle for users
- Includes a cover image (`is-ai-enabled.png`) hosted on
`twenty.com/images/lab/`, following the existing convention for lab
feature flag images
- AI is now ready for public beta — users can enable it directly from
the Lab
## Test plan
- [ ] Verify the AI card appears in Settings > Lab with the cover image
banner
- [ ] Toggle AI on/off and confirm the feature flag is persisted
- [ ] Confirm AI features (agent chat, etc.) activate when the flag is
enabled
Made with [Cursor](https://cursor.com)
Avoid overloading the workspace creation with the standard and custom
app sdk generation, do through a job
```ts
[Nest] 90397 - 04/02/2026, 4:44:20 PM LOG [CoreEntityCacheService] Cache stats: localCacheSize=0 memoizerCacheSize=0 memoizerPendingSize=0 entriesByKey={} versionsByKey={}
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Processing job 4325 with name CallWebhookJobsForMetadataJob on queue webhook-queue
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Processing job 1 with name GenerateSdkClientJob on queue workspace-queue
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Job 1 with name GenerateSdkClientJob processed on queue workspace-queue in 0.64ms
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Processing job 2 with name GenerateSdkClientJob on queue workspace-queue
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Job 2 with name GenerateSdkClientJob processed on queue workspace-queue in 0.06ms
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Job 4325 with name CallWebhookJobsForMetadataJob processed on queue webhook-queue in 17.47ms
[Nest] 90397 - 04/02/2026, 4:45:03 PM LOG [BullMQDriver] Processing job 4326 with name CallWebhookJobsForMetadataJob on queue webhook-queue
```
- Replace soft-deletion (deletedAt) with isActive boolean for
overridable entities (tabs, widgets, viewFieldGroups, viewFields).
Standard entities are deactivated (isActive: false) when removed from
update payloads, while custom entities are hard-deleted.
- When a viewFieldGroup is deactivated/deleted, its viewFields are
reassigned to the next section by position (or null if none remain).
- Add isActive: true filters to viewFieldGroup and viewField API queries
so deactivated entities are excluded from responses.
Next:
- fields-widget-upsert.service.ts should be refactored a bit
- Add restore logic
Insert operations blocked by field-level update permissions on
non-editable fields
The insert code path in permissions.utils.ts fell through to the update
case (no break), causing validateUpdateFieldPermissionOrThrow to reject
inserts when any field had "Edit disabled" which could conflict with RLS
predicates (used for insertion of new records)
Fixes https://github.com/twentyhq/twenty/issues/19201
We will keep checking update permissions for insertion (until we decide
to have a separate permission flag for insertion) but to fix the issue
we will skip this part if it conflicts with an RLS predicate
This PR makes the Home Visual interactive.
Not the perfect code, but does the trick for now to convey what we're
trying to do. As per the conversation with Thomas, we will iterate over
it and make it better when we get past the first release.
---------
Co-authored-by: Thomas des Francs <[email protected]>
# Introduction
Currently preparing the `UpgradeCommand` refactor, in this way started
by cleaning up the existing avoiding unecessary dependencies to others
services allowing easier readability and concern centralization for
upcoming refactor
The UpgradeCommand was extending up to five classes, overriding
abstracted class and so on. It was also cascade
injecting 3 services
Introducing the `WorkspaceIteratorService` that centralize the commands
set to run over a single workspace logic shared between both atomic
upgrade command call and global upgradeCommand
## Tradeoff
Duplicated `@Option` between both `UpgradeCommandRunner` and
`WorkspaceMigrationRunner`
## Before
```
UpgradeCommand
└─ extends UpgradeCommandRunner
└─ extends ActiveOrSuspendedWorkspacesMigrationCommandRunner
└─ extends WorkspacesMigrationCommandRunner (owns workspace iteration loop + ORM deps)
└─ extends MigrationCommandRunner (dry-run, verbose, error handling)
└─ extends CommandRunner (nest-commander)
```
## Now
```
UpgradeCommand
└─ extends UpgradeCommandRunner
└─ extends CommandRunner (nest-commander)
uses ─► Services (via composition)
```
## Logging management
At the moment all services are logging, in the best of the world only
the runners should be doing so
The backend validation for field permissions was using !== null to check
canReadFieldValue and canUpdateFieldValue, but these optional GraphQL
fields can also be undefined when omitted from the input. This caused
the validation to incorrectly reject legitimate requests (e.g.,
restricting only "update" without specifying "read") with the error
"Field permissions can only be used to restrict access, not to grant
additional permissions."
Replaced !== null checks with isDefined() so that both null and
undefined are treated as "no opinion" on that permission.
To reproduce:
- Create a single FieldPermission on an object with canEdit: false
without any other rule on that same object
## Summary
- Set `accent="blue"` on InformationBanner action button so it renders
blue instead of default gray
- Add Banner storybook stories for all color × variant combinations
(BluePrimary, BlueSecondary, DangerPrimary, DangerSecondary)
- Use `useUserTimezone()` in `SettingsDatePickerInput` instead of
browser timezone (`Temporal.Now.timeZoneId()`) so dates respect the
admin's profile timezone preference
- Separate `onChange` from `onClose` in `SettingsDatePickerInput` so
changing the hour no longer forces the date picker to close
## Summary
- **Apollo factory** (`apollo.factory.ts`): Bail early with `EMPTY` when
no token pair exists so no request is forwarded without credentials.
Propagate renewal success/failure as a boolean so failed renewals stop
the operation chain instead of forwarding with stale tokens.
- **Refresh token service** (`refresh-token.service.ts`): When a revoked
refresh token is reused past the grace period, reject only that token
instead of mass-revoking all user tokens. The most common cause is a
lost renewal response (e.g. navigation during refresh), not actual token
theft. This eliminates the "Suspicious activity detected" errors users
were seeing. Also switches to `findOneBy` since the `appTokens` relation
is no longer needed.
## Test plan
- [x] `refresh-token.service.spec.ts` — all 7 tests pass
- [ ] Verify login flow: sign in from `app.localhost`, get redirected to
workspace subdomain without "Suspicious activity" errors
- [ ] Verify token renewal: let access token expire, confirm silent
renewal works and operations resume
- [ ] Verify concurrent tabs: open multiple tabs, let tokens expire,
confirm no mass revocation cascade
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
## Summary
- Adds an index on `viewField.viewFieldGroupId` to fix a ~28s `DELETE
FROM core.viewFieldGroup WHERE workspaceId = $1` query observed in
production
- The slow query is triggered during workspace hard deletion
(`deleteWorkspaceSyncableMetadataEntities` in `workspace.service.ts`)
- Root cause: the FK constraint `viewField.viewFieldGroupId →
viewFieldGroup.id` with `ON DELETE SET NULL` forces PostgreSQL to
sequentially scan the entire `viewField` table for every deleted
`viewFieldGroup` row, because no index exists on the referencing column
- Uses `CREATE INDEX CONCURRENTLY` in the migration to avoid locking the
table on production
### Context
GraphQL introspection queries (__schema, __type) were going through the
full Yoga server pipeline, which forces a complete workspace schema
build, loading flat metadata maps, building all GraphQL types, wiring
all resolver factories, and calling makeExecutableSchema. This is
expensive, even though introspection only needs the type structure and
zero resolver execution.
A new WorkspaceGraphqlSchemaSDLService extracts the SDL computation that
was previously embedded inside WorkspaceSchemaFactory.
From `direct-execution.service.ts` :
- `buildSchema(sdl)` reconstructs a resolver-free GraphQLSchema from the
SDL
- pure CPU, not cached, not sure it worths it ?
- `execute({ schema, document, variableValues })` from graphql-js,
introspection is answered entirely by the graphql-js runtime from type
metadata, no resolver execution needed
#### Nice to do ?
- Use new cache service for typeDefs ?
### Renaming bonus: `typeDefs` → `sdl`
`typeDefs` is an Apollo/graphql-tools convention. It's the parameter
name in
`makeExecutableSchema({ typeDefs, resolvers })`, not a native GraphQL
spec term.
In proper GraphQL semantics, what this service produces is the **SDL**
(Schema
Definition Language): the official term for the string representation of
a schema.
`printSchema()` produces it, `buildSchema()` consumes it.
##### Why the distinction matters
- **Type definitions** implies partial type declarations (objects,
scalars, enums…)
- **Schema SDL** conveys a *complete* schema document: all types
**plus** the root
operation types (`Query`, `Mutation`) which is exactly what
`printSchema(schema)`
produces
This PR completes the markup for all the sections of all pages of the
new website. There are a lot of improvements to come, but the entire
structure is in place now.
closes
https://discord.com/channels/1130383047699738754/1480991390782455838
- Use data-code-execution as the streaming source of truth and hide
duplicate code-interpreter tool parts (including tool-execute_tool
wrappers).
- Ensure wrapped execute_tool code-interpreter outputs still render
correctly after refetch.
- Gate code-interpreter server behavior by enablement state and keep
assistant messages full-width to avoid streaming vs completed width
shifts.
Co-authored-by: Félix Malfait <[email protected]>
## Summary
- Add `WORKSPACE_SCHEMA_DDL_LOCKED` env-only boolean config variable
that blocks all workspace schema DDL changes when set to `true`. This is
intended for hot upgrades where logical replication cannot handle DDL
changes. Enforced at two chokepoints:
- `WorkspaceMigrationRunnerService.run` — blocks all metadata-driven DDL
(object/field/index CRUD, app sync/uninstall, standard app sync, upgrade
commands)
- `WorkspaceDataSourceService.createWorkspaceDBSchema` /
`deleteWorkspaceDBSchema` — blocks workspace creation (sign-up) and hard
deletion. Uses a dedicated `WorkspaceDataSourceException` (not
ForbiddenException)
- Add maintenance mode feature with Admin Panel UI and user-facing
banner:
- **Backend**: `MaintenanceModeService` stores maintenance window
(startAt, endAt, optional link) in `core.keyValuePair` as
`CONFIG_VARIABLE`. Validates endAt > startAt. Uses `GraphQLISODateTime`
scalar for date fields. Exposed via `clientConfig` REST endpoint and
admin GraphQL mutations (`setMaintenanceMode`, `clearMaintenanceMode`)
- **Admin Panel**: New "Maintenance Mode" section in Health tab with UTC
datetime pickers and activate/deactivate controls
- **Banner**: `InformationBannerMaintenance` displayed at the top of
`DefaultLayout` for all users, using Temporal API for timezone-aware
formatting with an optional "Learn more" link
These two features are **independent** — the DDL lock is controlled via
env var for operational use, while maintenance mode is a UI notification
mechanism controlled from the admin panel.
- Deduplicate standard record command menu items by merging
single-record and multi-record delete, restore, destroy, and export
actions into shared record commands.
- Update frontend command registrations and engine component mappings to
use the new unified keys, while keeping deprecated engine keys
temporarily mapped for backward compatibility.
- Add a 1-21 upgrade command that removes legacy command menu items from
workspaces and creates the new unified standard items.
- `frontComponentHostCommunicationApi` gets a new object reference on
every render, causing the `useMemo`/`useEffect` in
`FrontComponentWorkerEffect` to tear down and re-create the web worker
each time.
- Decouple the host API lifecycle from the worker lifecycle by moving
thread.exports updates into a dedicated
`FrontComponentUpdateHostCommunicationApiEffect` that mutates the
thread's exports object in place via Object.assign.
- Rename `FrontComponentHostCommunicationApiEffect` to
`FrontComponentInitializeHostCommunicationApiEffect` for clarity.
## Before
https://github.com/user-attachments/assets/6f3a5c14-2ae7-4317-82b5-1625abb4143e
## After
https://github.com/user-attachments/assets/1059d6cd-e02c-4477-b3e8-8e965a716434
## Summary
- **Queue messages while streaming**: Messages sent during active AI
streaming are queued server-side and auto-flushed when the current
stream completes. Frontend renders queued messages optimistically in a
dedicated queue UI.
- **Drop `@ai-sdk/react` + `resumable-stream`**: Replace the dual HTTP
SSE + AI SDK client architecture with a single GraphQL SSE subscription
per thread. All events (token chunks, message persistence, queue
updates, errors) flow through Redis PubSub → GraphQL subscription.
- **Server-driven architecture**: The server decides whether to queue or
stream (via `POST /:threadId/message`). The frontend mirrors this
decision for optimistic rendering but defers to the server response.
- **Reuse AI SDK accumulation logic**: `readUIMessageStream` from the
`ai` package handles chunk-to-message accumulation on the frontend,
avoiding a custom 780-line accumulator.
## Key files
**Backend:**
- `agent-chat-event-publisher.service.ts` — publishes events to Redis
PubSub
- `agent-chat-subscription.resolver.ts` — GraphQL subscription resolver
- `stream-agent-chat.job.ts` — publishes chunks via PubSub instead of
resumable-stream
- `agent-chat.controller.ts` — unified `POST /:threadId/message`
endpoint
**Frontend:**
- `useAgentChatSubscription.ts` — subscribes to `onAgentChatEvent`,
bridges to `readUIMessageStream`
- `useAgentChat.ts` — send/stop/optimistic rendering (no more AI SDK)
- `AgentChatStreamSubscriptionEffect.tsx` — replaces
`AgentChatAiSdkStreamEffect.tsx`
## Test plan
- [ ] Send message on new thread → optimistic render, streaming response
appears
- [ ] Send message while streaming → queued instantly (no flash in main
thread)
- [ ] Queued message auto-flushes after current stream completes
- [ ] Remove queued message via queue UI
- [ ] Stop streaming mid-response
- [ ] Leave chat idle for several minutes → streaming still works after
(SSE client recycling)
- [ ] Token refresh during session → requests succeed (authenticated
fetch)
- [ ] Switch threads while streaming → clean subscription handoff
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
## Context
Add a new "Manage" section in FIELDS widget side panel with a "Delete
widget" action
Next step: Deleting a non-custom fields widget should be reversible
("Reset to default" followup)
<img width="1286" height="398" alt="Screenshot 2026-03-31 at 15 17 31"
src="https://github.com/user-attachments/assets/9ee63c14-52f1-470e-9112-4538271dc6fc"
/>
- Adds a resolveObjectMetadataLabel utility that returns the singular or
plural label from object metadata based on the number of selected
records (e.g., "person" vs "people").
- Exposes a pre-computed objectMetadataLabel string on
CommandMenuContextApi, making it available for label interpolation
(e.g., Delete ${capitalize(objectMetadataLabel)}).
As per title, update a documentation on how to upload a file given
increasing amount of questions for this problem
CC: @StephanieJoly4
---------
Co-authored-by: Etienne <[email protected]>
📄 Summary
This PR upgrades the nodemailer dependency to a secure version (≥ 8.0.4)
to fix a known SMTP command injection vulnerability
(GHSA-c7w3-x93f-qmm8).
🚨 Issue
The current version used in twenty-server (^7.0.11, resolved to 7.0.11 /
7.0.13) is vulnerable to SMTP command injection due to improper
sanitization of the envelope.size parameter.
This could allow CRLF injection, potentially enabling attackers to add
unauthorized recipients to outgoing emails.
🔍 Root Cause
The vulnerability originates from insufficient validation of
user-controlled input in the SMTP envelope, specifically the size field,
which can be exploited via crafted input containing CRLF sequences.
✅ Changes
Upgraded nodemailer to version ^8.0.4
Ensured compatibility with existing email sending logic
Verified that no breaking changes affect current usage
🔐 Security Impact
This update mitigates the risk of:
SMTP command injection
Unauthorized email recipient manipulation
Potential data leakage via crafted email payloads
📎 References
GHSA: GHSA-c7w3-x93f-qmm8
CVE: (see linked report in issue)
---------
Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Charles Bochet <[email protected]>
## Summary
- Replaces `twentycrm/twenty-postgres-spilo` with the official
`postgres:16` image across all 7 CI workflow files
- Removes Docker Hub `credentials` blocks from all service containers
(postgres, redis, clickhouse)
- Removes the `Login to Docker Hub` step from the breaking changes
workflow
## Context
Fork PRs cannot access repository secrets/variables, causing `${{
vars.DOCKERHUB_USERNAME }}` and `${{ secrets.DOCKERHUB_PASSWORD }}` to
resolve to empty strings. GitHub Actions rejects empty credential values
at template validation time, failing the job before any step runs.
The custom spilo image was the original reason credentials were needed
(to avoid Docker Hub rate limits on non-official images). The only
Postgres extensions required in CI (`uuid-ossp`, `unaccent`) are built
into the official `postgres:16` image. Official Docker Hub images have
significantly higher pull rate limits and don't require authentication.
## Summary
- Adds `search` and `searches` to the `RESERVED_METADATA_NAME_KEYWORDS`
list in `twenty-shared`
- Prevents users from creating custom objects named "search", which
collides with the core `search` GraphQL resolver
## Summary
- Adds a new `upgrade:1-21:backfill-datasource-to-workspace` command
that copies `dataSource.schema` into `workspace.databaseSchema` for all
active/suspended workspaces that haven't been migrated yet
- Registers the command in the 1-21 upgrade module and wires it into the
upgrade runner (runs before other 1-21 commands)
- Part of the ongoing deprecation of the `dataSource` table in favor of
storing `databaseSchema` directly on `WorkspaceEntity`
This PR completes the sections we need for the Homepage. Assets, such as
images, are still placeholder, and will be replaced as they become
available. We're still waiting on Lottie and 3d asset files from the
design team.
Database batch events (update / insert / soft-delete / hard-delete) were
building recordsBefore / recordsAfter after formatResult ran twice: once
inside WorkspaceSelectQueryBuilder.getMany() / getOne(), and again in
the CUD query builders. On the second pass, already-shaped composite
fields (e.g. emails) went through formatFieldMetadataValue and kept the
same object references as the live TypeORM row, so recordsBefore could
change when the entity was updated—breaking workflow triggers and diffs.
Fixes: #18943
Follow-up pr: #19001
## Summary:
- Timeline activity shows file upload history, but deleted files had no
signed URL and were still rendered as clickable — clicking did nothing
- grab the fileId from properties.diff.after, look it up in the current
record's files field: if present, use live signed URL; if absent, mark
as deleted
- Deleted file chips show line-through label, not-allowed cursor, and
"File no longer exists" tooltip on hover
https://github.com/user-attachments/assets/5df6a675-0003-4fd1-ad57-a07e4338923f
---------
Co-authored-by: Etienne <[email protected]>
## Context
Checked in the codebase where we are trying to rollback a non-active
transaction.
packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts
seemed to be the only place where it happens.
We could enforce this with a lint rule in the future 🤔
## Overview
Add resumable stream support for agent chat to allow clients to
reconnect and resume streaming responses if the connection is
interrupted (e.g., during page refresh).
## Changes
### Backend (Twenty Server)
- Add `activeStreamId` column to `AgentChatThreadEntity` to track
ongoing streams
- Create `AgentChatResumableStreamService` to manage Redis-backed
resumable streams using the `resumable-stream` library with ioredis
- Extend `AgentChatController` with:
- `GET /:threadId/stream` endpoint to resume an existing stream
- `DELETE /:threadId/stream` endpoint to stop an active stream
- Update `AgentChatStreamingService` to store streams in Redis and track
active stream IDs
- Add `resumable-stream@^2.2.12` dependency to package.json
### Frontend (Twenty Front)
- Update `useAgentChat` hook to:
- Use a persistent transport with `prepareReconnectToStreamRequest` for
resumable streams
- Export `resumeStream` function from useChat
- Add `handleStop` callback to clear active stream on DELETE endpoint
- Use thread ID as stable message ID instead of including message count
- Add stream resumption logic in `AgentChatAiSdkStreamEffect` component
to automatically call `resumeStream()` when switching threads
## Database Migration
New migration `1774003611071-add-active-stream-id-to-agent-chat-thread`
adds the `activeStreamId` column to store the current resumable stream
identifier.
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
#### Direct Execution __typename & null backfill
##### __typename filling
The direct execution path now correctly derives __typename at every
level of the GraphQL response:
Connection types — CompanyConnection, CompanyEdge, Company, PageInfo
GroupBy types — TaskGroupByConnection (was incorrectly producing
TaskConnection)
Composite fields — Links, FullName, Currency, etc. handled by a
dedicated formatter (was inheriting the parent object's typename)
Previously, __typename was derived from the object's universal
identifier (a UUID), producing broken values like
20202020B3744779A56180086Cb2E17FConnection.
##### Null backfill
Selected fields missing from the resolver result are backfilled with
null, matching the standard Yoga schema behavior.
##### Integration test
A new test runs the same findMany query (with __typename at all
structural levels) through both paths — standard Yoga schema and direct
execution — and asserts identical output via toStrictEqual.
# Introduction
Improved the ci assertions to be sure the default logic function worked
fully
## what's next
About to introduce a s3 + lambda e2e test to cover the lambda driver too
in addition of the local driver
## Summary
- Deletes the entire `1-19` upgrade version command directory (7 files,
~1500 lines) and removes all references from the upgrade module and
command runner.
- Removes `IS_NAVIGATION_MENU_ITEM_ENABLED` and
`IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED` feature flags from the enum,
seed data, generated schema files, and test mocks. These flags had no
remaining feature-gated code — navigation menu items are now always
enabled.
## Summary
- Fix stale `ObjectMetadataItems` GraphQL response cache after field
creation by using `request.workspaceMetadataVersion` (sourced from
Redis) instead of `workspace.metadataVersion` (from the potentially
stale CoreEntityCacheService)
- Make the E2E kanban view test selector more robust with a regex match
## Root Cause
The `useCachedMetadata` GraphQL plugin keys cached responses using
`workspace.metadataVersion` from the `CoreEntityCacheService`. When a
field is created:
1. The migration runner increments `metadataVersion` in DB and Redis
2. But the `CoreEntityCacheService` for `WorkspaceEntity` is **not**
invalidated
3. So `request.workspace.metadataVersion` still has the old version
4. The cache key resolves to the old cached response
5. The frontend gets stale metadata without the newly created field
This breaks E2E tests (and likely affects users) - after creating a
custom field, the metadata isn't visible until the workspace entity
cache refreshes.
## Fix
Use `request.workspaceMetadataVersion` (populated from Redis by the
middleware, always up-to-date) as the primary version for cache keys,
falling back to the entity cache version.
## Test plan
- [ ] E2E `create-kanban-view` tests should pass (creating a Select
field and immediately using it in a Kanban view)
- [ ] Verify `ObjectMetadataItems` returns fresh data after field
creation (no stale cache)
Made with [Cursor](https://cursor.com)
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).
This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.
**Please review before merging** — verify no critical models were
incorrectly deprecated.
Co-authored-by: FelixMalfait <[email protected]>
## Summary
- Removes `IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED` feature flag,
making row-level permission predicates always enabled. Removes
early-return guards from query builders (select, update, insert) and the
shared utility, the public feature flag metadata entry, and
`updateFeatureFlag` calls from integration tests.
- Removes `IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED` feature flag, making
whole-day datetime filtering always enabled. Simplifies filter input
components and hooks to always use date-only format for `IS` operand on
`DATE_TIME` fields.
- Cleans up enum definitions, seed data, generated schema files, and
test mocks for both flags.
## Summary
- Remove the `IS_APPLICATION_ENABLED` feature flag — application
features are now always enabled
- Remove `@RequireFeatureFlag` decorators and `FeatureFlagGuard` from 7
application resolvers (registration, development, manifest, install,
upgrade, marketplace, oauth)
- Remove frontend feature flag checks: settings navigation visibility,
route protection wrapper, side panel widget type gating, and role
permission flag filtering
- Delete the integration test for disabled-flag behavior
- Clean up seed data, test mocks, and generated schema files
## Summary
- Remove the `IS_DASHBOARD_V2_ENABLED` feature flag from the codebase
- Dashboard V2 features (gauge charts, line charts, pie charts) are now
always enabled
- Remove the validator gate that blocked gauge chart creation/update
without the flag
- Clean up all related code: seed data, dev-seeder service, widget
seeds, and test mocks
## Summary
- Remove 3 completed migration feature flags: `IS_ATTACHMENT_MIGRATED`,
`IS_NOTE_TARGET_MIGRATED`, `IS_TASK_TARGET_MIGRATED` — these were
already enabled by default for all new workspaces via
`DEFAULT_FEATURE_FLAGS`
- Delete all upgrade command directories for versions <= 1.18 (`1-16/`,
`1-17/`, `1-18/`) along with their module registrations, removing ~6,600
lines of dead migration code
- Simplify frontend utility functions
(`getActivityTargetObjectFieldIdName`, `getActivityTargetsFilter`,
`getActivityTargetFieldNameForObject`,
`generateActivityTargetMorphFieldKeys`,
`findActivitiesOperationSignatureFactory`) by removing the
`isMorphRelation` parameter and always using the morph relation path
- Remove feature flag checks from 7 frontend hooks/components that were
gating attachment and activity target behavior behind the removed flags
- Simplify `buildDefaultRelationFlatFieldMetadatasForCustomObject`
server util to always treat attachment, noteTarget, and taskTarget as
morph relations without checking feature flags
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).
This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.
**Please review before merging** — verify no critical models were
incorrectly deprecated.
Co-authored-by: FelixMalfait <[email protected]>
## Summary
- **Fix settings/usage page crash**: The `GraphWidgetLineChart`
component used on `settings/usage` was crashing with "Instance id is not
provided and cannot be found in context" because it requires
`WidgetComponentInstanceContext` (for tooltip/crosshair component
states) which is only provided inside the widget system. Wraps the
standalone chart usages with the required context provider.
- **Avoid mounting `GraphWidgetLegend` when hidden**: The legend
component calls `useIsPageLayoutInEditMode()` which requires
`PageLayoutEditModeProviderContext` — another context only available
inside the widget system. Since the settings page passes
`showLegend={false}`, the fix conditionally unmounts the legend instead
of always mounting it with a `show` prop. Applied consistently across
all four chart types (line, bar, pie, gauge).
- **Add ClickHouse usage event seeds**: Generates ~400 realistic
`usageEvent` rows spanning the past 35 days with weighted user activity,
weekday/weekend patterns, and gradual ramp-up. Enables developers to see
the usage analytics page with data locally.
## Test plan
- [ ] Navigate to `settings/usage` — page should render without errors
- [ ] Verify the daily usage line chart displays correctly
- [ ] Navigate to a user detail page from the usage list
- [ ] Verify the user detail chart renders without errors
- [ ] Run `npx nx clickhouse:seed twenty-server` and confirm usage
events are seeded
- [ ] Verify chart legend still works correctly on dashboard widgets (no
regression)
Made with [Cursor](https://cursor.com)
echo "::error::Unexpected migration files were generated. Please create a proper migration manually."
echo "::error::Unexpected migration files were generated. Please run 'npx nx database:migrate:generate twenty-server -- --name <migration-name>' and commit the result."
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zero‑config project bootstrap
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- Docker (for the local Twenty dev server)
The official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). Sets up a ready-to-run project with [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
## Quick start
```bash
# Scaffold a new app — the CLI will offer to start a local Twenty server
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
**Example files (controlled by scaffolding mode):**
-`objects/example-object.ts` — Example custom object with a text field
-`fields/example-field.ts` — Example standalone field extending the example object
-`logic-functions/hello-world.ts` — Example logic function with HTTP trigger
-`front-components/hello-world.tsx` — Example front component
-`views/example-view.ts` — Example saved view for the example object
-`navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
-`skills/example-skill.ts` — Example AI agent skill definition
-`__tests__/app-install.integration-test.ts` — Integration test that builds, installs, and verifies the app (includes `vitest.config.ts`, `tsconfig.spec.json`, and a setup file)
## Local server
The scaffolder can start a local Twenty dev server for you (all-in-one Docker image with PostgreSQL, Redis, server, and worker). You can also manage it manually:
```bash
yarn twenty server start # Start (pulls image if needed)
yarn twenty server status # Check if it's healthy
yarn twenty server logs # Stream logs
yarn twenty server stop # Stop (data is preserved)
yarn twenty server reset # Wipe all data and start fresh
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty remote add <url>` to authenticate with your Twenty workspace via OAuth.
- Explore the generated project and add your first entity with `yarn twenty add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
-`CoreApiClient` is auto-generated by `yarn twenty dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient } from 'twenty-client-sdk/core'` and `import { MetadataApiClient } from 'twenty-client-sdk/metadata'`.
## Build and publish your application
Once your app is ready, build and publish it using the CLI:
```bash
# Build the app (output goes to .twenty/output/)
yarn twenty build
# Build and create a tarball (.tgz) for distribution
yarn twenty build --tarball
# Publish to npm (requires npm login)
yarn twenty publish
# Publish with a dist-tag (e.g. beta, next)
yarn twenty publish --tag beta
# Deploy directly to a Twenty server (builds, uploads, and installs in one step)
yarn twenty deploy
```
### Publish to the Twenty marketplace
You can also contribute your application to the curated marketplace:
```bash
git clone https://github.com/twentyhq/twenty.git
cd twenty
git checkout -b feature/my-awesome-app
```
- Copy your app folder into `twenty/packages/twenty-apps`.
- Commit your changes and open a pull request on https://github.com/twentyhq/twenty
Our team reviews contributions for quality, security, and reusability before merging.
- [Getting Started](https://docs.twenty.com/developers/extend/apps/getting-started) — step-by-step setup, project structure, server management, CI
- [Building Apps](https://docs.twenty.com/developers/extend/apps/building) — entity definitions, API clients, testing
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
- Creating an object without an index view associated. Unless this is a technical object, user will need to visualize it.
- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.
- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.