defineConnectionProvider({ type: 'oauth', ... }) won’t need to migrate.
defineConnectionProvider
Declare how your app's connections are obtained
defineConnectionProvider
Declare how your app's connections are obtained
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 Key points:
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
src/application.config.ts
nameis the unique identifier string used inlistConnections({ providerName })(kebab-case, must match^[a-z][a-z0-9-]*$).displayNameshows in the per-app settings tab and in the AI tool list.clientIdVariable/clientSecretVariableare names, not values — they must match keys declared indefineApplication.serverVariables. The actualclient_idandclient_secretare entered by the server admin through the app registration UI, never committed to your repo.- Use
serverVariables(notapplicationVariables) — OAuth credentials are server-wide and one OAuth app per Twenty server. - Until both
serverVariablesare 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 alongsideoauth.
Run a logic function on connect
React the moment a connection is established
Run a logic function on connect
React the moment a connection is established
Some providers hand you data at connect time that you need to persist before the connection is usable — the classic example is Slack, where the OAuth response identifies the workspace’s The hook runs asynchronously in the connecting workspace (it is enqueued, not awaited), so a slow or failing hook never blocks or breaks the OAuth callback — make it idempotent and handle its own retries. The handler receives:From there use
team_id that inbound events will be keyed by. Set onConnectLogicFunction to reference a logic function in the same app (by its universalIdentifier), and it runs right after the ConnectedAccount is created.src/connection-providers/slack-connection.ts
getConnection(connectedAccountId) to read the fresh access token and call the provider’s API (e.g. Slack auth.test) or persist a mapping with the key-value store.Run a logic function on disconnect
Clean up when a connection is removed
Run a logic function on disconnect
Clean up when a connection is removed
Anything an app claims at connect time has to be released when the connection goes away. A Slack integration that claims a Unlike the on-connect hook it runs inline in the disconnecting workspace, once, without retries: a failure is captured and the disconnect still completes. The handler receives the same payload shape:The
team_id on connect, for instance, has to release that claim so another workspace can connect the same Slack team. Set onDisconnectLogicFunction to reference a logic function in the same app, and it runs right before the ConnectedAccount and its token are deleted, so the handler can still call the provider with getConnection to release anything the on-connect hook set up remotely.src/connection-providers/slack-connection.ts
ConnectedAccount and its token are still present when the hook runs, so getConnection(connectedAccountId) resolves with a fresh access token and the handler can delete whatever it registered remotely. State the provider cannot give back (a team_id, an external subscription id) still belongs in the key-value store, written at connect time and keyed by connectedAccountId. Keep the handler short: it runs inside the disconnect request, so its timeoutSeconds is the upper bound on how long that request can take.The hook fires when a connection is removed on its own. Uninstalling the app drops its connections through a database cascade instead, so the hook does not run there. Declare an uninstallLogicFunction on defineApplication for that path: it runs before the app’s metadata is deleted, so it can still call listConnections and clean up whatever is left.listConnections / getConnection
Use connections from a logic function
listConnections / getConnection
Use connections from a logic function
Inside a logic function handler, Each connection has:
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
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
authFailedAtset). getConnection(id)is the single-row equivalent.
reportConnectionAuthFailure
Tell the platform a credential is dead
reportConnectionAuthFailure
Tell the platform a credential is dead
The platform sets This sets
authFailedAt on its own only when an OAuth refresh fails. Providers whose tokens are never refreshed (a Slack bot token, for example) fail only at call time inside your handlers, so the connection row in settings would keep showing Connected while every call dies. When your code hits a definitive auth rejection, report it:authFailedAt (and the optional reason, capped at 1000 characters) on the connection. The settings row flips to Reconnect needed with a Reconnect button, the detail page shows the reason, and getConnection starts throwing AppConnectionAuthFailedError for that row. The flag and reason clear automatically when the user reconnects.Only report failures that a reconnect would actually fix (an invalid_auth-class rejection from the provider), never transient network errors. An app can only report its own connections, and a request user can only report their own user-visibility credentials.How users choose between private and shared credentials
How users choose between private and shared credentials
One-time provider setup
Register your OAuth app with the third-party service
One-time provider setup
Register your OAuth app with the third-party service
For each connection provider, the server admin needs to register an OAuth app at the third party first.
- Go to the provider’s developer settings (e.g. https://linear.app/settings/api/applications/new).
- Set the Redirect URI to
<SERVER_URL>/auth/apps/callback. - Copy the generated Client ID and Client Secret.
- Open the installed app in Twenty as a server admin → set the values on the corresponding
serverVariables. - Workspace members can then add connections from the per-app Connections section.