## Summary - Adds a `color` column to `ObjectMetadataEntity` with full GraphQL support so object icon colors are persisted at the metadata level - Adds a `type` column to `NavigationMenuItemEntity` (enum: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD`) replacing field-based type inference - Updates frontend to read object colors from `objectMetadata.color` (falling back to standard defaults) in the sidebar nav, record index header, and record show breadcrumb - Simplifies `NavigationMenuItemIcon` color resolution via `getEffectiveNavigationMenuItemColor` util ## Color rules | Item type | Color source | Editable in sidebar? | |-----------|-------------|---------------------| | **Object** | `objectMetadata.color` | Yes — persisted to `objectMetadata.color` on Save | | **Folder** | `navigationMenuItem.color` | Yes | | **Link** | Fixed default (`DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK`) | No | | **View** | `objectMetadata.color` (from the parent object) | No | | **Record** | None | No | - **Object** items represent the whole object (e.g. "Companies") and point to the INDEX view. Changing their color updates `objectMetadata.color` via `useSaveObjectMetadataColorsFromDraft`. - **View** items represent specific non-INDEX views. Their color comes from the parent object's metadata (read-only). - Only **folders** store their color on `navigationMenuItem.color` — enforced by `hasNavigationMenuItemOwnColor` util. - `getEffectiveNavigationMenuItemColor` returns `objectColor` for both OBJECT and VIEW items, folder's own color for folders, and the fixed default for links. ## NavigationMenuItemType enum - Shared enum created in `twenty-shared` with values: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD` - Registered as a GraphQL enum on the backend - Replaces string literals across entity, DTOs, input, converters, and frontend hooks - Migration backfills existing rows: INDEX views → `OBJECT`, non-INDEX views → `VIEW`, based on join with the view table ## Design decisions - **OBJECT vs VIEW distinction**: Items pointing to INDEX views are typed as `OBJECT` (represent the whole object, color editable). Items pointing to non-INDEX views are typed as `VIEW` (specific view, color read-only from parent object). - **Dual color storage**: `navigationMenuItem.color` is preserved for folders only. Objects use `objectMetadata.color` as their source of truth. - **Type discriminator**: The `type` column replaces field-based inference (checking `viewId`, `link`, `targetRecordId` presence) with an explicit enum, simplifying `isNavigationMenuItemLink` / `isNavigationMenuItemFolder` to simple `item.type ===` checks. - **No settings page color picker**: Object color editing is done from the sidebar edit panel, not the data model settings page. ## Test plan - [ ] Verify objects display their default standard colors in the sidebar - [ ] Verify object color editing works in the sidebar edit panel (persists to objectMetadata.color) - [ ] Verify folder color editing works in the sidebar edit panel - [ ] Verify views, links, and records do NOT show a color picker in the sidebar edit panel - [ ] Run `npx nx typecheck twenty-front` and `npx nx typecheck twenty-server` - [ ] Verify the database migrations add `color` to `objectMetadata` and `type` to `navigationMenuItem` Made with [Cursor](https://cursor.com)
A CLI and SDK to develop, build, and publish applications that extend Twenty CRM.
- Typed GraphQL clients:
CoreApiClient(auto-generated per app for workspace data) andMetadataApiClient(pre-built with the SDK for workspace configuration & file uploads) - Built‑in CLI for auth, dev mode (watch & sync), uninstall, and function management
- Works great with the scaffolder: create-twenty-app
Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key. Generate one at https://app.twenty.com/settings/api-webhooks
Installation
npm install twenty-sdk
# or
yarn add twenty-sdk
Usage
Usage: twenty [options] [command]
CLI for Twenty application development
Options:
--workspace <name> Use a specific workspace configuration (default: "default")
-V, --version output the version number
-h, --help display help for command
Commands:
auth:login Authenticate with Twenty
auth:logout Remove authentication credentials
auth:status Check authentication status
auth:switch Switch the default workspace
auth:list List all configured workspaces
app:dev Watch and sync local application changes
app:build Build, sync, and generate API client
app:publish Build and publish to npm or a Twenty server
app:typecheck Run TypeScript type checking on the application
app:uninstall Uninstall application from Twenty
entity:add Add a new entity to your application
function:logs Watch application function logs
function:execute Execute a logic function with a JSON payload
help [command] display help for command
In a scaffolded project (via create-twenty-app), use yarn twenty <command> instead of calling twenty directly. For example: yarn twenty help, yarn twenty app:dev, etc.
Global Options
--workspace <name>: Use a specific workspace configuration profile. Defaults todefault. See Configuration for details.
Commands
Auth
Authenticate the CLI against your Twenty workspace.
-
twenty auth:login— Authenticate with Twenty.- Options:
--api-key <key>: API key for authentication.--api-url <url>: Twenty API URL (defaults to your current profile's value orhttp://localhost:3000).
- Behavior: Prompts for any missing values, persists them to the active workspace profile, and validates the credentials.
- Options:
-
twenty auth:logout— Remove authentication credentials for the active workspace profile. -
twenty auth:status— Print the current authentication status (API URL, masked API key, validity). -
twenty auth:list— List all configured workspaces.- Behavior: Displays all available workspaces with their authentication status and API URLs. Shows which workspace is the current default.
-
twenty auth:switch [workspace]— Switch the default workspace for authentication.- Arguments:
workspace(optional): Name of the workspace to switch to. If omitted, shows an interactive selection.
- Behavior: Sets the specified workspace as the default, so subsequent commands use it without needing
--workspace.
- Arguments:
Examples:
# Login interactively (recommended)
twenty auth:login
# Provide values in flags
twenty auth:login --api-key $TWENTY_API_KEY --api-url https://api.twenty.com
# Login interactively for a specific workspace profile
twenty auth:login --workspace my-custom-workspace
# Check status
twenty auth:status
# Logout current profile
twenty auth:logout
# List all configured workspaces
twenty auth:list
# Switch default workspace interactively
twenty auth:switch
# Switch to a specific workspace
twenty auth:switch production
App
Application development commands.
-
twenty app:dev [appPath]— Start development mode: watch and sync local application changes.- Behavior: Builds your application (functions and front components), computes the manifest, syncs everything to your workspace, then watches the directory for changes and re-syncs automatically. Displays an interactive UI showing build and sync status in real time. Press Ctrl+C to stop.
-
twenty app:build [appPath]— Build the application, sync to the server, generate the typed API client, then rebuild with the real client.- Options:
--tarball: Also pack the output into a.tgztarball.
- Options:
-
twenty app:publish [appPath]— Build and publish the application.- Default (no flags): builds and runs
npm publishon the output directory. - Options:
--server <url>: Publish to a Twenty server instead of npm (builds tarball, uploads, and installs).--token <token>: Auth token for the server.--tag <tag>: npm dist-tag (e.g.beta,next).
- Default (no flags): builds and runs
-
twenty app:typecheck [appPath]— Run TypeScript type checking on the application (runstsc --noEmit). Exits with code 1 if type errors are found. -
twenty app:uninstall [appPath]— Uninstall the application from the current workspace.
Entity
twenty entity:add [entityType]— Add a new entity to your application.- Arguments:
entityType: one ofobject,field,function,front-component,role,view,navigation-menu-item, orskill. If omitted, an interactive prompt is shown.
- Options:
--path <path>: The path where the entity file should be created (relative to the current directory).
- Behavior:
object: prompts for singular/plural names and labels, then creates a*.object.tsdefinition file.field: prompts for name, label, type, and target object, then creates a*.field.tsdefinition file.function: prompts for a name and scaffolds a*.function.tslogic function file.front-component: prompts for a name and scaffolds a*.front-component.tsxfile.role: prompts for a name and scaffolds a*.role.tsrole definition file.view: prompts for a name and target object, then creates a*.view.tsdefinition file.navigation-menu-item: prompts for a name and scaffolds a*.navigation-menu-item.tsfile.skill: prompts for a name and scaffolds a*.skill.tsskill definition file.
- Arguments:
Function
-
twenty function:logs [appPath]— Stream application function logs.- Options:
-u, --functionUniversalIdentifier <id>: Only show logs for a specific function universal ID.-n, --functionName <name>: Only show logs for a specific function name.
- Options:
-
twenty function:execute [appPath]— Execute a logic function with a JSON payload.- Options:
--preInstall: Execute the pre-install logic function defined in the application manifest (required if--postInstall,-n, and-unot provided).--postInstall: Execute the post-install logic function defined in the application manifest (required if--preInstall,-n, and-unot provided).-n, --functionName <name>: Name of the function to execute (required if--postInstalland-unot provided).-u, --functionUniversalIdentifier <id>: Universal ID of the function to execute (required if--postInstalland-nnot provided).-p, --payload <payload>: JSON payload to send to the function (default:{}).
- Options:
Examples:
# Start dev mode (watch, build, and sync)
twenty app:dev
# Start dev mode with a custom workspace profile
twenty app:dev --workspace my-custom-workspace
# Type check the application
twenty app:typecheck
# Add a new entity interactively
twenty entity:add
# Add a new function
twenty entity:add function
# Add a new front component
twenty entity:add front-component
# Add a new view
twenty entity:add view
# Add a new navigation menu item
twenty entity:add navigation-menu-item
# Add a new skill
twenty entity:add skill
# Build the app (output in .twenty/output/)
twenty app:build
# Build and create a tarball
twenty app:build --tarball
# Publish to npm
twenty app:publish
# Publish with a dist-tag
twenty app:publish --tag beta
# Publish directly to a Twenty server (builds, uploads, and installs)
twenty app:publish --server https://app.twenty.com
# Uninstall the app from the workspace
twenty app:uninstall
# Watch all function logs
twenty function:logs
# Watch logs for a specific function by name
twenty function:logs -n my-function
# Execute a function by name (with empty payload)
twenty function:execute -n my-function
# Execute a function with a JSON payload
twenty function:execute -n my-function -p '{"name": "test"}'
# Execute a function by universal identifier
twenty function:execute -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf -p '{"key": "value"}'
# Execute the pre-install function
twenty function:execute --preInstall
# Execute the post-install function
twenty function:execute --postInstall
Configuration
The CLI stores configuration per user in a JSON file:
- Location:
~/.twenty/config.json - Structure: Profiles keyed by workspace name. The active profile is selected with
--workspace <name>or by thedefaultWorkspacesetting.
Example configuration file:
{
"defaultWorkspace": "prod",
"profiles": {
"default": {
"apiUrl": "http://localhost:3000",
"apiKey": "<your-api-key>"
},
"prod": {
"apiUrl": "https://api.twenty.com",
"apiKey": "<your-api-key>"
}
}
}
Notes:
- If a profile is missing,
apiUrldefaults tohttp://localhost:3000until set. twenty auth:loginwrites theapiUrlandapiKeyfor the active workspace profile.twenty auth:login --workspace custom-workspacewrites theapiUrlandapiKeyfor a customcustom-workspaceprofile.twenty auth:switchsets thedefaultWorkspacefield, which is used when--workspaceis not specified.twenty auth:listshows all configured workspaces and their authentication status.
Troubleshooting
- Auth errors: run
twenty auth:loginagain and ensure the API key has the required permissions. - Typings out of date: restart
twenty app:devto refresh the client and types. - Not seeing changes in dev: make sure dev mode is running (
twenty app:dev).
Contributing
Development Setup
To contribute to the twenty-sdk package, clone the repository and install dependencies:
git clone https://github.com/twentyhq/twenty.git
cd twenty
yarn install
Development Mode
Run the SDK build in watch mode to automatically rebuild on file changes:
npx nx run twenty-sdk:dev
This will watch for changes and rebuild the dist folder automatically.
Production Build
Build the SDK for production:
npx nx run twenty-sdk:build
Running the CLI Locally
After building, you can run the CLI directly:
npx nx run twenty-sdk:start -- <command>
# Example: npx nx run twenty-sdk:start -- auth:status
Or run the built CLI directly:
node packages/twenty-sdk/dist/cli.cjs <command>