From 37ca09e8f90dcda38a355cf467565d65bc087a64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 17:06:43 +0200 Subject: [PATCH] i18n - docs translations (#20238) Created by Github action Co-authored-by: github-actions --- .../de/developers/extend/apps/connections.mdx | 193 ++++++++++++++++++ .../de/developers/extend/apps/publishing.mdx | 33 +++ .../pt/developers/extend/apps/connections.mdx | 193 ++++++++++++++++++ .../pt/developers/extend/apps/publishing.mdx | 33 +++ .../ru/developers/extend/apps/connections.mdx | 193 ++++++++++++++++++ .../ru/developers/extend/apps/publishing.mdx | 33 +++ .../tr/developers/extend/apps/connections.mdx | 193 ++++++++++++++++++ .../tr/developers/extend/apps/publishing.mdx | 33 +++ .../zh/developers/extend/apps/connections.mdx | 193 ++++++++++++++++++ .../zh/developers/extend/apps/publishing.mdx | 33 +++ 10 files changed, 1130 insertions(+) create mode 100644 packages/twenty-docs/l/de/developers/extend/apps/connections.mdx create mode 100644 packages/twenty-docs/l/pt/developers/extend/apps/connections.mdx create mode 100644 packages/twenty-docs/l/ru/developers/extend/apps/connections.mdx create mode 100644 packages/twenty-docs/l/tr/developers/extend/apps/connections.mdx create mode 100644 packages/twenty-docs/l/zh/developers/extend/apps/connections.mdx diff --git a/packages/twenty-docs/l/de/developers/extend/apps/connections.mdx b/packages/twenty-docs/l/de/developers/extend/apps/connections.mdx new file mode 100644 index 0000000000..02f1ce90fd --- /dev/null +++ b/packages/twenty-docs/l/de/developers/extend/apps/connections.mdx @@ -0,0 +1,193 @@ +--- +title: Connections +description: Let your app act on a user's behalf in third-party services via OAuth. +icon: plug +--- + +Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API. + +Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate. + + + + + +A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace. + +A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials. + +```ts src/connection-providers/linear-connection.ts +import { defineConnectionProvider } from 'twenty-sdk/define'; + +export default defineConnectionProvider({ + universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', + name: 'linear', + displayName: 'Linear', + icon: 'IconBrandLinear', + type: 'oauth', + oauth: { + authorizationEndpoint: 'https://linear.app/oauth/authorize', + tokenEndpoint: 'https://api.linear.app/oauth/token', + scopes: ['read', 'write'], + // These must match keys in `defineApplication.serverVariables` below. + clientIdVariable: 'LINEAR_CLIENT_ID', + clientSecretVariable: 'LINEAR_CLIENT_SECRET', + // Optional: defaults to 'json'. Some providers (Linear, Slack) want + // 'form-urlencoded' for the token request. + tokenRequestContentType: 'form-urlencoded', + // Optional: defaults to true. Disable only if the provider rejects PKCE. + usePkce: false, + // Optional: extra query params on the authorize URL. + // authorizationParams: { prompt: 'consent' }, + // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. + // revokeEndpoint: 'https://example.com/oauth/revoke', + }, +}); +``` + +```ts src/application.config.ts +import { defineApplication } from 'twenty-sdk/define'; + +export default defineApplication({ + universalIdentifier: '...', + displayName: 'Linear', + description: 'Connect Linear to Twenty.', + defaultRoleUniversalIdentifier: '...', + // OAuth client credentials live on the app registration (one OAuth app per + // Twenty server, configured by the admin) — not per-workspace. Declare them + // as serverVariables so the admin can fill them in once for all installs. + serverVariables: { + LINEAR_CLIENT_ID: { + description: 'OAuth client ID from your Linear OAuth application.', + isSecret: false, + isRequired: true, + }, + LINEAR_CLIENT_SECRET: { + description: 'OAuth client secret from your Linear OAuth application.', + isSecret: true, + isRequired: true, + }, + }, +}); +``` + +Hauptpunkte: + +* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`). +* `displayName` shows in the per-app settings tab and in the AI tool list. +* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo. +* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server. +* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled. +* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`. + +The OAuth callback URL your provider needs to whitelist is: + +``` +https:///apps/oauth/callback +``` + + + + + +Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens. + +```ts src/logic-functions/handlers/create-linear-issue-handler.ts +import { listConnections } from 'twenty-sdk/logic-function'; + +export const createLinearIssueHandler = async (input: { + teamId?: string; + title?: string; +}) => { + if (!input.teamId || !input.title) { + return { success: false, error: 'teamId and title are required' }; + } + + const connections = await listConnections({ providerName: 'linear' }); + + // Workspace-shared credentials win when present; fall back to the first + // user-visibility one. For HTTP-route triggers you typically pick the + // request user's connection via event.userWorkspaceId instead. + const connection = + connections.find((c) => c.visibility === 'workspace') ?? connections[0]; + + if (!connection) { + return { + success: false, + error: + 'Linear is not connected. Open the app settings and click "Add connection".', + }; + } + + // Use connection.accessToken to call the third-party API. + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${connection.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, + }), + }); + + return { success: response.ok }; +}; +``` + +Each connection has: + +| Feld | Beschreibung | +| ------------------ | -------------------------------------------------------------------------------------------------------- | +| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one | +| `sichtbarkeit` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) | +| `geltungsbereiche` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) | +| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers | +| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) | +| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) | +| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect | + +Hauptpunkte: + +* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers. +* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set). +* `getConnection(id)` is the single-row equivalent. + + + + + +When a user clicks "Add connection," they're prompted to pick a visibility: + +* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not. +* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user. + +Use the right one for each handler: + +```ts +// HTTP-route trigger — prefer the request user's own connection. +const conn = + connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? + connections.find((c) => c.visibility === 'workspace'); + +// Cron trigger — no request user; only shared credentials are sensible. +const conn = connections.find((c) => c.visibility === 'workspace'); +``` + +Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side. + + + + + +For each connection provider, the server admin needs to register an OAuth app at the third party first. + +1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new). +2. Set the **Redirect URI** to `\/apps/oauth/callback`. +3. Copy the generated **Client ID** and **Client Secret**. +4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`. +5. Workspace members can then add connections from the per-app **Connections** section. + + + + diff --git a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx index 03e2972b0c..9c739c4d85 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx @@ -77,6 +77,39 @@ Pre-Release-Tags funktionieren wie erwartet: Das Erhöhen von `1.0.0-rc.1` → ` {/* TODO: add screenshot of the Upgrade button */} +### Server version compatibility + +If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: + +```json filename="package.json" +{ + "name": "twenty-my-app", + "version": "1.0.0", + "engines": { + "node": "^24.5.0", + "twenty": ">=2.3.0" + } +} +``` + +The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: + +| Range | Meaning | +| ---------------------------------- | ------------------------------------------ | +| `>=2.3.0` | Any server from 2.3.0 onward | +| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major | +| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` | + +**What happens at deploy and install time:** + +* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. +* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). +* If the server has no `APP_VERSION` configured, the check is skipped. + + +The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. + + ## Automatisiertes CI/CD (vorgefertigte Workflows) Apps, die mit `create-twenty-app` erzeugt wurden, enthalten von Haus aus zwei GitHub-Actions-Workflows unter `.github/workflows/`. Sie sind einsatzbereit, sobald Sie das Repository zu GitHub pushen — für CI ist keine zusätzliche Einrichtung erforderlich, und für CD ist nur ein einziges Secret nötig. diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/connections.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/connections.mdx new file mode 100644 index 0000000000..e46666c0c2 --- /dev/null +++ b/packages/twenty-docs/l/pt/developers/extend/apps/connections.mdx @@ -0,0 +1,193 @@ +--- +title: Connections +description: Let your app act on a user's behalf in third-party services via OAuth. +icon: plug +--- + +Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API. + +Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate. + + + + + +A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace. + +A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials. + +```ts src/connection-providers/linear-connection.ts +import { defineConnectionProvider } from 'twenty-sdk/define'; + +export default defineConnectionProvider({ + universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', + name: 'linear', + displayName: 'Linear', + icon: 'IconBrandLinear', + type: 'oauth', + oauth: { + authorizationEndpoint: 'https://linear.app/oauth/authorize', + tokenEndpoint: 'https://api.linear.app/oauth/token', + scopes: ['read', 'write'], + // These must match keys in `defineApplication.serverVariables` below. + clientIdVariable: 'LINEAR_CLIENT_ID', + clientSecretVariable: 'LINEAR_CLIENT_SECRET', + // Optional: defaults to 'json'. Some providers (Linear, Slack) want + // 'form-urlencoded' for the token request. + tokenRequestContentType: 'form-urlencoded', + // Optional: defaults to true. Disable only if the provider rejects PKCE. + usePkce: false, + // Optional: extra query params on the authorize URL. + // authorizationParams: { prompt: 'consent' }, + // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. + // revokeEndpoint: 'https://example.com/oauth/revoke', + }, +}); +``` + +```ts src/application.config.ts +import { defineApplication } from 'twenty-sdk/define'; + +export default defineApplication({ + universalIdentifier: '...', + displayName: 'Linear', + description: 'Connect Linear to Twenty.', + defaultRoleUniversalIdentifier: '...', + // OAuth client credentials live on the app registration (one OAuth app per + // Twenty server, configured by the admin) — not per-workspace. Declare them + // as serverVariables so the admin can fill them in once for all installs. + serverVariables: { + LINEAR_CLIENT_ID: { + description: 'OAuth client ID from your Linear OAuth application.', + isSecret: false, + isRequired: true, + }, + LINEAR_CLIENT_SECRET: { + description: 'OAuth client secret from your Linear OAuth application.', + isSecret: true, + isRequired: true, + }, + }, +}); +``` + +Pontos-chave: + +* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`). +* `displayName` shows in the per-app settings tab and in the AI tool list. +* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo. +* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server. +* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled. +* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`. + +The OAuth callback URL your provider needs to whitelist is: + +``` +https:///apps/oauth/callback +``` + + + + + +Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens. + +```ts src/logic-functions/handlers/create-linear-issue-handler.ts +import { listConnections } from 'twenty-sdk/logic-function'; + +export const createLinearIssueHandler = async (input: { + teamId?: string; + title?: string; +}) => { + if (!input.teamId || !input.title) { + return { success: false, error: 'teamId and title are required' }; + } + + const connections = await listConnections({ providerName: 'linear' }); + + // Workspace-shared credentials win when present; fall back to the first + // user-visibility one. For HTTP-route triggers you typically pick the + // request user's connection via event.userWorkspaceId instead. + const connection = + connections.find((c) => c.visibility === 'workspace') ?? connections[0]; + + if (!connection) { + return { + success: false, + error: + 'Linear is not connected. Open the app settings and click "Add connection".', + }; + } + + // Use connection.accessToken to call the third-party API. + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${connection.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, + }), + }); + + return { success: response.ok }; +}; +``` + +Each connection has: + +| Campo | Descrição | +| ----------------- | -------------------------------------------------------------------------------------------------------- | +| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one | +| `visibilidade` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) | +| `escopos` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) | +| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers | +| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) | +| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) | +| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect | + +Pontos-chave: + +* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers. +* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set). +* `getConnection(id)` is the single-row equivalent. + + + + + +When a user clicks "Add connection," they're prompted to pick a visibility: + +* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not. +* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user. + +Use the right one for each handler: + +```ts +// HTTP-route trigger — prefer the request user's own connection. +const conn = + connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? + connections.find((c) => c.visibility === 'workspace'); + +// Cron trigger — no request user; only shared credentials are sensible. +const conn = connections.find((c) => c.visibility === 'workspace'); +``` + +Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side. + + + + + +For each connection provider, the server admin needs to register an OAuth app at the third party first. + +1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new). +2. Set the **Redirect URI** to `\/apps/oauth/callback`. +3. Copy the generated **Client ID** and **Client Secret**. +4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`. +5. Workspace members can then add connections from the per-app **Connections** section. + + + + diff --git a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx index 743967a752..3ad6233f94 100644 --- a/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/apps/publishing.mdx @@ -77,6 +77,39 @@ Tags de pré-lançamento funcionam como esperado: incrementar `1.0.0-rc.1` → ` {/* TODO: add screenshot of the Upgrade button */} +### Server version compatibility + +If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: + +```json filename="package.json" +{ + "name": "twenty-my-app", + "version": "1.0.0", + "engines": { + "node": "^24.5.0", + "twenty": ">=2.3.0" + } +} +``` + +The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: + +| Range | Meaning | +| ---------------------------------- | ------------------------------------------ | +| `>=2.3.0` | Any server from 2.3.0 onward | +| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major | +| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` | + +**What happens at deploy and install time:** + +* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. +* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). +* If the server has no `APP_VERSION` configured, the check is skipped. + + +The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. + + ## CI/CD automatizado (fluxos de trabalho pré-configurados) Os apps gerados com `create-twenty-app` já vêm com dois fluxos de trabalho do GitHub Actions prontos, em `.github/workflows/`. Eles estão prontos para executar assim que você fizer push do repositório para o GitHub — nenhuma configuração extra é necessária para CI, e CD requer apenas um único segredo. diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/connections.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/connections.mdx new file mode 100644 index 0000000000..2f026bf4eb --- /dev/null +++ b/packages/twenty-docs/l/ru/developers/extend/apps/connections.mdx @@ -0,0 +1,193 @@ +--- +title: Connections +description: Let your app act on a user's behalf in third-party services via OAuth. +icon: plug +--- + +Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API. + +Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate. + + + + + +A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace. + +A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials. + +```ts src/connection-providers/linear-connection.ts +import { defineConnectionProvider } from 'twenty-sdk/define'; + +export default defineConnectionProvider({ + universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', + name: 'linear', + displayName: 'Linear', + icon: 'IconBrandLinear', + type: 'oauth', + oauth: { + authorizationEndpoint: 'https://linear.app/oauth/authorize', + tokenEndpoint: 'https://api.linear.app/oauth/token', + scopes: ['read', 'write'], + // These must match keys in `defineApplication.serverVariables` below. + clientIdVariable: 'LINEAR_CLIENT_ID', + clientSecretVariable: 'LINEAR_CLIENT_SECRET', + // Optional: defaults to 'json'. Some providers (Linear, Slack) want + // 'form-urlencoded' for the token request. + tokenRequestContentType: 'form-urlencoded', + // Optional: defaults to true. Disable only if the provider rejects PKCE. + usePkce: false, + // Optional: extra query params on the authorize URL. + // authorizationParams: { prompt: 'consent' }, + // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. + // revokeEndpoint: 'https://example.com/oauth/revoke', + }, +}); +``` + +```ts src/application.config.ts +import { defineApplication } from 'twenty-sdk/define'; + +export default defineApplication({ + universalIdentifier: '...', + displayName: 'Linear', + description: 'Connect Linear to Twenty.', + defaultRoleUniversalIdentifier: '...', + // OAuth client credentials live on the app registration (one OAuth app per + // Twenty server, configured by the admin) — not per-workspace. Declare them + // as serverVariables so the admin can fill them in once for all installs. + serverVariables: { + LINEAR_CLIENT_ID: { + description: 'OAuth client ID from your Linear OAuth application.', + isSecret: false, + isRequired: true, + }, + LINEAR_CLIENT_SECRET: { + description: 'OAuth client secret from your Linear OAuth application.', + isSecret: true, + isRequired: true, + }, + }, +}); +``` + +Основные моменты: + +* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`). +* `displayName` shows in the per-app settings tab and in the AI tool list. +* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo. +* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server. +* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled. +* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`. + +The OAuth callback URL your provider needs to whitelist is: + +``` +https:///apps/oauth/callback +``` + + + + + +Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens. + +```ts src/logic-functions/handlers/create-linear-issue-handler.ts +import { listConnections } from 'twenty-sdk/logic-function'; + +export const createLinearIssueHandler = async (input: { + teamId?: string; + title?: string; +}) => { + if (!input.teamId || !input.title) { + return { success: false, error: 'teamId and title are required' }; + } + + const connections = await listConnections({ providerName: 'linear' }); + + // Workspace-shared credentials win when present; fall back to the first + // user-visibility one. For HTTP-route triggers you typically pick the + // request user's connection via event.userWorkspaceId instead. + const connection = + connections.find((c) => c.visibility === 'workspace') ?? connections[0]; + + if (!connection) { + return { + success: false, + error: + 'Linear is not connected. Open the app settings and click "Add connection".', + }; + } + + // Use connection.accessToken to call the third-party API. + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${connection.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, + }), + }); + + return { success: response.ok }; +}; +``` + +Each connection has: + +| Поле | Описание | +| ----------------- | -------------------------------------------------------------------------------------------------------- | +| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one | +| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) | +| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) | +| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers | +| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) | +| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) | +| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect | + +Основные моменты: + +* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers. +* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set). +* `getConnection(id)` is the single-row equivalent. + + + + + +When a user clicks "Add connection," they're prompted to pick a visibility: + +* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not. +* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user. + +Use the right one for each handler: + +```ts +// HTTP-route trigger — prefer the request user's own connection. +const conn = + connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? + connections.find((c) => c.visibility === 'workspace'); + +// Cron trigger — no request user; only shared credentials are sensible. +const conn = connections.find((c) => c.visibility === 'workspace'); +``` + +Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side. + + + + + +For each connection provider, the server admin needs to register an OAuth app at the third party first. + +1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new). +2. Set the **Redirect URI** to `\/apps/oauth/callback`. +3. Copy the generated **Client ID** and **Client Secret**. +4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`. +5. Workspace members can then add connections from the per-app **Connections** section. + + + + diff --git a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx index 1d67841564..d0a1d3604c 100644 --- a/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/apps/publishing.mdx @@ -77,6 +77,39 @@ When updating an already deployed tarball app, the server requires the `version` {/* TODO: add screenshot of the Upgrade button */} +### Server version compatibility + +If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: + +```json filename="package.json" +{ + "name": "twenty-my-app", + "version": "1.0.0", + "engines": { + "node": "^24.5.0", + "twenty": ">=2.3.0" + } +} +``` + +The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: + +| Range | Meaning | +| ---------------------------------- | ------------------------------------------ | +| `>=2.3.0` | Any server from 2.3.0 onward | +| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major | +| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` | + +**What happens at deploy and install time:** + +* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. +* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). +* If the server has no `APP_VERSION` configured, the check is skipped. + + +The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. + + ## Автоматизированный CI/CD (рабочие процессы, сгенерированные шаблоном) Приложения, созданные с помощью `create-twenty-app`, «из коробки» включают два рабочих процесса GitHub Actions в каталоге `.github/workflows/`. Они готовы к запуску, как только вы запушите репозиторий на GitHub — для CI не требуется дополнительной настройки, а для CD нужен лишь один секрет. diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/connections.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/connections.mdx new file mode 100644 index 0000000000..d06594ae9c --- /dev/null +++ b/packages/twenty-docs/l/tr/developers/extend/apps/connections.mdx @@ -0,0 +1,193 @@ +--- +title: Connections +description: Let your app act on a user's behalf in third-party services via OAuth. +icon: plug +--- + +Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API. + +Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate. + + + + + +A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace. + +A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials. + +```ts src/connection-providers/linear-connection.ts +import { defineConnectionProvider } from 'twenty-sdk/define'; + +export default defineConnectionProvider({ + universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', + name: 'linear', + displayName: 'Linear', + icon: 'IconBrandLinear', + type: 'oauth', + oauth: { + authorizationEndpoint: 'https://linear.app/oauth/authorize', + tokenEndpoint: 'https://api.linear.app/oauth/token', + scopes: ['read', 'write'], + // These must match keys in `defineApplication.serverVariables` below. + clientIdVariable: 'LINEAR_CLIENT_ID', + clientSecretVariable: 'LINEAR_CLIENT_SECRET', + // Optional: defaults to 'json'. Some providers (Linear, Slack) want + // 'form-urlencoded' for the token request. + tokenRequestContentType: 'form-urlencoded', + // Optional: defaults to true. Disable only if the provider rejects PKCE. + usePkce: false, + // Optional: extra query params on the authorize URL. + // authorizationParams: { prompt: 'consent' }, + // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. + // revokeEndpoint: 'https://example.com/oauth/revoke', + }, +}); +``` + +```ts src/application.config.ts +import { defineApplication } from 'twenty-sdk/define'; + +export default defineApplication({ + universalIdentifier: '...', + displayName: 'Linear', + description: 'Connect Linear to Twenty.', + defaultRoleUniversalIdentifier: '...', + // OAuth client credentials live on the app registration (one OAuth app per + // Twenty server, configured by the admin) — not per-workspace. Declare them + // as serverVariables so the admin can fill them in once for all installs. + serverVariables: { + LINEAR_CLIENT_ID: { + description: 'OAuth client ID from your Linear OAuth application.', + isSecret: false, + isRequired: true, + }, + LINEAR_CLIENT_SECRET: { + description: 'OAuth client secret from your Linear OAuth application.', + isSecret: true, + isRequired: true, + }, + }, +}); +``` + +Önemli noktalar: + +* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`). +* `displayName` shows in the per-app settings tab and in the AI tool list. +* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo. +* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server. +* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled. +* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`. + +The OAuth callback URL your provider needs to whitelist is: + +``` +https:///apps/oauth/callback +``` + + + + + +Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens. + +```ts src/logic-functions/handlers/create-linear-issue-handler.ts +import { listConnections } from 'twenty-sdk/logic-function'; + +export const createLinearIssueHandler = async (input: { + teamId?: string; + title?: string; +}) => { + if (!input.teamId || !input.title) { + return { success: false, error: 'teamId and title are required' }; + } + + const connections = await listConnections({ providerName: 'linear' }); + + // Workspace-shared credentials win when present; fall back to the first + // user-visibility one. For HTTP-route triggers you typically pick the + // request user's connection via event.userWorkspaceId instead. + const connection = + connections.find((c) => c.visibility === 'workspace') ?? connections[0]; + + if (!connection) { + return { + success: false, + error: + 'Linear is not connected. Open the app settings and click "Add connection".', + }; + } + + // Use connection.accessToken to call the third-party API. + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${connection.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, + }), + }); + + return { success: response.ok }; +}; +``` + +Each connection has: + +| Alan | Açıklama | +| ----------------- | -------------------------------------------------------------------------------------------------------- | +| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one | +| `visibility` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) | +| `scopes` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) | +| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers | +| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) | +| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) | +| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect | + +Önemli noktalar: + +* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers. +* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set). +* `getConnection(id)` is the single-row equivalent. + + + + + +When a user clicks "Add connection," they're prompted to pick a visibility: + +* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not. +* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user. + +Use the right one for each handler: + +```ts +// HTTP-route trigger — prefer the request user's own connection. +const conn = + connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? + connections.find((c) => c.visibility === 'workspace'); + +// Cron trigger — no request user; only shared credentials are sensible. +const conn = connections.find((c) => c.visibility === 'workspace'); +``` + +Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side. + + + + + +For each connection provider, the server admin needs to register an OAuth app at the third party first. + +1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new). +2. Set the **Redirect URI** to `\/apps/oauth/callback`. +3. Copy the generated **Client ID** and **Client Secret**. +4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`. +5. Workspace members can then add connections from the per-app **Connections** section. + + + + diff --git a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx index 0f82662a23..a78af24fee 100644 --- a/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/apps/publishing.mdx @@ -77,6 +77,39 @@ Bir güncelleme yayımlamak için: {/* TODO: add screenshot of the Upgrade button */} +### Server version compatibility + +If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: + +```json filename="package.json" +{ + "name": "twenty-my-app", + "version": "1.0.0", + "engines": { + "node": "^24.5.0", + "twenty": ">=2.3.0" + } +} +``` + +The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: + +| Range | Meaning | +| ---------------------------------- | ------------------------------------------ | +| `>=2.3.0` | Any server from 2.3.0 onward | +| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major | +| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` | + +**What happens at deploy and install time:** + +* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. +* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). +* If the server has no `APP_VERSION` configured, the check is skipped. + + +The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. + + ## Otomatik CI/CD (hazır şablonlu iş akışları) `create-twenty-app` ile oluşturulan uygulamalar, kutudan çıktığı gibi `.github/workflows/` altında iki GitHub Actions iş akışıyla gelir. Depoyu GitHub’a iter itmez çalışmaya hazırdır — CI için ek bir kurulum gerekmez ve CD yalnızca tek bir gizli anahtar gerektirir. diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/connections.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/connections.mdx new file mode 100644 index 0000000000..2a44bbabfd --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/connections.mdx @@ -0,0 +1,193 @@ +--- +title: Connections +description: Let your app act on a user's behalf in third-party services via OAuth. +icon: plug +--- + +Connections are credentials a user holds for an external service (Linear, GitHub, Slack, ...). Your app declares **how** those credentials are obtained — a **connection provider** — and consumes them at runtime to make authenticated calls to the third-party API. + +Today only OAuth 2.0 is supported. Future credential types (personal access tokens, API keys, basic auth) will plug into the same surface — apps already using `defineConnectionProvider({ type: 'oauth', ... })` won't need to migrate. + + + + + +A connection provider describes the OAuth handshake your app needs. The user clicks "Add connection" in your app's settings, completes the provider's consent screen, and a `ConnectedAccount` row is created in their workspace. + +A working setup needs **two files** — the connection provider, and a matching `serverVariables` declaration on `defineApplication` that holds the OAuth client credentials. + +```ts src/connection-providers/linear-connection.ts +import { defineConnectionProvider } from 'twenty-sdk/define'; + +export default defineConnectionProvider({ + universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', + name: 'linear', + displayName: 'Linear', + icon: 'IconBrandLinear', + type: 'oauth', + oauth: { + authorizationEndpoint: 'https://linear.app/oauth/authorize', + tokenEndpoint: 'https://api.linear.app/oauth/token', + scopes: ['read', 'write'], + // These must match keys in `defineApplication.serverVariables` below. + clientIdVariable: 'LINEAR_CLIENT_ID', + clientSecretVariable: 'LINEAR_CLIENT_SECRET', + // Optional: defaults to 'json'. Some providers (Linear, Slack) want + // 'form-urlencoded' for the token request. + tokenRequestContentType: 'form-urlencoded', + // Optional: defaults to true. Disable only if the provider rejects PKCE. + usePkce: false, + // Optional: extra query params on the authorize URL. + // authorizationParams: { prompt: 'consent' }, + // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. + // revokeEndpoint: 'https://example.com/oauth/revoke', + }, +}); +``` + +```ts src/application.config.ts +import { defineApplication } from 'twenty-sdk/define'; + +export default defineApplication({ + universalIdentifier: '...', + displayName: 'Linear', + description: 'Connect Linear to Twenty.', + defaultRoleUniversalIdentifier: '...', + // OAuth client credentials live on the app registration (one OAuth app per + // Twenty server, configured by the admin) — not per-workspace. Declare them + // as serverVariables so the admin can fill them in once for all installs. + serverVariables: { + LINEAR_CLIENT_ID: { + description: 'OAuth client ID from your Linear OAuth application.', + isSecret: false, + isRequired: true, + }, + LINEAR_CLIENT_SECRET: { + description: 'OAuth client secret from your Linear OAuth application.', + isSecret: true, + isRequired: true, + }, + }, +}); +``` + +关键点: + +* `name` is the unique identifier string used in `listConnections({ providerName })` (kebab-case, must match `^[a-z][a-z0-9-]*$`). +* `displayName` shows in the per-app settings tab and in the AI tool list. +* `clientIdVariable` / `clientSecretVariable` are **names**, not values — they must match keys declared in `defineApplication.serverVariables`. The actual `client_id` and `client_secret` are entered by the server admin through the app registration UI, never committed to your repo. +* Use `serverVariables` (not `applicationVariables`) — OAuth credentials are server-wide and one OAuth app per Twenty server. +* Until both `serverVariables` are filled in, the per-app settings tab shows a "needs server admin" hint and the "Add connection" button is disabled. +* `type: 'oauth'` is the only supported value today. The discriminator is forward-compatible: future types (`'pat'`, `'api-key'`, ...) will add new sub-config blocks alongside `oauth`. + +The OAuth callback URL your provider needs to whitelist is: + +``` +https:///apps/oauth/callback +``` + + + + + +Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens. + +```ts src/logic-functions/handlers/create-linear-issue-handler.ts +import { listConnections } from 'twenty-sdk/logic-function'; + +export const createLinearIssueHandler = async (input: { + teamId?: string; + title?: string; +}) => { + if (!input.teamId || !input.title) { + return { success: false, error: 'teamId and title are required' }; + } + + const connections = await listConnections({ providerName: 'linear' }); + + // Workspace-shared credentials win when present; fall back to the first + // user-visibility one. For HTTP-route triggers you typically pick the + // request user's connection via event.userWorkspaceId instead. + const connection = + connections.find((c) => c.visibility === 'workspace') ?? connections[0]; + + if (!connection) { + return { + success: false, + error: + 'Linear is not connected. Open the app settings and click "Add connection".', + }; + } + + // Use connection.accessToken to call the third-party API. + const response = await fetch('https://api.linear.app/graphql', { + method: 'POST', + headers: { + Authorization: `Bearer ${connection.accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, + }), + }); + + return { success: response.ok }; +}; +``` + +Each connection has: + +| 字段 | 描述 | +| ----------------- | -------------------------------------------------------------------------------------------------------- | +| `id` | Unique row id; pass to `getConnection(id)` to refetch a single one | +| `可见性` | `'user'` (private to one workspace member) or `'workspace'` (shared with all members) | +| `范围` | OAuth permissions granted by the upstream provider (distinct from `visibility` — those are unrelated) | +| `userWorkspaceId` | The owner's userWorkspace id — useful for picking "the request user's connection" in HTTP-route triggers | +| `accessToken` | Fresh OAuth access token (refreshed automatically if expired) | +| `name` / `handle` | The connection's display name (auto-derived at OAuth callback, user-renameable) | +| `authFailedAt` | Set when the most recent refresh failed; the user must reconnect | + +关键点: + +* Pass `{ providerName }` to filter by provider; omit it to get all connections this app owns across all providers. +* The server transparently refreshes the access token before returning. Your handler always sees a usable token (or `authFailedAt` set). +* `getConnection(id)` is the single-row equivalent. + + + + + +When a user clicks "Add connection," they're prompted to pick a visibility: + +* **Just for me** — the credential is private to the connecting user. Any logic function called on their behalf (HTTP-route trigger with `isAuthRequired: true`) sees it; cron triggers and database events do not. +* **Workspace shared** — any workspace member can use the credential. Cron / database triggers also see it, since they have no request user. + +Use the right one for each handler: + +```ts +// HTTP-route trigger — prefer the request user's own connection. +const conn = + connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? + connections.find((c) => c.visibility === 'workspace'); + +// Cron trigger — no request user; only shared credentials are sensible. +const conn = connections.find((c) => c.visibility === 'workspace'); +``` + +Multiple connections per (user, provider) are allowed, so the same user can hold "Personal Linear" and "Work Linear" side by side. + + + + + +For each connection provider, the server admin needs to register an OAuth app at the third party first. + +1. Go to the provider's developer settings (e.g. https://linear.app/settings/api/applications/new). +2. Set the **Redirect URI** to `\/apps/oauth/callback`. +3. Copy the generated **Client ID** and **Client Secret**. +4. Open the installed app in Twenty as a server admin → set the values on the corresponding `serverVariables`. +5. Workspace members can then add connections from the per-app **Connections** section. + + + + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx index 2854f77a3d..317042578d 100644 --- a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx @@ -77,6 +77,39 @@ yarn twenty deploy {/* TODO: add screenshot of the Upgrade button */} +### Server version compatibility + +If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`: + +```json filename="package.json" +{ + "name": "twenty-my-app", + "version": "1.0.0", + "engines": { + "node": "^24.5.0", + "twenty": ">=2.3.0" + } +} +``` + +The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns: + +| Range | Meaning | +| ---------------------------------- | ------------------------------------------ | +| `>=2.3.0` | Any server from 2.3.0 onward | +| `>=2.3.0 \<3.0.0` | 2.3.0 or later, but below the next major | +| `^2.3.0` | Same as `>=2.3.0 \<3.0.0` | + +**What happens at deploy and install time:** + +* If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version. +* If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps). +* If the server has no `APP_VERSION` configured, the check is skipped. + + +The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility. + + ## 自动化 CI/CD(脚手架生成的工作流) 使用 `create-twenty-app` 生成的应用开箱即带有两个 GitHub Actions 工作流,位于 `.github/workflows/`。 当你将仓库推送到 GitHub 后即可运行——CI 无需额外设置,CD 只需要一个机密。