docs/guides/frameworks/nango.mdx
Nango handles OAuth for 250+ APIs, storing and automatically refreshing access tokens on your behalf. This makes it a natural fit for Trigger.dev tasks that need to call third-party APIs on behalf of your users.
In this guide you'll build a task that:
connectionId from your frontendThis pattern works for any API Nango supports. Swap GitHub for HubSpot, Slack, Notion, or any other provider.
sequenceDiagram
participant User
participant Frontend
participant API as Next.js API
participant TD as Trigger.dev task
participant Nango
participant GH as GitHub API
participant Claude
User->>Frontend: Clicks "Analyze my PRs"
Frontend->>API: POST /api/nango-session
API->>Nango: POST /connect/sessions (secret key)
Nango-->>API: session token (30 min TTL)
API-->>Frontend: session token
Frontend->>Nango: OAuth connect (frontend SDK + session token)
Nango-->>Frontend: connectionId
Frontend->>API: POST /api/analyze-prs { connectionId, repo }
API->>TD: tasks.trigger(...)
TD->>Nango: getConnection(connectionId)
Nango-->>TD: access_token
TD->>GH: GET /repos/:repo/pulls
GH-->>TD: open pull requests
TD->>Claude: Summarize PRs
Claude-->>TD: Summary
```bash
npm install @nangohq/frontend
```
The frontend SDK requires a short-lived **connect session token** issued by your backend. Add an API route that creates the session:
```ts app/api/nango-session/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { userId } = await req.json();
if (!userId || typeof userId !== "string") {
return NextResponse.json({ error: "Missing or invalid userId" }, { status: 400 });
}
const response = await fetch("https://api.nango.dev/connect/sessions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
end_user: { id: userId },
}),
});
if (!response.ok) {
const text = await response.text();
console.error("Nango error:", response.status, text);
return NextResponse.json({ error: text }, { status: response.status });
}
const { data } = await response.json();
return NextResponse.json({ token: data.token });
}
```
Then add a connect button to your UI that fetches the token and opens the Nango OAuth flow:
```tsx app/page.tsx
"use client";
import Nango from "@nangohq/frontend";
export default function Page() {
async function connectGitHub() {
// Get a short-lived session token from your backend
const sessionRes = await fetch("/api/nango-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: "user_123" }), // replace with your actual user ID
});
const { token } = await sessionRes.json();
const nango = new Nango({ connectSessionToken: token });
// Use the exact integration slug from your Nango dashboard
const result = await nango.auth("<your-integration-slug>");
// result.connectionId is what you pass to your task
await fetch("/api/analyze-prs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
connectionId: result.connectionId,
repo: "triggerdotdev/trigger.dev",
}),
});
}
return <button onClick={connectGitHub}>Analyze my PRs</button>;
}
```
Install the required packages:
npm install @nangohq/node @anthropic-ai/sdk
Create the task:
<CodeGroup>import { task } from "@trigger.dev/sdk";
import { Nango } from "@nangohq/node";
import Anthropic from "@anthropic-ai/sdk";
const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! });
const anthropic = new Anthropic();
export const analyzePRs = task({
id: "analyze-prs",
run: async (payload: { connectionId: string; repo: string }) => {
const { connectionId, repo } = payload;
// Fetch a fresh access token from Nango. It handles refresh automatically.
// Use the exact integration slug from your Nango dashboard, e.g. "github-getting-started"
const connection = await nango.getConnection("<your-integration-slug>", connectionId);
if (connection.credentials.type !== "OAUTH2") {
throw new Error(`Unexpected credentials type: ${connection.credentials.type}`);
}
const accessToken = connection.credentials.access_token;
// Call the GitHub API on behalf of the user
const response = await fetch(
`https://api.github.com/repos/${repo}/pulls?state=open&per_page=20`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/vnd.github.v3+json",
},
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const prs = await response.json();
if (prs.length === 0) {
return { summary: "No open pull requests found.", prCount: 0 };
}
// Use Claude to summarize what's being worked on
const prList = prs
.map(
(pr: { number: number; title: string; user: { login: string }; body: string | null }) =>
`#${pr.number} by @${pr.user.login}: ${pr.title}\n${pr.body?.slice(0, 200) ?? ""}`
)
.join("\n\n");
const message = await anthropic.messages.create({
model: "claude-opus-4-6",
max_tokens: 1024,
messages: [
{
role: "user",
content: `Here are the open pull requests for ${repo}. Give a concise summary of what's being worked on, grouped by theme where possible.\n\n${prList}`,
},
],
});
const summary = message.content[0].type === "text" ? message.content[0].text : "";
return { summary, prCount: prs.length };
},
});
Add a route handler that receives the connectionId from your frontend and triggers the task:
import { analyzePRs } from "@/trigger/analyze-prs";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const { connectionId, repo } = await req.json();
if (!connectionId || !repo) {
return NextResponse.json({ error: "Missing connectionId or repo" }, { status: 400 });
}
const handle = await analyzePRs.trigger({ connectionId, repo });
return NextResponse.json(handle);
}
Add the following to your .env.local file:
NANGO_SECRET_KEY= # From Nango dashboard → Environment → Secret key
TRIGGER_SECRET_KEY= # From Trigger.dev dashboard → API keys
ANTHROPIC_API_KEY= # From Anthropic console
Add NANGO_SECRET_KEY and ANTHROPIC_API_KEY as environment variables in your Trigger.dev project too. These are used inside the task at runtime.
```bash
npm run dev
npx trigger.dev@latest dev
```
connectionId: Once a user has connected, store their connectionId and pass it in future task payloads. No need to re-authenticate."github" to "hubspot", "slack", "notion", or any other provider.