Salt la conținutul principal

Documentation Index

Fetch the complete documentation index at: https://docs.twenty.com/llms.txt

Use this file to discover all available pages before exploring further.

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.
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',
  },
});
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,
    },
  },
});
Key points:
  • 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://<your-twenty-server>/apps/oauth/callback
Inside a logic function handler, listConnections({ providerName }) returns this app’s ConnectedAccount rows for the given provider, with refreshed access tokens.
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:
FieldDescription
idUnique row id; pass to getConnection(id) to refetch a single one
visibility'user' (private to one workspace member) or 'workspace' (shared with all members)
scopesOAuth permissions granted by the upstream provider (distinct from visibility — those are unrelated)
userWorkspaceIdThe owner’s userWorkspace id — useful for picking “the request user’s connection” in HTTP-route triggers
accessTokenFresh OAuth access token (refreshed automatically if expired)
name / handleThe connection’s display name (auto-derived at OAuth callback, user-renameable)
authFailedAtSet when the most recent refresh failed; the user must reconnect
Key points:
  • 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:
// 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 \<SERVER_URL>/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.