## Context Record pages can show a list of directly related records (Field widget in Table display mode), but not records two relation hops away. The canonical ask: on a Client page, list the Transactions of the Client's Wallets. Stacked on #23814 (merged) and #23832 (merged); their commits are included in this branch. #23836 stacks on this PR to add many-to-one first hops. ## How it works The 2-hop case does not need any new query capability. It reuses the relation traversal filter shipped for advanced filters: the widget embeds a view on the terminal object (Transaction) with one seeded filter `inverse relation IS current record`, traversed one hop (`fieldMetadataId` = Transaction.wallet, `relationTargetFieldMetadataId` = Wallet.client, value `isCurrentRecordSelected`). At query time this compiles to `{ wallet: { clientId: { in: [currentRecordId] } } }`, which is within the backend's `MAX_RELATION_FILTER_DEPTH = 1` since the second hop lands on the join column. Records from all intermediate records (all wallets of the client) are listed, so one-to-many fan-out on the first hop works out of the box. ## Changes Configuration - `FieldConfiguration` gains an optional `nestedRelationFieldMetadataId` (shared type, DTO, GraphQL fragment, regenerated metadata types). Backward compatible: existing widgets are untouched. UI - The Field picker drills into one-to-many relation fields, mirroring the advanced filter submenu pattern: back header, an entry to select the relation itself (previous behavior), then the target object's one-to-many relations. Selecting a nested field creates a widget titled `First hop → Second hop` in Table display mode. First-level rows that open a submenu never show the checkmark; the selected chain is only visible inside the submenu, matching the chart group by field selection. - The layout dropdown, settings panel and renderer resolve the terminal object of the chain; a widget whose second hop was deleted or deactivated renders nothing instead of silently showing first-hop records. - Nested widgets only offer embedded view layouts (Table / Kanban / Calendar), since inline display modes would render the first hop's relation field. - The relation table view resolver regenerates the embedded view whenever the selection results in a table widget and the chain changed or the view id is missing, so a table widget can never carry a view belonging to a different chain. Server - `FieldConfigurationDTO` accepts the new optional field. - Both universal configuration mappers (to and from universal identifiers) carry it for app manifest sync. - New `validateFieldConfigurationNestedRelationOrThrow` enforces that both hops are active one-to-many relation fields on the right objects, wired next to the existing chart field reference validation. Record creation - `buildRecordInputFromFilter` skips relation-traversal filters: they constrain a related record's column, so prefilling the created record's own foreign key from them would link the wrong record (e.g. `walletId = clientId`). - Add New in a nested widget table instead prompts for the record to create through: the row opens a picker listing the current record's first-hop records (the client's wallets), scoped with a find filter on the relation join column, and creates the record with the picked id prefilled. Covers the plain table and per-group add rows. Board and calendar layouts hide their create buttons in nested widgets since they cannot know the record to create through. - Matching the created record against the widget's traversal filter client side is handled by #23832. Out of scope, deliberately: depth stays at exactly two levels (matches the backend filter depth cap), junction and morph relations are not drillable, and chart widgets on record pages are untouched. ## Tests - Unit: nested chain resolution util, draft view seeding with the traversal filter, view id change resolver, picker parameter derivation, server-side validation. Full `page-layout` and `record-filter` front suites pass (187 suites / 1233 tests), server `page-layout-widget` suites pass. - Manual, on seeded data: created a `People → Opportunities` widget on a Company page; it lists exactly the opportunities whose point of contact belongs to that company, persists across save and reload, and scopes per record. Add New opens a picker showing only that company's people; picking one creates an opportunity with `pointOfContactId` set (verified in DB) and the row appears in the widget immediately. --- _Generated by [Claude Code](https://claude.ai/code/session_01Xp3AgGtc4kSP8PpgpKMWLQ)_ <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23815?utm_source=github" rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
Twenty Documentation
Official documentation for Twenty CRM, powered by Mintlify.
🌐 Live Site
Visit the documentation at docs.twenty.com
📚 Content
This repository contains:
- User Guide (46 pages) - Complete guide for Twenty users
- Developers (24 pages) - Technical documentation for developers
- Twenty UI (25 pages) - UI component library documentation
🚀 Local Development
To run the documentation locally:
# From the twenty monorepo root
npx nx run twenty-docs:dev
The documentation will be available at http://localhost:3000
📝 Editing Content
Adding/Editing Pages
-
Edit MDX files in the appropriate directory:
user-guide/- User documentationdevelopers/- Developer documentationtwenty-ui/- Component documentation
-
Update
navigation/base-structure.jsonif you need to change the tab/group hierarchy or add/remove pages. This file stays in the repo and is not uploaded to Crowdin. -
Keep the translation template (
navigation/navigation.template.json) in sync by runningyarn docs:generate-navigation-templateafter editing the base structure. This template is the only file that should be pushed to Crowdin. -
For each translated locale pulled from Crowdin, ensure a
packages/twenty-docs/l/<language>/navigation.jsonfile exists. These files contain labels only; page slugs always come from the base structure. -
Run
yarn docs:generateto rebuilddocs.jsonfrom the base structure + translated labels.
MDX Format
All documentation pages use MDX format with frontmatter:
---
title: Page Title
description: Page description
image: /images/path/to/image.png
---
Your content here...
Adding Images
- Place images in the
/images/directory - Reference them in MDX:
 - Or use Mintlify Frame component:
<Frame>
<img src="/images/your-image.png" alt="Description" />
</Frame>
🔧 Configuration
navigation/base-structure.json- Source of truth for tabs, groups, icons, and page slugs (English only, not sent to Crowdin).navigation/navigation.template.json- Generated translation template (labels only) that is uploaded to Crowdin.l/<language>/navigation.json- Locale-specific label files pulled from Crowdin.docs.json- Generated Mintlify configuration (always runyarn docs:generateafter modifying navigation files).package.json- Package dependencies and scripts (docs:generate,docs:generate-navigation-template, …).project.json- Nx workspace configuration
📦 Validation
# Validate the documentation build
npx nx run twenty-docs:validate
🔗 Links
🤝 Contributing
To contribute to the documentation:
- Fork the repository
- Make your changes in the
packages/twenty-docsdirectory - Test locally with
npx nx run twenty-docs:dev - Submit a pull request
📄 License
This documentation is part of the Twenty project and is licensed under AGPL-3.0.