메인 콘텐츠로 건너뛰기
연결은 사용자가 외부 서비스(Linear, GitHub, Slack 등)에 대한 자격 증명을 보유하는 것을 말합니다. 앱은 해당 자격 증명을 어떻게 얻는지(즉, 연결 제공자)를 선언하고, 런타임에 이를 사용해 서드파티 API에 인증된 호출을 수행합니다. 현재는 OAuth 2.0만 지원됩니다. 향후 자격 증명 유형(개인 액세스 토큰, API 키, 기본 인증)은 동일한 인터페이스에 연동되며 — 이미 defineConnectionProvider({ type: 'oauth', ... })를 사용하는 앱은 마이그레이션이 필요하지 않습니다.
연결 제공자는 앱에 필요한 OAuth 핸드셰이크를 설명합니다. 사용자가 앱 설정에서 “연결 추가”를 클릭하고 제공자의 동의 화면을 완료하면, 워크스페이스에 ConnectedAccount 행이 생성됩니다.정상 동작하려면 두 개의 파일이 필요합니다 — 연결 제공자, 그리고 OAuth 클라이언트 자격 증명을 보유하는 defineApplication의 해당 serverVariables 선언.
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.',
  // 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,
    },
  },
});
핵심 요점:
  • namelistConnections({ providerName })에서 사용하는 고유 식별자 문자열입니다(kebab-case, ^[a-z][a-z0-9-]*$와 일치해야 함).
  • displayName은 앱별 설정 탭과 AI 도구 목록에 표시됩니다.
  • clientIdVariable / clientSecretVariable은 값이 아닌 이름이며, defineApplication.serverVariables에 선언된 키와 일치해야 합니다. 실제 client_idclient_secret은 서버 관리자가 앱 등록 UI를 통해 입력하며, 저장소에 커밋되지 않습니다.
  • serverVariables(applicationVariables 아님)을 사용하세요 — OAuth 자격 증명은 서버 전체에 적용되며 Twenty 서버마다 하나의 OAuth 앱만 사용합니다.
  • serverVariables가 모두 채워질 때까지, 앱별 설정 탭에는 “서버 관리자 필요” 힌트가 표시되고 “연결 추가” 버튼이 비활성화됩니다.
  • type: 'oauth'는 현재 지원되는 유일한 값입니다. 구분자는 전방 호환됩니다: 향후 유형('pat', 'api-key', …) oauth와 함께 새로운 하위 구성 블록이 추가됩니다.
제공자가 허용 목록에 추가해야 할 OAuth 콜백 URL은 다음과 같습니다:
https://<your-twenty-server>/auth/apps/callback
로직 함수 핸들러 내부에서 listConnections({ providerName })는 지정된 제공자에 대한 이 앱의 ConnectedAccount 행을 갱신된 액세스 토큰과 함께 반환합니다.
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 };
};
각 연결에는 다음이 포함됩니다:
필드설명
id고유한 행 ID; 단일 항목을 다시 가져오려면 getConnection(id)에 전달하세요.
visibility'user'(한 워크스페이스 구성원에게만 비공개) 또는 'workspace'(모든 구성원과 공유)
scopes상위 제공자가 부여한 OAuth 권한(visibility와는 구별됨 — 서로 관련 없음)
userWorkspaceId소유자의 userWorkspace ID — HTTP 경로 트리거에서 “요청 사용자의 연결”을 선택할 때 유용합니다
accessToken최신 OAuth 액세스 토큰(만료 시 자동으로 갱신됨)
name / handle연결의 표시 이름(OAuth 콜백 시 자동으로 결정되며, 사용자가 이름을 변경할 수 있음)
authFailedAt가장 최근 갱신이 실패했을 때 설정됩니다; 사용자가 다시 연결해야 합니다
핵심 요점:
  • 제공자별로 필터링하려면 { providerName }를 전달하세요; 생략하면 이 앱이 모든 제공자에서 보유한 모든 연결을 가져옵니다.
  • 서버는 반환하기 전에 액세스 토큰을 투명하게 갱신합니다. 핸들러는 항상 사용 가능한 토큰(또는 authFailedAt이 설정된 상태)을 보게 됩니다.
  • getConnection(id)는 단일 행 버전입니다.
사용자가 “연결 추가”를 클릭하면, 가시성을 선택하라는 프롬프트가 표시됩니다:
  • 나만 사용 — 해당 자격 증명은 연결한 사용자에게만 비공개입니다. 그 사용자를 대신해 호출되는 모든 로직 함수(isAuthRequired: true가 설정된 HTTP 경로 트리거)는 이를 볼 수 있습니다; cron 트리거와 데이터베이스 이벤트는 볼 수 없습니다.
  • 워크스페이스 공유 — 모든 워크스페이스 구성원이 해당 자격 증명을 사용할 수 있습니다. 요청 사용자가 없으므로 Cron/데이터베이스 트리거도 이를 볼 수 있습니다.
각 핸들러에 맞는 유형을 사용하세요:
// 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');
각 (사용자, 제공자)당 여러 연결이 허용되므로, 동일한 사용자가 “Personal Linear”와 “Work Linear”를 나란히 보유할 수 있습니다.
각 연결 제공자마다 서버 관리자가 먼저 서드파티에 OAuth 앱을 등록해야 합니다.
  1. 제공자의 개발자 설정으로 이동하세요(예: https://linear.app/settings/api/applications/new).
  2. Redirect URI\<SERVER_URL>/auth/apps/callback으로 설정하세요.
  3. 생성된 Client IDClient Secret을 복사하세요.
  4. 서버 관리자 권한으로 Twenty에서 설치된 앱을 열고 → 해당 serverVariables에 값을 설정하세요.
  5. 그런 다음 워크스페이스 구성원은 앱별 연결 섹션에서 연결을 추가할 수 있습니다.