Fixes #23339 Follow-up to #23410, which fixed the neighbouring issue (#23341) for users *without* the `DATA_MODEL` permission. ## Problem In #23339 the reporter added a multiselect field to Person and then found the inline "create option" prompt unresponsive. They clearly have `DATA_MODEL` permission, since they just created the field, so the permission check isn't what's blocking them. The blocker is the *other* precondition. Both hooks read the object name from the router: ```ts const { objectNamePlural } = useParams(); ``` `objectNamePlural` only exists on record index routes (`/objects/:objectNamePlural`). Record **detail** pages are `/object/:objectNameSingular/:objectRecordId`, so on a record page the param is `undefined` and: - `useCanAddSelectOption` returns false via `isNonEmptyString(objectNamePlural)` - `useAddSelectOption` bails out at `if (!fieldName || !objectNamePlural) return;` So the action was dead on record pages for **every** user, admins included, and the navigation target it needed was never reachable from there. ## Fix Resolve the field and its object from `fieldMetadataId`, which `FieldDefinition` already carries, instead of reading the object name off the URL: ```ts const { fieldMetadataItem, objectMetadataItem } = useFieldMetadataItemById(fieldMetadataId); ``` This drops the route dependency entirely, so the action behaves the same wherever the field is rendered, and `canAddSelectOption` now reflects only the real permission check. It also lets both hooks take a single `fieldMetadataId` argument: `fieldMetadataItem.name` is the same value the callers were previously passing as `fieldName`, so that parameter is no longer needed. Resolving both values from one id means the guard and the action can't disagree about which field they're describing. `useFieldMetadataItemById` is used rather than `useFieldMetadataItemByIdOrThrow` because a lookup miss should disable the prompt, not crash the field input. ## Reproduction On the code the reporter was running (immediately before #23410), as an **admin** with full `DATA_MODEL`, on a company record page, typing a value matching no option: - `Add "…" to options` renders - clicking it does nothing — URL unchanged, no navigation - pressing <kbd>Enter</kbd> does nothing either which matches #23339 exactly, including the note about the Enter keypress. After #23410 the same root cause shows up differently: the prompt is no longer rendered at all on record pages, since the guard it's now gated on is false there. Still broken, just silent. ## Testing Verified manually on a local instance, swapping only these files between three states and re-running the identical steps on the same cell. | code state | user | route | result | |---|---|---|---| | before #23410 | Admin | `/object/company/:id` | prompt shown, click and Enter both do nothing | | current main | Admin | `/object/company/:id` | prompt not shown, action unreachable | | **this PR** | Admin | `/object/company/:id` | prompt shown, navigates to `/settings/objects/companies/workPolicy?newOption=…` | | **this PR** | Admin | `/objects/tasks` (`Status`, single select) | still works, navigates to `/settings/objects/tasks/status?newOption=…` | | **this PR** | Member (no `DATA_MODEL`) | `/object/company/:id` | prompt not shown | The settings form opens with the typed value prefilled alongside the existing options, so the end-to-end flow works from a record page for the first time. The Member row confirms #23341 stays fixed: dropping the route dependency doesn't weaken the permission gate. The single-select row covers `SelectFieldInput`, which takes the same change. `nx lint:diff-with-main twenty-front` and `nx typecheck twenty-front` both pass.
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty app:publish --private
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





