Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 686b046c2a fix: pass instanceId or guard against missing context in CreateNewIndexRecordNoSelectionRecordCommand
https://sonarly.com/issue/6956?type=bug

`CreateNewIndexRecordNoSelectionRecordCommand` renders on dashboard show pages without a `ViewComponentInstanceContext` provider, causing `useCreateNewIndexRecord` to throw because neither `instanceId` prop nor context value is available for the `recordGroupDefinitionsComponentSelector`.
2026-03-25 22:27:42 +00:00
2024 changed files with 80638 additions and 97847 deletions
+11 -27
View File
@@ -35,10 +35,12 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
@@ -75,8 +77,6 @@ jobs:
run: |
echo "Attempting to merge main into current branch..."
git config user.email "ci@twenty.com"
git config user.name "CI"
git fetch origin main
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
@@ -90,8 +90,8 @@ jobs:
echo "❌ Merge failed due to conflicts"
echo "⚠️ Falling back to comparing current branch against main without merge"
# Abort the failed merge (may not exist if merge never started)
git merge --abort 2>/dev/null || true
# Abort the failed merge
git merge --abort
echo "merged=false" >> $GITHUB_OUTPUT
echo "BRANCH_STATE=conflicts" >> $GITHUB_ENV
@@ -143,6 +143,7 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed current branch database with test data
run: |
@@ -295,6 +296,7 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed main branch database with test data
run: |
@@ -393,30 +395,12 @@ jobs:
# Clean up temp directory
rm -rf /tmp/current-branch-files
- name: Validate downloaded schema files
id: validate-schemas
run: |
valid=true
for file in main-schema-introspection.json current-schema-introspection.json \
main-metadata-schema-introspection.json current-metadata-schema-introspection.json \
main-rest-api.json current-rest-api.json \
main-rest-metadata-api.json current-rest-metadata-api.json; do
if [ ! -f "$file" ] || ! jq empty "$file" 2>/dev/null; then
echo "::warning::Invalid or missing schema file: $file"
valid=false
fi
done
echo "valid=$valid" >> $GITHUB_OUTPUT
- name: Install OpenAPI Diff Tool
run: |
# Using the Java-based OpenAPITools/openapi-diff via Docker
echo "Using OpenAPITools/openapi-diff via Docker"
- name: Generate GraphQL Schema Diff Reports
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== INSTALLING GRAPHQL INSPECTOR CLI ==="
npm install -g @graphql-inspector/cli
@@ -427,6 +411,7 @@ jobs:
echo "Checking GraphQL schema for changes..."
if graphql-inspector diff main-schema-introspection.json current-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL schema"
# Don't create a diff file for no changes
else
echo "⚠️ Changes detected in GraphQL schema, generating report..."
echo "# GraphQL Schema Changes" > graphql-schema-diff.md
@@ -444,6 +429,7 @@ jobs:
echo "Checking GraphQL metadata schema for changes..."
if graphql-inspector diff main-metadata-schema-introspection.json current-metadata-schema-introspection.json >/dev/null 2>&1; then
echo "✅ No changes in GraphQL metadata schema"
# Don't create a diff file for no changes
else
echo "⚠️ Changes detected in GraphQL metadata schema, generating report..."
echo "# GraphQL Metadata Schema Changes" > graphql-metadata-diff.md
@@ -462,7 +448,6 @@ jobs:
ls -la *-diff.md 2>/dev/null || echo "No diff files generated (no changes detected)"
- name: Check REST API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST API FOR BREAKING CHANGES ==="
@@ -531,7 +516,6 @@ jobs:
fi
- name: Check REST Metadata API Breaking Changes
if: steps.validate-schemas.outputs.valid == 'true'
run: |
echo "=== CHECKING REST METADATA API FOR BREAKING CHANGES ==="
+14 -16
View File
@@ -36,10 +36,12 @@ jobs:
runs-on: ubuntu-latest-4-cores
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
@@ -51,8 +53,6 @@ jobs:
image: redis
ports:
- 6379:6379
env:
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -66,13 +66,13 @@ jobs:
run: |
CI_VERSION="0.0.0-ci.$(date +%s)"
echo "CI_VERSION=$CI_VERSION" >> $GITHUB_ENV
npx nx run-many -t set-local-version -p $PUBLISHABLE_PACKAGES --releaseVersion=$CI_VERSION
npx nx run-many -t set-local-version -p twenty-sdk twenty-client-sdk create-twenty-app --releaseVersion=$CI_VERSION
- name: Build packages
run: |
for pkg in $PUBLISHABLE_PACKAGES; do
npx nx build $pkg
done
npx nx build twenty-sdk
npx nx build twenty-client-sdk
npx nx build create-twenty-app
- name: Install and start Verdaccio
run: |
@@ -89,13 +89,11 @@ jobs:
- name: Publish packages to local registry
run: |
yarn config set npmRegistryServer http://localhost:4873
yarn config set unsafeHttpWhitelist --json '["localhost"]'
yarn config set npmAuthToken ci-auth-token
npm set //localhost:4873/:_authToken "ci-auth-token"
for pkg in $PUBLISHABLE_PACKAGES; do
for pkg in twenty-sdk twenty-client-sdk create-twenty-app; do
cd packages/$pkg
yarn npm publish --tag ci
npm publish --registry http://localhost:4873 --tag ci
cd ../..
done
@@ -156,7 +154,7 @@ jobs:
SEED_API_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik'
run: |
cd /tmp/e2e-test-workspace/test-app
npx --no-install twenty remote add --api-key $SEED_API_KEY --api-url http://localhost:3000
npx --no-install twenty remote add --token $SEED_API_KEY --url http://localhost:3000
- name: Deploy scaffolded app
run: |
@@ -180,7 +178,7 @@ jobs:
cd /tmp/e2e-test-workspace/test-app
EXEC_OUTPUT=$(npx --no-install twenty exec --functionName create-hello-world-company)
echo "$EXEC_OUTPUT"
echo "$EXEC_OUTPUT" | grep -q 'Created company.*Hello World.*with id'
echo "$EXEC_OUTPUT" | grep -q "Created company"
- name: Run scaffolded app integration test
env:
@@ -1,114 +0,0 @@
name: CI Front Component Renderer
on:
pull_request:
merge_group:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changed-files-check:
if: github.event_name != 'merge_group'
uses: ./.github/workflows/changed-files.yaml
with:
files: |
package.json
yarn.lock
packages/twenty-front-component-renderer/**
packages/twenty-sdk/**
packages/twenty-shared/**
!packages/twenty-sdk/package.json
renderer-task:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
matrix:
task: [build, typecheck, lint]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Run ${{ matrix.task }}
run: npx nx ${{ matrix.task }} twenty-front-component-renderer
renderer-sb-build:
needs: changed-files-check
if: needs.changed-files-check.outputs.any_changed == 'true'
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build storybook
run: npx nx storybook:build twenty-front-component-renderer
- name: Upload storybook build
uses: actions/upload-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
retention-days: 1
renderer-sb-test:
timeout-minutes: 30
runs-on: ubuntu-latest
needs: renderer-sb-build
env:
STORYBOOK_URL: http://localhost:6008
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
with:
fetch-depth: 10
- name: Install dependencies
uses: ./.github/actions/yarn-install
- name: Build dependencies
run: npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@v4
with:
name: storybook-twenty-front-component-renderer
path: packages/twenty-front-component-renderer/storybook-static
- name: Install Playwright
run: |
cd packages/twenty-front-component-renderer
npx playwright install
- name: Serve storybook & run tests
run: |
npx http-server packages/twenty-front-component-renderer/storybook-static --port 6008 --silent &
timeout 30 bash -c 'until curl -sf http://localhost:6008 > /dev/null 2>&1; do sleep 1; done'
npx nx storybook:test twenty-front-component-renderer
ci-front-component-renderer-status-check:
if: always() && !cancelled()
timeout-minutes: 5
runs-on: ubuntu-latest
needs:
[
changed-files-check,
renderer-task,
renderer-sb-build,
renderer-sb-test,
]
steps:
- name: Fail job if any needs failed
if: contains(needs.*.result, 'failure')
run: exit 1
+1 -2
View File
@@ -25,7 +25,6 @@ jobs:
package.json
yarn.lock
packages/twenty-front/**
packages/twenty-front-component-renderer/**
packages/twenty-ui/**
packages/twenty-shared/**
packages/twenty-sdk/**
@@ -94,7 +93,7 @@ jobs:
run: |
npx nx build twenty-shared
npx nx build twenty-ui
npx nx build twenty-front-component-renderer
npx nx build twenty-sdk
- name: Download storybook build
uses: actions/download-artifact@v4
with:
+5 -3
View File
@@ -25,10 +25,12 @@ jobs:
NODE_OPTIONS: "--max-old-space-size=10240"
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: "true"
SPILO_PROVIDER: "local"
ports:
- 5432:5432
options: >-
+9 -4
View File
@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
task: [lint, typecheck, test:unit, test:integration]
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
@@ -41,6 +41,9 @@ jobs:
uses: ./.github/actions/yarn-install
- name: Build
run: npx nx build twenty-sdk
- name: Install Playwright
if: contains(matrix.task, 'storybook')
run: npx playwright install chromium
- name: Run ${{ matrix.task }} task
uses: ./.github/actions/nx-affected
with:
@@ -53,10 +56,12 @@ jobs:
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
+11 -6
View File
@@ -83,10 +83,12 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
@@ -120,6 +122,7 @@ jobs:
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Worker / Run
run: |
timeout 30s npx nx run twenty-server:worker || exit_code=$?
@@ -223,10 +226,12 @@ jobs:
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
+3 -13
View File
@@ -27,11 +27,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose
run: |
echo "Patching docker-compose.yml..."
@@ -102,11 +97,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Create frontend placeholder
run: |
mkdir -p packages/twenty-front/build
@@ -121,7 +111,7 @@ jobs:
- name: Start container
run: |
docker run -d --name twenty-app-dev \
-p 2020:2020 \
-p 3000:3000 \
twenty-app-dev-ci
docker logs twenty-app-dev -f &
- name: Wait for server health
@@ -129,10 +119,10 @@ jobs:
echo "Waiting for twenty-app-dev to become healthy..."
count=0
while true; do
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:2020/healthz 2>/dev/null || echo "000")
status=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/healthz 2>/dev/null || echo "000")
if [ "$status" = "200" ]; then
echo "Server is healthy!"
curl -s http://localhost:2020/healthz
curl -s http://localhost:3000/healthz
break
fi
+5 -3
View File
@@ -26,10 +26,12 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
+5 -3
View File
@@ -29,10 +29,12 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
image: twentycrm/twenty-postgres-spilo
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
ALLOW_NOSSL: 'true'
SPILO_PROVIDER: 'local'
ports:
- 5432:5432
options: >-
@@ -17,12 +17,6 @@ jobs:
with:
ref: ${{ github.event.client_payload.pr_head_sha }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose setup
run: |
echo "Patching docker-compose.yml..."
-940
View File
File diff suppressed because one or more lines are too long
+942
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,4 +6,4 @@ enableInlineHunks: true
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.13.0.cjs
yarnPath: .yarn/releases/yarn-4.9.2.cjs
+7 -10
View File
@@ -82,11 +82,11 @@
"@sentry/types": "^8",
"@storybook-community/storybook-addon-cookie": "^5.0.0",
"@storybook/addon-coverage": "^3.0.0",
"@storybook/addon-docs": "^10.3.3",
"@storybook/addon-links": "^10.3.3",
"@storybook/addon-vitest": "^10.3.3",
"@storybook/addon-docs": "^10.2.13",
"@storybook/addon-links": "^10.2.13",
"@storybook/addon-vitest": "^10.2.13",
"@storybook/icons": "^2.0.1",
"@storybook/react-vite": "^10.3.3",
"@storybook/react-vite": "^10.2.13",
"@storybook/test-runner": "^0.24.2",
"@swc-node/register": "^1.11.1",
"@swc/cli": "^0.7.10",
@@ -154,9 +154,9 @@
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
"source-map-support": "^0.5.20",
"storybook": "^10.3.3",
"storybook": "^10.2.13",
"storybook-addon-mock-date": "2.0.0",
"storybook-addon-pseudo-states": "^10.3.3",
"storybook-addon-pseudo-states": "^10.2.13",
"supertest": "^6.1.3",
"ts-jest": "^29.1.1",
"ts-loader": "^9.2.3",
@@ -175,12 +175,11 @@
},
"license": "AGPL-3.0",
"name": "twenty",
"packageManager": "yarn@4.13.0",
"packageManager": "yarn@4.9.2",
"resolutions": {
"graphql": "16.8.1",
"type-fest": "4.10.1",
"typescript": "5.9.2",
"nodemailer": "8.0.4",
"graphql-redis-subscriptions/ioredis": "^5.6.0",
"@lingui/core": "5.1.2",
"@types/qs": "6.9.16",
@@ -204,12 +203,10 @@
"packages/twenty-utils",
"packages/twenty-zapier",
"packages/twenty-website",
"packages/twenty-website-new",
"packages/twenty-docs",
"packages/twenty-e2e-testing",
"packages/twenty-shared",
"packages/twenty-sdk",
"packages/twenty-front-component-renderer",
"packages/twenty-client-sdk",
"packages/twenty-apps",
"packages/twenty-cli",
+146 -23
View File
@@ -12,49 +12,172 @@
</div>
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).
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a readytorun project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zeroconfig 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)
## Quick start
```bash
# Scaffold a new app — the CLI will offer to start a local Twenty server
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# The scaffolder can automatically:
# 1. Start a local Twenty server (Docker)
# 2. Open the browser to log in (tim@apple.dev / tim@apple.dev)
# 3. Authenticate your app via OAuth
# Or do it manually:
yarn twenty server start # Start local Twenty server
yarn twenty remote add --local # Authenticate via OAuth
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates typed CoreApiClient — MetadataApiClient ships pre-built — both available via `twenty-client-sdk`)
yarn twenty dev
# Watch your application's function logs
yarn twenty logs
# Execute a function with a JSON payload
yarn twenty exec -n my-function -p '{"key": "value"}'
# Execute the pre-install function
yarn twenty exec --preInstall
# Execute the post-install function
yarn twenty exec --postInstall
# Build the app for distribution
yarn twenty build
# Publish the app to npm or directly to a Twenty server
yarn twenty publish
# Uninstall the application from the current workspace
yarn twenty uninstall
```
The scaffolder will:
1. Create a new project with TypeScript, linting, and a preconfigured `twenty` CLI
2. Optionally start a local Twenty server (Docker)
3. Open the browser for OAuth authentication
4. Scaffold example entities and an integration test
## Scaffolding modes
| Flag | Behavior |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `--minimal` | **(default)** Creates only core files (`application-config.ts`, `default-role.ts`, pre/post-install functions) |
| `--exhaustive` | Creates all example entities |
Control which example files are included when creating a new app:
Other flags:
| Flag | Behavior |
| ------------------ | ----------------------------------------------------------------------- |
| `-e, --exhaustive` | **(default)** Creates all example files |
| `-m, --minimal` | Creates only core files (`application-config.ts` and `default-role.ts`) |
- `--name <name>` — set the app name (skips the prompt)
- `--display-name <displayName>` — set the display name (skips the prompt)
- `--description <description>` — set the description (skips the prompt)
- `--skip-local-instance` — skip the local server setup prompt
```bash
# Default: all examples included
npx create-twenty-app@latest my-app
## Documentation
# Minimal: only core files
npx create-twenty-app@latest my-app -m
```
Full documentation is available at **[docs.twenty.com/developers/extend/apps](https://docs.twenty.com/developers/extend/apps/getting-started)**:
## What gets scaffolded
- [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
- [Publishing](https://docs.twenty.com/developers/extend/apps/publishing) — deploy, npm publish, marketplace
**Core files (always created):**
- `application-config.ts` — Application metadata configuration
- `roles/default-role.ts` — Default role for logic functions
- `logic-functions/pre-install.ts` — Pre-install logic function (runs before app installation)
- `logic-functions/post-install.ts` — Post-install logic function (runs after app installation)
- TypeScript configuration, Oxlint, package.json, .gitignore
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**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
```
The server is pre-seeded with a workspace and user (`tim@apple.dev` / `tim@apple.dev`).
### How to use a local Twenty instance
If you're already running a local Twenty instance, you can connect to it instead of using Docker. Pass the port your local server is listening on (default: `3000`):
```bash
npx create-twenty-app@latest my-app --port 3000
```
## Next steps
- Run `yarn twenty help` to see all available commands.
- Use `yarn twenty remote add --local` 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.
## Troubleshooting
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
- Auth not working: make sure you are logged in to Twenty in the browser, then run `yarn twenty remote add`.
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add --local`.
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
## Contributing
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.8.0-canary.8",
"version": "0.8.0-canary.2",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -36,7 +36,6 @@
"lodash.camelcase": "^4.3.0",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
"twenty-sdk": "workspace:*",
"uuid": "^13.0.0"
},
"devDependencies": {
@@ -46,6 +45,7 @@
"@types/lodash.kebabcase": "^4.1.7",
"@types/lodash.startcase": "^4",
"@types/node": "^20.0.0",
"twenty-sdk": "workspace:*",
"twenty-shared": "workspace:*",
"typescript": "^5.9.2",
"vite": "^7.0.0",
+11 -5
View File
@@ -13,10 +13,10 @@ const program = new Command(packageJson.name)
'Output the current version of create-twenty-app.',
)
.argument('[directory]')
.option('-e, --exhaustive', 'Create all example entities')
.option('-e, --exhaustive', 'Create all example entities (default)')
.option(
'-m, --minimal',
'Create only core entities (application-config and default-role) (default)',
'Create only core entities (application-config and default-role)',
)
.option('-n, --name <name>', 'Application name (skips prompt)')
.option(
@@ -31,6 +31,10 @@ const program = new Command(packageJson.name)
'--skip-local-instance',
'Skip the local Twenty instance setup prompt',
)
.option(
'-p, --port <port>',
'Port of an existing Twenty server (skips Docker setup)',
)
.helpOption('-h, --help', 'Display this help message.')
.action(
async (
@@ -42,6 +46,7 @@ const program = new Command(packageJson.name)
displayName?: string;
description?: string;
skipLocalInstance?: boolean;
port?: string;
},
) => {
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
@@ -69,9 +74,9 @@ const program = new Command(packageJson.name)
process.exit(1);
}
const mode: ScaffoldingMode = options?.exhaustive
? 'exhaustive'
: 'minimal';
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
const port = options?.port ? parseInt(options.port, 10) : undefined;
await new CreateAppCommand().execute({
directory,
@@ -80,6 +85,7 @@ const program = new Command(packageJson.name)
displayName: options?.displayName,
description: options?.description,
skipLocalInstance: options?.skipLocalInstance,
port,
});
},
);
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
@@ -1,19 +1,18 @@
import { basename } from 'path';
import { copyBaseApplicationProject } from '@/utils/app-template';
import { convertToLabel } from '@/utils/convert-to-label';
import { install } from '@/utils/install';
import {
type LocalInstanceResult,
setupLocalInstance,
} from '@/utils/setup-local-instance';
import { tryGitInit } from '@/utils/try-git-init';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import kebabCase from 'lodash.kebabcase';
import { execSync } from 'node:child_process';
import * as path from 'path';
import { basename } from 'path';
import {
authLoginOAuth,
detectLocalServer,
serverStart,
type ServerStartResult,
} from 'twenty-sdk/cli';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -30,6 +29,7 @@ type CreateAppOptions = {
displayName?: string;
description?: string;
skipLocalInstance?: boolean;
port?: number;
};
export class CreateAppCommand {
@@ -39,7 +39,7 @@ export class CreateAppCommand {
try {
const exampleOptions = this.resolveExampleOptions(
options.mode ?? 'minimal',
options.mode ?? 'exhaustive',
);
await this.validateDirectory(appDirectory);
@@ -60,26 +60,17 @@ export class CreateAppCommand {
await tryGitInit(appDirectory);
let serverResult: ServerStartResult | undefined;
let localResult: LocalInstanceResult = { running: false };
if (!options.skipLocalInstance) {
const shouldStartServer = await this.shouldStartServer();
localResult = await setupLocalInstance(appDirectory, options.port);
if (shouldStartServer) {
const startResult = await serverStart({
onProgress: (message: string) => console.log(chalk.gray(message)),
});
if (startResult.success) {
serverResult = startResult.data;
await this.promptConnectToLocal(serverResult.url);
} else {
console.log(chalk.yellow(`\n${startResult.error.message}`));
}
if (localResult.running && localResult.serverUrl) {
await this.connectToLocal(appDirectory, localResult.serverUrl);
}
}
this.logSuccess(appDirectory, serverResult);
this.logSuccess(appDirectory, localResult);
} catch (error) {
console.error(
chalk.red('\nCreate application failed:'),
@@ -163,7 +154,7 @@ export class CreateAppCommand {
includeExampleNavigationMenuItem: false,
includeExampleSkill: false,
includeExampleAgent: false,
includeExampleIntegrationTest: true,
includeExampleIntegrationTest: false,
};
}
@@ -206,63 +197,19 @@ export class CreateAppCommand {
);
}
private async shouldStartServer(): Promise<boolean> {
const existingServerUrl = await detectLocalServer();
if (existingServerUrl) {
return true;
}
const { startDocker } = await inquirer.prompt([
{
type: 'confirm',
name: 'startDocker',
message:
'No running Twenty instance found. Would you like to start one using Docker?',
default: true,
},
]);
return startDocker;
}
private async promptConnectToLocal(serverUrl: string): Promise<void> {
const { shouldAuthenticate } = await inquirer.prompt([
{
type: 'confirm',
name: 'shouldAuthenticate',
message: `Would you like to authenticate to the local Twenty instance (${serverUrl})?`,
default: true,
},
]);
if (!shouldAuthenticate) {
console.log(
chalk.gray(
'Authentication skipped. Run `yarn twenty remote add` manually.',
),
);
return;
}
private async connectToLocal(
appDirectory: string,
serverUrl: string,
): Promise<void> {
try {
const result = await authLoginOAuth({
apiUrl: serverUrl,
remote: 'local',
execSync(`yarn twenty remote add ${serverUrl} --as local`, {
cwd: appDirectory,
stdio: 'inherit',
});
if (!result.success) {
console.log(
chalk.yellow(
'Authentication failed. Run `yarn twenty remote add` manually.',
),
);
}
} catch {
console.log(
chalk.yellow(
'Authentication failed. Run `yarn twenty remote add` manually.',
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
),
);
}
@@ -270,17 +217,17 @@ export class CreateAppCommand {
private logSuccess(
appDirectory: string,
serverResult?: ServerStartResult,
localResult: LocalInstanceResult,
): void {
const dirName = basename(appDirectory);
console.log(chalk.blue('\nApplication created. Next steps:'));
console.log(chalk.gray(`- cd ${dirName}`));
if (!serverResult) {
if (!localResult.running) {
console.log(
chalk.gray(
'- yarn twenty remote add # Authenticate with Twenty',
'- yarn twenty remote add --local # Authenticate with Twenty',
),
);
}
@@ -425,12 +425,8 @@ const handler = async (): Promise<{ message: string }> => {
},
});
if (!createCompany?.id || !createCompany?.name) {
throw new Error('Failed to create company: missing id or name in response');
}
return {
message: \`Created company "\${createCompany.name}" with id \${createCompany.id}\`,
message: \`Created company "\${createCompany?.name}" with id \${createCompany?.id}\`,
};
};
@@ -798,7 +794,6 @@ const createPackageJson = async ({
npm: 'please-use-yarn',
yarn: '>=4.0.2',
},
keywords: ['twenty-app'],
packageManager: 'yarn@4.9.2',
scripts,
devDependencies,
@@ -0,0 +1,106 @@
import chalk from 'chalk';
import { execSync } from 'node:child_process';
const LOCAL_PORTS = [2020, 3000];
// Minimal health check — the full implementation lives in twenty-sdk
const isServerReady = async (port: number): Promise<boolean> => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
try {
const response = await fetch(`http://localhost:${port}/healthz`, {
signal: controller.signal,
});
const body = await response.json();
return body.status === 'ok';
} catch {
return false;
} finally {
clearTimeout(timeoutId);
}
};
const detectRunningServer = async (
preferredPort?: number,
): Promise<number | null> => {
const ports = preferredPort ? [preferredPort] : LOCAL_PORTS;
for (const port of ports) {
if (await isServerReady(port)) {
return port;
}
}
return null;
};
export type LocalInstanceResult = {
running: boolean;
serverUrl?: string;
};
export const setupLocalInstance = async (
appDirectory: string,
preferredPort?: number,
): Promise<LocalInstanceResult> => {
const detectedPort = await detectRunningServer(preferredPort);
if (detectedPort) {
const serverUrl = `http://localhost:${detectedPort}`;
console.log(chalk.green(`Twenty server detected on ${serverUrl}.\n`));
return { running: true, serverUrl };
}
if (preferredPort) {
console.log(
chalk.yellow(
`No Twenty server found on port ${preferredPort}.\n` +
'Start your server and run `yarn twenty remote add --local` manually.\n',
),
);
return { running: false };
}
console.log(chalk.blue('Setting up local Twenty instance...\n'));
try {
execSync('yarn twenty server start', {
cwd: appDirectory,
stdio: 'inherit',
});
} catch {
return { running: false };
}
console.log(chalk.gray('Waiting for Twenty to be ready...\n'));
const startTime = Date.now();
const timeoutMs = 180 * 1000;
while (Date.now() - startTime < timeoutMs) {
if (await isServerReady(LOCAL_PORTS[0])) {
const serverUrl = `http://localhost:${LOCAL_PORTS[0]}`;
console.log(chalk.green(`Server running on '${serverUrl}'\n`));
return { running: true, serverUrl };
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
console.log(
chalk.yellow(
'Twenty server did not become healthy in time.\n',
"Check: 'yarn twenty server logs'\n",
),
);
return { running: false };
};
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
- All generated UUIDs must be valid UUID v4.
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn twenty remote add http://localhost:2020 --as local
yarn twenty remote add --local
```
Then, start development mode to sync your app and watch for changes:
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
```bash
# Remotes & Authentication
yarn twenty remote add http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote add --local # Authenticate with Twenty
yarn twenty remote status # Check auth status
yarn twenty remote switch # Switch default remote
yarn twenty remote list # List all configured remotes
@@ -1,19 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -1,19 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,7 +9,17 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"remote:add": "twenty remote add --local",
"remote:status": "twenty remote status",
"remote:switch": "twenty remote switch",
"remote:list": "twenty remote list",
"remote:remove": "twenty remote remove",
"dev": "twenty dev",
"add": "twenty add",
"logs": "twenty logs",
"exec": "twenty exec",
"uninstall": "twenty uninstall",
"help": "twenty help",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
@@ -1,19 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,7 +9,17 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"remote:add": "twenty remote add --local",
"remote:status": "twenty remote status",
"remote:switch": "twenty remote switch",
"remote:list": "twenty remote list",
"remote:remove": "twenty remote remove",
"dev": "twenty dev",
"add": "twenty add",
"logs": "twenty logs",
"exec": "twenty exec",
"uninstall": "twenty uninstall",
"help": "twenty help",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
@@ -1,19 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "off"
},
"ignorePatterns": ["node_modules", "dist"],
"rules": {
"no-unused-vars": "off",
"typescript/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "off"
}
}
@@ -9,13 +9,22 @@
},
"packageManager": "yarn@4.9.2",
"scripts": {
"twenty": "twenty",
"remote:add": "twenty remote add --local",
"remote:status": "twenty remote status",
"remote:switch": "twenty remote switch",
"remote:list": "twenty remote list",
"remote:remove": "twenty remote remove",
"dev": "twenty dev",
"add": "twenty add",
"logs": "twenty logs",
"exec": "twenty exec",
"uninstall": "twenty uninstall",
"help": "twenty help",
"lint": "oxlint -c .oxlintrc.json .",
"lint:fix": "oxlint --fix -c .oxlintrc.json ."
},
"dependencies": {
"twenty-sdk": "latest",
"twenty-client-sdk": "latest"
"twenty-sdk": "latest"
},
"devDependencies": {
"typescript": "^5.9.3",
@@ -1,18 +0,0 @@
import {
defineField,
FieldType,
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS,
} from 'twenty-sdk';
// Field on existing company object
export default defineField({
universalIdentifier: 'f922fdb8-10a9-4f11-a1d0-992a779f6dff',
objectUniversalIdentifier:
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
type: FieldType.BOOLEAN,
name: 'canReceivePostcards',
label: 'Can Receive Postcards',
description: 'Whether the company can receive postcards',
icon: 'IconMailbox',
defaultValue: false,
});
@@ -6,9 +6,6 @@ export const RECIPIENT_UNIVERSAL_IDENTIFIER =
export const RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER =
'd2a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
export const RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER =
'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d';
export default defineObject({
universalIdentifier: RECIPIENT_UNIVERSAL_IDENTIFIER,
nameSingular: 'recipient',
@@ -26,7 +23,7 @@ export default defineObject({
icon: 'IconMail',
},
{
universalIdentifier: RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
universalIdentifier: 'd3a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
type: FieldType.ADDRESS,
label: 'Mailing Address',
name: 'mailingAddress',
@@ -1,16 +1,13 @@
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
import {
POST_CARD_RECIPIENT_UNIVERSAL_IDENTIFIER,
SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
} from '../objects/post-card-recipient.object';
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
export const ALL_POST_CARD_RECIPIENTS_VIEW_ID =
'b1a2b3c4-0003-4a7b-8c9d-0e1f2a3b4c5d';
export const POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER =
'fd959c6f-3465-4a3a-b7ad-3f4004fffc9a';
export default defineView({
universalIdentifier: ALL_POST_CARD_RECIPIENTS_VIEW_ID,
name: 'All Post Card Recipients',
@@ -20,8 +17,7 @@ export default defineView({
position: 2,
fields: [
{
universalIdentifier:
POST_CARD_RECIPIENT_SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
fieldMetadataUniversalIdentifier: SENT_AT_FIELD_UNIVERSAL_IDENTIFIER,
position: 0,
isVisible: true,
@@ -1,10 +1,9 @@
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
import {
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
RECIPIENT_EMAIL_FIELD_UNIVERSAL_IDENTIFIER,
RECIPIENT_UNIVERSAL_IDENTIFIER,
} from '../objects/recipient.object';
import { defineView } from 'twenty-sdk';
import { ViewType } from 'twenty-shared/types';
export const ALL_RECIPIENTS_VIEW_ID = 'b1a2b3c4-0002-4a7b-8c9d-0e1f2a3b4c5d';
@@ -24,13 +23,5 @@ export default defineView({
isVisible: true,
size: 200,
},
{
universalIdentifier: 'bf1a2b3c-0004-4a7b-8c9d-0e1f2a3b4c5d',
fieldMetadataUniversalIdentifier:
RECIPIENT_ADDRESS_FIELD_UNIVERSAL_IDENTIFIER,
position: 1,
isVisible: true,
size: 150,
},
],
});
+1 -1
View File
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## UUID requirement
+2 -2
View File
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn twenty remote add --api-url http://localhost:2020 --as local
yarn twenty remote add --local
```
Then, start development mode to sync your app and watch for changes:
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
```bash
# Remotes & Authentication
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote add --local # Authenticate with Twenty
yarn twenty remote status # Check auth status
yarn twenty remote switch # Switch default remote
yarn twenty remote list # List all configured remotes
@@ -20,8 +20,8 @@
"@types/react": "^18.2.0",
"oxlint": "^0.16.0",
"react": "^18.2.0",
"twenty-client-sdk": "0.8.0-canary.5",
"twenty-sdk": "0.8.0-canary.5",
"twenty-client-sdk": "portal:../../twenty-client-sdk",
"twenty-sdk": "portal:../../twenty-sdk",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^3.1.1"
@@ -16,12 +16,8 @@ const handler = async (): Promise<{ message: string }> => {
},
});
if (!createCompany?.id || !createCompany?.name) {
throw new Error('Failed to create company: missing id or name in response');
}
return {
message: `Created company "${createCompany.name}" with id ${createCompany.id}`,
message: `Created company "${createCompany?.name}" with id ${createCompany?.id}`,
};
};
@@ -14,8 +14,7 @@ export default defineObject({
labelPlural: 'Example items',
description: 'A sample custom object',
icon: 'IconBox',
labelIdentifierFieldMetadataUniversalIdentifier:
NAME_FIELD_UNIVERSAL_IDENTIFIER,
labelIdentifierFieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
fields: [
{
universalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
+107 -123
View File
@@ -5,13 +5,13 @@ __metadata:
version: 8
cacheKey: 10c0
"@alcalzone/ansi-tokenize@npm:^0.2.4":
version: 0.2.5
resolution: "@alcalzone/ansi-tokenize@npm:0.2.5"
"@alcalzone/ansi-tokenize@npm:^0.1.3":
version: 0.1.3
resolution: "@alcalzone/ansi-tokenize@npm:0.1.3"
dependencies:
ansi-styles: "npm:^6.2.1"
is-fullwidth-code-point: "npm:^5.0.0"
checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4
is-fullwidth-code-point: "npm:^4.0.0"
checksum: 10c0/b88c5708271bb64ce132fc80dac8d5b87fc1699bf3abfdf10ecae40dbb56ab82460818f5746ecdd9870a00be9854e039d5cb3a121b808d0ff6f5bc7d0146cb38
languageName: node
linkType: hard
@@ -2655,7 +2655,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-escapes@npm:^7.3.0":
"ansi-escapes@npm:^7.0.0":
version: 7.3.0
resolution: "ansi-escapes@npm:7.3.0"
dependencies:
@@ -2717,7 +2717,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3":
"ansi-styles@npm:^6.0.0, ansi-styles@npm:^6.2.1":
version: 6.2.3
resolution: "ansi-styles@npm:6.2.3"
checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868
@@ -2948,7 +2948,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^5.3.0, chalk@npm:^5.6.0":
"chalk@npm:^5.3.0":
version: 5.6.2
resolution: "chalk@npm:5.6.2"
checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976
@@ -3020,13 +3020,13 @@ __metadata:
languageName: node
linkType: hard
"cli-truncate@npm:^5.1.1":
version: 5.2.0
resolution: "cli-truncate@npm:5.2.0"
"cli-truncate@npm:^4.0.0":
version: 4.0.0
resolution: "cli-truncate@npm:4.0.0"
dependencies:
slice-ansi: "npm:^8.0.0"
string-width: "npm:^8.2.0"
checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471
slice-ansi: "npm:^5.0.0"
string-width: "npm:^7.0.0"
checksum: 10c0/d7f0b73e3d9b88cb496e6c086df7410b541b56a43d18ade6a573c9c18bd001b1c3fba1ad578f741a4218fdc794d042385f8ac02c25e1c295a2d8b9f3cb86eb4c
languageName: node
linkType: hard
@@ -3306,7 +3306,7 @@ __metadata:
languageName: node
linkType: hard
"es-toolkit@npm:^1.39.10":
"es-toolkit@npm:^1.22.0":
version: 1.45.1
resolution: "es-toolkit@npm:1.45.1"
dependenciesMeta:
@@ -3727,7 +3727,7 @@ __metadata:
languageName: node
linkType: hard
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0":
"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1":
version: 1.5.0
resolution: "get-east-asian-width@npm:1.5.0"
checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167
@@ -3906,8 +3906,8 @@ __metadata:
"@types/react": "npm:^18.2.0"
oxlint: "npm:^0.16.0"
react: "npm:^18.2.0"
twenty-client-sdk: "npm:0.8.0-canary.5"
twenty-sdk: "npm:0.8.0-canary.5"
twenty-client-sdk: "portal:../../twenty-client-sdk"
twenty-sdk: "portal:../../twenty-sdk"
typescript: "npm:^5.9.3"
vite-tsconfig-paths: "npm:^4.2.1"
vitest: "npm:^3.1.1"
@@ -4030,45 +4030,44 @@ __metadata:
languageName: node
linkType: hard
"ink@npm:^6.8.0":
version: 6.8.0
resolution: "ink@npm:6.8.0"
"ink@npm:^5.1.1":
version: 5.2.1
resolution: "ink@npm:5.2.1"
dependencies:
"@alcalzone/ansi-tokenize": "npm:^0.2.4"
ansi-escapes: "npm:^7.3.0"
"@alcalzone/ansi-tokenize": "npm:^0.1.3"
ansi-escapes: "npm:^7.0.0"
ansi-styles: "npm:^6.2.1"
auto-bind: "npm:^5.0.1"
chalk: "npm:^5.6.0"
chalk: "npm:^5.3.0"
cli-boxes: "npm:^3.0.0"
cli-cursor: "npm:^4.0.0"
cli-truncate: "npm:^5.1.1"
cli-truncate: "npm:^4.0.0"
code-excerpt: "npm:^4.0.0"
es-toolkit: "npm:^1.39.10"
es-toolkit: "npm:^1.22.0"
indent-string: "npm:^5.0.0"
is-in-ci: "npm:^2.0.0"
is-in-ci: "npm:^1.0.0"
patch-console: "npm:^2.0.0"
react-reconciler: "npm:^0.33.0"
scheduler: "npm:^0.27.0"
react-reconciler: "npm:^0.29.0"
scheduler: "npm:^0.23.0"
signal-exit: "npm:^3.0.7"
slice-ansi: "npm:^8.0.0"
slice-ansi: "npm:^7.1.0"
stack-utils: "npm:^2.0.6"
string-width: "npm:^8.1.1"
terminal-size: "npm:^4.0.1"
type-fest: "npm:^5.4.1"
widest-line: "npm:^6.0.0"
string-width: "npm:^7.2.0"
type-fest: "npm:^4.27.0"
widest-line: "npm:^5.0.0"
wrap-ansi: "npm:^9.0.0"
ws: "npm:^8.18.0"
yoga-layout: "npm:~3.2.1"
peerDependencies:
"@types/react": ">=19.0.0"
react: ">=19.0.0"
react-devtools-core: ">=6.1.2"
"@types/react": ">=18.0.0"
react: ">=18.0.0"
react-devtools-core: ^4.19.1
peerDependenciesMeta:
"@types/react":
optional: true
react-devtools-core:
optional: true
checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff
checksum: 10c0/0e2607712783353fbe99438ca4220db3d5874c7ae1a07e2b232f7539489b13a9db929b3046eedeccf318a3ffa222a572287dbe2307c848b8e7f6ec4bba39e550
languageName: node
linkType: hard
@@ -4141,7 +4140,14 @@ __metadata:
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0":
"is-fullwidth-code-point@npm:^4.0.0":
version: 4.0.0
resolution: "is-fullwidth-code-point@npm:4.0.0"
checksum: 10c0/df2a717e813567db0f659c306d61f2f804d480752526886954a2a3e2246c7745fd07a52b5fecf2b68caf0a6c79dcdace6166fdf29cc76ed9975cc334f0a018b8
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^5.0.0":
version: 5.1.0
resolution: "is-fullwidth-code-point@npm:5.1.0"
dependencies:
@@ -4159,12 +4165,12 @@ __metadata:
languageName: node
linkType: hard
"is-in-ci@npm:^2.0.0":
version: 2.0.0
resolution: "is-in-ci@npm:2.0.0"
"is-in-ci@npm:^1.0.0":
version: 1.0.0
resolution: "is-in-ci@npm:1.0.0"
bin:
is-in-ci: cli.js
checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb
checksum: 10c0/98f9cec4c35aece4cf731abf35ebf28359a9b0324fac810da05b842515d9ccb33b8999c1d9a679f0362e1a4df3292065c38d7f86327b1387fa667bc0150f4898
languageName: node
linkType: hard
@@ -5008,14 +5014,15 @@ __metadata:
languageName: node
linkType: hard
"react-dom@npm:^19.0.0":
version: 19.2.4
resolution: "react-dom@npm:19.2.4"
"react-dom@npm:^18.2.0":
version: 18.3.1
resolution: "react-dom@npm:18.3.1"
dependencies:
scheduler: "npm:^0.27.0"
loose-envify: "npm:^1.1.0"
scheduler: "npm:^0.23.2"
peerDependencies:
react: ^19.2.4
checksum: 10c0/f0c63f1794dedb154136d4d0f59af00b41907f4859571c155940296808f4b94bf9c0c20633db75b5b2112ec13d8d7dd4f9bf57362ed48782f317b11d05a44f35
react: ^18.3.1
checksum: 10c0/a752496c1941f958f2e8ac56239172296fcddce1365ce45222d04a1947e0cc5547df3e8447f855a81d6d39f008d7c32eab43db3712077f09e3f67c4874973e85
languageName: node
linkType: hard
@@ -5026,14 +5033,15 @@ __metadata:
languageName: node
linkType: hard
"react-reconciler@npm:^0.33.0":
version: 0.33.0
resolution: "react-reconciler@npm:0.33.0"
"react-reconciler@npm:^0.29.0":
version: 0.29.2
resolution: "react-reconciler@npm:0.29.2"
dependencies:
scheduler: "npm:^0.27.0"
loose-envify: "npm:^1.1.0"
scheduler: "npm:^0.23.2"
peerDependencies:
react: ^19.2.0
checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a
react: ^18.3.1
checksum: 10c0/94f48ddc348a974256cf13c859f5a94efdb0cd72e04c51b1a4d5c72a8b960ccd35df2196057ee6a4cbcb26145e12b01e3f9ba3b183fddb901414db36a07cbf43
languageName: node
linkType: hard
@@ -5046,13 +5054,6 @@ __metadata:
languageName: node
linkType: hard
"react@npm:^19.0.0":
version: 19.2.4
resolution: "react@npm:19.2.4"
checksum: 10c0/cd2c9ff67a720799cc3b38a516009986f7fc4cb8d3e15716c6211cf098d1357ee3e348ab05ad0600042bbb0fd888530ba92e329198c92eafa0994f5213396596
languageName: node
linkType: hard
"readdirp@npm:^4.0.1":
version: 4.1.2
resolution: "readdirp@npm:4.1.2"
@@ -5297,10 +5298,12 @@ __metadata:
languageName: node
linkType: hard
"scheduler@npm:^0.27.0":
version: 0.27.0
resolution: "scheduler@npm:0.27.0"
checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452
"scheduler@npm:^0.23.0, scheduler@npm:^0.23.2":
version: 0.23.2
resolution: "scheduler@npm:0.23.2"
dependencies:
loose-envify: "npm:^1.1.0"
checksum: 10c0/26383305e249651d4c58e6705d5f8425f153211aef95f15161c151f7b8de885f24751b377e4a0b3dd42cce09aad3f87a61dab7636859c0d89b7daf1a1e2a5c78
languageName: node
linkType: hard
@@ -5403,13 +5406,23 @@ __metadata:
languageName: node
linkType: hard
"slice-ansi@npm:^8.0.0":
version: 8.0.0
resolution: "slice-ansi@npm:8.0.0"
"slice-ansi@npm:^5.0.0":
version: 5.0.0
resolution: "slice-ansi@npm:5.0.0"
dependencies:
ansi-styles: "npm:^6.2.3"
is-fullwidth-code-point: "npm:^5.1.0"
checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a
ansi-styles: "npm:^6.0.0"
is-fullwidth-code-point: "npm:^4.0.0"
checksum: 10c0/2d4d40b2a9d5cf4e8caae3f698fe24ae31a4d778701724f578e984dcb485ec8c49f0c04dab59c401821e80fcdfe89cace9c66693b0244e40ec485d72e543914f
languageName: node
linkType: hard
"slice-ansi@npm:^7.1.0":
version: 7.1.2
resolution: "slice-ansi@npm:7.1.2"
dependencies:
ansi-styles: "npm:^6.2.1"
is-fullwidth-code-point: "npm:^5.0.0"
checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2
languageName: node
linkType: hard
@@ -5519,7 +5532,7 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^7.0.0":
"string-width@npm:^7.0.0, string-width@npm:^7.2.0":
version: 7.2.0
resolution: "string-width@npm:7.2.0"
dependencies:
@@ -5530,16 +5543,6 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0":
version: 8.2.0
resolution: "string-width@npm:8.2.0"
dependencies:
get-east-asian-width: "npm:^1.5.0"
strip-ansi: "npm:^7.1.2"
checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02
languageName: node
linkType: hard
"strip-ansi@npm:^3.0.0, strip-ansi@npm:^3.0.1":
version: 3.0.1
resolution: "strip-ansi@npm:3.0.1"
@@ -5567,7 +5570,7 @@ __metadata:
languageName: node
linkType: hard
"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2":
"strip-ansi@npm:^7.1.0":
version: 7.2.0
resolution: "strip-ansi@npm:7.2.0"
dependencies:
@@ -5637,13 +5640,6 @@ __metadata:
languageName: node
linkType: hard
"tagged-tag@npm:^1.0.0":
version: 1.0.0
resolution: "tagged-tag@npm:1.0.0"
checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1
languageName: node
linkType: hard
"tar@npm:^7.5.4":
version: 7.5.11
resolution: "tar@npm:7.5.11"
@@ -5657,13 +5653,6 @@ __metadata:
languageName: node
linkType: hard
"terminal-size@npm:^4.0.1":
version: 4.0.1
resolution: "terminal-size@npm:4.0.1"
checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8
languageName: node
linkType: hard
"tinybench@npm:^2.9.0":
version: 2.9.0
resolution: "tinybench@npm:2.9.0"
@@ -5762,21 +5751,20 @@ __metadata:
languageName: node
linkType: hard
"twenty-client-sdk@npm:0.8.0-canary.5":
version: 0.8.0-canary.5
resolution: "twenty-client-sdk@npm:0.8.0-canary.5"
"twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A.":
version: 0.0.0-use.local
resolution: "twenty-client-sdk@portal:../../twenty-client-sdk::locator=hello-world%40workspace%3A."
dependencies:
"@genql/cli": "npm:^3.0.3"
"@genql/runtime": "npm:^2.10.0"
esbuild: "npm:^0.25.0"
graphql: "npm:^16.8.1"
checksum: 10c0/754b92b732c8e03c9779f6557324710afe4c07f7e6efbe766bd8ff3b6dd8a00c0d9f3fecce2098d68ef9556cafa722b2189e490473e420fdb9cf30c56beb3619
languageName: node
linkType: hard
linkType: soft
"twenty-sdk@npm:0.8.0-canary.5":
version: 0.8.0-canary.5
resolution: "twenty-sdk@npm:0.8.0-canary.5"
"twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A.":
version: 0.0.0-use.local
resolution: "twenty-sdk@portal:../../twenty-sdk::locator=hello-world%40workspace%3A."
dependencies:
"@chakra-ui/react": "npm:^3.33.0"
"@emotion/react": "npm:^11.14.0"
@@ -5794,14 +5782,13 @@ __metadata:
esbuild: "npm:^0.25.0"
graphql: "npm:^16.8.1"
graphql-sse: "npm:^2.5.4"
ink: "npm:^6.8.0"
ink: "npm:^5.1.1"
inquirer: "npm:^10.0.0"
jsonc-parser: "npm:^3.2.0"
preact: "npm:^10.28.3"
react: "npm:^19.0.0"
react-dom: "npm:^19.0.0"
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
tinyglobby: "npm:^0.2.15"
twenty-client-sdk: "npm:0.8.0-canary.5"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
vite: "npm:^7.0.0"
@@ -5809,9 +5796,8 @@ __metadata:
zod: "npm:^4.1.11"
bin:
twenty: dist/cli.cjs
checksum: 10c0/47d0970c88f59c092e8eca34dca38bf88d9d52678997349068ca8a8e5cebed2ea71dbd4e8b7299f40b99b5419d4cecd53b31f821b65959267e8d97eb91d2ddde
languageName: node
linkType: hard
linkType: soft
"type-fest@npm:^0.21.3":
version: 0.21.3
@@ -5820,12 +5806,10 @@ __metadata:
languageName: node
linkType: hard
"type-fest@npm:^5.4.1":
version: 5.5.0
resolution: "type-fest@npm:5.5.0"
dependencies:
tagged-tag: "npm:^1.0.0"
checksum: 10c0/60bf79a8df45abf99490e3204eceb5cf7f915413f8a69fb578c75cab37ddcb7d29ee21f185f0e1617323ac0b2a441e001b8dc691e220d0b087e9c29ea205538c
"type-fest@npm:^4.27.0":
version: 4.41.0
resolution: "type-fest@npm:4.41.0"
checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4
languageName: node
linkType: hard
@@ -6139,12 +6123,12 @@ __metadata:
languageName: node
linkType: hard
"widest-line@npm:^6.0.0":
version: 6.0.0
resolution: "widest-line@npm:6.0.0"
"widest-line@npm:^5.0.0":
version: 5.0.0
resolution: "widest-line@npm:5.0.0"
dependencies:
string-width: "npm:^8.1.0"
checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7
string-width: "npm:^7.0.0"
checksum: 10c0/6bd6cca8cda502ef50e05353fd25de0df8c704ffc43ada7e0a9cf9a5d4f4e12520485d80e0b77cec8a21f6c3909042fcf732aa9281e5dbb98cc9384a138b2578
languageName: node
linkType: hard
@@ -1,7 +1,7 @@
## Base documentation
- Documentation: https://docs.twenty.com/developers/extend/capabilities/apps
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/fixtures/postcard-app
- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-sdk/src/app-seeds/rich-app
## Common Pitfalls
@@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c
First, authenticate to your workspace:
```bash
yarn twenty remote add --api-url http://localhost:2020 --as local
yarn twenty remote add --local
```
Then, start development mode to sync your app and watch for changes:
@@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
```bash
# Remotes & Authentication
yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty
yarn twenty remote add --local # Authenticate with Twenty
yarn twenty remote status # Check auth status
yarn twenty remote switch # Switch default remote
yarn twenty remote list # List all configured remotes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-client-sdk",
"version": "0.8.0-canary.8",
"version": "0.7.0-canary.0",
"sideEffects": false,
"license": "AGPL-3.0",
"scripts": {
@@ -38,6 +38,9 @@ type ApplicationRegistration {
id: UUID!
universalIdentifier: String!
name: String!
description: String
logoUrl: String
author: String
oAuthClientId: String!
oAuthRedirectUris: [String!]!
oAuthScopes: [String!]!
@@ -45,6 +48,8 @@ type ApplicationRegistration {
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
websiteUrl: String
termsUrl: String
isListed: Boolean!
isFeatured: Boolean!
createdAt: DateTime!
@@ -338,7 +343,6 @@ type Field {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID!
isLabelSyncedWithName: Boolean
morphId: UUID
createdAt: DateTime!
@@ -536,7 +540,6 @@ input FieldFilter {
isActive: BooleanFieldComparison
isSystem: BooleanFieldComparison
isUIReadOnly: BooleanFieldComparison
objectMetadataId: UUIDFilterComparison
}
input IndexFilter {
@@ -775,7 +778,8 @@ type Workspace {
viewGroups: [ViewGroup!]
viewSorts: [ViewSort!]
metadataVersion: Float!
databaseSchema: String
databaseUrl: String!
databaseSchema: String!
subdomain: String!
customDomain: String
isGoogleAuthEnabled: Boolean!
@@ -1720,26 +1724,29 @@ enum FeatureFlagKey {
IS_UNIQUE_INDEXES_ENABLED
IS_JSON_FILTER_ENABLED
IS_AI_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_APPLICATION_ENABLED
IS_MARKETPLACE_ENABLED
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED
IS_PUBLIC_DOMAIN_ENABLED
IS_EMAILING_DOMAIN_ENABLED
IS_DASHBOARD_V2_ENABLED
IS_ATTACHMENT_MIGRATED
IS_NOTE_TARGET_MIGRATED
IS_TASK_TARGET_MIGRATED
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED
IS_JUNCTION_RELATIONS_ENABLED
IS_COMMAND_MENU_ITEM_ENABLED
IS_NAVIGATION_MENU_ITEM_ENABLED
IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED
IS_DRAFT_EMAIL_ENABLED
IS_USAGE_ANALYTICS_ENABLED
IS_RICH_TEXT_V1_MIGRATED
IS_DIRECT_GRAPHQL_EXECUTION_ENABLED
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED
IS_CONNECTED_ACCOUNT_MIGRATED
IS_GRAPHQL_QUERY_TIMING_ENABLED
IS_RECORD_TABLE_WIDGET_ENABLED
IS_DATASOURCE_MIGRATED
}
type ClientConfigMaintenanceMode {
startAt: DateTime!
endAt: DateTime!
link: String
}
type ClientConfig {
@@ -1770,14 +1777,6 @@ type ClientConfig {
calendarBookingPageId: String
isCloudflareIntegrationEnabled: Boolean!
isClickHouseConfigured: Boolean!
isWorkspaceSchemaDDLLocked: Boolean!
maintenance: ClientConfigMaintenanceMode
}
type UsageBreakdownItem {
key: String!
label: String
creditsUsed: Float!
}
type ConfigVariable {
@@ -1968,12 +1967,6 @@ type AdminPanelHealthServiceData {
queues: [AdminPanelWorkerQueueHealth!]
}
type MaintenanceMode {
startAt: DateTime!
endAt: DateTime!
link: String
}
type ModelsDevModelSuggestion {
modelId: String!
name: String!
@@ -2280,38 +2273,6 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
}
type DeleteTwoFactorAuthenticationMethod {
"""Boolean that confirms query was dispatched"""
success: Boolean!
@@ -2329,6 +2290,11 @@ type AuthorizeApp {
redirectUrl: String!
}
type AuthToken {
token: String!
expiresAt: DateTime!
}
type AuthTokenPair {
accessOrWorkspaceAgnosticToken: AuthToken!
refreshToken: AuthToken!
@@ -2407,26 +2373,6 @@ type Impersonate {
workspace: WorkspaceUrlsAndId!
}
type UsageTimeSeries {
date: String!
creditsUsed: Float!
}
type UsageUserDaily {
userWorkspaceId: String!
dailyUsage: [UsageTimeSeries!]!
}
type UsageAnalytics {
usageByUser: [UsageBreakdownItem!]!
usageByOperationType: [UsageBreakdownItem!]!
usageByModel: [UsageBreakdownItem!]!
timeSeries: [UsageTimeSeries!]!
periodStart: DateTime!
periodEnd: DateTime!
userDailyUsage: UsageUserDaily
}
type DevelopmentApplication {
id: String!
universalIdentifier: String!
@@ -2437,6 +2383,11 @@ type WorkspaceMigration {
actions: JSON!
}
type ApplicationTokenPair {
applicationAccessToken: AuthToken!
applicationRefreshToken: AuthToken!
}
type File {
id: UUID!
path: String!
@@ -2444,30 +2395,91 @@ type File {
createdAt: DateTime!
}
type MarketplaceAppField {
name: String!
type: String!
label: String!
description: String
icon: String
objectUniversalIdentifier: String
universalIdentifier: String
}
type MarketplaceAppObject {
universalIdentifier: String!
nameSingular: String!
namePlural: String!
labelSingular: String!
labelPlural: String!
description: String
icon: String
fields: [MarketplaceAppField!]!
}
type MarketplaceAppLogicFunction {
name: String!
description: String
timeoutSeconds: Int
}
type MarketplaceAppFrontComponent {
name: String!
description: String
}
type MarketplaceAppRoleObjectPermission {
objectUniversalIdentifier: String!
canReadObjectRecords: Boolean
canUpdateObjectRecords: Boolean
canSoftDeleteObjectRecords: Boolean
canDestroyObjectRecords: Boolean
}
type MarketplaceAppRoleFieldPermission {
objectUniversalIdentifier: String!
fieldUniversalIdentifier: String!
canReadFieldValue: Boolean
canUpdateFieldValue: Boolean
}
type MarketplaceAppDefaultRole {
id: String!
label: String!
description: String
canReadAllObjectRecords: Boolean!
canUpdateAllObjectRecords: Boolean!
canSoftDeleteAllObjectRecords: Boolean!
canDestroyAllObjectRecords: Boolean!
canUpdateAllSettings: Boolean!
canAccessAllTools: Boolean!
objectPermissions: [MarketplaceAppRoleObjectPermission!]!
fieldPermissions: [MarketplaceAppRoleFieldPermission!]!
permissionFlags: [String!]!
}
type MarketplaceApp {
id: String!
name: String!
description: String!
icon: String!
version: String!
author: String!
category: String!
logo: String
screenshots: [String!]!
aboutDescription: String!
providers: [String!]!
websiteUrl: String
termsUrl: String
objects: [MarketplaceAppObject!]!
fields: [MarketplaceAppField!]!
logicFunctions: [MarketplaceAppLogicFunction!]!
frontComponents: [MarketplaceAppFrontComponent!]!
defaultRole: MarketplaceAppDefaultRole
sourcePackage: String
isFeatured: Boolean!
}
type MarketplaceAppDetail {
universalIdentifier: String!
id: String!
name: String!
sourceType: ApplicationRegistrationSourceType!
sourcePackage: String
latestAvailableVersion: String
isListed: Boolean!
isFeatured: Boolean!
manifest: JSON
}
type PublicDomain {
id: UUID!
domain: String!
@@ -2556,6 +2568,53 @@ type PostgresCredentials {
workspaceId: UUID!
}
type UsageBreakdownItem {
key: String!
label: String
creditsUsed: Float!
}
type UsageTimeSeries {
date: String!
creditsUsed: Float!
}
type UsageUserDaily {
userWorkspaceId: String!
dailyUsage: [UsageTimeSeries!]!
}
type UsageAnalytics {
usageByUser: [UsageBreakdownItem!]!
usageByOperationType: [UsageBreakdownItem!]!
timeSeries: [UsageTimeSeries!]!
periodStart: DateTime!
periodEnd: DateTime!
userDailyUsage: UsageUserDaily
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
}
type FrontComponent {
id: UUID!
name: String!
description: String
sourceComponentPath: String!
builtComponentPath: String!
componentName: String!
builtComponentChecksum: String!
universalIdentifier: UUID
applicationId: UUID!
createdAt: DateTime!
updatedAt: DateTime!
isHeadless: Boolean!
usesSdkClient: Boolean!
applicationTokenPair: ApplicationTokenPair
}
type CommandMenuItem {
id: UUID!
workflowVersionId: UUID
@@ -2580,15 +2639,20 @@ enum EngineComponentKey {
NAVIGATE_TO_NEXT_RECORD
NAVIGATE_TO_PREVIOUS_RECORD
CREATE_NEW_RECORD
DELETE_RECORDS
RESTORE_RECORDS
DESTROY_RECORDS
DELETE_SINGLE_RECORD
DELETE_MULTIPLE_RECORDS
RESTORE_SINGLE_RECORD
RESTORE_MULTIPLE_RECORDS
DESTROY_SINGLE_RECORD
DESTROY_MULTIPLE_RECORDS
ADD_TO_FAVORITES
REMOVE_FROM_FAVORITES
EXPORT_NOTE_TO_PDF
EXPORT_RECORDS
EXPORT_FROM_RECORD_INDEX
EXPORT_FROM_RECORD_SHOW
UPDATE_MULTIPLE_RECORDS
MERGE_MULTIPLE_RECORDS
EXPORT_MULTIPLE_RECORDS
IMPORT_RECORDS
EXPORT_VIEW
SEE_DELETED_RECORDS
@@ -2631,15 +2695,6 @@ enum EngineComponentKey {
VIEW_PREVIOUS_AI_CHATS
TRIGGER_WORKFLOW_VERSION
FRONT_COMPONENT_RENDERER
DELETE_SINGLE_RECORD
DELETE_MULTIPLE_RECORDS
RESTORE_SINGLE_RECORD
RESTORE_MULTIPLE_RECORDS
DESTROY_SINGLE_RECORD
DESTROY_MULTIPLE_RECORDS
EXPORT_FROM_RECORD_INDEX
EXPORT_FROM_RECORD_SHOW
EXPORT_MULTIPLE_RECORDS
}
enum CommandMenuItemAvailabilityType {
@@ -2804,12 +2859,10 @@ type AgentChatThread {
type AgentMessage {
id: UUID!
threadId: UUID!
turnId: UUID
turnId: UUID!
agentId: UUID
role: String!
status: String!
parts: [AgentMessagePart!]!
processedAt: DateTime
createdAt: DateTime!
}
@@ -2824,22 +2877,6 @@ type AISystemPromptPreview {
estimatedTokenCount: Int!
}
type ChatStreamCatchupChunks {
chunks: [JSON!]!
maxSeq: Int!
}
type SendChatMessageResult {
messageId: String!
queued: Boolean!
streamId: String
}
type AgentChatEvent {
threadId: String!
event: JSON!
}
type AgentChatThreadEdge {
"""The node containing the AgentChatThread"""
node: AgentChatThread!
@@ -3053,7 +3090,6 @@ enum AllMetadataName {
navigationMenuItem
permissionFlag
objectPermission
fieldPermission
frontComponent
webhook
}
@@ -3187,7 +3223,6 @@ type Query {
minimalMetadata: MinimalMetadata!
chatThread(id: UUID!): AgentChatThread!
chatMessages(threadId: UUID!): [AgentMessage!]!
chatStreamCatchupChunks(threadId: UUID!): ChatStreamCatchupChunks!
getAISystemPromptPreview: AISystemPromptPreview!
skills: [Skill!]!
skill(id: UUID!): Skill
@@ -3217,7 +3252,6 @@ type Query {
findApplicationRegistrationStats(id: String!): ApplicationRegistrationStats!
findApplicationRegistrationVariables(applicationRegistrationId: String!): [ApplicationRegistrationVariable!]!
applicationRegistrationTarballUrl(id: String!): String
getApplicationShareLink(id: String!): String!
currentUser: User!
currentWorkspace: Workspace!
getPublicWorkspaceDataByDomain(origin: String): PublicWorkspaceData!
@@ -3237,16 +3271,14 @@ type Query {
getAiProviders: JSON!
getModelsDevProviders: [ModelsDevProviderSuggestion!]!
getModelsDevSuggestions(providerType: String!): [ModelsDevModelSuggestion!]!
getAdminAiUsageByWorkspace(periodStart: DateTime, periodEnd: DateTime): [UsageBreakdownItem!]!
getMaintenanceMode: MaintenanceMode
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
getPostgresCredentials: PostgresCredentials
findManyPublicDomains: [PublicDomain!]!
getEmailingDomains: [EmailingDomain!]!
findManyMarketplaceApps: [MarketplaceApp!]!
findMarketplaceAppDetail(universalIdentifier: String!): MarketplaceAppDetail!
findOneMarketplaceApp(universalIdentifier: String!): MarketplaceApp!
findManyApplications: [Application!]!
findOneApplication(id: UUID, universalIdentifier: UUID): Application!
getUsageAnalytics(input: UsageAnalyticsInput): UsageAnalytics!
}
input GetApiKeyInput {
@@ -3359,14 +3391,6 @@ input UsageAnalyticsInput {
periodStart: DateTime
periodEnd: DateTime
userWorkspaceId: String
operationTypes: [UsageOperationType!]
}
enum UsageOperationType {
AI_CHAT_TOKEN
AI_WORKFLOW_TOKEN
WORKFLOW_EXECUTION
CODE_EXECUTION
}
type Mutation {
@@ -3477,7 +3501,6 @@ type Mutation {
createViewGroup(input: CreateViewGroupInput!): ViewGroup!
createManyViewGroups(inputs: [CreateViewGroupInput!]!): [ViewGroup!]!
updateViewGroup(input: UpdateViewGroupInput!): ViewGroup!
updateManyViewGroups(inputs: [UpdateViewGroupInput!]!): [ViewGroup!]!
deleteViewGroup(input: DeleteViewGroupInput!): ViewGroup!
destroyViewGroup(input: DestroyViewGroupInput!): ViewGroup!
updateMessageFolder(input: UpdateMessageFolderInput!): MessageFolder!
@@ -3489,9 +3512,6 @@ type Mutation {
updateWebhook(input: UpdateWebhookInput!): Webhook!
deleteWebhook(id: UUID!): Webhook!
createChatThread: AgentChatThread!
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String): SendChatMessageResult!
stopAgentChatStream(threadId: UUID!): Boolean!
deleteQueuedChatMessage(messageId: UUID!): Boolean!
createSkill(input: CreateSkillInput!): Skill!
updateSkill(input: UpdateSkillInput!): Skill!
deleteSkill(id: UUID!): Skill!
@@ -3559,8 +3579,6 @@ type Mutation {
removeAiProvider(providerName: String!): Boolean!
addModelToProvider(providerName: String!, modelConfig: JSON!): Boolean!
removeModelFromProvider(providerName: String!, modelName: String!): Boolean!
setMaintenanceMode(startAt: DateTime!, endAt: DateTime!, link: String): Boolean!
clearMaintenanceMode: Boolean!
enablePostgresProxy: PostgresCredentials!
disablePostgresProxy: PostgresCredentials!
createPublicDomain(domain: String!): PublicDomain!
@@ -3571,7 +3589,6 @@ type Mutation {
verifyEmailingDomain(id: String!): EmailingDomain!
createOneAppToken(input: CreateOneAppTokenInput!): AppToken!
installMarketplaceApp(universalIdentifier: String!, version: String): Boolean!
syncMarketplaceCatalog: Boolean!
installApplication(appRegistrationId: String!, version: String): Boolean!
runWorkspaceMigration(workspaceMigration: WorkspaceMigrationInput!): Boolean!
uninstallApplication(universalIdentifier: String!): Boolean!
@@ -3808,13 +3825,8 @@ input UpsertFieldsWidgetGroupInput {
}
input UpsertFieldsWidgetFieldInput {
"""The id of the view field. Required if fieldMetadataId is not provided."""
viewFieldId: UUID
"""
The id of the field metadata. Used to create a new view field when viewFieldId is not provided.
"""
fieldMetadataId: UUID
"""The id of the view field"""
viewFieldId: UUID!
isVisible: Boolean!
position: Float!
}
@@ -4260,8 +4272,8 @@ input CreateFieldInput {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID!
isLabelSyncedWithName: Boolean
objectMetadataId: UUID!
isRemoteCreation: Boolean
relationCreationPayload: JSON
morphRelationsCreationPayload: [JSON!]
@@ -4289,7 +4301,6 @@ input UpdateFieldInput {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID
isLabelSyncedWithName: Boolean
morphRelationsUpdatePayload: [JSON!]
}
@@ -4422,9 +4433,14 @@ input GetAuthorizationUrlForSSOInput {
input CreateApplicationRegistrationInput {
name: String!
description: String
logoUrl: String
author: String
universalIdentifier: String
oAuthRedirectUris: [String!]
oAuthScopes: [String!]
websiteUrl: String
termsUrl: String
}
input UpdateApplicationRegistrationInput {
@@ -4434,8 +4450,13 @@ input UpdateApplicationRegistrationInput {
input UpdateApplicationRegistrationPayload {
name: String
description: String
logoUrl: String
author: String
oAuthRedirectUris: [String!]
oAuthScopes: [String!]
websiteUrl: String
termsUrl: String
isListed: Boolean
}
@@ -4583,7 +4604,6 @@ enum FileFolder {
type Subscription {
onEventSubscription(eventStreamId: String!): EventSubscription
logicFunctionLogs(input: LogicFunctionLogsInput!): LogicFunctionLogs!
onAgentChatEvent(threadId: UUID!): AgentChatEvent!
}
input LogicFunctionLogsInput {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -14
View File
@@ -1,19 +1,6 @@
{
"compileOnSave": false,
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2018",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
@@ -1 +0,0 @@
/bin/sh /etc/s6-overlay/scripts/register-crons.sh
@@ -1,13 +1,10 @@
#!/bin/sh
set -e
step_start() { echo "==> START $1"; }
step_done() { echo "==> DONE"; }
# Wait for PostgreSQL to be ready (timeout after 60s)
step_start "Waiting for PostgreSQL"
echo "Waiting for PostgreSQL..."
TRIES=0
until su-exec postgres pg_isready -h localhost > /dev/null 2>&1; do
until su-exec postgres pg_isready -h localhost; do
TRIES=$((TRIES + 1))
if [ "$TRIES" -ge 120 ]; then
echo "ERROR: PostgreSQL did not become ready within 60s"
@@ -15,7 +12,7 @@ until su-exec postgres pg_isready -h localhost > /dev/null 2>&1; do
fi
sleep 0.5
done
step_done
echo "PostgreSQL is ready."
# Create role if it doesn't exist
su-exec postgres psql -h localhost -tc \
@@ -34,36 +31,26 @@ has_schema=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
"SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')")
if [ "$has_schema" = "f" ]; then
step_start "Running initial database setup"
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/database/scripts/setup-db.js
step_done
echo "Database appears to be empty, running initial setup..."
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
fi
step_start "Running migrations"
yarn database:migrate:prod --force
step_done
# Always run migrations (idempotent — skips already-applied ones)
yarn database:migrate:prod
step_start "Flushing cache"
yarn command:prod cache:flush
step_done
step_start "Running upgrade"
yarn command:prod upgrade
step_done
step_start "Flushing cache"
yarn command:prod cache:flush
step_done
# Only seed on first boot — check if the dev workspace already exists
has_workspace=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
"SELECT EXISTS (SELECT 1 FROM core.workspace WHERE id = '20202020-1c25-4d02-bf25-6aeccf7ea419')")
if [ "$has_workspace" = "f" ]; then
step_start "Seeding workspace data"
echo "Seeding app dev data..."
yarn command:prod workspace:seed:dev --light || true
step_done
else
echo "Dev workspace already seeded, skipping."
fi
echo "==> START Database ready"
echo "==> DONE"
echo "Database initialization complete."
@@ -1,9 +0,0 @@
#!/bin/sh
set -e
echo "==> START Registering cron jobs"
cd /app/packages/twenty-server
yarn command:prod cron:register:all --dev-mode
echo "==> DONE"
+11 -7
View File
@@ -16,7 +16,6 @@ COPY ./packages/twenty-server/patches /app/packages/twenty-server/patches
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-front/package.json /app/packages/twenty-front/
COPY ./packages/twenty-front-component-renderer/package.json /app/packages/twenty-front-component-renderer/
COPY ./packages/twenty-sdk/package.json /app/packages/twenty-sdk/
COPY ./packages/twenty-client-sdk/package.json /app/packages/twenty-client-sdk/
@@ -39,6 +38,12 @@ RUN npx nx run twenty-server:lingui:extract && \
RUN npx nx run twenty-server:build
# Bundle setup-db script into a standalone JS file so the final image
# doesn't need tsx or the TypeScript source tree at runtime.
RUN npx esbuild packages/twenty-server/scripts/setup-db.ts \
--bundle --platform=node --outfile=packages/twenty-server/dist/scripts/setup-db.js \
--external:typeorm --external:dotenv --external:pg
# Clean server build output (type declarations and compiled tests are not needed at runtime;
# source maps are kept because twenty-infra extracts them from the image for Sentry uploads)
RUN find /app/packages/twenty-server/dist -name '*.d.ts' -delete \
@@ -52,7 +57,6 @@ FROM common-deps AS twenty-front-build
ARG REACT_APP_SERVER_BASE_URL
COPY ./packages/twenty-front /app/packages/twenty-front
COPY ./packages/twenty-front-component-renderer /app/packages/twenty-front-component-renderer
COPY ./packages/twenty-ui /app/packages/twenty-ui
COPY ./packages/twenty-shared /app/packages/twenty-shared
COPY ./packages/twenty-sdk /app/packages/twenty-sdk
@@ -195,9 +199,9 @@ RUN find /app/packages/twenty-server/dist -name '*.js.map' -delete
# s6 service definitions
COPY packages/twenty-docker/twenty-app-dev/rootfs/ /
RUN mkdir -p /data/postgres /data/redis /app/packages/twenty-server/.local-storage \
RUN mkdir -p /data/postgres /data/redis /app/.local-storage \
&& chown -R postgres:postgres /data/postgres \
&& chown 1000:1000 /data/redis /app/packages/twenty-server/.local-storage
&& chown 1000:1000 /data/redis /app/.local-storage
ARG REACT_APP_SERVER_BASE_URL
ARG APP_VERSION=0.0.0
@@ -211,14 +215,14 @@ ENV PG_DATABASE_URL=postgres://twenty:twenty@localhost:5432/default \
REACT_APP_SERVER_BASE_URL=$REACT_APP_SERVER_BASE_URL \
APP_VERSION=$APP_VERSION \
NODE_ENV=development \
NODE_PORT=2020 \
NODE_PORT=3000 \
DISABLE_DB_MIGRATIONS=true \
DISABLE_CRON_JOBS_REGISTRATION=true \
IS_BILLING_ENABLED=false \
SIGN_IN_PREFILLED=true
EXPOSE 2020
VOLUME ["/data/postgres", "/app/packages/twenty-server/.local-storage"]
EXPOSE 3000
VOLUME ["/data/postgres", "/app/.local-storage"]
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="All-in-one Twenty image for local development and SDK usage. Includes PostgreSQL, Redis, server, and worker."
+2 -1
View File
@@ -13,7 +13,8 @@ setup_and_migrate_db() {
has_schema=$(psql -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')" ${PG_DATABASE_URL})
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running migrations."
yarn database:init:prod
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
yarn database:migrate:prod
fi
yarn command:prod cache:flush
File diff suppressed because it is too large Load Diff
@@ -4,142 +4,83 @@ description: Create your first Twenty app in minutes.
---
<Warning>
Apps are currently in alpha. The feature works but is still evolving.
Apps are currently in alpha testing. The feature is functional but still evolving.
</Warning>
Apps let you extend Twenty with custom objects, fields, logic functions, AI skills, and UI components — all managed as code.
**What you can do today:**
- Define custom objects and fields as code (managed data model)
- Build logic functions with custom triggers (HTTP routes, cron, database events)
- Define skills for AI agents
- Build front components that render inside Twenty's UI
- Deploy the same app across multiple workspaces
## Prerequisites
Before you begin, make sure the following is installed on your machine:
- Node.js 24+ and Yarn 4
- Docker (for the local Twenty dev server)
- **Node.js 24+** — [Download here](https://nodejs.org/)
- **Yarn 4** — Comes with Node.js via Corepack. Enable it by running `corepack enable`
- **Docker** — [Download here](https://www.docker.com/products/docker-desktop/). Required to run a local Twenty instance. Not needed if you already have a Twenty server running.
## Getting Started
## Step 1: Scaffold your app
Open a terminal and run:
Create a new app using the official scaffolder, then authenticate and start developing:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
You will be prompted to enter a name and a description for your app. Press **Enter** to accept the defaults.
This creates a new folder called `my-twenty-app` with everything you need.
<Note>
The scaffolder supports these flags:
- `--minimal` — scaffold only the essential files, no examples (default)
- `--exhaustive` — scaffold all example entities
- `--name <name>` — set the app name (skips the prompt)
- `--display-name <displayName>` — set the display name (skips the prompt)
- `--description <description>` — set the description (skips the prompt)
- `--skip-local-instance` — skip the local server setup prompt
</Note>
## Step 2: Set up a local Twenty instance
The scaffolder will ask:
> **Would you like to set up a local Twenty instance?**
- **Type `yes`** (recommended) — This pulls the `twenty-app-dev` Docker image and starts a local Twenty server on port `2020`. Make sure Docker is running before you continue.
- **Type `no`** — Choose this if you already have a Twenty server running locally.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Should start local instance?" />
</div>
## Step 3: Sign in to your workspace
Next, a browser window will open with the Twenty login page. Sign in with the pre-seeded demo account:
- **Email:** `tim@apple.dev`
- **Password:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty login screen" />
</div>
## Step 4: Authorize the app
After you sign in, you will see an authorization screen. This lets your app interact with your workspace.
Click **Authorize** to continue.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI authorization screen" />
</div>
Once authorized, your terminal will confirm that everything is set up.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App scaffolded successfully" />
</div>
## Step 5: Start developing
Go into your new app folder and start the development server:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
This watches your source files, rebuilds on every change, and syncs your app to the local Twenty server automatically. You should see a live status panel in your terminal.
For more detailed output (build logs, sync requests, error traces), use the `--verbose` flag:
The scaffolder supports two modes for controlling which example files are included:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
Dev mode is only available on Twenty instances running in development (`NODE_ENV=development`). Production instances reject dev sync requests. Use `yarn twenty deploy` to deploy to production servers — see [Publishing Apps](/developers/extend/apps/publishing) for details.
</Warning>
From here you can:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Dev mode terminal output" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Step 6: See your app in Twenty
# Watch your application's function logs
yarn twenty function:logs
Open [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in your browser. Navigate to **Settings > Apps** and select the **Developer** tab. You should see your app listed under **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Your Apps list showing My twenty app" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Click on **My twenty app** to open its **application registration**. A registration is a server-level record that describes your app — its name, unique identifier, OAuth credentials, and source (local, npm, or tarball). It lives on the server, not inside any specific workspace. When you install an app into a workspace, Twenty creates a workspace-scoped **application** that points back to this registration. One registration can be installed across multiple workspaces on the same server.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Application registration details" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Click **View installed app** to see the installed app. The **About** tab shows the current version and management options:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installed app — About tab" />
</div>
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Switch to the **Content** tab to see everything your app provides — objects, fields, logic functions, and agents:
## Project structure (scaffolded)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installed app — Content tab" />
</div>
When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
You are all set! Edit any file in `src/` and the changes will be picked up automatically.
- Copies a minimal base application into `my-twenty-app/`
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
- Creates config files and scripts wired to the `twenty` CLI
- Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode
Head over to [Building Apps](/developers/extend/apps/building) for a detailed guide on creating objects, logic functions, front components, skills, and more.
---
## Project structure
The scaffolder generates the following file structure (shown with `--exhaustive` mode, which includes examples for every entity type):
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +93,123 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
By default (`--minimal`), only the core files are created: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`. Use `--exhaustive` to include all the example files shown above.
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`).
### Key files
At a high level:
| File / Folder | Purpose |
|---|---|
| `package.json` | Declares your app name, version, and dependencies. Includes a `twenty` script so you can run `yarn twenty help` to see all commands. |
| `src/application-config.ts` | **Required.** The main configuration file for your app. |
| `src/roles/` | Defines roles that control what your logic functions can access. |
| `src/logic-functions/` | Server-side functions triggered by routes, cron schedules, or database events. |
| `src/front-components/` | React components that render inside Twenty's UI. |
| `src/objects/` | Custom object definitions to extend your data model. |
| `src/fields/` | Custom fields added to existing objects. |
| `src/views/` | Saved view configurations. |
| `src/navigation-menu-items/` | Custom links in the sidebar navigation. |
| `src/skills/` | Skills that extend Twenty's AI agents. |
| `src/agents/` | AI agents with custom prompts. |
| `src/page-layouts/` | Custom page layouts for record views. |
| `src/__tests__/` | Integration tests (setup + example test). |
| `public/` | Static assets (images, fonts) served with your app. |
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus a `twenty` script that delegates to the local `twenty` CLI. Run `yarn twenty help` to list all available commands.
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
- **.nvmrc**: Pins the Node.js version expected by the project.
- **.oxlintrc.json** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
- **README.md**: A short README in the app root with basic instructions.
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- **src/**: The main place where you define your application-as-code
## Managing remotes
### Entity detection
A **remote** is a Twenty server that your app connects to. During setup, the scaffolder creates one for you automatically. You can add more remotes or switch between them at any time.
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Your credentials are stored in `~/.twenty/config.json`.
## Local development server (`yarn twenty server`)
The CLI can manage a local Twenty server running in Docker. This is the same server started automatically when you scaffold an app with `create-twenty-app`, but you can also manage it manually.
### Starting the server
```bash filename="Terminal"
yarn twenty server start
```
This pulls the `twentycrm/twenty-app-dev:latest` Docker image (if not already present), creates a container named `twenty-app-dev`, and starts it on port **2020**. The CLI waits until the server passes its health check before returning.
Two Docker volumes are created to persist data between restarts:
- `twenty-app-dev-data` — PostgreSQL database
- `twenty-app-dev-storage` — file storage
If port 2020 is already in use, you can start on a different port:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
The CLI automatically configures the container's internal `NODE_PORT` and `SERVER_URL` to match the chosen port, so logic functions, OAuth, and all other internal networking work correctly.
Once started, the server is automatically registered as the `local` remote in your CLI config.
### Checking server status
```bash filename="Terminal"
yarn twenty server status
```
Displays whether the server is running, its URL, and the default login credentials (`tim@apple.dev` / `tim@apple.dev`).
### Viewing server logs
```bash filename="Terminal"
yarn twenty server logs
```
Streams the container logs. Use `--lines` to control how many recent lines to show:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Stopping the server
```bash filename="Terminal"
yarn twenty server stop
```
Stops the container. Your data is preserved in the Docker volumes — the next `start` picks up where you left off.
### Resetting the server
```bash filename="Terminal"
yarn twenty server reset
```
Removes the container **and** deletes both Docker volumes, wiping all data. The next `start` creates a fresh instance.
| Helper function | Entity type |
|-----------------|-------------|
| `defineObject` | Custom object definitions |
| `defineLogicFunction` | Logic function definitions |
| `definePreInstallLogicFunction` | Pre-install logic function (runs before installation) |
| `definePostInstallLogicFunction` | Post-install logic function (runs after installation) |
| `defineFrontComponent` | Front component definitions |
| `defineRole` | Role definitions |
| `defineField` | Field extensions for existing objects |
| `defineView` | Saved view definitions |
| `defineNavigationMenuItem` | Navigation menu item definitions |
| `defineSkill` | AI agent skill definitions |
<Note>
The server requires **Docker** to be running. If you see a "Docker not running" error, make sure Docker Desktop (or the Docker daemon) is started.
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
</Note>
### Command reference
Example of a detected entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
| Command | Description |
|---------|-------------|
| `yarn twenty server start` | Start the local server (pulls image if needed) |
| `yarn twenty server start --port 3030` | Start on a custom port |
| `yarn twenty server stop` | Stop the server (preserves data) |
| `yarn twenty server status` | Show server status, URL, and credentials |
| `yarn twenty server logs` | Stream server logs |
| `yarn twenty server logs --lines 100` | Show the last 100 log lines |
| `yarn twenty server reset` | Delete all data and start fresh |
## CI with GitHub Actions
The scaffolder generates a ready-to-use GitHub Actions workflow at `.github/workflows/ci.yml`. It runs your integration tests automatically on every push to `main` and on pull requests.
The workflow:
1. Checks out your code
2. Spins up a temporary Twenty server using the `twentyhq/twenty/.github/actions/spawn-twenty-docker-image` action
3. Installs dependencies with `yarn install --immutable`
4. Runs `yarn test` with `TWENTY_API_URL` and `TWENTY_API_KEY` injected from the action outputs
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
You don't need to configure any secrets — the `spawn-twenty-docker-image` action starts an ephemeral Twenty server directly in the runner and outputs the connection details. The `GITHUB_TOKEN` secret is provided automatically by GitHub.
Later commands will add more files and folders:
To pin a specific Twenty version instead of `latest`, change the `TWENTY_VERSION` environment variable at the top of the workflow.
- `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
- `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
## Authentication
The first time you run `yarn twenty auth:login`, you'll be prompted for:
- API URL (defaults to http://localhost:3000 or your current workspace profile)
- API key
Your credentials are stored per-user in `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
### Managing workspaces
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
Once you've switched workspaces with `yarn twenty auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
## Manual setup (without the scaffolder)
If you prefer to set things up yourself instead of using `create-twenty-app`, you can do it in two steps.
**1. Add `twenty-sdk` and `twenty-client-sdk` as dependencies:**
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire a single script in your package.json:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Add a `twenty` script to your `package.json`:**
Then add a `twenty` script:
```json filename="package.json"
{
@@ -393,19 +219,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
You can now run `yarn twenty dev`, `yarn twenty help`, and all other commands.
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
<Note>
Do not install `twenty-sdk` globally. Always use it as a local project dependency so that each project can pin its own version.
</Note>
## How to use a local Twenty instance
If you're already running a Twenty instance locally (e.g. via `npx nx start twenty-server`), you can connect to it instead of using Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Troubleshooting
If you run into issues:
- Authentication errors: run `yarn twenty auth:login` and ensure your API key has the required permissions.
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
- Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client.
- Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
- Make sure **Docker is running** before starting the scaffolder with a local instance.
- Make sure you are using **Node.js 24+** (`node -v` to check).
- Make sure **Corepack is enabled** (`corepack enable`) so Yarn 4 is available.
- Try deleting `node_modules` and running `yarn install` again if dependencies seem broken.
Still stuck? Ask for help on the [Twenty Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: Distribute your Twenty app to the marketplace or deploy it internal
---
<Warning>
Apps are currently in alpha. The feature works but is still evolving.
Apps are currently in alpha testing. The feature is functional but still evolving.
</Warning>
## Overview
Once your app is [built and tested locally](/developers/extend/apps/building), you have two paths for distributing it:
- **Deploy a tarball** — upload your app directly to a specific Twenty server for internal or private use.
- **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install.
Both paths start from the same **build** step.
## Building your app
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
## Deploying to a server (tarball)
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can deploy a tarball directly to a Twenty server.
### Prerequisites
Before deploying, you need a configured remote pointing to the target server. Remotes store the server URL and authentication credentials locally in `~/.twenty/config.json`.
Add a remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Deploying
Build and upload your app to the server in one step:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Sharing a deployed app
Tarball apps are not listed in the public marketplace, so other workspaces on the same server won't discover them by browsing. To share a deployed app:
1. Go to **Settings > Applications > Registrations** and open your app
2. In the **Distribution** tab, click **Copy share link**
3. Share this link with users on other workspaces — it takes them directly to the app's install page
The share link uses the server's base URL (without any workspace subdomain) so it works for any workspace on the server.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Version management
To release an update:
1. Bump the `version` field in your `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Workspaces that have the app installed will see the upgrade available in their settings
{/* TODO: add screenshot of the Upgrade button */}
- **Push a tarball** — deploy your app to a specific Twenty server for internal use without making it publicly available.
## Publishing to npm
@@ -81,69 +21,29 @@ Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twe
### Requirements
- An [npm](https://www.npmjs.com) account
- The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
- Your package name **must** use the `twenty-app-` prefix (e.g., `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Steps
### Marketplace metadata
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
See the [defineApplication accordion](/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
1. **Build your app** — the CLI compiles your TypeScript sources and generates the application manifest:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
To publish under a specific dist-tag (e.g., `beta` or `next`):
2. **Publish to npm** — push the built package to the npm registry:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### How marketplace discovery works
### Auto-discovery
The Twenty server syncs its marketplace catalog from the npm registry **every hour**.
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
If your app does not define an `aboutDescription` in `defineApplication()`, the marketplace will automatically use your package's `README.md` from npm as the about page content. This means you can maintain a single README for both npm and the Twenty marketplace. If you want a different description in the marketplace, explicitly set `aboutDescription`.
</Note>
Packages with the `twenty-app-` prefix are automatically discovered by the Twenty marketplace catalog. Once published, your app appears in the marketplace within a few minutes — no manual registration or approval required.
### CI publishing
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
The scaffolded project includes a GitHub Actions workflow that publishes on every release. It runs `app:build`, then `npm publish --provenance` from the build output:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`.
For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `npx twenty build`, then `npm publish` from `.twenty/output`.
<Note>
<Tip>
**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions.
</Note>
</Tip>
## Installing apps
## Internal distribution
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can push a tarball directly to a Twenty server.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
### Push a tarball
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
Build your app and deploy it to a specific server in one step:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
Any workspace on that server can then install and upgrade the app from the **Applications** settings page.
### Version management
To release an update:
1. Bump the `version` field in your `package.json`
2. Push a new tarball with `npx twenty publish --server <server-url>`
3. Workspaces on that server will see the upgrade available in their settings
<Note>
Internal apps are scoped to the server they're pushed to. They won't appear in the public marketplace and can't be installed by workspaces on other servers.
</Note>
## App categories
Twenty organizes apps into three categories based on how they're distributed:
| Category | How it works | Visible in marketplace? |
|----------|-------------|------------------------|
| **Development** | Local dev mode apps running via `yarn twenty dev`. Used for building and testing. | No |
| **Published** | Apps published to npm with the `twenty-app-` prefix. Listed in the marketplace for any workspace to install. | Yes |
| **Internal** | Apps deployed via tarball to a specific server. Available only to workspaces on that server. | No |
<Tip>
Start in **Development** mode while building your app. When it's ready, choose **Published** (npm) for broad distribution or **Internal** (tarball) for private deployment.
</Tip>
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because it is too large Load Diff
@@ -4,142 +4,84 @@ description: أنشئ أول تطبيق Twenty خلال دقائق.
---
<Warning>
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
تتيح لك التطبيقات توسيع Twenty باستخدام كائنات وحقول ووظائف منطقية ومهارات ذكاء اصطناعي ومكونات واجهة مستخدم مخصصة — جميعها تُدار ككود.
**ما الذي يمكنك فعله اليوم:**
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
* أنشئ وظائف منطقية مع مشغلات مخصصة (مسارات HTTP، cron، أحداث قاعدة البيانات)
* حدد المهارات لوكلاء الذكاء الاصطناعي
* أنشئ مكونات واجهية تُعرَض داخل واجهة مستخدم Twenty
* انشر التطبيق نفسه عبر مساحات عمل متعددة
## المتطلبات الأساسية
قبل أن تبدأ، تأكّد من تثبيت ما يلي على جهازك:
* Node.js 24+ وYarn 4
* Docker (لخادم تطوير Twenty المحلي)
* **Node.js 24+** — [نزّل من هنا](https://nodejs.org/)
* **Yarn 4** — يأتي مع Node.js عبر Corepack. قم بتمكينه عبر تشغيل `corepack enable`
* **Docker** — [نزّل من هنا](https://www.docker.com/products/docker-desktop/). مطلوب لتشغيل مثيل محلي من Twenty. غير مطلوب إذا كان لديك خادم Twenty قيد التشغيل بالفعل.
## البدء
## الخطوة 1: إنشاء هيكل تطبيقك
افتح الطرفية وشغّل:
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
سيُطلب منك إدخال اسم ووصف لتطبيقك. اضغط **Enter** لقبول الإعدادات الافتراضية.
سيؤدي ذلك إلى إنشاء مجلد جديد باسم `my-twenty-app` يحتوي على كل ما تحتاجه.
<Note>
أداة إنشاء الهيكل تدعم الأعلام التالية:
* `--minimal` — إنشاء الهيكل للملفات الأساسية فقط، بدون أمثلة (افتراضي)
* `--exhaustive` — إنشاء الهيكل لجميع كيانات الأمثلة
* `--name <name>` — تعيين اسم التطبيق (يتخطى المطالبة)
* `--display-name <displayName>` — تعيين اسم العرض (يتخطى المطالبة)
* `--description <description>` — تعيين الوصف (يتخطى المطالبة)
* `--skip-local-instance` — تخطي مطالبة إعداد الخادم المحلي
</Note>
## الخطوة 2: إعداد مثيل محلي من Twenty
ستسأل أداة إنشاء الهيكل:
> **هل ترغب في إعداد مثيل محلي من Twenty؟**
* **اكتب `yes`** (موصى به) — سيؤدي ذلك إلى سحب صورة Docker `twenty-app-dev` وبدء تشغيل خادم Twenty محلي على المنفذ `2020`. تأكّد من أن Docker قيد التشغيل قبل المتابعة.
* **اكتب `no`** — اختر هذا إذا كان لديك خادم Twenty يعمل محليًا بالفعل.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="هل يجب بدء المثيل المحلي؟" />
</div>
## الخطوة 3: سجّل الدخول إلى مساحة العمل الخاصة بك
بعد ذلك، ستُفتح نافذة متصفح تعرض صفحة تسجيل الدخول الخاصة بـ Twenty. سجّل الدخول باستخدام حساب العرض التوضيحي المُجهَّز مسبقًا:
* **البريد الإلكتروني:** `tim@apple.dev`
* **كلمة المرور:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="شاشة تسجيل الدخول إلى Twenty" />
</div>
## الخطوة 4: تفويض التطبيق
بعد تسجيل الدخول، ستظهر لك شاشة تفويض. يتيح هذا لتطبيقك التفاعل مع مساحة العمل الخاصة بك.
انقر **Authorize** للمتابعة.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="شاشة تفويض واجهة الأوامر (CLI) الخاصة بـ Twenty" />
</div>
بمجرد منح التفويض، ستؤكّد الطرفية أن كل شيء قد تم إعداده.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="تم إنشاء هيكل التطبيق بنجاح" />
</div>
## الخطوة 5: ابدأ التطوير
انتقل إلى مجلد تطبيقك الجديد وابدأ خادم التطوير:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
يقوم هذا بمراقبة ملفات المصدر لديك، وإعادة البناء عند كل تغيير، ومزامنة تطبيقك تلقائيًا مع خادم Twenty المحلي. يفترض أن ترى لوحة حالة مباشرة في الطرفية.
للحصول على مخرجات أكثر تفصيلاً (سجلات البناء، طلبات المزامنة، تتبعات الأخطاء)، استخدم العلم `--verbose`:
يدعم المُهيئ وضعين للتحكم في ملفات الأمثلة التي سيتم تضمينها:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
وضع التطوير متاح فقط على مثيلات Twenty التي تعمل في وضع التطوير (`NODE_ENV=development`). المثيلات الإنتاجية ترفض طلبات مزامنة وضع التطوير. استخدم `yarn twenty deploy` للنشر إلى خوادم الإنتاج — اطّلع على [نشر التطبيقات](/l/ar/developers/extend/apps/publishing) للتفاصيل.
</Warning>
من هنا يمكنك:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="مخرجات الطرفية في وضع التطوير" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## الخطوة 6: اعرض تطبيقك في Twenty
# Watch your application's function logs
yarn twenty function:logs
افتح [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) في متصفحك. انتقل إلى **Settings > Apps** واختر علامة التبويب **Developer**. يُفترض أن ترى تطبيقك مُدرجًا تحت **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="قائمة &#x22;Your Apps&#x22; تعرض &#x22;My twenty app&#x22;" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
انقر على **My twenty app** لفتح **تسجيل التطبيق** الخاص به. التسجيل عبارة عن سجل على مستوى الخادم يصف تطبيقك — اسمه، والمعرّف الفريد، وبيانات اعتماد OAuth، والمصدر (محلي، npm، أو tarball). يُخزَّن على الخادم، وليس داخل أي مساحة عمل محددة. عند تثبيت تطبيق في مساحة عمل، ينشئ Twenty **تطبيقًا** بنطاق مساحة العمل يُشير مرة أخرى إلى هذا التسجيل. يمكن تثبيت تسجيل واحد عبر عدة مساحات عمل على الخادم نفسه.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="تفاصيل تسجيل التطبيق" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
انقر **View installed app** لعرض التطبيق المثبّت. تعرض علامة التبويب **About** الإصدار الحالي وخيارات الإدارة:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="التطبيق المثبّت — علامة تبويب About" />
</div>
راجع أيضًا: صفحات مرجع CLI لـ [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) و[twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
انتقل إلى علامة التبويب **Content** لمشاهدة كل ما يقدمه تطبيقك — الكائنات، والحقول، ودوال المنطق، والوكلاء:
## هيكل المشروع (مُنشأ بالقالب)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="التطبيق المثبّت — علامة تبويب Content" />
</div>
عند تشغيل `npx create-twenty-app@latest my-twenty-app`، يقوم المُهيئ بما يلي:
أنت جاهز تمامًا! حرّر أي ملف في `src/` وسيتم التقاط التغييرات تلقائيًا.
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
انتقل إلى [بناء التطبيقات](/l/ar/developers/extend/apps/building) للحصول على دليل مفصّل حول إنشاء الكائنات، ودوال المنطق، ومكونات الواجهة الأمامية، والمهارات، والمزيد.
---
## هيكل المشروع
تولّد أداة إنشاء الهيكل بنية الملفات التالية (مُبيّنة بوضع `--exhaustive` الذي يتضمن أمثلة لكل نوع من الكيانات):
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
افتراضيًا (`--minimal`)، تُنشأ الملفات الأساسية فقط: `application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`. استخدم `--exhaustive` لتضمين جميع ملفات الأمثلة الموضّحة أعلاه.
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`).
### الملفات الرئيسية
بشكل عام:
| ملف / مجلد | الغرض |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `package.json` | يصرّح باسم تطبيقك وإصداره واعتماداته. يتضمن نصًا برمجيًا باسم `twenty` بحيث يمكنك تشغيل `yarn twenty help` للاطلاع على جميع الأوامر. |
| `src/application-config.ts` | **مطلوب.** ملف الإعداد الرئيسي لتطبيقك. |
| `src/roles/` | يعرِّف الأدوار التي تتحكم بما يمكن لدوال المنطق الوصول إليه. |
| `src/logic-functions/` | دوال على جانب الخادم يتم تشغيلها عبر المسارات، وجداول cron، أو أحداث قاعدة البيانات. |
| `src/front-components/` | مكونات React تُعرَض داخل واجهة مستخدم Twenty. |
| `src/objects/` | تعريفات كائنات مخصّصة لتوسيع نموذج البيانات لديك. |
| `src/fields/` | حقول مخصّصة تُضاف إلى الكائنات الموجودة. |
| `src/views/` | تكوينات العروض المحفوظة. |
| `src/navigation-menu-items/` | روابط مخصّصة في شريط التنقل الجانبي. |
| `src/skills/` | مهارات توسّع قدرات وكلاء الذكاء الاصطناعي في Twenty. |
| `src/agents/` | وكلاء ذكاء اصطناعي مع موجهات مخصّصة. |
| `src/page-layouts/` | تخطيطات صفحات مخصّصة لعرض السجلات. |
| `src/__tests__/` | اختبارات تكامل (إعداد + اختبار مثال). |
| `public/` | أصول ثابتة (صور، خطوط) تُقدَّم مع تطبيقك. |
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **.oxlintrc.json** و **tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **README.md**: ملف README قصير في جذر التطبيق يتضمن تعليمات أساسية.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
## إدارة الريموتات
### اكتشاف الكيانات
**الريموت** هو خادم Twenty يتصل به تطبيقك. أثناء الإعداد، تُنشئ أداة إنشاء الهيكل واحدًا لك تلقائيًا. يمكنك إضافة ريموتات أخرى أو التبديل بينها في أي وقت.
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
تُخزَّن بيانات اعتمادك في `~/.twenty/config.json`.
## خادم التطوير المحلي (`yarn twenty server`)
يمكن لأداة سطر الأوامر (CLI) إدارة خادم Twenty محلي يعمل داخل Docker. هذا هو الخادم نفسه الذي يبدأ تلقائيًا عند إنشاء هيكل تطبيق باستخدام `create-twenty-app`، لكن يمكنك أيضًا إدارته يدويًا.
### بدء الخادم
```bash filename="Terminal"
yarn twenty server start
```
سيؤدي ذلك إلى سحب صورة Docker `twentycrm/twenty-app-dev:latest` (إن لم تكن موجودة بالفعل)، وإنشاء حاوية باسم `twenty-app-dev`، وبدء تشغيلها على المنفذ **2020**. تنتظر أداة CLI حتى يجتاز الخادم فحص السلامة قبل الإنهاء.
يتم إنشاء حجمين في Docker للاحتفاظ بالبيانات بين عمليات إعادة التشغيل:
* `twenty-app-dev-data` — قاعدة بيانات PostgreSQL
* `twenty-app-dev-storage` — تخزين ملفات
إذا كان المنفذ 2020 مستخدمًا بالفعل، يمكنك البدء على منفذ مختلف:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
تقوم أداة CLI تلقائيًا بتهيئة قيم `NODE_PORT` و`SERVER_URL` الداخلية في الحاوية لتطابق المنفذ المختار، بحيث تعمل دوال المنطق وOAuth وكل الشبكات الداخلية الأخرى بشكل صحيح.
بمجرد البدء، يُسجَّل الخادم تلقائيًا كـ `local` remote في إعدادات CLI لديك.
### التحقق من حالة الخادم
```bash filename="Terminal"
yarn twenty server status
```
يعرض ما إذا كان الخادم قيد التشغيل، وعنوان URL الخاص به، وبيانات اعتماد تسجيل الدخول الافتراضية (`tim@apple.dev` / `tim@apple.dev`).
### عرض سجلات الخادم
```bash filename="Terminal"
yarn twenty server logs
```
يبث سجلات الحاوية. استخدم `--lines` للتحكّم بعدد الأسطر الحديثة المراد عرضها:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### إيقاف الخادم
```bash filename="Terminal"
yarn twenty server stop
```
يوقف الحاوية. تُحفَظ بياناتك في أحجام Docker — وستُستأنف الحالة مع عملية `start` التالية من حيث توقفت.
### إعادة تعيين الخادم
```bash filename="Terminal"
yarn twenty server reset
```
يزيل الحاوية **و** يحذف كلا حجمي Docker، ممّا يمحو جميع البيانات. ستنشئ عملية `start` التالية مثيلًا جديدًا من البداية.
| دالة مساعدة | نوع الكيان |
| -------------------------------- | ---------------------------------------------- |
| `defineObject` | تعريفات كائنات مخصصة |
| `defineLogicFunction` | تعريفات الوظائف المنطقية |
| `definePreInstallLogicFunction` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
| `definePostInstallLogicFunction` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
| `defineFrontComponent` | تعريفات المكونات الواجهية |
| `defineRole` | تعريفات الأدوار |
| `defineField` | امتدادات الحقول للكائنات الموجودة |
| `defineView` | تعريفات العروض المحفوظة |
| `defineNavigationMenuItem` | تعريفات عناصر قائمة التنقل |
| `defineSkill` | تعريفات مهارات وكلاء الذكاء الاصطناعي |
<Note>
يتطلّب الخادم أن يكون **Docker** قيد التشغيل. إذا ظهرت لك رسالة خطأ "Docker not running"، فتأكّد من تشغيل Docker Desktop (أو خادوم Docker).
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
</Note>
### مرجع الأوامر
مثال على كيان تم اكتشافه:
| أمر | الوصف |
| -------------------------------------- | --------------------------------------------- |
| `yarn twenty server start` | بدء الخادم المحلي (يسحب الصورة إذا لزم الأمر) |
| `yarn twenty server start --port 3030` | ابدأ على منفذ مخصّص |
| `yarn twenty server stop` | إيقاف الخادم (مع الحفاظ على البيانات) |
| `yarn twenty server status` | عرض حالة الخادم، وعنوان URL، وبيانات الاعتماد |
| `yarn twenty server logs` | بث سجلات الخادم |
| `yarn twenty server logs --lines 100` | عرض آخر 100 سطر من السجلات |
| `yarn twenty server reset` | حذف جميع البيانات والبدء من جديد |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## التكامل المستمر (CI) باستخدام GitHub Actions
تولّد أداة إنشاء الهيكل سير عمل GitHub Actions جاهزًا للاستخدام في `.github/workflows/ci.yml`. يشغّل اختبارات التكامل لديك تلقائيًا عند كل دفع إلى `main` وعلى طلبات السحب.
سير العمل:
1. يجلب الشيفرة الخاصة بك
2. يشغّل خادم Twenty مؤقتًا باستخدام الإجراء `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. يثبّت الاعتمادات باستخدام `yarn install --immutable`
4. يشغّل `yarn test` مع حقن `TWENTY_API_URL` و`TWENTY_API_KEY` من مخرجات الإجراء
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
لا تحتاج إلى تهيئة أي أسرار — إذ يبدأ إجراء `spawn-twenty-docker-image` خادم Twenty عابرًا مباشرة في المشغّل ويُخرِج تفاصيل الاتصال. يتم توفير السر `GITHUB_TOKEN` تلقائيًا من قِبل GitHub.
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
لتثبيت إصدار محدّد من Twenty بدلًا من `latest`، غيّر متغير البيئة `TWENTY_VERSION` في أعلى سير العمل.
* سيقوم `yarn twenty dev` بتوليد عميلين API مضبوطي الأنواع تلقائيًا في `node_modules/twenty-sdk/generated`: `CoreApiClient` (لبيانات مساحة العمل عبر `/graphql`) و`MetadataApiClient` (لتكوين مساحة العمل وتحميل الملفات عبر `/metadata`).
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
## المصادقة
في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال:
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
* مفتاح واجهة برمجة التطبيقات
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل بينها.
### إدارة مساحات العمل
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. لا يزال بإمكانك تجاوزه مؤقتًا باستخدام `--workspace <name>`.
## إعداد يدوي (بدون المهيئ)
إذا كنت تفضّل إعداد الأمور بنفسك بدلًا من استخدام `create-twenty-app`، فيمكنك ذلك بخطوتين.
**1. أضِف `twenty-sdk` و`twenty-client-sdk` كاعتمادات:**
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. أضِف نصًا برمجيًا باسم `twenty` إلى `package.json` لديك:**
ثم أضف سكربتًا باسم `twenty`:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
يمكنك الآن تشغيل `yarn twenty dev`، و`yarn twenty help`، وجميع الأوامر الأخرى.
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty dev`، `yarn twenty help`، إلخ.
<Note>
لا تثبّت `twenty-sdk` عالميًا. استخدمه دائمًا كاعتماد محلي للمشروع بحيث يتمكن كل مشروع من تثبيت إصداره الخاص.
</Note>
## كيفية استخدام مثيل محلي من Twenty
إذا كنت تقوم بتشغيل مثيل محلي من Twenty بالفعل (على سبيل المثال عبر `npx nx start twenty-server`)، فيمكنك الاتصال به بدلًا من استخدام Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## استكشاف الأخطاء وإصلاحها
إذا واجهت مشاكل:
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة برمجة التطبيقات وأن خادم Twenty قابل للوصول.
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty dev` — فهو يولِّد العميل مضبوط الأنواع تلقائيًا.
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty dev` قيد التشغيل وأن التغييرات غير متجاهلة في بيئتك.
* تأكّد من أن **Docker قيد التشغيل** قبل تشغيل أداة إنشاء الهيكل مع مثيل محلي.
* تأكّد من أنك تستخدم **Node.js 24+** (`node -v` للتحقق).
* تأكّد من **تمكين Corepack** (`corepack enable`) حتى يتوفر Yarn 4.
* جرّب حذف `node_modules` وتشغيل `yarn install` مرة أخرى إذا بدت الاعتمادات معطّلة.
ما زلت عالقًا؟ اطلب المساعدة على [خادم Twenty على Discord](https://discord.com/channels/1130383047699738754/1130386664812982322).
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: وزّع تطبيق Twenty الخاص بك على سوق Twenty أ
---
<Warning>
التطبيقات حاليًا في مرحلة الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
التطبيقات حاليًا في مرحلة الاختبار الألفا. الميزة تعمل لكنها لا تزال قيد التطور.
</Warning>
## نظرة عامة
بمجرد أن يكون تطبيقك [مبنيًا ومختبرًا محليًا](/l/ar/developers/extend/apps/building)، لديك مساران لتوزيعه:
* **نشر أرشيف tar** — ارفع تطبيقك مباشرةً إلى خادم Twenty محدد للاستخدام الداخلي أو الخاص.
* **النشر على npm** — أدرج تطبيقك في سوق Twenty ليتسنى لأي مساحة عمل اكتشافه وتثبيته.
كلا المسارين يبدآن من نفس خطوة **build**.
## بناء تطبيقك
شغّل أمر build لتجميع تطبيقك وإنشاء ملف `manifest.json` جاهز للتوزيع:
```bash filename="Terminal"
yarn twenty build
```
يقوم هذا بتجميع مصادر TypeScript، وتحويل دوال المنطق ومكوّنات الواجهة الأمامية، وكتابة كل شيء إلى `.twenty/output/`. أضِف `--tarball` لإنتاج حزمة `.tgz` أيضًا للتوزيع اليدوي أو لأمر deploy.
## النشر إلى خادم (tarball)
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك نشر tarball مباشرةً إلى خادم Twenty.
### المتطلبات الأساسية
قبل النشر، تحتاج إلى remote مُعدّ يشير إلى خادم الهدف. تُخزّن remotes عنوان URL للخادم وبيانات اعتماد المصادقة محليًا في `~/.twenty/config.json`.
أضِف remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### النشر
بناء تطبيقك ورفعه إلى الخادم في خطوة واحدة:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### مشاركة تطبيق منشور
تطبيقات tarball لا تُدرَج في السوق العامة، لذا لن تكتشفها مساحات العمل الأخرى على الخادم نفسه عبر الاستعراض. لمشاركة تطبيق منشور:
1. اذهب إلى **الإعدادات > التطبيقات > التسجيلات** وافتح تطبيقك
2. في علامة التبويب **التوزيع**، انقر **نسخ رابط المشاركة**
3. شارك هذا الرابط مع المستخدمين في مساحات عمل أخرى — سيأخذهم مباشرةً إلى صفحة تثبيت التطبيق
يستخدم رابط المشاركة عنوان URL الأساسي للخادم (من دون أي نطاق فرعي لمساحة عمل)، لذا يعمل مع أي مساحة عمل على الخادم.
<Warning>
مشاركة التطبيقات الخاصة هي ميزة ضمن باقة Enterprise. اذهب إلى [الإعدادات > لوحة الإدارة > Enterprise](/settings/admin-panel#enterprise) لتمكينها.
</Warning>
### إدارة الإصدارات
لطرح تحديث:
1. ارفع قيمة الحقل `version` في ملف `package.json`
2. شغّل `yarn twenty deploy` (أو `yarn twenty deploy --remote production`)
3. سترى مساحات العمل التي ثبّتت التطبيق الترقية متاحة في إعداداتها
{/* TODO: add screenshot of the Upgrade button */}
* **إرسال tarball** — انشر تطبيقك إلى خادم Twenty معيّن للاستخدام الداخلي من دون جعله متاحًا للعامة.
## النشر على npm
@@ -81,69 +21,29 @@ yarn twenty deploy
### المتطلبات
* حساب على [npm](https://www.npmjs.com)
* الكلمة المفتاحية `twenty-app` في مصفوفة `keywords` في `package.json` (موجودة مسبقًا عند تهيئة المشروع باستخدام `create-twenty-app`)
* يجب أن يستخدم اسم الحزمة البادئة `twenty-app-` (مثلًا، `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### الخطوات
### بيانات التعريف لسوق التطبيقات
يدعم إعداد `defineApplication()` حقولًا اختيارية تتحكم في كيفية ظهور تطبيقك في السوق. استخدم `logoUrl` و`screenshots` للإشارة إلى الصور من مجلد `public/`:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
اطّلع على [أكورديون defineApplication](/l/ar/developers/extend/apps/building#defineentity-functions) في صفحة بناء التطبيقات للاطلاع على القائمة الكاملة لحقول السوق (`author` و`category` و`aboutDescription` و`websiteUrl` و`termsUrl` وغيرها).
### النشر
1. **قم ببناء تطبيقك** — تقوم أداة CLI بتجميع مصادر TypeScript الخاصة بك وإنشاء ملف بيان التطبيق:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
للنشر تحت dist-tag معيّن (مثلًا: `beta` أو `next`):
2. **النشر على npm** — ادفع الحزمة المبنية إلى سجل npm:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### كيف تعمل آلية الاكتشاف في السوق
### الاكتشاف التلقائي
يقوم خادم Twenty بمزامنة كتالوج السوق من سجل npm **كل ساعة**.
يمكنك تشغيل المزامنة فورًا بدلًا من الانتظار:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
تأتي بيانات التعريف المعروضة في السوق من إعداد `defineApplication()` — حقول مثل `displayName` و`description` و`author` و`category` و`logoUrl` و`screenshots` و`aboutDescription` و`websiteUrl` و`termsUrl`.
<Note>
إذا لم يحدد تطبيقك `aboutDescription` في `defineApplication()`، فسيستخدم السوق تلقائيًا ملف `README.md` الخاص بحزمتك من npm كمحتوى لصفحة حول. هذا يعني أنه يمكنك الاحتفاظ بملف README واحد لكل من npm وسوق Twenty. إذا كنت تريد وصفًا مختلفًا في السوق، فقم بتعيين `aboutDescription` بشكل صريح.
</Note>
تُكتشف الحِزم التي تحمل البادئة `twenty-app-` تلقائيًا بواسطة فهرس سوق Twenty. بعد نشره، سيظهر تطبيقك في السوق خلال بضع دقائق — من دون الحاجة إلى تسجيل يدوي أو موافقة يدوية.
### النشر عبر CI
استخدم سير عمل GitHub Actions هذا للنشر تلقائيًا مع كل إصدار (يستخدم [OIDC](https://docs.npmjs.com/trusted-publishers)):
يتضمن المشروع المُولَّد سير عمل GitHub Actions يقوم بالنشر عند كل إصدار. يشغِّل `app:build`، ثم ينفِّذ `npm publish --provenance` من مخرجات البناء:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
بالنسبة لأنظمة CI الأخرى (GitLab CI، وCircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `yarn twenty build`، ثم `npm publish` من `.twenty/output`.
بالنسبة لأنظمة CI الأخرى (GitLab CI، CircleCI، إلخ)، تنطبق الأوامر الثلاثة نفسها: `yarn install`، ثم `npx twenty build`، ثم `npm publish` من `.twenty/output`.
<Note>
<Tip>
**npm provenance** اختياري ولكنه موصى به. يضيف النشر باستخدام `--provenance` شارة ثقة إلى إدراجك على npm، مما يتيح للمستخدمين التحقق من أن الحزمة تم بناؤها من التزام محدد ضمن خط أنابيب CI عام. راجع [وثائق npm provenance](https://docs.npmjs.com/generating-provenance-statements) للحصول على تعليمات الإعداد.
</Note>
</Tip>
## تثبيت التطبيقات
## التوزيع الداخلي
بعد نشر التطبيق (npm) أو نشره (tarball)، يمكن لمساحات العمل تثبيته عبر واجهة المستخدم.
بالنسبة للتطبيقات التي لا تريد إتاحتها للعامة — مثل الأدوات المملوكة، أو عمليات التكامل الخاصة بالمؤسسات فقط، أو الإصدارات التجريبية — يمكنك إرسال tarball مباشرةً إلى خادم Twenty.
اذهب إلى صفحة **الإعدادات > التطبيقات** في Twenty، حيث يمكن استعراض تطبيقات السوق والتطبيقات المنشورة عبر tarball وتثبيتها.
### إرسال tarball
{/* TODO: add screenshot of the UI when the app is registered */}
يمكنك أيضًا تثبيت التطبيقات من سطر الأوامر:
قم ببناء تطبيقك وانشره إلى خادم محدد في خطوة واحدة:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
يمكن لأي مساحة عمل على ذلك الخادم بعدها تثبيت التطبيق وترقيته من صفحة الإعدادات **التطبيقات**.
### إدارة الإصدارات
لطرح تحديث:
1. ارفع قيمة الحقل `version` في ملف `package.json`
2. أرسل tarball جديدًا باستخدام `npx twenty publish --server <server-url>`
3. سترى مساحات العمل على ذلك الخادم الترقية متاحة في إعداداتها
<Note>
التطبيقات الداخلية مقتصرة على الخادم الذي تُرسل إليه. لن تظهر في السوق العام ولا يمكن لمساحات العمل على خوادم أخرى تثبيتها.
</Note>
## فئات التطبيقات
تُنظِّم Twenty التطبيقات في ثلاث فئات استنادًا إلى طريقة توزيعها:
| الفئة | كيف يعمل | مرئي في سوق Twenty؟ |
| ----------- | ---------------------------------------------------------------------------------------------------- | ------------------- |
| **التطوير** | تطبيقات وضع التطوير المحلي التي تعمل عبر `yarn twenty dev`. تُستخدم للبناء والاختبار. | لا |
| **منشور** | تطبيقات منشورة على npm مع البادئة `twenty-app-`. مدرجة في سوق Twenty لتتمكن أي مساحة عمل من تثبيتها. | نعم |
| **داخلي** | تطبيقات منشورة عبر tarball إلى خادم محدد. متاحة فقط لمساحات العمل على ذلك الخادم. | لا |
<Tip>
ابدأ في وضع **التطوير** أثناء بناء تطبيقك. عندما يصبح جاهزًا، اختر **منشور** (npm) للتوزيع الواسع أو **داخلي** (tarball) للنشر الخاص.
</Tip>
File diff suppressed because it is too large Load Diff
@@ -55,12 +55,11 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -80,40 +79,11 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
@@ -121,12 +91,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -138,15 +108,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
const filePath = uploadResult.data?.uploadFile?.path;
if (!fileId) {
throw new Error('No file id returned from upload');
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 4: Create the attachment linked to the company
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -154,7 +124,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -165,13 +135,8 @@ export const main = async (
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
fullPath: filePath,
companyId,
},
},
}),
@@ -191,14 +156,14 @@ export const main = async (
#### لإرفاقه بكائن مختلف
استبدل `targetCompanyId` بالحقل المناسب:
استبدل `companyId` بالحقل المناسب:
| كائن | اسم الحقل |
| ---------- | -------------------------- |
| الشركة | `targetCompanyId` |
| شخص | `targetPersonId` |
| الفرصة | `targetOpportunityId` |
| كائن مخصّص | `targetYourCustomObjectId` |
| كائن | اسم الحقل |
| ---------- | -------------------- |
| الشركة | `companyId` |
| شخص | `personId` |
| الفرصة | `opportunityId` |
| كائن مخصّص | `yourCustomObjectId` |
حدّث كل من معلمة الوظيفة وكائن `variables.data` في عملية الـ mutation الخاصة بالمرفق.
File diff suppressed because it is too large Load Diff
@@ -4,142 +4,84 @@ description: Vytvořte svou první aplikaci Twenty během několika minut.
---
<Warning>
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
</Warning>
Aplikace vám umožňují rozšířit Twenty o vlastní objekty, pole, logické funkce, AI schopnosti a komponenty uživatelského rozhraní — vše je spravováno jako kód.
**Co můžete dělat už dnes:**
* Definujte vlastní objekty a pole jako kód (spravovaný datový model)
* Vytvářejte logické funkce s vlastními spouštěči (HTTP routy, cron, databázové události)
* Definujte dovednosti agentů AI
* Vytvářejte frontendové komponenty, které se vykreslují uvnitř uživatelského rozhraní Twenty
* Nasazujte stejnou aplikaci do více pracovních prostorů
## Předpoklady
Než začnete, ujistěte se, že máte ve svém počítači nainstalováno následující:
* Node.js 24+ a Yarn 4
* Docker (pro místní vývojový server Twenty)
* **Node.js 24+** — [Stáhnout zde](https://nodejs.org/)
* **Yarn 4** — Dodává se s Node.js prostřednictvím Corepacku. Povolte jej spuštěním `corepack enable`
* **Docker** — [Stáhnout zde](https://www.docker.com/products/docker-desktop/). Nutné pro spuštění lokální instance Twenty. Není potřeba, pokud už máte spuštěný server Twenty.
## Začínáme
## Krok 1: Vytvořte kostru své aplikace
Otevřete terminál a spusťte:
Vytvořte novou aplikaci pomocí oficiálního scaffolderu, poté se ověřte a začněte vyvíjet:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
Budete vyzváni k zadání názvu a popisu své aplikace. Stisknutím **Enter** přijmete výchozí hodnoty.
Tím se vytvoří nová složka s názvem `my-twenty-app` se vším potřebným.
<Note>
Generátor kostry podporuje tyto přepínače:
* `--minimal` — vygeneruje pouze nezbytné soubory, bez příkladů (výchozí)
* `--exhaustive` — vygeneruje všechny ukázkové entity
* `--name <name>` — nastaví název aplikace (přeskočí výzvu)
* `--display-name <displayName>` — nastaví zobrazovaný název (přeskočí výzvu)
* `--description <description>` — nastaví popis (přeskočí výzvu)
* `--skip-local-instance` — přeskočí výzvu k nastavení lokálního serveru
</Note>
## Krok 2: Nastavte lokální instanci Twenty
Generátor kostry se zeptá:
> **Chcete nastavit lokální instanci Twenty?**
* **Zadejte `yes`** (doporučeno) — Stáhne image Dockeru `twenty-app-dev` a spustí lokální server Twenty na portu `2020`. Než budete pokračovat, ujistěte se, že Docker běží.
* **Zadejte `no`** — Zvolte, pokud už máte lokálně spuštěný server Twenty.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Spustit lokální instanci?" />
</div>
## Krok 3: Přihlaste se do svého pracovního prostoru
Poté se otevře okno prohlížeče se stránkou přihlášení do Twenty. Přihlaste se předpřipraveným demo účtem:
* **E-mail:** `tim@apple.dev`
* **Heslo:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Přihlašovací obrazovka Twenty" />
</div>
## Krok 4: Autorizujte aplikaci
Po přihlášení uvidíte autorizační obrazovku. Tím umožníte vaší aplikaci pracovat s vaším pracovním prostorem.
Pokračujte kliknutím na **Authorize**.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Autorizační obrazovka Twenty CLI" />
</div>
Po autorizaci váš terminál potvrdí, že je vše nastaveno.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplikace byla úspěšně vygenerována" />
</div>
## Krok 5: Začněte vyvíjet
Přejděte do nové složky aplikace a spusťte vývojový server:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
Sleduje zdrojové soubory, při každé změně znovu sestaví a automaticky synchronizuje vaši aplikaci s lokálním serverem Twenty. V terminálu byste měli vidět panel se stavem v reálném čase.
Pro podrobnější výstup (protokoly sestavení, požadavky na synchronizaci, stopy chyb) použijte přepínač `--verbose`:
Nástroj pro generování kostry podporuje dva režimy pro řízení toho, které ukázkové soubory jsou zahrnuty:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
Vývojový režim je k dispozici pouze na instancích Twenty běžících v režimu development (`NODE_ENV=development`). Produkční instance odmítají požadavky na vývojovou synchronizaci. Pro nasazení na produkční servery použijte `yarn twenty deploy` — podrobnosti viz [Publikování aplikací](/l/cs/developers/extend/apps/publishing).
</Warning>
Odtud můžete:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Výstup terminálu ve vývojovém režimu" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Krok 6: Zobrazte svou aplikaci v Twenty
# Watch your application's function logs
yarn twenty function:logs
Otevřete ve svém prohlížeči [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer). Přejděte do **Settings > Apps** a vyberte kartu **Developer**. Vaše aplikace by měla být uvedena v části **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Seznam Your Apps zobrazující My twenty app" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Klikněte na **My twenty app** a otevřete její **registraci aplikace**. Registrace je záznam na úrovni serveru, který popisuje vaši aplikaci — její název, jedinečný identifikátor, přihlašovací údaje OAuth a zdroj (lokální, npm nebo tarball). Existuje na serveru, ne uvnitř žádného konkrétního pracovního prostoru. Když nainstalujete aplikaci do pracovního prostoru, Twenty vytvoří **aplikaci** v rozsahu pracovního prostoru, která odkazuje zpět na tuto registraci. Jedna registrace může být nainstalována ve více pracovních prostorech na stejném serveru.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Podrobnosti registrace aplikace" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Klikněte na **View installed app**, abyste zobrazili nainstalovanou aplikaci. Karta **About** zobrazuje aktuální verzi a možnosti správy:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Nainstalovaná aplikace — karta About" />
</div>
Viz také: referenční stránky CLI pro [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) a [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Přepněte na kartu **Content**, abyste viděli vše, co vaše aplikace poskytuje — objekty, pole, logické funkce a agenty:
## Struktura projektu (vytvořená scaffolderem)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Nainstalovaná aplikace — karta Content" />
</div>
Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
Vše je připraveno! Upravte libovolný soubor v `src/` a změny se automaticky projeví.
* Zkopíruje minimální základní aplikaci do `my-twenty-app/`
* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4
* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
Přejděte na [Tvorba aplikací](/l/cs/developers/extend/apps/building) pro podrobný průvodce vytvářením objektů, logických funkcí, frontendových komponent, dovedností a dalšího.
---
## Struktura projektu
Generátor kostry vytvoří následující strukturu souborů (zobrazeno v režimu `--exhaustive`, který zahrnuje příklady pro každý typ entity):
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
Ve výchozím nastavení (`--minimal`) se vytvoří pouze základní soubory: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`. Pro zahrnutí všech ukázkových souborů výše použijte `--exhaustive`.
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`).
### Klíčové soubory
V kostce:
| Soubor / Složka | Účel |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Definuje název, verzi a závislosti vaší aplikace. Obsahuje skript `twenty`, takže můžete spustit `yarn twenty help` a zobrazit všechny příkazy. |
| `src/application-config.ts` | **Povinné.** Hlavní konfigurační soubor vaší aplikace. |
| `src/roles/` | Definuje role, které určují, k čemu mají vaše logické funkce přístup. |
| `src/logic-functions/` | Serverové funkce spouštěné trasami, plánovačem cron nebo událostmi databáze. |
| `src/front-components/` | Komponenty Reactu, které se vykreslují uvnitř uživatelského rozhraní Twenty. |
| `src/objects/` | Vlastní definice objektů pro rozšíření vašeho datového modelu. |
| `src/fields/` | Vlastní pole přidaná k existujícím objektům. |
| `src/views/` | Konfigurace uložených zobrazení. |
| `src/navigation-menu-items/` | Vlastní odkazy v postranní navigaci. |
| `src/skills/` | Dovednosti, které rozšiřují možnosti AI agentů Twenty. |
| `src/agents/` | AI agenti s vlastními prompty. |
| `src/page-layouts/` | Vlastní rozvržení stránek pro zobrazení záznamů. |
| `src/__tests__/` | Integrační testy (nastavení + ukázkový test). |
| `public/` | Statická aktiva (obrázky, písma) poskytovaná s vaší aplikací. |
* **package.json**: Deklaruje název aplikace, verzi, engines (Node 24+, Yarn 4) a přidává `twenty-sdk` plus skript `twenty`, který deleguje na lokální `twenty` CLI. Spusťte `yarn twenty help` pro výpis všech dostupných příkazů.
* **.gitignore**: Ignoruje běžné artefakty jako `node_modules`, `.yarn`, `generated/` (typovaný klient), `dist/`, `build/`, složky s coverage, logy a soubory `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Zamykají a konfigurují nástrojový řetězec Yarn 4 používaný projektem.
* **.nvmrc**: Fixuje verzi Node.js požadovanou projektem.
* **.oxlintrc.json** a **tsconfig.json**: Poskytují lintování a konfiguraci TypeScriptu pro zdrojové soubory vaší aplikace v TypeScriptu.
* **README.md**: Krátké README v kořeni aplikace se základními pokyny.
* **public/**: Složka pro ukládání veřejných prostředků (obrázky, písma, statické soubory), které bude vaše aplikace poskytovat. Soubory umístěné zde se během synchronizace nahrají a jsou za běhu dostupné.
* **src/**: Hlavní místo, kde definujete svou aplikaci jako kód
## Správa vzdálených serverů
### Detekce entit
**Remote** je server Twenty, ke kterému se vaše aplikace připojuje. Během nastavení jej generátor kostry automaticky vytvoří. Můžete kdykoli přidat další vzdálené servery nebo mezi nimi přepínat.
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Vaše přihlašovací údaje jsou uloženy v `~/.twenty/config.json`.
## Lokální vývojový server (`yarn twenty server`)
CLI může spravovat lokální server Twenty běžící v Dockeru. Jde o stejný server, který se spustí automaticky při vytvoření kostry aplikace pomocí `create-twenty-app`, ale můžete jej spravovat i ručně.
### Spuštění serveru
```bash filename="Terminal"
yarn twenty server start
```
Stáhne image Dockeru `twentycrm/twenty-app-dev:latest` (pokud již není k dispozici), vytvoří kontejner s názvem `twenty-app-dev` a spustí jej na portu **2020**. CLI čeká, dokud server neprojde kontrolou stavu, než vrátí řízení.
Vytvoří se dva svazky Dockeru pro zachování dat mezi restartováními:
* `twenty-app-dev-data` — databáze PostgreSQL
* `twenty-app-dev-storage` — úložiště souborů
Pokud je port 2020 již používán, můžete spustit na jiném portu:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI automaticky nakonfiguruje interní `NODE_PORT` a `SERVER_URL` kontejneru tak, aby odpovídaly zvolenému portu, takže logické funkce, OAuth a veškerá ostatní vnitřní síťová komunikace fungují správně.
Po spuštění je server automaticky zaregistrován jako `local` remote ve vaší konfiguraci CLI.
### Kontrola stavu serveru
```bash filename="Terminal"
yarn twenty server status
```
Zobrazí, zda server běží, jeho URL a výchozí přihlašovací údaje (`tim@apple.dev` / `tim@apple.dev`).
### Zobrazení protokolů serveru
```bash filename="Terminal"
yarn twenty server logs
```
Streamuje protokoly kontejneru. Pomocí `--lines` ovládnete, kolik posledních řádků se má zobrazit:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Zastavení serveru
```bash filename="Terminal"
yarn twenty server stop
```
Zastaví kontejner. Vaše data jsou zachována ve svazcích Dockeru — další `start` naváže tam, kde jste skončili.
### Resetování serveru
```bash filename="Terminal"
yarn twenty server reset
```
Odstraní kontejner **a** smaže oba svazky Dockeru, čímž vymaže všechna data. Další `start` vytvoří čistou instanci.
| Pomocná funkce | Typ entity |
| -------------------------------- | --------------------------------------------------------- |
| `defineObject` | Definice vlastních objektů |
| `defineLogicFunction` | Definice logických funkcí |
| `definePreInstallLogicFunction` | Předinstalační logická funkce (spouští se před instalací) |
| `definePostInstallLogicFunction` | Postinstalační logická funkce (spouští se po instalaci) |
| `defineFrontComponent` | Definice frontendových komponent |
| `defineRole` | Definice rolí |
| `defineField` | Rozšíření polí u existujících objektů |
| `defineView` | Definice uložených zobrazení |
| `defineNavigationMenuItem` | Definice položek navigační nabídky |
| `defineSkill` | Definice dovedností agenta AI |
<Note>
Server vyžaduje, aby **Docker** běžel. Pokud vidíte chybu "Docker not running", ujistěte se, že je spuštěný Docker Desktop (nebo démon Dockeru).
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
</Note>
### Přehled příkazů
Příklad detekované entity:
| Příkaz | Popis |
| -------------------------------------- | ------------------------------------------------------ |
| `yarn twenty server start` | Spustí lokální server (v případě potřeby stáhne image) |
| `yarn twenty server start --port 3030` | Spustí na vlastním portu |
| `yarn twenty server stop` | Zastaví server (zachová data) |
| `yarn twenty server status` | Zobrazí stav serveru, URL a přihlašovací údaje |
| `yarn twenty server logs` | Streamuje protokoly serveru |
| `yarn twenty server logs --lines 100` | Zobrazí posledních 100 řádků logu |
| `yarn twenty server reset` | Smaže všechna data a začne znovu |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## CI s GitHub Actions
Generátor kostry vytvoří připravený k použití workflow GitHub Actions v `.github/workflows/ci.yml`. Automaticky spouští integrační testy při každém pushi do `main` a u pull requestů.
Workflow:
1. Načte váš kód (checkout).
2. Spustí dočasný server Twenty pomocí akce `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Nainstaluje závislosti pomocí `yarn install --immutable`
4. Spustí `yarn test` s proměnnými `TWENTY_API_URL` a `TWENTY_API_KEY` vloženými z výstupů akce
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Není potřeba konfigurovat žádné secrets — akce `spawn-twenty-docker-image` spustí efemérní server Twenty přímo v runneru a vypíše podrobnosti připojení. Secret `GITHUB_TOKEN` je poskytován GitHubem automaticky.
Pozdější příkazy přidají další soubory a složky:
Chcete-li připnout konkrétní verzi Twenty místo `latest`, změňte proměnnou prostředí `TWENTY_VERSION` na začátku workflow.
* `yarn twenty dev` automaticky vygeneruje dva typované API klienty v `node_modules/twenty-sdk/generated`: `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) a `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`).
* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další.
## Ověření
Při prvním spuštění `yarn twenty auth:login` budete vyzváni k zadání:
* URL API (výchozí je http://localhost:3000 nebo váš aktuální profil pracovního prostoru)
* Klíč API
Vaše přihlašovací údaje se ukládají pro jednotlivé uživatele do `~/.twenty/config.json`. Můžete spravovat více profilů a přepínat mezi nimi.
### Správa pracovních prostorů
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
Jakmile přepnete pracovní prostor pomocí `yarn twenty auth:switch`, všechny následující příkazy budou tento pracovní prostor používat jako výchozí. Můžete jej stále dočasně přepsat pomocí `--workspace <name>`.
## Ruční nastavení (bez scaffolderu)
Pokud dáváte přednost vlastnímu nastavení místo použití `create-twenty-app`, můžete to udělat ve dvou krocích.
**1. Přidejte `twenty-sdk` a `twenty-client-sdk` jako závislosti:**
Ačkoli pro nejlepší začátky doporučujeme použít `create-twenty-app`, projekt můžete nastavit i ručně. Neinstalujte CLI globálně. Místo toho přidejte `twenty-sdk` jako lokální závislost a přidejte jeden skript do souboru package.json:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Přidejte skript `twenty` do svého `package.json`:**
Poté přidejte skript `twenty`:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
Nyní můžete spouštět `yarn twenty dev`, `yarn twenty help` a všechny ostatní příkazy.
Nyní můžete spouštět všechny příkazy přes `yarn twenty <command>`, např. `yarn twenty dev`, `yarn twenty help` atd.
<Note>
Neinstalujte `twenty-sdk` globálně. Vždy jej používejte jako lokální závislost projektu, aby si každý projekt mohl připnout svou vlastní verzi.
</Note>
## Jak používat lokální instanci Twenty
Pokud již lokálně provozujete instanci Twenty (např. pomocí `npx nx start twenty-server`), můžete se k ní připojit namísto použití Dockeru:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Řešení potíží
Pokud narazíte na potíže:
* Chyby ověření: spusťte `yarn twenty auth:login` a ujistěte se, že váš klíč API má požadovaná oprávnění.
* Nelze se připojit k serveru: ověřte URL API a že je server Twenty dosažitelný.
* Typy nebo klient chybí nebo jsou zastaralé: restartujte `yarn twenty dev` — automaticky generuje typovaného klienta.
* Režim vývoje se nesynchronizuje: ujistěte se, že běží `yarn twenty dev` a že vaše prostředí změny neignoruje.
* Před spuštěním generátoru kostry s lokální instancí se ujistěte, že **Docker běží**.
* Ujistěte se, že používáte **Node.js 24+** (ověříte příkazem `node -v`).
* Ujistěte se, že je **Corepack povolen** (`corepack enable`), aby byl k dispozici Yarn 4.
* Zkuste smazat `node_modules` a znovu spustit `yarn install`, pokud se zdají závislosti poškozené.
Pořád se nedaří? Požádejte o pomoc na [Discordu Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
Kanál podpory na Discordu: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: Distribuujte svou aplikaci Twenty do Marketplace nebo ji nasaďte i
---
<Warning>
Aplikace jsou aktuálně v alfa fázi. Funkce funguje, ale stále se vyvíjí.
Aplikace jsou aktuálně v alfa testování. Tato funkce je funkční, ale stále se vyvíjí.
</Warning>
## Přehled
Jakmile je vaše aplikace [sestavena a otestována lokálně](/l/cs/developers/extend/apps/building), máte dvě cesty, jak ji distribuovat:
* **Nasaďte tarball** — nahrajte svou aplikaci přímo na konkrétní server Twenty pro interní nebo soukromé použití.
* **Publish to npm** — uveďte svou aplikaci v Marketplace Twenty, aby ji mohl kterýkoli pracovní prostor objevit a nainstalovat.
Obě cesty začínají stejným krokem **build**.
## Sestavení vaší aplikace
Spusťte příkaz build ke zkompilování své aplikace a k vygenerování souboru `manifest.json` připraveného k distribuci:
```bash filename="Terminal"
yarn twenty build
```
Tím se zkompilují zdrojové soubory TypeScriptu, transpilují logické funkce a frontendové komponenty a vše se zapíše do `.twenty/output/`. Přidejte `--tarball`, abyste také vytvořili balíček `.tgz` pro ruční distribuci nebo příkaz deploy.
## Nasazení na server (tarball)
U aplikací, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete nasadit tarball přímo na server Twenty.
### Předpoklady
Před nasazením potřebujete nakonfigurovaný vzdálený cíl směřující na cílový server. Vzdálené cíle ukládají adresu URL serveru a přihlašovací údaje lokálně v `~/.twenty/config.json`.
Přidat vzdálený cíl:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Nasazení
Sestavte a nahrajte svou aplikaci na server v jednom kroku:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Sdílení nasazené aplikace
Aplikace ve formě tarball nejsou uvedeny ve veřejném tržišti, takže je ostatní pracovní prostory na tomtéž serveru procházením neobjeví. Chcete-li sdílet nasazenou aplikaci:
1. Přejděte do **Nastavení > Aplikace > Registrace** a otevřete svou aplikaci
2. Na kartě **Distribuce** klikněte na **Zkopírovat odkaz ke sdílení**
3. Sdílejte tento odkaz s uživateli v jiných pracovních prostorech — zavede je přímo na instalační stránku aplikace
Odkaz ke sdílení používá základní adresu URL serveru (bez jakékoli subdomény pracovního prostoru), takže funguje pro libovolný pracovní prostor na serveru.
<Warning>
Sdílení soukromých aplikací je funkce Enterprise. Přejděte do [Nastavení > Admin Panel > Enterprise](/settings/admin-panel#enterprise) a povolte ji.
</Warning>
### Správa verzí
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json`
2. Spusťte `yarn twenty deploy` (nebo `yarn twenty deploy --remote production`)
3. Pracovní prostory, které mají aplikaci nainstalovanou, uvidí dostupnou aktualizaci ve svém nastavení
{/* TODO: add screenshot of the Upgrade button */}
* **Odeslat tarball** — nasaďte svou aplikaci na konkrétní server Twenty pro interní použití, aniž by byla veřejně dostupná.
## Publikování na npm
@@ -81,69 +21,29 @@ Publikování na npm zajistí, že bude vaše aplikace dohledatelná v Marketpla
### Požadavky
* Účet na [npm](https://www.npmjs.com)
* Klíčové slovo `twenty-app` ve vašem poli `keywords` v souboru `package.json` (již je zahrnuto, když založíte projekt pomocí `create-twenty-app`)
* Název vašeho balíčku **musí** používat předponu `twenty-app-` (např. `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Postup
### Metadata tržiště
Konfigurace `defineApplication()` podporuje volitelná pole, která určují, jak se vaše aplikace zobrazuje v tržišti. Použijte `logoUrl` a `screenshots` k odkazování na obrázky ze složky `public/`:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
Podívejte se na [sekci defineApplication](/l/cs/developers/extend/apps/building#defineentity-functions) na stránce Building Apps pro úplný seznam polí tržiště (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` atd.).
### Publikování
1. **Sestavte svou aplikaci** — CLI zkompiluje vaše zdrojové soubory TypeScript a vygeneruje manifest aplikace:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
Chcete-li publikovat pod konkrétním dist-tagem (např. `beta` nebo `next`):
2. **Publikujte na npm** — odešlete sestavený balíček do registru npm:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### Jak funguje objevování v tržišti
### Automatické rozpoznání
Server Twenty synchronizuje svůj katalog tržiště z registru npm **každou hodinu**.
Synchronizaci můžete spustit okamžitě místo čekání:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Metadata zobrazená v tržišti pocházejí z vaší konfigurace `defineApplication()` — z polí jako `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` a `termsUrl`.
<Note>
Pokud vaše aplikace nedefinuje `aboutDescription` v `defineApplication()`, tržiště automaticky použije soubor `README.md` vašeho balíčku z npm jako obsah stránky O aplikaci. To znamená, že můžete spravovat jediný soubor README jak pro npm, tak pro tržiště Twenty. Pokud chcete v tržišti jiný popis, explicitně nastavte `aboutDescription`.
</Note>
Balíčky s předponou `twenty-app-` jsou automaticky rozpoznávány katalogem Marketplace Twenty. Po publikování se vaše aplikace během několika minut objeví v Marketplace — nevyžaduje žádnou ruční registraci ani schvalování.
### Publikování pomocí CI
Použijte tento pracovní postup GitHub Actions k automatickému publikování při každém vydání (používá [OIDC](https://docs.npmjs.com/trusted-publishers)):
Vygenerovaný projekt obsahuje pracovní postup GitHub Actions, který publikuje při každém vydání. Spouští `app:build`, a poté `npm publish --provenance` z výstupu buildu:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `yarn twenty build` a poté `npm publish` z `.twenty/output`.
Pro jiné systémy CI (GitLab CI, CircleCI atd.) platí stejné tři příkazy: `yarn install`, `npx twenty build` a poté `npm publish` z `.twenty/output`.
<Note>
<Tip>
**npm provenance** je volitelné, ale doporučené. Publikování s `--provenance` přidá k vašemu záznamu na npm odznak důvěryhodnosti a umožní uživatelům ověřit, že balíček byl sestaven z konkrétního commitu ve veřejné CI pipeline. Pokyny k nastavení najdete v [dokumentaci k npm provenance](https://docs.npmjs.com/generating-provenance-statements).
</Note>
</Tip>
## Instalace aplikací
## Interní distribuce
Jakmile je aplikace publikována (npm) nebo nasazena (tarball), mohou ji pracovní prostory nainstalovat prostřednictvím uživatelského rozhraní.
Pro aplikace, které nechcete zpřístupnit veřejně — proprietární nástroje, integrace pouze pro enterprise nebo experimentální buildy — můžete odeslat tarball přímo na server Twenty.
Přejděte na stránku **Nastavení > Aplikace** v Twenty, kde lze procházet a instalovat jak aplikace z tržiště, tak aplikace nasazené jako tarball.
### Odeslat tarball
{/* TODO: add screenshot of the UI when the app is registered */}
Aplikace můžete nainstalovat také z příkazového řádku:
Sestavte svou aplikaci a nasaďte ji na konkrétní server v jednom kroku:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
Jakýkoli pracovní prostor na tomto serveru pak může aplikaci instalovat a aktualizovat ze stránky nastavení **Applications**.
### Správa verzí
Chcete-li vydat aktualizaci:
1. Zvyšte hodnotu pole `version` v souboru `package.json`
2. Odešlete nový tarball pomocí `npx twenty publish --server <server-url>`
3. Pracovní prostory na tomto serveru uvidí dostupnou aktualizaci ve svém nastavení
<Note>
Interní aplikace jsou omezené na server, na který jsou odeslány. Nezobrazí se ve veřejném Marketplace a nelze je instalovat v pracovních prostorech na jiných serverech.
</Note>
## Kategorie aplikací
Twenty organizuje aplikace do tří kategorií podle způsobu distribuce:
| Kategorie | Jak to funguje | Viditelné v Marketplace? |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| **Vývoj** | Aplikace v místním vývojářském režimu spuštěné přes `yarn twenty dev`. Slouží k sestavování a testování. | Ne |
| **Publikováno** | Aplikace publikované na npm s předponou `twenty-app-`. Uvedeny v Marketplace, aby je mohl kterýkoli pracovní prostor nainstalovat. | Ano |
| **Interní** | Aplikace nasazené pomocí tarballu na konkrétní server. Dostupné pouze pro pracovní prostory na tomto serveru. | Ne |
<Tip>
Začněte v režimu **Development** při sestavování své aplikace. Až bude připravena, zvolte **Published** (npm) pro širokou distribuci nebo **Internal** (tarball) pro soukromé nasazení.
</Tip>
File diff suppressed because it is too large Load Diff
@@ -55,12 +55,11 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -80,40 +79,11 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
@@ -121,12 +91,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -138,15 +108,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
const filePath = uploadResult.data?.uploadFile?.path;
if (!fileId) {
throw new Error('No file id returned from upload');
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 4: Create the attachment linked to the company
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -154,7 +124,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -165,13 +135,8 @@ export const main = async (
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
fullPath: filePath,
companyId,
},
},
}),
@@ -191,14 +156,14 @@ export const main = async (
#### Chcete-li připojit k jinému objektu
Nahraďte `targetCompanyId` příslušným polem:
Nahraďte `companyId` příslušným polem:
| Objekt | Název pole |
| -------------- | -------------------------- |
| Společnost | `targetCompanyId` |
| Osoba | `targetPersonId` |
| Příležitost | `targetOpportunityId` |
| Vlastní objekt | `targetYourCustomObjectId` |
| Objekt | Název pole |
| -------------- | -------------------- |
| Společnost | `companyId` |
| Osoba | `personId` |
| Příležitost | `opportunityId` |
| Vlastní objekt | `yourCustomObjectId` |
Aktualizujte jak parametr funkce, tak objekt `variables.data` v mutaci přílohy.
File diff suppressed because it is too large Load Diff
@@ -4,142 +4,84 @@ description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App.
---
<Warning>
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
</Warning>
Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, KI-Fähigkeiten und UI-Komponenten zu erweitern — alles als Code verwaltet.
**Was Sie heute tun können:**
* Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell)
* Erstellen Sie Logikfunktionen mit benutzerdefinierten Triggern (HTTP-Routen, cron, Datenbankereignisse)
* Fähigkeiten für KI-Agenten definieren
* Erstellen Sie Frontend-Komponenten, die in der Twenty-UI gerendert werden
* Dieselbe App in mehreren Workspaces bereitstellen
## Voraussetzungen
Bevor Sie beginnen, stellen Sie sicher, dass Folgendes auf Ihrem Rechner installiert ist:
* Node.js 24+ und Yarn 4
* Docker (für den lokalen Twenty-Dev-Server)
* **Node.js 24+** — [Hier herunterladen](https://nodejs.org/)
* **Yarn 4** — Wird mit Node.js über Corepack mitgeliefert. Aktivieren Sie es, indem Sie `corepack enable` ausführen
* **Docker** — [Hier herunterladen](https://www.docker.com/products/docker-desktop/). Erforderlich, um eine lokale Twenty-Instanz auszuführen. Nicht erforderlich, wenn bereits ein Twenty-Server läuft.
## Erste Schritte
## Schritt 1: App-Gerüst erstellen
Öffnen Sie ein Terminal und führen Sie Folgendes aus:
Erstellen Sie mit dem offiziellen Scaffolder eine neue App, authentifizieren Sie sich und beginnen Sie mit der Entwicklung:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
Sie werden aufgefordert, einen Namen und eine Beschreibung für Ihre App einzugeben. Drücken Sie **Enter**, um die Standardwerte zu übernehmen.
Dadurch wird ein neuer Ordner namens `my-twenty-app` mit allem erstellt, was Sie benötigen.
<Note>
Das Scaffolding-Tool unterstützt diese Flags:
* `--minimal` — erstellt nur die wesentlichen Dateien, keine Beispiele (Standard)
* `--exhaustive` — erstellt alle Beispiel-Entitäten
* `--name <name>` — legt den App-Namen fest (überspringt die Abfrage)
* `--display-name <displayName>` — legt den Anzeigenamen fest (überspringt die Abfrage)
* `--description <description>` — legt die Beschreibung fest (überspringt die Abfrage)
* `--skip-local-instance` — überspringt die Eingabeaufforderung zur Einrichtung des lokalen Servers
</Note>
## Schritt 2: Lokale Twenty-Instanz einrichten
Das Scaffolding-Tool fragt:
> **Möchten Sie eine lokale Twenty-Instanz einrichten?**
* **Geben Sie `yes` ein** (empfohlen) — Dadurch wird das Docker-Image `twenty-app-dev` heruntergeladen und ein lokaler Twenty-Server auf Port `2020` gestartet. Stellen Sie sicher, dass Docker läuft, bevor Sie fortfahren.
* **Geben Sie `no` ein** — Wählen Sie dies, wenn bereits ein Twenty-Server lokal läuft.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Soll die lokale Instanz gestartet werden?" />
</div>
## Schritt 3: Bei Ihrem Arbeitsbereich anmelden
Anschließend öffnet sich ein Browserfenster mit der Twenty-Anmeldeseite. Melden Sie sich mit dem vorab eingerichteten Demo-Konto an:
* **E-Mail:** `tim@apple.dev`
* **Passwort:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty-Anmeldebildschirm" />
</div>
## Schritt 4: Die App autorisieren
Nach der Anmeldung sehen Sie einen Autorisierungsbildschirm. Dadurch kann Ihre App mit Ihrem Arbeitsbereich interagieren.
Klicken Sie auf **Authorize**, um fortzufahren.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty-CLI-Autorisierungsbildschirm" />
</div>
Nach der Autorisierung bestätigt Ihr Terminal, dass alles eingerichtet ist.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App-Gerüst erfolgreich erstellt" />
</div>
## Schritt 5: Mit der Entwicklung beginnen
Wechseln Sie in Ihren neuen App-Ordner und starten Sie den Entwicklungsserver:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
Dadurch werden Ihre Quelldateien überwacht, bei jeder Änderung neu gebaut und Ihre App automatisch mit dem lokalen Twenty-Server synchronisiert. In Ihrem Terminal sollte eine Live-Statusanzeige angezeigt werden.
Für ausführlichere Ausgaben (Build-Protokolle, Sync-Anfragen, Fehlerspuren) verwenden Sie das Flag `--verbose`:
Das Scaffolding-Tool unterstützt zwei Modi, um zu steuern, welche Beispieldateien enthalten sind:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
Der Dev-Modus ist nur auf Twenty-Instanzen verfügbar, die im Entwicklungsmodus laufen (`NODE_ENV=development`). Produktionsinstanzen lehnen Dev-Synchronisierungsanfragen ab. Verwenden Sie `yarn twenty deploy`, um auf Produktionsservern bereitzustellen — Details finden Sie unter [Apps veröffentlichen](/l/de/developers/extend/apps/publishing).
</Warning>
Von hier aus können Sie:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Terminalausgabe im Dev-Modus" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Schritt 6: Ihre App in Twenty ansehen
# Watch your application's function logs
yarn twenty function:logs
Öffnen Sie [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) in Ihrem Browser. Navigieren Sie zu **Settings > Apps** und wählen Sie die Registerkarte **Developer**. Unter **Your Apps** sollte Ihre App aufgeführt sein:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Liste &#x22;Your Apps&#x22;, die &#x22;My twenty app&#x22; anzeigt" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Klicken Sie auf **My twenty app**, um die **Anwendungsregistrierung** zu öffnen. Eine Registrierung ist ein Servereintrag, der Ihre App beschreibt — ihren Namen, den eindeutigen Bezeichner, OAuth-Zugangsdaten und die Quelle (lokal, npm oder Tarball). Sie befindet sich auf dem Server, nicht in einem bestimmten Arbeitsbereich. Wenn Sie eine App in einen Arbeitsbereich installieren, erstellt Twenty eine arbeitsbereichsbezogene Anwendung, die auf diese Registrierung verweist. Eine Registrierung kann in mehreren Arbeitsbereichen auf demselben Server installiert werden.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Details der Anwendungsregistrierung" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Klicken Sie auf **View installed app**, um die installierte App anzuzeigen. Die Registerkarte **About** zeigt die aktuelle Version und Verwaltungsoptionen:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Installierte App — Registerkarte About" />
</div>
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Wechseln Sie zur Registerkarte **Content**, um alles zu sehen, was Ihre App bereitstellt — Objekte, Felder, Logikfunktionen und Agenten:
## Projektstruktur (vom Scaffolder erzeugt)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Installierte App — Registerkarte Content" />
</div>
Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der Scaffolder Folgendes:
Alles erledigt! Bearbeiten Sie eine beliebige Datei in `src/`, und die Änderungen werden automatisch übernommen.
* Kopiert eine minimale Basisanwendung nach `my-twenty-app/`
* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu
* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus
Wechseln Sie zu [Apps erstellen](/l/de/developers/extend/apps/building) für eine ausführliche Anleitung zum Erstellen von Objekten, Logikfunktionen, Frontend-Komponenten, Skills und mehr.
---
## Projektstruktur
Das Scaffolding-Tool erzeugt die folgende Verzeichnisstruktur (gezeigt im Modus `--exhaustive`, der Beispiele für jeden Entitätstyp enthält):
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
Standardmäßig (`--minimal`) werden nur die Kerndateien erstellt: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`. Verwenden Sie `--exhaustive`, um alle oben gezeigten Beispieldateien einzuschließen.
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`).
### Wichtige Dateien
Auf hoher Ebene:
| Datei / Ordner | Zweck |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `package.json` | Deklariert den App-Namen, die Version und Abhängigkeiten. Enthält ein `twenty`-Skript, sodass Sie `yarn twenty help` ausführen können, um alle Befehle anzuzeigen. |
| `src/application-config.ts` | **Erforderlich.** Die Hauptkonfigurationsdatei für Ihre App. |
| `src/roles/` | Definiert Rollen, die steuern, worauf Ihre Logikfunktionen zugreifen können. |
| `src/logic-functions/` | Serverseitige Funktionen, die durch Routen, Cron-Zeitpläne oder Datenbankereignisse ausgelöst werden. |
| `src/front-components/` | React-Komponenten, die innerhalb der Twenty-UI gerendert werden. |
| `src/objects/` | Benutzerdefinierte Objektdefinitionen zur Erweiterung Ihres Datenmodells. |
| `src/fields/` | Benutzerdefinierte Felder, die vorhandenen Objekten hinzugefügt werden. |
| `src/views/` | Konfigurationen gespeicherter Ansichten. |
| `src/navigation-menu-items/` | Benutzerdefinierte Links in der Seitenleisten-Navigation. |
| `src/skills/` | Skills, die die KI-Agenten von Twenty erweitern. |
| `src/agents/` | KI-Agenten mit benutzerdefinierten Prompts. |
| `src/page-layouts/` | Benutzerdefinierte Seitenlayouts für Datensatzansichten. |
| `src/__tests__/` | Integrationstests (Setup + Beispieltest). |
| `public/` | Statische Assets (Bilder, Schriftarten), die mit Ihrer App ausgeliefert werden. |
* **package.json**: Deklariert den App-Namen, die Version und die Engines (Node 24+, Yarn 4) und fügt `twenty-sdk` sowie ein `twenty`-Skript hinzu, das an die lokale `twenty`-CLI delegiert. Führen Sie `yarn twenty help` aus, um alle verfügbaren Befehle aufzulisten.
* **.gitignore**: Ignoriert übliche Artefakte wie `node_modules`, `.yarn`, `generated/` (typisierter Client), `dist/`, `build/`, Coverage-Ordner, Logdateien und `.env*`-Dateien.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Fixieren und konfigurieren die vom Projekt verwendete Yarn-4-Toolchain.
* **.nvmrc**: Legt die vom Projekt erwartete Node.js-Version fest.
* **.oxlintrc.json** und **tsconfig.json**: Stellen Linting und TypeScript-Konfiguration für die TypeScript-Quellen Ihrer App bereit.
* **README.md**: Ein kurzes README im App-Root mit grundlegenden Anweisungen.
* **public/**: Ein Ordner zum Speichern öffentlicher Assets (Bilder, Schriftarten, statische Dateien), die zusammen mit Ihrer Anwendung bereitgestellt werden. Hier abgelegte Dateien werden während der Synchronisierung hochgeladen und sind zur Laufzeit zugänglich.
* **src/**: Der Hauptort, an dem Sie Ihre Anwendung als Code definieren
## Remotes verwalten
### Entitätserkennung
Ein **Remote** ist ein Twenty-Server, mit dem sich Ihre App verbindet. Während der Einrichtung erstellt das Scaffolding-Tool automatisch eines für Sie. Sie können jederzeit weitere Remotes hinzufügen oder zwischen ihnen wechseln.
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Ihre Anmeldedaten werden in `~/.twenty/config.json` gespeichert.
## Lokaler Entwicklungsserver (`yarn twenty server`)
Die CLI kann einen lokalen, in Docker laufenden Twenty-Server verwalten. Dies ist derselbe Server, der automatisch gestartet wird, wenn Sie mit `create-twenty-app` eine App aufsetzen, aber Sie können ihn auch manuell verwalten.
### Server starten
```bash filename="Terminal"
yarn twenty server start
```
Dadurch wird das Docker-Image `twentycrm/twenty-app-dev:latest` heruntergeladen (falls nicht bereits vorhanden), ein Container namens `twenty-app-dev` erstellt und auf Port **2020** gestartet. Die CLI wartet, bis der Server seinen Health-Check bestanden hat, bevor sie zurückkehrt.
Es werden zwei Docker-Volumes erstellt, um Daten zwischen Neustarts beizubehalten:
* `twenty-app-dev-data` — PostgreSQL-Datenbank
* `twenty-app-dev-storage` — Dateispeicher
Wenn Port 2020 bereits verwendet wird, können Sie auf einem anderen Port starten:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
Die CLI konfiguriert automatisch die internen Werte `NODE_PORT` und `SERVER_URL` des Containers passend zum gewählten Port, sodass Logikfunktionen, OAuth und alle anderen internen Netzwerkfunktionen korrekt arbeiten.
Nach dem Start wird der Server automatisch als `local`-Remote in Ihrer CLI-Konfiguration registriert.
### Serverstatus prüfen
```bash filename="Terminal"
yarn twenty server status
```
Zeigt an, ob der Server läuft, seine URL und die Standard-Anmeldedaten (`tim@apple.dev` / `tim@apple.dev`).
### Serverprotokolle anzeigen
```bash filename="Terminal"
yarn twenty server logs
```
Streamt die Containerprotokolle. Verwenden Sie `--lines`, um zu steuern, wie viele der letzten Zeilen angezeigt werden:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Server stoppen
```bash filename="Terminal"
yarn twenty server stop
```
Stoppt den Container. Ihre Daten bleiben in den Docker-Volumes erhalten — der nächste `start` macht dort weiter, wo Sie aufgehört haben.
### Server zurücksetzen
```bash filename="Terminal"
yarn twenty server reset
```
Entfernt den Container **und** löscht beide Docker-Volumes, wobei alle Daten gelöscht werden. Der nächste `start` erstellt eine frische Instanz.
| Hilfsfunktion | Entitätstyp |
| -------------------------------- | ------------------------------------------------------------------------ |
| `defineObject` | Benutzerdefinierte Objektdefinitionen |
| `defineLogicFunction` | Definitionen von Logikfunktionen |
| `definePreInstallLogicFunction` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) |
| `definePostInstallLogicFunction` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) |
| `defineFrontComponent` | Definitionen von Frontend-Komponenten |
| `defineRole` | Rollendefinitionen |
| `defineField` | Felderweiterungen für bestehende Objekte |
| `defineView` | Gespeicherte View-Definitionen |
| `defineNavigationMenuItem` | Definitionen von Navigationsmenüeinträgen |
| `defineSkill` | Skill-Definitionen für KI-Agenten |
<Note>
Der Server erfordert, dass **Docker** läuft. Wenn der Fehler "Docker not running" angezeigt wird, stellen Sie sicher, dass Docker Desktop (oder der Docker-Daemon) gestartet ist.
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
</Note>
### Befehlsreferenz
Beispiel für eine erkannte Entität:
| Befehl | Beschreibung |
| -------------------------------------- | ----------------------------------------------------------- |
| `yarn twenty server start` | Lokalen Server starten (lädt das Image bei Bedarf herunter) |
| `yarn twenty server start --port 3030` | Auf einem benutzerdefinierten Port starten |
| `yarn twenty server stop` | Server stoppen (Daten bleiben erhalten) |
| `yarn twenty server status` | Serverstatus, URL und Anmeldedaten anzeigen |
| `yarn twenty server logs` | Serverprotokolle streamen |
| `yarn twenty server logs --lines 100` | Die letzten 100 Protokollzeilen anzeigen |
| `yarn twenty server reset` | Alle Daten löschen und neu starten |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## CI mit GitHub Actions
Das Scaffolding-Tool erzeugt einen einsatzbereiten GitHub-Actions-Workflow in `.github/workflows/ci.yml`. Er führt Ihre Integrationstests automatisch bei jedem Push auf `main` und bei Pull Requests aus.
Der Workflow:
1. Checkt Ihren Code aus
2. Startet einen temporären Twenty-Server mit der Aktion `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Installiert Abhängigkeiten mit `yarn install --immutable`
4. Führt `yarn test` aus, wobei `TWENTY_API_URL` und `TWENTY_API_KEY` aus den Aktionsausgaben injiziert werden.
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Sie müssen keine Secrets konfigurieren — die Aktion `spawn-twenty-docker-image` startet einen flüchtigen Twenty-Server direkt im Runner und gibt die Verbindungsdetails aus. Das Secret `GITHUB_TOKEN` wird automatisch von GitHub bereitgestellt.
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
Um eine bestimmte Twenty-Version statt `latest` festzulegen, ändern Sie die Umgebungsvariable `TWENTY_VERSION` oben im Workflow.
* `yarn twenty dev` generiert automatisch zwei typisierte API-Clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) und `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`).
* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu.
## Authentifizierung
Wenn Sie `yarn twenty auth:login` zum ersten Mal ausführen, werden Sie nach Folgendem gefragt:
* API-URL (standardmäßig http://localhost:3000 oder Ihr aktuelles Workspace-Profil)
* API-Schlüssel
Ihre Anmeldedaten werden pro Benutzer in `~/.twenty/config.json` gespeichert. Sie können mehrere Profile verwalten und zwischen ihnen wechseln.
### Arbeitsbereiche verwalten
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
Sobald Sie mit `yarn twenty auth:switch` den Arbeitsbereich gewechselt haben, verwenden alle nachfolgenden Befehle standardmäßig diesen Arbeitsbereich. Sie können es weiterhin vorübergehend mit `--workspace <name>` überschreiben.
## Manuelle Einrichtung (ohne Scaffolder)
Wenn Sie die Einrichtung lieber selbst vornehmen möchten, anstatt `create-twenty-app` zu verwenden, können Sie dies in zwei Schritten tun.
**1. Fügen Sie `twenty-sdk` und `twenty-client-sdk` als Abhängigkeiten hinzu:**
Wir empfehlen zwar `create-twenty-app` für das beste Einstiegserlebnis, Sie können ein Projekt aber auch manuell einrichten. Installieren Sie die CLI nicht global. Fügen Sie stattdessen `twenty-sdk` als lokale Abhängigkeit hinzu und binden Sie ein einzelnes Skript in Ihrer package.json ein:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Fügen Sie Ihrer `package.json` ein `twenty`-Skript hinzu:**
Fügen Sie dann ein `twenty`-Skript hinzu:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
Sie können jetzt `yarn twenty dev`, `yarn twenty help` und alle anderen Befehle ausführen.
Jetzt können Sie alle Befehle über `yarn twenty <command>` ausführen, z. B. `yarn twenty dev`, `yarn twenty help` usw.
<Note>
Installieren Sie `twenty-sdk` nicht global. Verwenden Sie es immer als lokale Projektabhängigkeit, damit jedes Projekt seine eigene Version festlegen kann.
</Note>
## So verwenden Sie eine lokale Twenty-Instanz
Wenn Sie bereits lokal eine Twenty-Instanz ausführen (z. B. über `npx nx start twenty-server`), können Sie sich damit verbinden, anstatt Docker zu verwenden:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Fehlerbehebung
Wenn Probleme auftreten:
* Authentifizierungsfehler: Führen Sie `yarn twenty auth:login` aus und stellen Sie sicher, dass Ihr API-Schlüssel die erforderlichen Berechtigungen hat.
* Verbindung zum Server nicht möglich: Überprüfen Sie die API-URL und dass der Twenty-Server erreichbar ist.
* Typen oder Client fehlen oder sind veraltet: Starten Sie `yarn twenty dev` neu — der typisierte Client wird automatisch generiert.
* Dev-Modus synchronisiert nicht: Stellen Sie sicher, dass `yarn twenty dev` läuft und dass Änderungen von Ihrer Umgebung nicht ignoriert werden.
* Stellen Sie sicher, dass **Docker läuft**, bevor Sie das Scaffolding-Tool mit einer lokalen Instanz starten.
* Stellen Sie sicher, dass Sie **Node.js 24+** verwenden (`node -v` zur Überprüfung).
* Stellen Sie sicher, dass **Corepack aktiviert ist** (`corepack enable`), damit Yarn 4 verfügbar ist.
* Versuchen Sie, `node_modules` zu löschen und `yarn install` erneut auszuführen, wenn Abhängigkeiten fehlerhaft erscheinen.
Hängen Sie immer noch fest? Bitten Sie im [Twenty-Discord](https://discord.com/channels/1130383047699738754/1130386664812982322) um Hilfe.
Discord-Hilfekanal: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder
---
<Warning>
Apps befinden sich derzeit in der Alpha-Phase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter.
</Warning>
## Übersicht
Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen:
* **Einen Tarball bereitstellen** — Laden Sie Ihre App direkt auf einen bestimmten Twenty-Server für die interne oder private Nutzung hoch.
* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann.
Beide Pfade beginnen mit demselben **Build**-Schritt.
## Erstellen Ihrer App
Führen Sie den Build-Befehl aus, um Ihre App zu kompilieren und eine distributionsfertige `manifest.json` zu erzeugen:
```bash filename="Terminal"
yarn twenty build
```
Dabei werden TypeScript-Quelltexte kompiliert, Logikfunktionen und Frontend-Komponenten transpiliert und alles in `.twenty/output/` geschrieben. Fügen Sie `--tarball` hinzu, um zusätzlich ein `.tgz`-Paket für die manuelle Verteilung oder den Deploy-Befehl zu erzeugen.
## Bereitstellung auf einem Server (Tarball)
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, ausschließlich für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einem Twenty-Server bereitstellen.
### Voraussetzungen
Bevor Sie bereitstellen, benötigen Sie ein konfiguriertes Remote, das auf den Zielserver zeigt. Remotes speichern die Server-URL und Anmeldeinformationen lokal in `~/.twenty/config.json`.
Ein Remote hinzufügen:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Bereitstellen
Bauen und laden Sie Ihre App in einem Schritt auf den Server hoch:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Eine bereitgestellte App freigeben
Tarball-Apps werden nicht im öffentlichen Marktplatz gelistet, daher entdecken andere Arbeitsbereiche auf demselben Server sie nicht durch Stöbern. So geben Sie eine bereitgestellte App frei:
1. Gehen Sie zu **Einstellungen > Anwendungen > Registrierungen** und öffnen Sie Ihre App
2. Klicken Sie im Tab **Distribution** auf **Freigabelink kopieren**
3. Teilen Sie diesen Link mit Nutzern in anderen Arbeitsbereichen — er führt sie direkt zur Installationsseite der App
Der Freigabelink verwendet die Basis-URL des Servers (ohne Workspace-Subdomain), sodass er für jeden Arbeitsbereich auf dem Server funktioniert.
<Warning>
Das Teilen privater Apps ist eine Enterprise-Funktion. Gehen Sie zu [Einstellungen > Admin-Panel > Enterprise](/settings/admin-panel#enterprise), um es zu aktivieren.
</Warning>
### Versionsverwaltung
So veröffentlichen Sie ein Update:
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
2. Führen Sie `yarn twenty deploy` aus (oder `yarn twenty deploy --remote production`)
3. Arbeitsbereiche, die die App installiert haben, sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
{/* TODO: add screenshot of the Upgrade button */}
* **Einen Tarball pushen** — stellen Sie Ihre App auf einem bestimmten Twenty-Server für die interne Nutzung bereit, ohne sie öffentlich verfügbar zu machen.
## Auf npm veröffentlichen
@@ -81,69 +21,29 @@ Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Je
### Anforderungen
* Ein [npm](https://www.npmjs.com)-Konto
* Das Schlüsselwort `twenty-app` in Ihrem `package.json`-Array `keywords` (bereits enthalten, wenn Sie mit `create-twenty-app` ein Gerüst erstellen)
* Ihr Paketname **muss** das Präfix `twenty-app-` verwenden (z. B. `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Schritte
### Marktplatz-Metadaten
Die `defineApplication()`-Konfiguration unterstützt optionale Felder, die steuern, wie Ihre App im Marktplatz erscheint. Verwenden Sie `logoUrl` und `screenshots`, um Bilder aus dem Ordner `public/` zu referenzieren:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
Siehe das [defineApplication-Akkordeon](/l/de/developers/extend/apps/building#defineentity-functions) auf der Seite Building Apps für die vollständige Liste der Marktplatzfelder (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` usw.).
### Veröffentlichen
1. **App erstellen** — die CLI kompiliert Ihre TypeScript-Quellen und erzeugt das Anwendungsmanifest:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
Um unter einem bestimmten dist-tag zu veröffentlichen (z. B. `beta` oder `next`):
2. **Auf npm veröffentlichen** — pushen Sie das gebaute Paket in die npm-Registry:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### So funktioniert die Marktplatz-Erkennung
### Automatische Erkennung
Der Twenty-Server synchronisiert seinen Marktplatzkatalog **stündlich** aus der npm-Registry.
Sie können die Synchronisierung sofort auslösen, anstatt zu warten:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
Die im Marktplatz angezeigten Metadaten stammen aus Ihrer `defineApplication()`-Konfiguration — Felder wie `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` und `termsUrl`.
<Note>
Wenn deine App keine `aboutDescription` in `defineApplication()` definiert, verwendet der Marktplatz automatisch die `README.md` deines Pakets von npm als Inhalt der Über-uns-Seite. Das bedeutet, dass du eine einzige README sowohl für npm als auch für den Twenty-Marktplatz pflegen kannst. Wenn du im Marktplatz eine andere Beschreibung möchtest, setze `aboutDescription` explizit.
</Note>
Pakete mit dem Präfix `twenty-app-` werden vom Twenty-Marktplatzkatalog automatisch erkannt. Nach der Veröffentlichung erscheint Ihre App innerhalb weniger Minuten im Marktplatz — keine manuelle Registrierung oder Genehmigung erforderlich.
### CI-Veröffentlichung
Verwenden Sie diesen GitHub-Actions-Workflow, um bei jedem Release automatisch zu veröffentlichen (verwendet [OIDC](https://docs.npmjs.com/trusted-publishers)):
Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt. Er führt `app:build` aus und danach `npm publish --provenance` aus dem Build-Output:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `yarn twenty build` und anschließend `npm publish` aus `.twenty/output`.
Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `npx twenty build` und anschließend `npm publish` aus `.twenty/output`.
<Note>
<Tip>
**npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise.
</Note>
</Tip>
## Apps installieren
## Interne Verteilung
Sobald eine App veröffentlicht (npm) oder bereitgestellt (Tarball) wurde, können Arbeitsbereiche sie über die Benutzeroberfläche installieren.
Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, nur für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einen Twenty-Server pushen.
Gehen Sie zur Seite **Einstellungen > Anwendungen** in Twenty, auf der sowohl Marktplatz- als auch per Tarball bereitgestellte Apps durchsucht und installiert werden können.
### Einen Tarball pushen
{/* TODO: add screenshot of the UI when the app is registered */}
Sie können Apps auch über die Befehlszeile installieren:
Erstellen Sie Ihre App und stellen Sie sie in einem Schritt auf einem bestimmten Server bereit:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
Jeder Arbeitsbereich auf diesem Server kann die App anschließend über die Seite **Applications** in den Einstellungen installieren und aktualisieren.
### Versionsverwaltung
So veröffentlichen Sie ein Update:
1. Erhöhen Sie das Feld `version` in Ihrer `package.json`
2. Pushen Sie einen neuen Tarball mit `npx twenty publish --server <server-url>`
3. Arbeitsbereiche auf diesem Server sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist.
<Note>
Interne Apps sind auf den Server beschränkt, auf den sie gepusht werden. Sie erscheinen nicht im öffentlichen Marktplatz und können von Arbeitsbereichen auf anderen Servern nicht installiert werden.
</Note>
## App-Kategorien
Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart:
| Kategorie | Wie es funktioniert | Im Marktplatz sichtbar? |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
| **Entwicklung** | Lokale Apps im Entwicklungsmodus, die über `yarn twenty dev` ausgeführt werden. Zum Erstellen und Testen verwendet. | Nein |
| **Veröffentlicht** | Auf npm veröffentlichte Apps mit dem Präfix `twenty-app-`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja |
| **Intern** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server verfügbar. | Nein |
<Tip>
Beginnen Sie im **Entwicklungsmodus**, während Sie Ihre App erstellen. Wenn sie bereit ist, wählen Sie **Veröffentlicht** (npm) für die breite Verteilung oder **Intern** (Tarball) für die private Bereitstellung.
</Tip>
File diff suppressed because it is too large Load Diff
@@ -55,12 +55,11 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -80,40 +79,11 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
@@ -121,12 +91,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -138,15 +108,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
const filePath = uploadResult.data?.uploadFile?.path;
if (!fileId) {
throw new Error('No file id returned from upload');
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 4: Create the attachment linked to the company
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -154,7 +124,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -165,13 +135,8 @@ export const main = async (
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
fullPath: filePath,
companyId,
},
},
}),
@@ -191,14 +156,14 @@ export const main = async (
#### An ein anderes Objekt anhängen
Ersetzen Sie `targetCompanyId` durch das entsprechende Feld:
Ersetzen Sie `companyId` durch das entsprechende Feld:
| Objekt | Feldname |
| -------------------------- | -------------------------- |
| Unternehmen | `targetCompanyId` |
| Person | `targetPersonId` |
| Opportunity | `targetOpportunityId` |
| Benutzerdefiniertes Objekt | `targetYourCustomObjectId` |
| Objekt | Feldname |
| -------------------------- | -------------------- |
| Unternehmen | `companyId` |
| Person | `personId` |
| Opportunity | `opportunityId` |
| Benutzerdefiniertes Objekt | `yourCustomObjectId` |
Aktualisieren Sie sowohl den Funktionsparameter als auch das Objekt `variables.data` in der Attachment-Mutation.
File diff suppressed because it is too large Load Diff
@@ -4,142 +4,84 @@ description: Crea la tua prima app Twenty in pochi minuti.
---
<Warning>
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
</Warning>
Le app ti permettono di estendere Twenty con oggetti, campi, funzioni logiche, competenze IA e componenti UI personalizzati — il tutto gestito come codice.
**Cosa puoi fare oggi:**
* Definisci oggetti e campi personalizzati come codice (modello dati gestito)
* Crea funzioni logiche con trigger personalizzati (route HTTP, cron, eventi del database)
* Definisci le abilità per gli agenti IA
* Crea componenti front-end che vengono renderizzati all'interno della UI di Twenty
* Distribuisci la stessa app su più spazi di lavoro
## Prerequisiti
Prima di iniziare, assicurati che quanto segue sia installato sul tuo computer:
* Node.js 24+ e Yarn 4
* Docker (per il server di sviluppo locale di Twenty)
* **Node.js 24+** — [Scarica qui](https://nodejs.org/)
* **Yarn 4** — Incluso con Node.js tramite Corepack. Abilitalo eseguendo `corepack enable`
* **Docker** — [Scarica qui](https://www.docker.com/products/docker-desktop/). Necessario per eseguire un'istanza locale di Twenty. Non necessario se hai già un server Twenty in esecuzione.
## Per iniziare
## Passaggio 1: Crea lo scheletro della tua app
Apri un terminale ed esegui:
Crea una nuova app utilizzando lo scaffolder ufficiale, quindi autenticati e inizia a sviluppare:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
Ti verrà chiesto di inserire un nome e una descrizione per la tua app. Premi **Invio** per accettare i valori predefiniti.
Questo crea una nuova cartella chiamata `my-twenty-app` con tutto il necessario.
<Note>
Lo strumento di scaffolding supporta questi flag:
* `--minimal` — genera solo i file essenziali, senza esempi (predefinito)
* `--exhaustive` — genera tutte le entità di esempio
* `--name <name>` — imposta il nome dell'app (salta la richiesta)
* `--display-name <displayName>` — imposta il nome visualizzato (salta la richiesta)
* `--description <description>` — imposta la descrizione (salta la richiesta)
* `--skip-local-instance` — salta la richiesta di configurazione del server locale
</Note>
## Passaggio 2: Configura un'istanza locale di Twenty
Lo strumento di scaffolding chiederà:
> **Vuoi configurare un'istanza locale di Twenty?**
* **Digita `yes`** (consigliato) — Questo scarica l'immagine Docker `twenty-app-dev` e avvia un server Twenty locale sulla porta `2020`. Assicurati che Docker sia in esecuzione prima di continuare.
* **Digita `no`** — Sceglilo se hai già un server Twenty in esecuzione in locale.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Avviare l'istanza locale?" />
</div>
## Passaggio 3: Accedi al tuo spazio di lavoro
Successivamente si aprirà una finestra del browser con la pagina di accesso di Twenty. Accedi con l'account demo preconfigurato:
* **Email:** `tim@apple.dev`
* **Password:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Schermata di accesso di Twenty" />
</div>
## Passaggio 4: Autorizza l'app
Dopo l'accesso, vedrai una schermata di autorizzazione. Questo consente alla tua app di interagire con il tuo spazio di lavoro.
Fai clic su **Authorize** per continuare.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Schermata di autorizzazione della CLI di Twenty" />
</div>
Una volta autorizzato, il terminale confermerà che tutto è configurato.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="App creata con successo" />
</div>
## Passaggio 5: Inizia a sviluppare
Entra nella nuova cartella della tua app e avvia il server di sviluppo:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
Questo controlla i file sorgente, ricompila a ogni modifica e sincronizza automaticamente la tua app con il server Twenty locale. Dovresti vedere un pannello di stato in tempo reale nel terminale.
Per un output più dettagliato (log di build, richieste di sincronizzazione, tracce di errore), usa il flag `--verbose`:
Lo strumento di scaffolding supporta due modalità per controllare quali file di esempio vengono inclusi:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
La modalità di sviluppo è disponibile solo sulle istanze di Twenty in esecuzione in modalità sviluppo (`NODE_ENV=development`). Le istanze di produzione rifiutano le richieste di sincronizzazione in modalità sviluppo. Usa `yarn twenty deploy` per distribuire sui server di produzione — vedi [Pubblicazione delle app](/l/it/developers/extend/apps/publishing) per i dettagli.
</Warning>
Da qui puoi:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Output del terminale in modalità sviluppo" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Passaggio 6: Visualizza la tua app in Twenty
# Watch your application's function logs
yarn twenty function:logs
Apri [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) nel browser. Vai su **Settings > Apps** e seleziona la scheda **Developer**. Dovresti vedere la tua app elencata in **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Elenco Your Apps che mostra My twenty app" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Fai clic su **My twenty app** per aprire la sua **registrazione dell'applicazione**. Una registrazione è un record a livello di server che descrive la tua app — il suo nome, identificatore univoco, credenziali OAuth e origine (locale, npm o tarball). Risiede sul server, non all'interno di uno spazio di lavoro specifico. Quando installi un'app in uno spazio di lavoro, Twenty crea un'**applicazione** con ambito dello spazio di lavoro che rimanda a questa registrazione. Una registrazione può essere installata in più spazi di lavoro sullo stesso server.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Dettagli della registrazione dell'applicazione" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Fai clic su **View installed app** per vedere l'app installata. La scheda **About** mostra la versione corrente e le opzioni di gestione:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="App installata — scheda About" />
</div>
Vedi anche: le pagine di riferimento della CLI per [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Passa alla scheda **Content** per vedere tutto ciò che la tua app fornisce — oggetti, campi, funzioni logiche e agenti:
## Struttura del progetto (generata dallo scaffolder)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="App installata — scheda Content" />
</div>
Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
È tutto pronto! Modifica qualsiasi file in `src/` e le modifiche verranno rilevate automaticamente.
* Copia un'applicazione base minimale in `my-twenty-app/`
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
* Crea file di configurazione e script collegati alla CLI `twenty`
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding
Vai a [Creare app](/l/it/developers/extend/apps/building) per una guida dettagliata sulla creazione di oggetti, funzioni logiche, componenti front-end, skill e altro.
---
## Struttura del progetto
Lo strumento di scaffolding genera la seguente struttura di file (mostrata con la modalità `--exhaustive`, che include esempi per ogni tipo di entità):
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
Per impostazione predefinita (`--minimal`), vengono creati solo i file principali: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Usa `--exhaustive` per includere tutti i file di esempio mostrati sopra.
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
### File principali
A livello generale:
| File / Cartella | Scopo |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Dichiara il nome, la versione e le dipendenze della tua app. Include uno script `twenty` così puoi eseguire `yarn twenty help` per vedere tutti i comandi. |
| `src/application-config.ts` | **Obbligatorio.** Il file di configurazione principale della tua app. |
| `src/roles/` | Definisce i ruoli che controllano a cosa possono accedere le tue funzioni logiche. |
| `src/logic-functions/` | Funzioni lato server attivate da route, pianificazioni cron o eventi del database. |
| `src/front-components/` | Componenti React che vengono renderizzati all'interno della UI di Twenty. |
| `src/objects/` | Definizioni di oggetti personalizzati per estendere il tuo modello dati. |
| `src/fields/` | Campi personalizzati aggiunti a oggetti esistenti. |
| `src/views/` | Configurazioni di viste salvate. |
| `src/navigation-menu-items/` | Link personalizzati nella navigazione laterale. |
| `src/skills/` | Abilità che estendono gli agenti IA di Twenty. |
| `src/agents/` | Agenti IA con prompt personalizzati. |
| `src/page-layouts/` | Layout di pagina personalizzati per le viste dei record. |
| `src/__tests__/` | Test di integrazione (setup + test di esempio). |
| `public/` | Asset statici (immagini, font) serviti insieme alla tua app. |
* **package.json**: Dichiara il nome dell'app, la versione, i motori (Node 24+, Yarn 4) e aggiunge `twenty-sdk` più uno script `twenty` che delega alla CLI locale `twenty`. Esegui `yarn twenty help` per elencare tutti i comandi disponibili.
* **.gitignore**: Ignora i file generati comuni come `node_modules`, `.yarn`, `generated/` (client tipizzato), `dist/`, `build/`, cartelle di coverage, file di log e file `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloccano e configurano la toolchain Yarn 4 utilizzata dal progetto.
* **.nvmrc**: Fissa la versione di Node.js prevista dal progetto.
* **.oxlintrc.json** e **tsconfig.json**: Forniscono linting e configurazione TypeScript per i sorgenti TypeScript della tua app.
* **README.md**: Un breve README nella radice dell'app con istruzioni di base.
* **public/**: Una cartella per archiviare risorse pubbliche (immagini, font, file statici) che saranno servite con la tua applicazione. I file collocati qui vengono caricati durante la sincronizzazione e sono accessibili in fase di esecuzione.
* **src/**: Il luogo principale in cui definisci la tua applicazione come codice
## Gestione dei remoti
### Rilevamento delle entità
Un **remoto** è un server Twenty a cui la tua app si connette. Durante la configurazione, lo strumento di scaffolding ne crea uno automaticamente per te. Puoi aggiungere altri remoti o passare da uno all'altro in qualsiasi momento.
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Le tue credenziali sono archiviate in `~/.twenty/config.json`.
## Server di sviluppo locale (`yarn twenty server`)
La CLI può gestire un server Twenty locale in esecuzione in Docker. Questo è lo stesso server avviato automaticamente quando crei lo scheletro di un'app con `create-twenty-app`, ma puoi anche gestirlo manualmente.
### Avvio del server
```bash filename="Terminal"
yarn twenty server start
```
Questo scarica l'immagine Docker `twentycrm/twenty-app-dev:latest` (se non è già presente), crea un container chiamato `twenty-app-dev` e lo avvia sulla porta **2020**. La CLI attende che il server superi il controllo di integrità prima di restituire il controllo.
Vengono creati due volumi Docker per mantenere i dati tra i riavvii:
* `twenty-app-dev-data` — database PostgreSQL
* `twenty-app-dev-storage` — archiviazione file
Se la porta 2020 è già in uso, puoi avviare su una porta diversa:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
La CLI configura automaticamente le variabili interne del container `NODE_PORT` e `SERVER_URL` per corrispondere alla porta scelta, in modo che funzioni logiche, OAuth e tutto il resto del networking interno funzionino correttamente.
Una volta avviato, il server viene registrato automaticamente come remoto `local` nella configurazione della CLI.
### Verifica dello stato del server
```bash filename="Terminal"
yarn twenty server status
```
Mostra se il server è in esecuzione, il suo URL e le credenziali di accesso predefinite (`tim@apple.dev` / `tim@apple.dev`).
### Visualizzazione dei log del server
```bash filename="Terminal"
yarn twenty server logs
```
Trasmette in streaming i log del container. Usa `--lines` per controllare quante righe recenti mostrare:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Arresto del server
```bash filename="Terminal"
yarn twenty server stop
```
Arresta il container. I tuoi dati vengono conservati nei volumi Docker — il prossimo `start` riprenderà da dove avevi lasciato.
### Reimpostazione del server
```bash filename="Terminal"
yarn twenty server reset
```
Rimuove il container **e** elimina entrambi i volumi Docker, cancellando tutti i dati. Il prossimo `start` crea un'istanza nuova.
| Funzione helper | Tipo di entità |
| -------------------------------- | ------------------------------------------------------------------------------ |
| `defineObject` | Definizioni di oggetti personalizzati |
| `defineLogicFunction` | Definizioni di funzioni logiche |
| `definePreInstallLogicFunction` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) |
| `definePostInstallLogicFunction` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) |
| `defineFrontComponent` | Definizioni dei componenti front-end |
| `defineRole` | Definizioni di ruoli |
| `defineField` | Estensioni di campo per oggetti esistenti |
| `defineView` | Definizioni di viste salvate |
| `defineNavigationMenuItem` | Definizioni delle voci del menu di navigazione |
| `defineSkill` | Definizioni delle competenze degli agenti IA |
<Note>
Il server richiede che **Docker** sia in esecuzione. Se vedi l'errore "Docker not running", assicurati che Docker Desktop (o il demone Docker) sia avviato.
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
</Note>
### Riferimento ai comandi
Esempio di entità rilevata:
| Comando | Descrizione |
| -------------------------------------- | --------------------------------------------------------- |
| `yarn twenty server start` | Avvia il server locale (scarica l'immagine se necessario) |
| `yarn twenty server start --port 3030` | Avvia su una porta personalizzata |
| `yarn twenty server stop` | Arresta il server (conserva i dati) |
| `yarn twenty server status` | Mostra stato del server, URL e credenziali |
| `yarn twenty server logs` | Trasmetti in streaming i log del server |
| `yarn twenty server logs --lines 100` | Mostra le ultime 100 righe di log |
| `yarn twenty server reset` | Elimina tutti i dati e riparti da zero |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## CI con GitHub Actions
Lo strumento di scaffolding genera un workflow GitHub Actions pronto all'uso in `.github/workflows/ci.yml`. Esegue automaticamente i test di integrazione a ogni push su `main` e sulle pull request.
Il workflow:
1. Esegue il checkout del tuo codice
2. Avvia un server Twenty temporaneo utilizzando l'azione `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Installa le dipendenze con `yarn install --immutable`
4. Esegue `yarn test` con `TWENTY_API_URL` e `TWENTY_API_KEY` iniettati dagli output dell'azione
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Non è necessario configurare alcun secret — l'azione `spawn-twenty-docker-image` avvia un server Twenty effimero direttamente nel runner e fornisce i dettagli di connessione. Il secret `GITHUB_TOKEN` è fornito automaticamente da GitHub.
Comandi successivi aggiungeranno altri file e cartelle:
Per fissare una versione specifica di Twenty invece di `latest`, modifica la variabile d'ambiente `TWENTY_VERSION` all'inizio del workflow.
* `yarn twenty dev` genererà automaticamente due client API tipizzati in `node_modules/twenty-sdk/generated`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`).
* `yarn twenty entity:add` aggiungerà file di definizione delle entità sotto `src/` per i tuoi oggetti, funzioni, componenti front-end, ruoli, competenze e altro ancora.
## Autenticazione
La prima volta che esegui `yarn twenty auth:login`, ti verranno richiesti:
* URL dell'API (predefinito a http://localhost:3000 o al profilo dello spazio di lavoro corrente)
* Chiave API
Le tue credenziali sono archiviate per utente in `~/.twenty/config.json`. Puoi mantenere più profili e passare da uno all'altro.
### Gestione delle aree di lavoro
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
Una volta che hai cambiato area di lavoro con `yarn twenty auth:switch`, tutti i comandi successivi utilizzeranno quell'area di lavoro per impostazione predefinita. Puoi comunque sovrascriverla temporaneamente con `--workspace <name>`.
## Configurazione manuale (senza lo scaffolder)
Se preferisci configurare tutto manualmente invece di usare `create-twenty-app`, puoi farlo in due passaggi.
**1. Aggiungi `twenty-sdk` e `twenty-client-sdk` come dipendenze:**
Sebbene consigliamo di utilizzare `create-twenty-app` per la migliore esperienza iniziale, puoi anche configurare un progetto manualmente. Non installare la CLI globalmente. Invece, aggiungi `twenty-sdk` come dipendenza locale e collega un unico script nel tuo package.json:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Aggiungi uno script `twenty` al tuo `package.json`:**
Quindi aggiungi uno script `twenty`:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
Ora puoi eseguire `yarn twenty dev`, `yarn twenty help` e tutti gli altri comandi.
Ora puoi eseguire tutti i comandi tramite `yarn twenty <command>`, ad es. `yarn twenty dev`, `yarn twenty help`, ecc.
<Note>
Non installare `twenty-sdk` globalmente. Usalo sempre come dipendenza locale del progetto, in modo che ogni progetto possa fissare la propria versione.
</Note>
## Come utilizzare un'istanza locale di Twenty
Se stai già eseguendo un'istanza di Twenty in locale (ad es. tramite `npx nx start twenty-server`), puoi connetterti ad essa invece di usare Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Risoluzione dei problemi
Se riscontri problemi:
* Errori di autenticazione: esegui `yarn twenty auth:login` e assicurati che la tua chiave API abbia i permessi richiesti.
* Impossibile connettersi al server: verifica l'URL dell'API e che il server Twenty sia raggiungibile.
* Tipi o client mancanti/obsoleti: riavvia `yarn twenty dev` — genera automaticamente il client tipizzato.
* Modalità di sviluppo non sincronizzata: assicurati che `yarn twenty dev` sia in esecuzione e che le modifiche non vengano ignorate dal tuo ambiente.
* Assicurati che **Docker sia in esecuzione** prima di avviare lo strumento di scaffolding con un'istanza locale.
* Assicurati di usare **Node.js 24+** (`node -v` per verificare).
* Assicurati che **Corepack sia abilitato** (`corepack enable`) in modo che Yarn 4 sia disponibile.
* Prova a eliminare `node_modules` ed eseguire di nuovo `yarn install` se le dipendenze sembrano danneggiate.
Ancora bloccato? Chiedi aiuto su [Discord di Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
Canale di supporto su Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: Distribuisci la tua app Twenty nel marketplace oppure distribuiscil
---
<Warning>
Le app sono attualmente in fase alfa. La funzionalità funziona ma è ancora in evoluzione.
Le app sono attualmente in fase alfa. La funzionalità è funzionante ma ancora in evoluzione.
</Warning>
## Panoramica
Una volta che la tua app è stata [compilata e testata localmente](/l/it/developers/extend/apps/building), hai due modalità per distribuirla:
* **Distribuisci un tarball** — carica la tua app direttamente su un server Twenty specifico per uso interno o privato.
* **Pubblica su npm** — elenca la tua app nel marketplace di Twenty affinché qualsiasi spazio di lavoro possa scoprirla e installarla.
Entrambi i percorsi partono dalla stessa fase di **build**.
## Compilazione della tua app
Esegui il comando di build per compilare la tua app e generare un `manifest.json` pronto per la distribuzione:
```bash filename="Terminal"
yarn twenty build
```
Questo compila i sorgenti TypeScript, transpila le funzioni di logica e i componenti front-end e scrive tutto in `.twenty/output/`. Aggiungi `--tarball` per produrre anche un pacchetto `.tgz` per la distribuzione manuale o il comando di deploy.
## Distribuzione su un server (tarball)
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo aziendali o build sperimentali — puoi distribuire un tarball direttamente su un server Twenty.
### Prerequisiti
Prima della distribuzione, ti serve un remote configurato che punti al server di destinazione. I remote memorizzano localmente l'URL del server e le credenziali di autenticazione in `~/.twenty/config.json`.
Aggiungi un remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Distribuzione
Compila e carica la tua app sul server in un solo passaggio:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Condivisione di un'app distribuita
Le app in formato tarball non sono elencate nel marketplace pubblico, quindi altri spazi di lavoro sullo stesso server non le troveranno navigando. Per condividere un'app distribuita:
1. Vai su **Impostazioni > Applicazioni > Registrazioni** e apri la tua app
2. Nella scheda **Distribuzione**, fai clic su **Copia link di condivisione**
3. Condividi questo link con utenti su altri spazi di lavoro — li porterà direttamente alla pagina di installazione dell'app
Il link di condivisione utilizza l'URL di base del server (senza alcun sottodominio dello spazio di lavoro) così funziona per qualsiasi spazio di lavoro sul server.
<Warning>
La condivisione delle app private è una funzionalità Enterprise. Vai a [Impostazioni > Pannello di amministrazione > Enterprise](/settings/admin-panel#enterprise) per abilitarla.
</Warning>
### Gestione delle versioni
Per rilasciare un aggiornamento:
1. Incrementa il campo `version` nel tuo `package.json`
2. Esegui `yarn twenty deploy` (oppure `yarn twenty deploy --remote production`)
3. Gli spazi di lavoro che hanno l'app installata vedranno l'aggiornamento disponibile nelle proprie impostazioni
{/* TODO: add screenshot of the Upgrade button */}
* **Esegui il push di un tarball** — distribuisci la tua app su un server Twenty specifico per uso interno senza renderla pubblicamente disponibile.
## Pubblicazione su npm
@@ -81,69 +21,29 @@ La pubblicazione su npm rende la tua app scopribile nel marketplace di Twenty. Q
### Requisiti
* Un account [npm](https://www.npmjs.com)
* La parola chiave `twenty-app` nell'array `keywords` del tuo `package.json` (già inclusa quando inizializzi con `create-twenty-app`)
* Il nome del tuo pacchetto **deve** usare il prefisso `twenty-app-` (es. `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Passaggi
### Metadati del marketplace
La configurazione `defineApplication()` supporta campi opzionali che controllano come la tua app appare nel marketplace. Usa `logoUrl` e `screenshots` per fare riferimento alle immagini nella cartella `public/`:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
Vedi l'[accordion defineApplication](/l/it/developers/extend/apps/building#defineentity-functions) nella pagina Building Apps per l'elenco completo dei campi del marketplace (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, ecc.).
### Pubblica
1. **Compila la tua app** — la CLI compila i tuoi sorgenti TypeScript e genera il manifest dell'applicazione:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
Per pubblicare con un dist-tag specifico (ad es. `beta` o `next`):
2. **Pubblica su npm** — esegui il push del pacchetto compilato al registro npm:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### Come funziona l'individuazione nel marketplace
### Rilevamento automatico
Il server Twenty sincronizza il proprio catalogo del marketplace dal registro npm **ogni ora**.
Puoi attivare la sincronizzazione immediatamente invece di aspettare:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
I metadati visualizzati nel marketplace provengono dalla configurazione `defineApplication()` — campi come `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl` e `termsUrl`.
<Note>
Se la tua app non definisce un `aboutDescription` in `defineApplication()`, il marketplace userà automaticamente il `README.md` del tuo pacchetto su npm come contenuto della pagina Informazioni. Questo significa che puoi mantenere un unico README sia per npm sia per il marketplace di Twenty. Se desideri una descrizione diversa nel marketplace, imposta esplicitamente `aboutDescription`.
</Note>
I pacchetti con il prefisso `twenty-app-` vengono rilevati automaticamente dal catalogo del marketplace di Twenty. Una volta pubblicata, la tua app compare nel marketplace entro pochi minuti — non è richiesta alcuna registrazione o approvazione manuale.
### Pubblicazione con CI
Usa questo workflow di GitHub Actions per pubblicare automaticamente a ogni release (usa [OIDC](https://docs.npmjs.com/trusted-publishers)):
Il progetto generato include un workflow di GitHub Actions che pubblica a ogni release. Esegue `app:build`, quindi `npm publish --provenance` dall'output di build:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `yarn twenty build`, quindi `npm publish` da `.twenty/output`.
Per altri sistemi CI (GitLab CI, CircleCI, ecc.), si applicano gli stessi tre comandi: `yarn install`, `npx twenty build`, quindi `npm publish` da `.twenty/output`.
<Note>
<Tip>
**npm provenance** è opzionale ma consigliata. La pubblicazione con `--provenance` aggiunge un badge di attendibilità alla tua scheda npm, consentendo agli utenti di verificare che il pacchetto sia stato creato a partire da uno specifico commit in una pipeline CI pubblica. Consulta la [documentazione su npm provenance](https://docs.npmjs.com/generating-provenance-statements) per le istruzioni di configurazione.
</Note>
</Tip>
## Installazione delle app
## Distribuzione interna
Una volta che un'app è stata pubblicata (npm) o distribuita (tarball), gli spazi di lavoro possono installarla tramite l'interfaccia utente.
Per le app che non vuoi rendere pubbliche — strumenti proprietari, integrazioni solo per l'azienda o build sperimentali — puoi eseguire il push di un tarball direttamente su un server Twenty.
Vai alla pagina **Impostazioni > Applicazioni** in Twenty, dove è possibile sfogliare e installare sia le app del marketplace sia quelle distribuite tramite tarball.
### Push di un tarball
{/* TODO: add screenshot of the UI when the app is registered */}
Puoi anche installare le app dalla riga di comando:
Compila la tua app e distribuiscila su un server specifico in un solo passaggio:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
Qualsiasi spazio di lavoro su quel server può quindi installare e aggiornare l'app dalla pagina delle impostazioni **Applicazioni**.
### Gestione delle versioni
Per rilasciare un aggiornamento:
1. Incrementa il campo `version` nel tuo `package.json`
2. Esegui il push di un nuovo tarball con `npx twenty publish --server <server-url>`
3. Gli spazi di lavoro su quel server vedranno l'aggiornamento disponibile nelle proprie impostazioni
<Note>
Le app interne sono limitate al server su cui vengono caricate. Non compariranno nel marketplace pubblico e non potranno essere installate dagli spazi di lavoro su altri server.
</Note>
## Categorie di app
Twenty organizza le app in tre categorie in base a come vengono distribuite:
| Categoria | Come funziona | Visibile nel marketplace? |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
| **Sviluppo** | App in modalità di sviluppo locale eseguite tramite `yarn twenty dev`. Usate per la compilazione e i test. | No |
| **Pubblicata** | App pubblicate su npm con il prefisso `twenty-app-`. Elencate nel marketplace per l'installazione da parte di qualsiasi spazio di lavoro. | Sì |
| **Interna** | App distribuite tramite tarball su un server specifico. Disponibili solo per gli spazi di lavoro su quel server. | No |
<Tip>
Inizia in modalità **Sviluppo** mentre crei la tua app. Quando è pronta, scegli **Pubblicata** (npm) per un'ampia distribuzione oppure **Interna** (tarball) per una distribuzione privata.
</Tip>
File diff suppressed because it is too large Load Diff
@@ -55,12 +55,11 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -80,40 +79,11 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
@@ -121,12 +91,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -138,15 +108,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
const filePath = uploadResult.data?.uploadFile?.path;
if (!fileId) {
throw new Error('No file id returned from upload');
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 4: Create the attachment linked to the company
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -154,7 +124,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -165,13 +135,8 @@ export const main = async (
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
fullPath: filePath,
companyId,
},
},
}),
@@ -191,14 +156,14 @@ export const main = async (
#### Per allegare a un oggetto diverso
Sostituisci `targetCompanyId` con il campo appropriato:
Sostituisci `companyId` con il campo appropriato:
| Oggetto | Nome del campo |
| ---------------------- | -------------------------- |
| Azienda | `targetCompanyId` |
| Persona | `targetPersonId` |
| Opportunità | `targetOpportunityId` |
| Oggetto personalizzato | `targetYourCustomObjectId` |
| Oggetto | Nome del campo |
| ---------------------- | -------------------- |
| Azienda | `companyId` |
| Persona | `personId` |
| Opportunità | `opportunityId` |
| Oggetto personalizzato | `yourCustomObjectId` |
Aggiorna sia il parametro della funzione sia l'oggetto `variables.data` nella mutation dell'allegato.
File diff suppressed because it is too large Load Diff
@@ -9,137 +9,79 @@ Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ain
Os apps permitem que você estenda o Twenty com objetos, campos, funções de lógica, habilidades de IA e componentes de UI personalizados — tudo gerenciado como código.
**O que você pode fazer hoje:**
* Defina objetos e campos personalizados como código (modelo de dados gerenciado)
* Crie funções de lógica com gatilhos personalizados (rotas HTTP, cron, eventos de banco de dados)
* Defina habilidades para agentes de IA
* Crie componentes de front-end que renderizam dentro da UI do Twenty
* Implemente o mesmo aplicativo em vários espaços de trabalho
## Pré-requisitos
Antes de começar, verifique se o seguinte está instalado na sua máquina:
* Node.js 24+ e Yarn 4
* Docker (para o servidor de desenvolvimento local do Twenty)
* **Node.js 24+** — [Baixe aqui](https://nodejs.org/)
* **Yarn 4** — Vem com o Node.js via Corepack. Ative-o executando `corepack enable`
* **Docker** — [Baixe aqui](https://www.docker.com/products/docker-desktop/). Necessário para executar uma instância local do Twenty. Não é necessário se você já tiver um servidor Twenty em execução.
## Primeiros passos
## Passo 1: Gere o scaffold do seu aplicativo
Abra um terminal e execute:
Crie um novo aplicativo usando o gerador oficial, depois autentique-se e comece a desenvolver:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
Será solicitado que você informe um nome e uma descrição para o seu aplicativo. Pressione **Enter** para aceitar os valores padrão.
Isso cria uma nova pasta chamada `my-twenty-app` com tudo de que você precisa.
<Note>
O gerador de scaffold oferece suporte a estas flags:
* `--minimal` — gera apenas os arquivos essenciais, sem exemplos (padrão)
* `--exhaustive` — gera todas as entidades de exemplo
* `--name <name>` — define o nome do aplicativo (pula o prompt)
* `--display-name <displayName>` — define o nome de exibição (pula o prompt)
* `--description <description>` — define a descrição (pula o prompt)
* `--skip-local-instance` — ignora o prompt de configuração do servidor local
</Note>
## Passo 2: Configure uma instância local do Twenty
O gerador de scaffold perguntará:
> **Você gostaria de configurar uma instância local do Twenty?**
* **Digite `yes`** (recomendado) — Isso baixa a imagem Docker `twenty-app-dev` e inicia um servidor Twenty local na porta `2020`. Certifique-se de que o Docker esteja em execução antes de continuar.
* **Digite `no`** — Escolha esta opção se você já tiver um servidor Twenty em execução localmente.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Deve iniciar instância local?" />
</div>
## Passo 3: Faça login no seu espaço de trabalho
Em seguida, uma janela do navegador será aberta com a página de login do Twenty. Faça login com a conta de demonstração pré-configurada:
* **E-mail:** `tim@apple.dev`
* **Senha:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Tela de login do Twenty" />
</div>
## Passo 4: Autorize o aplicativo
Após fazer login, você verá uma tela de autorização. Isso permite que seu aplicativo interaja com seu espaço de trabalho.
Clique em **Authorize** para continuar.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Tela de autorização da CLI do Twenty" />
</div>
Depois de autorizado, seu terminal confirmará que tudo está configurado.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Scaffold do aplicativo criado com sucesso" />
</div>
## Passo 5: Comece a desenvolver
Entre na nova pasta do seu aplicativo e inicie o servidor de desenvolvimento:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
Isso observa seus arquivos-fonte, recompila a cada alteração e sincroniza seu aplicativo com o servidor Twenty local automaticamente. Você deverá ver um painel de status em tempo real no seu terminal.
Para uma saída mais detalhada (logs de build, solicitações de sincronização, rastros de erro), use a flag `--verbose`:
O gerador de estrutura oferece suporte a dois modos para controlar quais arquivos de exemplo são incluídos:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
O modo de desenvolvimento só está disponível em instâncias do Twenty em modo de desenvolvimento (`NODE_ENV=development`). Instâncias de produção rejeitam solicitações de sincronização de desenvolvimento. Use `yarn twenty deploy` para fazer o deploy em servidores de produção — veja [Publicando aplicativos](/l/pt/developers/extend/apps/publishing) para detalhes.
</Warning>
A partir daqui você pode:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Saída do terminal no modo de desenvolvimento" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Passo 6: Veja seu aplicativo no Twenty
# Watch your application's function logs
yarn twenty function:logs
Abra [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) no seu navegador. Navegue até **Settings > Apps** e selecione a aba **Developer**. Você deverá ver seu aplicativo listado em **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps exibindo My twenty app" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Clique em **My twenty app** para abrir o seu **registro do aplicativo**. Um registro é um registro em nível de servidor que descreve seu aplicativo — seu nome, identificador exclusivo, credenciais OAuth e origem (local, npm ou tarball). Ele reside no servidor, não dentro de nenhum espaço de trabalho específico. Quando você instala um aplicativo em um espaço de trabalho, o Twenty cria uma **aplicação** com escopo do espaço de trabalho que aponta para esse registro. Um registro pode ser instalado em vários espaços de trabalho no mesmo servidor.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalhes do registro do aplicativo" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Clique em **View installed app** para ver o aplicativo instalado. A aba **About** mostra a versão atual e as opções de gerenciamento:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicativo instalado — aba About" />
</div>
Veja também: as páginas de referência da CLI para [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) e [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Altere para a aba **Content** para ver tudo o que seu aplicativo oferece — objetos, campos, funções de lógica e agentes:
## Estrutura do projeto (com scaffold)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicativo instalado — aba Content" />
</div>
Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:
Tudo pronto! Edite qualquer arquivo em `src/` e as alterações serão detectadas automaticamente.
* Copia um aplicativo base mínimo para `my-twenty-app/`
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
* Gera arquivos principais (configuração da aplicação, papel padrão para funções de lógica, funções de pré-instalação e pós-instalação) além de arquivos de exemplo com base no modo de geração de estrutura
Acesse [Criando aplicativos](/l/pt/developers/extend/apps/building) para um guia detalhado sobre criação de objetos, funções de lógica, componentes de front-end, habilidades e mais.
---
## Estrutura do projeto
O gerador de scaffold cria a seguinte estrutura de arquivos (mostrada com o modo `--exhaustive`, que inclui exemplos para cada tipo de entidade):
Um app recém-criado com o modo padrão `--exhaustive` fica assim:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
Por padrão (`--minimal`), apenas os arquivos principais são criados: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`. Use `--exhaustive` para incluir todos os arquivos de exemplo mostrados acima.
Com `--minimal`, apenas os arquivos principais são criados (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`).
### Arquivos principais
Em alto nível:
| Arquivo / Pasta | Finalidade |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Declara o nome, a versão e as dependências do seu aplicativo. Inclui um script `twenty` para que você possa executar `yarn twenty help` e ver todos os comandos. |
| `src/application-config.ts` | **Obrigatório.** O principal arquivo de configuração do seu aplicativo. |
| `src/roles/` | Define papéis que controlam o que suas funções de lógica podem acessar. |
| `src/logic-functions/` | Funções do lado do servidor acionadas por rotas, agendamentos do cron ou eventos de banco de dados. |
| `src/front-components/` | Componentes React que renderizam dentro da interface do Twenty. |
| `src/objects/` | Definições de objetos personalizados para estender seu modelo de dados. |
| `src/fields/` | Campos personalizados adicionados a objetos existentes. |
| `src/views/` | Configurações de visualizações salvas. |
| `src/navigation-menu-items/` | Links personalizados na navegação da barra lateral. |
| `src/skills/` | Habilidades que estendem os agentes de IA do Twenty. |
| `src/agents/` | Agentes de IA com prompts personalizados. |
| `src/page-layouts/` | Layouts de página personalizados para visualizações de registros. |
| `src/__tests__/` | Testes de integração (configuração + teste de exemplo). |
| `public/` | Recursos estáticos (imagens, fontes) servidos com seu aplicativo. |
* **package.json**: Declara o nome do app, versão, engines (Node 24+, Yarn 4), e adiciona `twenty-sdk` além de um script `twenty` que delega para a CLI `twenty` local. Execute `yarn twenty help` para listar todos os comandos disponíveis.
* **.gitignore**: Ignora artefatos comuns como `node_modules`, `.yarn`, `generated/` (cliente tipado), `dist/`, `build/`, pastas de cobertura, arquivos de log e arquivos `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Bloqueiam e configuram a ferramenta Yarn 4 usada pelo projeto.
* **.nvmrc**: Fixa a versão do Node.js esperada pelo projeto.
* **.oxlintrc.json** e **tsconfig.json**: Fornecem lint e configuração do TypeScript para os fontes TypeScript do seu aplicativo.
* **README.md**: Um README curto na raiz do aplicativo com instruções básicas.
* **public/**: Uma pasta para armazenar recursos públicos (imagens, fontes, arquivos estáticos) que serão servidos com sua aplicação. Os arquivos colocados aqui são enviados durante a sincronização e ficam acessíveis em tempo de execução.
* **src/**: O local principal onde você define seu aplicativo como código
## Gerenciando remotos
### Detecção de entidades
Um **remoto** é um servidor Twenty ao qual seu aplicativo se conecta. Durante a configuração, o gerador de scaffold cria um para você automaticamente. Você pode adicionar mais remotos ou alternar entre eles a qualquer momento.
O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Suas credenciais são armazenadas em `~/.twenty/config.json`.
## Servidor de desenvolvimento local (`yarn twenty server`)
A CLI pode gerenciar um servidor Twenty local em execução no Docker. Este é o mesmo servidor iniciado automaticamente quando você cria o scaffold de um aplicativo com `create-twenty-app`, mas você também pode gerenciá-lo manualmente.
### Iniciando o servidor
```bash filename="Terminal"
yarn twenty server start
```
Isso baixa a imagem Docker `twentycrm/twenty-app-dev:latest` (se ainda não estiver presente), cria um contêiner chamado `twenty-app-dev` e o inicia na porta **2020**. A CLI aguarda até que o servidor passe na verificação de integridade antes de retornar.
Dois volumes do Docker são criados para persistir os dados entre reinicializações:
* `twenty-app-dev-data` — banco de dados PostgreSQL
* `twenty-app-dev-storage` — armazenamento de arquivos
Se a porta 2020 já estiver em uso, você pode iniciar em uma porta diferente:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
A CLI configura automaticamente as variáveis internas do contêiner `NODE_PORT` e `SERVER_URL` para corresponderem à porta escolhida, para que as funções de lógica, o OAuth e toda a comunicação interna de rede funcionem corretamente.
Depois de iniciado, o servidor é registrado automaticamente como o remoto `local` na configuração da sua CLI.
### Verificando o status do servidor
```bash filename="Terminal"
yarn twenty server status
```
Exibe se o servidor está em execução, sua URL e as credenciais de login padrão (`tim@apple.dev` / `tim@apple.dev`).
### Visualizando os logs do servidor
```bash filename="Terminal"
yarn twenty server logs
```
Transmite os logs do contêiner. Use `--lines` para controlar quantas linhas recentes mostrar:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Parando o servidor
```bash filename="Terminal"
yarn twenty server stop
```
Interrompe o contêiner. Seus dados são preservados nos volumes do Docker — o próximo `start` continua de onde você parou.
### Redefinindo o servidor
```bash filename="Terminal"
yarn twenty server reset
```
Remove o contêiner **e** exclui os dois volumes do Docker, apagando todos os dados. O próximo `start` cria uma instância nova.
| Função utilitária | Tipo de entidade |
| -------------------------------- | -------------------------------------------------------------------- |
| `defineObject` | Definições de objetos personalizados |
| `defineLogicFunction` | Definições de funções de lógica |
| `definePreInstallLogicFunction` | Função de lógica de pré-instalação (é executada antes da instalação) |
| `definePostInstallLogicFunction` | Função de lógica de pós-instalação (é executada após a instalação) |
| `defineFrontComponent` | Definições de componentes de front-end |
| `defineRole` | Definições de papéis |
| `defineField` | Extensões de campos para objetos existentes |
| `defineView` | Definições de visualizações salvas |
| `defineNavigationMenuItem` | Definições de itens do menu de navegação |
| `defineSkill` | Definições de habilidades de agente de IA |
<Note>
O servidor requer que o **Docker** esteja em execução. Se você vir um erro "Docker not running", certifique-se de que o Docker Desktop (ou o daemon do Docker) esteja iniciado.
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
</Note>
### Referência de comandos
Exemplo de uma entidade detectada:
| Comando | Descrição |
| -------------------------------------- | ------------------------------------------------------ |
| `yarn twenty server start` | Inicia o servidor local (baixa a imagem se necessário) |
| `yarn twenty server start --port 3030` | Iniciar em uma porta personalizada |
| `yarn twenty server stop` | Interrompe o servidor (preserva os dados) |
| `yarn twenty server status` | Mostra o status do servidor, a URL e as credenciais |
| `yarn twenty server logs` | Transmite os logs do servidor |
| `yarn twenty server logs --lines 100` | Mostra as últimas 100 linhas de log |
| `yarn twenty server reset` | Exclui todos os dados e inicia do zero |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## CI com GitHub Actions
O gerador de scaffold cria um workflow do GitHub Actions pronto para uso em `.github/workflows/ci.yml`. Ele executa seus testes de integração automaticamente a cada push para `main` e em pull requests.
O workflow:
1. Faz checkout do seu código
2. Inicializa um servidor Twenty temporário usando a ação `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instala as dependências com `yarn install --immutable`
4. Executa `yarn test` com `TWENTY_API_URL` e `TWENTY_API_KEY` injetados a partir das saídas da ação
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Você não precisa configurar nenhum segredo — a ação `spawn-twenty-docker-image` inicia um servidor Twenty efêmero diretamente no runner e fornece os detalhes de conexão. O segredo `GITHUB_TOKEN` é fornecido automaticamente pelo GitHub.
Comandos posteriores adicionarão mais arquivos e pastas:
Para fixar uma versão específica do Twenty em vez de `latest`, altere a variável de ambiente `TWENTY_VERSION` no topo do workflow.
* `yarn twenty dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros.
## Autenticação
Na primeira vez que você executar `yarn twenty auth:login`, será solicitado o seguinte:
* URL da API (padrão: http://localhost:3000 ou o perfil do seu espaço de trabalho atual)
* Chave de API
Suas credenciais são armazenadas por usuário em `~/.twenty/config.json`. Você pode manter vários perfis e alternar entre eles.
### Gerenciando espaços de trabalho
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
Depois que você alternar os espaços de trabalho com `yarn twenty auth:switch`, todos os comandos subsequentes usarão esse espaço de trabalho por padrão. Você ainda pode substituí-lo temporariamente com `--workspace <name>`.
## Configuração manual (sem o gerador)
Se preferir configurar tudo por conta própria em vez de usar `create-twenty-app`, você pode fazer isso em duas etapas.
**1. Adicione `twenty-sdk` e `twenty-client-sdk` como dependências:**
Embora recomendemos usar `create-twenty-app` para a melhor experiência inicial, você também pode configurar um projeto manualmente. Não instale a CLI globalmente. Em vez disso, adicione `twenty-sdk` como uma dependência local e configure um único script no seu package.json:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Adicione um script `twenty` ao seu `package.json`:**
Em seguida, adicione um script `twenty`:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
Agora você pode executar `yarn twenty dev`, `yarn twenty help` e todos os outros comandos.
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty dev`, `yarn twenty help`, etc.
<Note>
Não instale o `twenty-sdk` globalmente. Use-o sempre como uma dependência local do projeto para que cada projeto possa fixar sua própria versão.
</Note>
## Como usar uma instância local do Twenty
Se você já estiver executando uma instância do Twenty localmente (por exemplo, via `npx nx start twenty-server`), você pode conectar-se a ela em vez de usar o Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Resolução de Problemas
Se você tiver problemas:
* Erros de autenticação: execute `yarn twenty auth:login` e certifique-se de que sua chave de API tenha as permissões necessárias.
* Não é possível conectar ao servidor: verifique a URL da API e se o servidor do Twenty está acessível.
* Types or client missing/outdated: restart `yarn twenty dev` — it auto-generates the typed client.
* Dev mode not syncing: ensure `yarn twenty dev` is running and that changes are not ignored by your environment.
* Certifique-se de que o **Docker está em execução** antes de iniciar o scaffolder com uma instância local.
* Certifique-se de que está usando **Node.js 24+** (`node -v` para verificar).
* Certifique-se de que o **Corepack está ativado** (`corepack enable`) para que o Yarn 4 esteja disponível.
* Tente excluir `node_modules` e executar `yarn install` novamente se as dependências parecerem corrompidas.
Ainda com dificuldades? Peça ajuda no [Discord da Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
Canal de ajuda no Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
@@ -4,75 +4,15 @@ description: Distribua seu aplicativo Twenty no Marketplace ou implante-o intern
---
<Warning>
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
Os aplicativos estão atualmente em testes alfa. O recurso é funcional, mas ainda está evoluindo.
</Warning>
## Visão Geral
Depois que seu aplicativo estiver [compilado e testado localmente](/l/pt/developers/extend/apps/building), você tem dois caminhos para distribuí-lo:
* **Implantar um tarball** — envie seu aplicativo diretamente para um servidor Twenty específico para uso interno ou privado.
* **Publicar no npm** — liste seu aplicativo no Marketplace da Twenty para que qualquer espaço de trabalho possa descobrir e instalar.
Ambos os caminhos começam na mesma etapa de **build**.
## Compilando seu app
Run the build command to compile your app and generate a distribution-ready `manifest.json`:
```bash filename="Terminal"
yarn twenty build
```
This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command.
## Implantando em um servidor (tarball)
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode implantar um tarball diretamente em um servidor Twenty.
### Pré-requisitos
Antes de implantar, você precisa de um remote configurado apontando para o servidor de destino. Os remotes armazenam a URL do servidor e as credenciais de autenticação localmente em `~/.twenty/config.json`.
Adicionar um remote:
```bash filename="Terminal"
yarn twenty remote add --api-url https://your-twenty-server.com --as production
```
### Implantando
Compile e envie seu aplicativo para o servidor em uma única etapa:
```bash filename="Terminal"
yarn twenty deploy
# To deploy to a specific remote:
# yarn twenty deploy --remote production
```
### Compartilhando um aplicativo implantado
Aplicativos em tarball não são listados no marketplace público, então outros espaços de trabalho no mesmo servidor não os descobrirão ao navegar. Para compartilhar um aplicativo implantado:
1. Vá para **Configurações > Aplicações > Registros** e abra seu aplicativo
2. Na guia **Distribuição**, clique em **Copiar link de compartilhamento**
3. Compartilhe esse link com usuários de outros espaços de trabalho — ele os leva diretamente para a página de instalação do aplicativo
O link de compartilhamento usa a URL base do servidor (sem qualquer subdomínio de espaço de trabalho), para funcionar em qualquer espaço de trabalho no servidor.
<Warning>
Sharing private apps is an Enterprise feature. Go to [Settings > Admin Panel > Enterprise](/settings/admin-panel#enterprise) to enable it.
</Warning>
### Gerenciamento de versões
Para lançar uma atualização:
1. Atualize o campo `version` no seu `package.json`
2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`)
3. Os espaços de trabalho que têm o aplicativo instalado verão a atualização disponível em suas configurações
{/* TODO: add screenshot of the Upgrade button */}
* **Enviar um tarball** — implante seu aplicativo em um servidor Twenty específico para uso interno sem torná-lo público.
## Publicação no npm
@@ -81,69 +21,29 @@ Publicar no npm torna seu aplicativo descobrível no Marketplace da Twenty. Qual
### Requisitos
* Uma conta no [npm](https://www.npmjs.com)
* The `twenty-app` keyword in your `package.json` `keywords` array (already included when you scaffold with `create-twenty-app`)
* O nome do seu pacote **deve** usar o prefixo `twenty-app-` (por exemplo, `twenty-app-postcard-sender`)
```json filename="package.json"
{
"name": "twenty-app-postcard-sender",
"version": "1.0.0",
"keywords": ["twenty-app"]
}
```
### Etapas
### Metadados do Marketplace
The `defineApplication()` config supports optional fields that control how your app appears in the marketplace. Use `logoUrl` and `screenshots` to reference images from the `public/` folder:
```ts src/application-config.ts
export default defineApplication({
universalIdentifier: '...',
displayName: 'My App',
description: 'A great app',
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
logoUrl: 'public/logo.png',
screenshots: [
'public/screenshot-1.png',
'public/screenshot-2.png',
],
});
```
See the [defineApplication accordion](/l/pt/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
### Publish
1. **Compile seu aplicativo** — a CLI compila seus códigos-fonte TypeScript e gera o manifesto do aplicativo:
```bash filename="Terminal"
yarn twenty publish
yarn twenty build
```
Para publicar sob uma dist-tag específica (por exemplo, `beta` ou `next`):
2. **Publicar no npm** — envie o pacote compilado para o registro do npm:
```bash filename="Terminal"
yarn twenty publish --tag beta
npx twenty publish
```
### Como funciona a descoberta no marketplace
### Descoberta automática
O servidor Twenty sincroniza seu catálogo do marketplace a partir do registro do npm **a cada hora**.
You can trigger the sync immediately instead of waiting:
```bash filename="Terminal"
yarn twenty catalog-sync
# To target a specific remote:
# yarn twenty catalog-sync --remote production
```
The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`.
<Note>
Se o seu aplicativo não definir um `aboutDescription` em `defineApplication()`, o marketplace usará automaticamente o `README.md` do seu pacote no npm como conteúdo da página Sobre. Isso significa que você pode manter um único README tanto para o npm quanto para o marketplace da Twenty. Se quiser uma descrição diferente no marketplace, defina explicitamente `aboutDescription`.
</Note>
Pacotes com o prefixo `twenty-app-` são detectados automaticamente pelo catálogo do Marketplace da Twenty. Depois de publicado, seu aplicativo aparece no Marketplace em poucos minutos — sem necessidade de registro manual ou aprovação.
### Publicação via CI
Use this GitHub Actions workflow to publish automatically on every release (uses [OIDC](https://docs.npmjs.com/trusted-publishers)):
O projeto gerado inclui um workflow do GitHub Actions que publica a cada lançamento. Ele executa `app:build` e depois `npm publish --provenance` a partir da saída do build:
```yaml filename=".github/workflows/publish.yml"
name: Publish
@@ -168,24 +68,52 @@ jobs:
- run: npx twenty build
- run: npm publish --provenance --access public
working-directory: .twenty/output
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `yarn twenty build` e, em seguida, `npm publish` a partir de `.twenty/output`.
Para outros sistemas de CI (GitLab CI, CircleCI etc.), aplicam-se os mesmos três comandos: `yarn install`, `npx twenty build` e depois `npm publish` a partir de `.twenty/output`.
<Note>
<Tip>
**Proveniência do npm** é opcional, mas recomendada. Publicar com `--provenance` adiciona um selo de confiança à sua listagem no npm, permitindo que os usuários verifiquem que o pacote foi construído a partir de um commit específico em um pipeline de CI público. Consulte a [documentação de proveniência do npm](https://docs.npmjs.com/generating-provenance-statements) para instruções de configuração.
</Note>
</Tip>
## Instalando aplicativos
## Distribuição interna
Once an app is published (npm) or deployed (tarball), workspaces can install it through the UI.
Para aplicativos que você não quer disponibilizar publicamente — ferramentas proprietárias, integrações apenas para empresas ou builds experimentais — você pode enviar um tarball diretamente para um servidor Twenty.
Go to the **Settings > Applications** page in Twenty, where both marketplace and tarball-deployed apps can be browsed and installed.
### Enviar um tarball
{/* TODO: add screenshot of the UI when the app is registered */}
You can also install apps from the command line:
Compile seu aplicativo e implante-o em um servidor específico em uma única etapa:
```bash filename="Terminal"
yarn twenty install
npx twenty publish --server <server-url>
```
Qualquer espaço de trabalho nesse servidor pode então instalar e atualizar o aplicativo na página de configurações de **Aplicativos**.
### Gerenciamento de versões
Para lançar uma atualização:
1. Atualize o campo `version` no seu `package.json`
2. Envie um novo tarball com `npx twenty publish --server <server-url>`
3. Os espaços de trabalho nesse servidor verão a atualização disponível nas suas configurações
<Note>
Aplicativos internos ficam restritos ao servidor para o qual são enviados. Eles não aparecem no Marketplace público e não podem ser instalados por espaços de trabalho em outros servidores.
</Note>
## Categorias de aplicativos
A Twenty organiza os aplicativos em três categorias com base em como são distribuídos:
| Categoria | Como Funciona | Visível no Marketplace? |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| **Desenvolvimento** | Aplicativos em modo de desenvolvimento local executados via `yarn twenty dev`. Usados para compilação e testes. | Não |
| **Publicado** | Aplicativos publicados no npm com o prefixo `twenty-app-`. Listados no Marketplace para que qualquer espaço de trabalho possa instalar. | Sim |
| **Interno** | Aplicativos implantados via tarball em um servidor específico. Disponível apenas para espaços de trabalho nesse servidor. | Não |
<Tip>
Comece no modo de **Desenvolvimento** enquanto cria seu aplicativo. Quando estiver pronto, escolha **Publicado** (npm) para ampla distribuição ou **Interno** (tarball) para implantação privada.
</Tip>
File diff suppressed because it is too large Load Diff
@@ -297,16 +297,6 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Modo somente ambiente:** Se você definir `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, adicione estas variáveis ao seu arquivo `.env`.
</Warning>
## Armazenamento S3
<Warning>
Por padrão, o Twenty armazena os arquivos enviados no sistema de arquivos local. Para implantações em produção, use o S3 ou um serviço compatível com S3 (MinIO, DigitalOcean Spaces, etc.) para garantir que os arquivos persistam entre reinicializações do contêiner e possam escalar em várias instâncias de servidor.
</Warning>
Defina `STORAGE_TYPE=S_3` e configure as variáveis `STORAGE_S3_*` pelo painel de administração ou `.env`. Veja a [referência de config-variables.ts](https://github.com/twentyhq/twenty/blob/main/packages/twenty-server/src/engine/core-modules/twenty-config/config-variables.ts) para a lista completa de variáveis do S3.
Ao usar o S3 com recursos dependentes de CORS (por exemplo, downloads de arquivos no navegador), verifique se o seu bucket permite a origem do seu frontend do Twenty na sua configuração de CORS.
## Funções lógicas e interpretador de código
O Twenty oferece suporte a funções lógicas para fluxos de trabalho e ao interpretador de código para análise de dados com IA. Ambos executam código fornecido pelo usuário e exigem configuração explícita por motivos de segurança.
@@ -55,12 +55,11 @@ export const main = async (
params: { companyId: string },
) => {
const { companyId } = params;
// Replace with your Twenty GraphQL endpoints (/metadata for metadata and files or /graphql for your records)
// Replace with your Twenty GraphQL endpoint
// Cloud: https://api.twenty.com/graphql
// Self-hosted: https://your-domain.com/graphql
const metadataGraphqlEndpoint = 'https://api.twenty.com/metadata';
const dataGraphqlEndpoint = 'https://api.twenty.com/graphql';
const graphqlEndpoint = 'https://api.twenty.com/graphql';
// Replace with your API key from Settings → APIs
const authToken = 'YOUR_API_KEY';
@@ -80,40 +79,11 @@ export const main = async (
const pdfBlob = await pdfResponse.blob();
const pdfFile = new File([pdfBlob], filename, { type: 'application/pdf' });
const fieldMetadataIdQuery = `
query FindUploadFileFieldMetadataId {
objects {
edges {
node {
nameSingular
fieldsList {
id
name
}
}
}
}
}
`;
// Step 2: Find a fieldMetadataId of "Attachment file" field in Attachments object with GraphQL API
const response = await fetch(metadataGraphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`
},
body: {
query: fieldMetadataIdQuery,
}
});
const result = await response.json();
const uploadFileFieldMetadataId = result.data.objects.edges.find(object => object.node.nameSingular === 'attachment').node.fieldsList.find(field => field.name === 'file').id;
// Step 3: Upload the file via GraphQL multipart upload
// Step 2: Upload the file via GraphQL multipart upload
const uploadMutation = `
mutation UploadFilesFieldFile($file: Upload!, $fieldMetadataId: String!) {
uploadFilesFieldFile(file: $file, fieldMetadataId: $fieldMetadataId) {
id
mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) {
path
}
}
`;
@@ -121,12 +91,12 @@ export const main = async (
const uploadForm = new FormData();
uploadForm.append('operations', JSON.stringify({
query: uploadMutation,
variables: { file: null, fieldMetadataId: uploadFileFieldMetadataId },
variables: { file: null, fileFolder: 'Attachment' },
}));
uploadForm.append('map', JSON.stringify({ '0': ['variables.file'] }));
uploadForm.append('0', pdfFile);
const uploadResponse = await fetch(metadataGraphqlEndpoint, {
const uploadResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: { Authorization: `Bearer ${authToken}` },
body: uploadForm,
@@ -138,15 +108,15 @@ export const main = async (
throw new Error(`Upload failed: ${uploadResult.errors[0].message}`);
}
const fileId = uploadResult.data?.uploadFilesFieldFile?.id;
const filePath = uploadResult.data?.uploadFile?.path;
if (!fileId) {
throw new Error('No file id returned from upload');
if (!filePath) {
throw new Error('No file path returned from upload');
}
// Step 4: Create the attachment linked to the company
// Step 3: Create the attachment linked to the company
const attachmentMutation = `
mutation CreateOneAttachment($data: AttachmentCreateInput!) {
mutation CreateAttachment($data: AttachmentCreateInput!) {
createAttachment(data: $data) {
id
name
@@ -154,7 +124,7 @@ export const main = async (
}
`;
const attachmentResponse = await fetch(dataGraphqlEndpoint, {
const attachmentResponse = await fetch(graphqlEndpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${authToken}`,
@@ -165,13 +135,8 @@ export const main = async (
variables: {
data: {
name: filename,
targetCompanyId: companyId,
file: [
{
fileId: fileId,
label: filename
}
]
fullPath: filePath,
companyId,
},
},
}),
@@ -191,14 +156,14 @@ export const main = async (
#### Para anexar a um objeto diferente
Substitua `targetCompanyId` pelo campo apropriado:
Substitua `companyId` pelo campo apropriado:
| Objeto | Nome do Campo |
| -------------------- | -------------------------- |
| Empresa | `targetCompanyId` |
| Pessoa | `targetPersonId` |
| Oportunidade | `targetOpportunityId` |
| Objeto personalizado | `targetYourCustomObjectId` |
| Objeto | Nome do Campo |
| -------------------- | -------------------- |
| Empresa | `companyId` |
| Pessoa | `personId` |
| Oportunidade | `opportunityId` |
| Objeto personalizado | `yourCustomObjectId` |
Atualize tanto o parâmetro da função como o objeto `variables.data` na mutação de anexo.
File diff suppressed because it is too large Load Diff
@@ -9,137 +9,79 @@ Aplicațiile sunt în prezent în testare alfa. Caracteristica funcționează, d
Aplicațiile vă permit să extindeți Twenty cu obiecte personalizate, câmpuri, funcții logice, abilități IA și componente UI — toate gestionate ca cod.
**Ce puteți face astăzi:**
* Definiți obiecte și câmpuri personalizate sub formă de cod (model de date gestionat)
* Construiți funcții logice cu declanșatoare personalizate (rute HTTP, cron, evenimente ale bazei de date)
* Definiți abilități pentru agenți de IA
* Construiți componente front-end care se afișează în interfața Twenty
* Implementați aceeași aplicație în mai multe spații de lucru
## Cerințe
Înainte de a începe, asigurați-vă că următoarele sunt instalate pe calculatorul dvs.:
* Node.js 24+ și Yarn 4
* Docker (pentru serverul local de dezvoltare Twenty)
* **Node.js 24+** — [Descărcați aici](https://nodejs.org/)
* **Yarn 4** — Vine împreună cu Node.js prin Corepack. Activați-l rulând `corepack enable`
* **Docker** — [Descărcați aici](https://www.docker.com/products/docker-desktop/). Necesar pentru a rula o instanță Twenty locală. Nu este necesar dacă aveți deja un server Twenty care rulează.
## Începeți
## Pasul 1: Creați scheletul aplicației
Deschideți un terminal și rulați:
Creați o aplicație nouă folosind generatorul oficial, apoi autentificați-vă și începeți să dezvoltați:
```bash filename="Terminal"
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
```
Vi se va cere să introduceți un nume și o descriere pentru aplicația dvs. Apăsați **Enter** pentru a accepta valorile implicite.
Aceasta creează un folder nou numit `my-twenty-app` cu tot ce aveți nevoie.
<Note>
Generatorul de schelet acceptă următoarele opțiuni:
* `--minimal` — generează doar fișierele esențiale, fără exemple (implicit)
* `--exhaustive` — generează toate entitățile de exemplu
* `--name <name>` — setează numele aplicației (omite solicitarea)
* `--display-name <displayName>` — setează numele afișat (omite solicitarea)
* `--description <description>` — setează descrierea (omite solicitarea)
* `--skip-local-instance` — omite solicitarea de configurare a serverului local
</Note>
## Pasul 2: Configurați o instanță Twenty locală
Generatorul de schelet va întreba:
> **Doriți să configurați o instanță Twenty locală?**
* **Tastați `yes`** (recomandat) — Aceasta descarcă imaginea Docker `twenty-app-dev` și pornește un server Twenty local pe portul `2020`. Asigurați-vă că Docker rulează înainte de a continua.
* **Tastați `no`** — Alegeți această opțiune dacă aveți deja un server Twenty care rulează local.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Porniți instanța locală?" />
</div>
## Pasul 3: Autentificați-vă în spațiul dvs. de lucru
În continuare, se va deschide o fereastră de browser cu pagina de autentificare Twenty. Autentificați-vă cu contul demo preconfigurat:
* **E-mail:** `tim@apple.dev`
* **Parolă:** `tim@apple.dev`
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/login.png" alt="Ecranul de autentificare Twenty" />
</div>
## Pasul 4: Autorizați aplicația
După autentificare, veți vedea un ecran de autorizare. Acest lucru permite aplicației dvs. să interacționeze cu spațiul dvs. de lucru.
Faceți clic pe **Authorize** pentru a continua.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Ecranul de autorizare Twenty CLI" />
</div>
După autorizare, terminalul va confirma că totul este configurat.
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/scaffolded.png" alt="Aplicația a fost creată cu succes" />
</div>
## Pasul 5: Începeți dezvoltarea
Intrați în noul folder al aplicației și porniți serverul de dezvoltare:
```bash filename="Terminal"
cd my-twenty-app
# Start dev mode: automatically syncs local changes to your workspace
yarn twenty dev
```
Acesta monitorizează fișierele sursă, reconstruiește la fiecare modificare și sincronizează automat aplicația cu serverul Twenty local. Ar trebui să vedeți în terminal un panou de stare în timp real.
Pentru un output mai detaliat (jurnale de build, cereri de sincronizare, urme ale erorilor), folosiți opțiunea `--verbose`:
Generatorul de schelet acceptă două moduri pentru a controla ce fișiere de exemplu sunt incluse:
```bash filename="Terminal"
yarn twenty dev --verbose
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill, agent)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
```
<Warning>
Modul de dezvoltare este disponibil doar pe instanțele Twenty care rulează în modul development (`NODE_ENV=development`). Instanțele de producție resping cererile de sincronizare pentru dezvoltare. Folosiți `yarn twenty deploy` pentru a implementa pe serverele de producție — vedeți [Publicarea aplicațiilor](/l/ro/developers/extend/apps/publishing) pentru detalii.
</Warning>
De aici puteți:
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/dev.jpg" alt="Ieșirea terminalului în modul de dezvoltare" />
</div>
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn twenty entity:add
## Pasul 6: Vedeți aplicația în Twenty
# Watch your application's function logs
yarn twenty function:logs
Deschideți [http://localhost:2020/settings/applications#developer](http://localhost:2020/settings/applications#developer) în browser. Navigați la **Settings > Apps** și selectați fila **Developer**. Ar trebui să vedeți aplicația listată la **Your Apps**:
# Execute a function by name
yarn twenty function:execute -n my-function -p '{"name": "test"}'
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-1.png" alt="Lista Your Apps care afișează My twenty app" />
</div>
# Execute the pre-install function
yarn twenty function:execute --preInstall
Faceți clic pe **My twenty app** pentru a deschide **înregistrarea aplicației**. O înregistrare este un element la nivel de server care descrie aplicația — numele, identificatorul unic, acreditările OAuth și sursa (locală, npm sau arhivă tar). Aceasta există pe server, nu în interiorul unui spațiu de lucru anume. Când instalați o aplicație într-un spațiu de lucru, Twenty creează o aplicație la nivelul spațiului de lucru care face referire la această înregistrare. O singură înregistrare poate fi instalată în mai multe spații de lucru pe același server.
# Execute the post-install function
yarn twenty function:execute --postInstall
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-2.png" alt="Detalii despre înregistrarea aplicației" />
</div>
# Uninstall the application from the current workspace
yarn twenty uninstall
Faceți clic pe **View installed app** pentru a vedea aplicația instalată. Fila **About** afișează versiunea curentă și opțiunile de administrare:
# Display commands' help
yarn twenty help
```
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-3.png" alt="Aplicație instalată — fila About" />
</div>
Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
Comutați la fila **Content** pentru a vedea tot ceea ce oferă aplicația — obiecte, câmpuri, funcții logice și agenți:
## Structura proiectului (generată)
<div style={{textAlign: 'center'}}>
<img src="/images/docs/developers/extends/apps/app-in-ui-4.png" alt="Aplicație instalată — fila Content" />
</div>
Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
Totul este gata! Editați orice fișier din `src/`, iar modificările vor fi preluate automat.
* Copiază o aplicație de bază minimală în `my-twenty-app/`
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului.
Accesați [Construirea aplicațiilor](/l/ro/developers/extend/apps/building) pentru un ghid detaliat despre crearea de obiecte, funcții logice, componente front-end, abilități și altele.
---
## Structura proiectului
Generatorul de schelet generează următoarea structură de fișiere (afișată cu modul `--exhaustive`, care include exemple pentru fiecare tip de entitate):
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -152,238 +94,124 @@ my-twenty-app/
install-state.gz
.oxlintrc.json
tsconfig.json
tsconfig.spec.json # TypeScript config for tests
vitest.config.ts # Vitest test runner configuration
LLMS.md
README.md
.github/
└── workflows/
└── ci.yml # GitHub Actions CI workflow
public/ # Public assets (images, fonts, etc.)
public/ # Public assets folder (images, fonts, etc.)
src/
├── application-config.ts # Required main application configuration
├── __tests__/
│ ├── setup-test.ts # Test setup (server health check, config)
│ └── app-install.integration-test.ts # Example integration test
├── application-config.ts # Required - main application configuration
├── roles/
│ └── default-role.ts # Default role for logic functions
│ └── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ ├── create-hello-world-company.ts # Example logic function using CoreApiClient
── pre-install.ts # Runs before installation
│ └── post-install.ts # Runs after installation
│ ├── hello-world.ts # Example logic function
│ ├── pre-install.ts # Pre-install logic function
── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── page-layouts/
│ └── example-record-page-layout.ts # Example page layout with front component
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
── skills/
└── example-skill.ts # Example AI agent skill definition
└── agents/
└── example-agent.ts # Example AI agent definition
── skills/
└── example-skill.ts # Example AI agent skill definition
```
În mod implicit (`--minimal`), sunt create doar fișierele de bază: `application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`. Folosiți `--exhaustive` pentru a include toate fișierele de exemplu prezentate mai sus.
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`).
### Fișiere cheie
Pe scurt:
| Fișier / Folder | Scop |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | Declară numele aplicației, versiunea și dependențele. Include un script `twenty` astfel încât să puteți rula `yarn twenty help` pentru a vedea toate comenzile. |
| `src/application-config.ts` | **Necesar.** Fișierul principal de configurare pentru aplicație. |
| `src/roles/` | Definește roluri care controlează la ce pot avea acces funcțiile logice. |
| `src/logic-functions/` | Funcții pe server declanșate de rute, programări cron sau evenimente din baza de date. |
| `src/front-components/` | Componente React care se afișează în interfața Twenty. |
| `src/objects/` | Definiții de obiecte personalizate pentru a extinde modelul de date. |
| `src/fields/` | Câmpuri personalizate adăugate obiectelor existente. |
| `src/views/` | Configurații pentru vizualizări salvate. |
| `src/navigation-menu-items/` | Linkuri personalizate în bara laterală de navigare. |
| `src/skills/` | Abilități care extind capabilitățile agenților AI ai Twenty. |
| `src/agents/` | Agenți AI cu prompturi personalizate. |
| `src/page-layouts/` | Machete de pagină personalizate pentru vizualizările de înregistrare. |
| `src/__tests__/` | Teste de integrare (configurare + test exemplu). |
| `public/` | Resurse statice (imagini, fonturi) servite împreună cu aplicația. |
* **package.json**: Declară numele aplicației, versiunea, motoarele (Node 24+, Yarn 4) și adaugă `twenty-sdk` plus un script `twenty` care deleagă către CLI-ul local `twenty`. Rulați `yarn twenty help` pentru a lista toate comenzile disponibile.
* **.gitignore**: Ignoră artefacte comune precum `node_modules`, `.yarn`, `generated/` (client tipizat), `dist/`, `build/`, foldere de coverage, fișiere jurnal și fișiere `.env*`.
* **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Blochează și configurează lanțul de instrumente Yarn 4 folosit de proiect.
* **.nvmrc**: Fixează versiunea Node.js așteptată de proiect.
* **.oxlintrc.json** și **tsconfig.json**: Oferă linting și configurație TypeScript pentru fișierele TypeScript ale aplicației.
* **README.md**: Un README scurt în rădăcina aplicației, cu instrucțiuni de bază.
* **public/**: Un folder pentru stocarea resurselor publice (imagini, fonturi, fișiere statice) care vor fi servite împreună cu aplicia dvs. Fișierele plasate aici sunt încărcate în timpul sincronizării și sunt accesibile la rulare.
* **src/**: Locul principal unde vă definiți aplicația sub formă de cod
## Gestionarea remote-urilor
### Detectarea entităților
Un „remote” este un server Twenty la care se conectează aplicația. În timpul configurării, generatorul de schelet creează automat unul pentru dvs. Puteți adăuga mai multe remote-uri sau comuta între ele oricând.
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
```bash filename="Terminal"
# Add a new remote (opens a browser for OAuth login)
yarn twenty remote add
# Connect to a local Twenty server (auto-detects port 2020 or 3000)
yarn twenty remote add --local
# Add a remote non-interactively (useful for CI)
yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote
# List all configured remotes
yarn twenty remote list
# Switch the active remote
yarn twenty remote switch <name>
```
Acreditările dvs. sunt stocate în `~/.twenty/config.json`.
## Server local de dezvoltare (`yarn twenty server`)
CLI-ul poate gestiona un server Twenty local care rulează în Docker. Acesta este același server pornit automat când creați scheletul unei aplicații cu `create-twenty-app`, dar îl puteți gestiona și manual.
### Pornirea serverului
```bash filename="Terminal"
yarn twenty server start
```
Aceasta descarcă imaginea Docker `twentycrm/twenty-app-dev:latest` (dacă nu este deja prezentă), creează un container numit `twenty-app-dev` și îl pornește pe portul **2020**. CLI-ul așteaptă până când serverul trece verificarea de integritate înainte de a reveni.
Sunt create două volume Docker pentru a păstra datele între reporniri:
* `twenty-app-dev-data` — bază de date PostgreSQL
* `twenty-app-dev-storage` — stocare fișiere
Dacă portul 2020 este deja utilizat, puteți porni pe un alt port:
```bash filename="Terminal"
yarn twenty server start --port 3030
```
CLI-ul configurează automat `NODE_PORT` și `SERVER_URL` interne ale containerului pentru a se potrivi cu portul ales, astfel încât funcțiile logice, OAuth și toată rețeaua internă să funcționeze corect.
După pornire, serverul este înregistrat automat ca remote `local` în configurația CLI.
### Verificarea stării serverului
```bash filename="Terminal"
yarn twenty server status
```
Afișează dacă serverul rulează, URL-ul său și acreditările implicite de autentificare (`tim@apple.dev` / `tim@apple.dev`).
### Vizualizarea jurnalelor serverului
```bash filename="Terminal"
yarn twenty server logs
```
Transmite în flux jurnalele containerului. Folosiți `--lines` pentru a controla câte linii recente să fie afișate:
```bash filename="Terminal"
yarn twenty server logs --lines 100
```
### Oprirea serverului
```bash filename="Terminal"
yarn twenty server stop
```
Oprește containerul. Datele dvs. sunt păstrate în volumele Docker — următoarea comandă `start` reia de unde ați rămas.
### Resetarea serverului
```bash filename="Terminal"
yarn twenty server reset
```
Elimină containerul **și** șterge ambele volume Docker, ștergând toate datele. Următoarea comandă `start` creează o instanță nouă.
| Funcție ajutătoare | Tipul entității |
| -------------------------------- | -------------------------------------------------------------- |
| `defineObject` | Definiții de obiecte personalizate |
| `defineLogicFunction` | Definiții de funcții de logică |
| `definePreInstallLogicFunction` | Funcție logică de pre-instalare (rulează înainte de instalare) |
| `definePostInstallLogicFunction` | Funcție logică post-instalare (rulează după instalare) |
| `defineFrontComponent` | Definiții ale componentelor de interfață |
| `defineRole` | Definiții de rol |
| `defineField` | Extensii de câmp pentru obiectele existente |
| `defineView` | Definiții pentru vizualizări salvate |
| `defineNavigationMenuItem` | Definiții pentru elemente de meniu de navigare |
| `defineSkill` | Definiții ale abilităților agentului IA |
<Note>
Serverul necesită ca **Docker** să ruleze. Dacă vedeți eroarea "Docker not running", asigurați-vă că Docker Desktop (sau demonul Docker) este pornit.
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
</Note>
### Referință pentru comenzi
Exemplu de entitate detectată:
| Comandă | Descriere |
| -------------------------------------- | ------------------------------------------------------------- |
| `yarn twenty server start` | Pornește serverul local (descarcă imaginea dacă este necesar) |
| `yarn twenty server start --port 3030` | Pornește pe un port personalizat |
| `yarn twenty server stop` | Oprește serverul (păstrează datele) |
| `yarn twenty server status` | Afișează starea serverului, URL-ul și acreditările |
| `yarn twenty server logs` | Transmite în flux jurnalele serverului |
| `yarn twenty server logs --lines 100` | Afișează ultimele 100 de linii de jurnal |
| `yarn twenty server reset` | Șterge toate datele și pornește de la zero |
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
## CI cu GitHub Actions
Scaffolderul generează un workflow GitHub Actions gata de utilizare în `.github/workflows/ci.yml`. Rulează automat testele de integrare la fiecare push pe `main` și la pull request-uri.
Workflow-ul:
1. Preia codul
2. Pornește un server Twenty temporar folosind acțiunea `twentyhq/twenty/.github/actions/spawn-twenty-docker-image`
3. Instalează dependențele cu `yarn install --immutable`
4. Rulează `yarn test` cu `TWENTY_API_URL` și `TWENTY_API_KEY` injectate din rezultatele acțiunii
```yaml .github/workflows/ci.yml
name: CI
on:
push:
branches:
- main
pull_request: {}
env:
TWENTY_VERSION: latest
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Spawn Twenty instance
id: twenty
uses: twentyhq/twenty/.github/actions/spawn-twenty-docker-image@main
with:
twenty-version: ${{ env.TWENTY_VERSION }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Enable Corepack
run: corepack enable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run integration tests
run: yarn test
env:
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
TWENTY_API_KEY: ${{ steps.twenty.outputs.access-token }}
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Nu trebuie să configurați niciun secret — acțiunea `spawn-twenty-docker-image` pornește un server Twenty efemer direct în runner și oferă detaliile de conectare. Secretul `GITHUB_TOKEN` este furnizat automat de GitHub.
Comenzile ulterioare vor adăuga mai multe fișiere și foldere:
Pentru a fixa o versiune Twenty specifică în loc de `latest`, modificați variabila de mediu `TWENTY_VERSION` din partea de sus a workflow-ului.
* `yarn twenty dev` va genera automat doi clienți API tipizați în `node_modules/twenty-sdk/generated`: `CoreApiClient` (pentru datele spațiului de lucru prin `/graphql`) și `MetadataApiClient` (pentru configurarea spațiului de lucru și încărcarea fișierelor prin `/metadata`).
* `yarn twenty entity:add` va adăuga fișiere de definire a entităților în `src/` pentru obiectele, funcțiile, componentele front-end, rolurile, abilitățile și altele.
## Autentificare
Prima dată când rulați `yarn twenty auth:login`, vi se vor solicita:
* URL-ul API (implicit http://localhost:3000 sau profilul spațiului de lucru curent)
* Cheie API
Acreditările dvs. sunt stocate per utilizator în `~/.twenty/config.json`. Puteți menține mai multe profiluri și comuta între ele.
### Gestionarea spațiilor de lucru
```bash filename="Terminal"
# Login interactively (recommended)
yarn twenty auth:login
# Login to a specific workspace profile
yarn twenty auth:login --workspace my-custom-workspace
# List all configured workspaces
yarn twenty auth:list
# Switch the default workspace (interactive)
yarn twenty auth:switch
# Switch to a specific workspace
yarn twenty auth:switch production
# Check current authentication status
yarn twenty auth:status
```
După ce ați schimbat spațiul de lucru cu `yarn twenty auth:switch`, toate comenzile ulterioare vor folosi implicit acel spațiu de lucru. Îl puteți totuși suprascrie temporar cu `--workspace <name>`.
## Configurare manuală (fără generator)
Dacă preferați să configurați totul manual în loc să folosiți `create-twenty-app`, o puteți face în doi pași.
**1. Adăugați `twenty-sdk` și `twenty-client-sdk` ca dependențe:**
Deși recomandăm utilizarea `create-twenty-app` pentru cea mai bună experiență de început, puteți configura și un proiect manual. Nu instalați CLI-ul global. În schimb, adăugați `twenty-sdk` ca dependență locală și conectați un singur script în package.json-ul dvs.:
```bash filename="Terminal"
yarn add twenty-sdk twenty-client-sdk
yarn add -D twenty-sdk
```
**2. Adăugați un script `twenty` în `package.json`:**
Apoi adăugați un script `twenty`:
```json filename="package.json"
{
@@ -393,19 +221,25 @@ yarn add twenty-sdk twenty-client-sdk
}
```
Acum puteți rula `yarn twenty dev`, `yarn twenty help` și toate celelalte comenzi.
Acum poți rula toate comenzile prin `yarn twenty <command>`, de ex. `yarn twenty dev`, `yarn twenty help`, etc.
<Note>
Nu instalați `twenty-sdk` global. Folosiți-l întotdeauna ca dependență locală de proiect, astfel încât fiecare proiect să își poată fixa propria versiune.
</Note>
## Cum să folosești o instanță Twenty locală
Dacă rulezi deja local o instanță Twenty (de exemplu prin `npx nx start twenty-server`), te poți conecta la ea în loc să folosești Docker:
```bash filename="Terminal"
# During scaffolding — skip Docker, connect to your running instance
npx create-twenty-app@latest my-app --port 3000
# Or after scaffolding — add a remote pointing to your instance
yarn twenty remote add --local --port 3000
```
## Depanare
Dacă întâmpinați probleme:
* Erori de autentificare: rulați `yarn twenty auth:login` și asigurați-vă că cheia API are permisiunile necesare.
* Nu se poate conecta la server: verificați URL-ul API și că serverul Twenty este accesibil.
* Tipuri sau client lipsă/învechite: repornește `yarn twenty dev` — acesta generează automat clientul tipizat.
* Modul dev nu sincronizează: asigură-te că `yarn twenty dev` rulează și că modificările nu sunt ignorate de mediul tău.
* Asigurați-vă că Docker rulează înainte de a porni scaffolderul cu o instanță locală.
* Asigurați-vă că folosiți **Node.js 24+** (`node -v` pentru verificare).
* Asigurați-vă că **Corepack este activat** (`corepack enable`) astfel încât Yarn 4 să fie disponibil.
* Încercați să ștergeți `node_modules` și să rulați din nou `yarn install` dacă dependențele par deteriorate.
Încă aveți probleme? Cereți ajutor pe [Discordul Twenty](https://discord.com/channels/1130383047699738754/1130386664812982322).
Canal de ajutor pe Discord: https://discord.com/channels/1130383047699738754/1130386664812982322

Some files were not shown because too many files have changed in this diff Show More