## Summary
Uniformizes the metadata store to support **all** backend flat metadata
types, introduces a **minimal metadata endpoint** for fast initial
renders, replaces custom localStorage persistence with **Jotai's
built-in `atomWithStorage`**, and wires up a
**MinimalMetadataLoadEffect** for stale-while-revalidate loading.
### Key changes
- **All flat metadata types**: Added `FlatCommandMenuItem`,
`FlatFrontComponent`, `FlatWebhook`, `FlatRole`, `FlatRoleTarget`,
`FlatAgent`, `FlatSkill`, `FlatRowLevelPermissionPredicate`,
`FlatRowLevelPermissionPredicateGroup` — every entity in the backend
`MetadataEntityTypeMap` now has a corresponding frontend flat type
registered in `ALL_METADATA_ENTITY_KEYS` and `MetadataEntityTypeMap`.
- **Minimal metadata endpoint** (`minimalMetadata` GraphQL query): New
backend module (`MinimalMetadataModule`) returns lightweight object
metadata (names, icons, labels, flags) and basic views (id, type, key,
objectMetadataId) plus a `metadataVersion`. This enables fast first
paint before full metadata loads.
- **Jotai `atomWithStorage` for persistence**: Replaced the custom
`MetadataLocalStorageEffect` with Jotai's built-in `atomWithStorage` on
both `metadataStoreState` (family) and `metadataVersionState`. Added
`localStorageOptions` support to `createAtomFamilyState` for `{
getOnInit: true }` synchronous hydration. Each entity atom auto-persists
under keys like `metadataStoreState__objectMetadataItems`.
- **MinimalMetadataLoadEffect**: New effect mounted before
`MetadataProviderInitialEffects` that checks if the store already has
data (from Jotai localStorage hydration). If empty, it fetches minimal
metadata from the new endpoint. The full metadata load continues in
parallel, eventually enriching the store with complete data.
- **SSE effects alignment**: All metadata entity types now have
corresponding SSE effects that directly patch the metadata store via
`patchMetadataStoreFromSSEEvent`.
- **Existing selectors and joining logic**:
`objectMetadataItemsWithFieldsSelector`, `viewsWithRelationsSelector`,
`pageLayoutsWithRelationsSelector` reconstruct nested data from flat
entities for components that need it.
### Loading flow
```
App mount
→ Jotai atomWithStorage hydrates store from localStorage (sync, getOnInit)
→ MinimalMetadataLoadEffect
→ Store has data? → skip (app renders immediately)
→ Store empty? → fetch minimalMetadata endpoint → populate objects + views
→ MetadataProviderInitialEffects (full metadata load, runs in parallel)
→ LazyMetadataLoadEffect (page layouts, logic functions, nav menu, etc.)
→ IsAppMetadataReadyEffect (sets isAppMetadataReady)
```
## Test plan
- [ ] Verify app loads with empty localStorage (should fetch minimal
metadata, then full)
- [ ] Verify app loads with populated localStorage (should skip minimal
fetch, render immediately)
- [ ] Verify SSE events correctly update metadata store for all entity
types
- [ ] Verify logout clears metadata store (atom reset propagates to
localStorage)
- [ ] Verify all metadata selectors return correct joined data
- [ ] CI: lint, typecheck, tests pass
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>