Corsair
PluginsLinear

Linear

Integrate Linear with Corsair

The Linear plugin allows you to manage issues, projects, teams, and comments in Linear from your Corsair instance. It provides full API access and webhook support for real-time event handling.

Quick Start

Install the plugin:

pnpm install @corsair-dev/linear

Add the Linear plugin to your Corsair instance:

corsair.ts
import { createCorsair } from "corsair";
import { linear } from "@corsair-dev/linear";

export const corsair = createCorsair({
    plugins: [
        linear(),
    ],
});

Once configured, you can access the API:

await corsair.linear.api.issues.create({
    title: "Bug: Login form not working",
    teamId: "team-id",
    priority: 1,
});

Authentication

Supported Auth Types

The Linear plugin supports:

  • api_key (default) - Use a Linear API key

Default Auth Type

If no authType is specified, the plugin defaults to api_key.

Configuring API Key Authentication

Store credentials with the CLI:

pnpm corsair setup --linear api_key=your-api-key

See Get Credentials for step-by-step instructions on obtaining an API key.

Webhook Signature Verification

Linear webhooks can be secured using HMAC signature verification. Store the secret with the CLI:

pnpm corsair setup --linear webhook_signature=your-webhook-secret

See Get Credentials for step-by-step instructions on setting up webhooks.

Options

The Linear plugin accepts the following configuration options:

OptionTypeDescription
authType'api_key'Authentication method (defaults to 'api_key')
keystringAPI key (optional, uses database if not provided)
hooksobjectEndpoint hooks for custom logic
webhookHooksobjectWebhook hooks for event handling
errorHandlersobjectCustom error handlers

Hooks

Hooks allow you to add custom logic before and after API calls:

linear({
    hooks: {
        issues: {
            create: {
                before: async (ctx, input) => {
                    console.log("Creating issue:", input.title);
                    return { ctx, input };
                },
                after: async (ctx, input, result) => {
                    console.log("Created issue:", result.id);
                    return result;
                },
            },
        },
    },
})

See Hooks for complete documentation.

Error Handling

The plugin includes built-in error handlers for common scenarios like rate limiting, authentication errors, and network failures. For complete documentation, see the Error Handlers reference.

Usage

Accessing the API

The Linear API is organized into resource-based endpoints:

// Issues
await corsair.linear.api.issues.list({ teamId: "team-id" });
await corsair.linear.api.issues.get({ id: "issue-id" });
await corsair.linear.api.issues.create({ title: "New issue", teamId: "team-id" });

// Projects
await corsair.linear.api.projects.list();
await corsair.linear.api.projects.create({ name: "Q1 Roadmap", teamIds: ["team-id"] });

// Comments
await corsair.linear.api.comments.list({ issueId: "issue-id" });
await corsair.linear.api.comments.create({ issueId: "issue-id", body: "Great work!" });

// Teams
await corsair.linear.api.teams.list();
await corsair.linear.api.teams.get({ id: "team-id" });

For detailed endpoint documentation, see API Endpoints.

Webhooks

Set up webhook event handlers to respond to changes in Linear:

linear({
    webhookHooks: {
        issues: {
            create: {
                after: async (ctx, result) => {
                    console.log("Issue created:", result.data.title);
                },
            },
            update: {
                after: async (ctx, result) => {
                    console.log("Issue updated:", result.data.title);
                },
            },
        },
    },
})

For all available webhooks, see Webhooks.

Database Access

The plugin automatically syncs data to your database. Query synced data:

// Find issues by team
const issues = await corsair.linear.db.issues.search({
    data: { teamId: "team-id" },
});

// Find projects by state
const activeProjects = await corsair.linear.db.projects.search({
    data: { state: "started" },
});

See Database for complete schema documentation.

Multi-Tenancy

Use the plugin with multiple tenants:

const tenant = corsair.withTenant("tenant-123");

await tenant.linear.api.issues.create({
    title: "Customer feedback",
    teamId: "team-id",
});

See Multi-Tenancy for more details.

Examples

Example 1: Create Issue from External Event

corsair.ts
import { inngest } from "./inngest";
import { createCorsair } from "corsair";
import { slack } from "@corsair-dev/slack";
import { linear } from "@corsair-dev/linear";

export const corsair = createCorsair({
    plugins: [
        slack({
            webhookHooks: {
                events: {
                    message: {
                        after: async (ctx, result) => {
                            // Check if message is in support channel
                            if (result.event.channel === "support-channel-id") {
                                // Send to event system
                                await inngest.send({ 
                                    name: "slack/support-message", 
                                    data: { 
                                        tenantId: ctx.tenantId, 
                                        message: result.event, 
                                    }, 
                                }); 
                            }
                        },
                    },
                },
            },
        }),
        linear(),
    ],
});
inngest/functions.ts
// Handle in background job - NOW you can call corsair
export const handleSupportMessage = inngest.createFunction(
    { id: "handle-support-message" },
    { event: "slack/support-message" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);
        
        // Create Linear issue from Slack message
        await tenant.linear.api.issues.create({ 
            title: `Support: ${event.data.message.text.slice(0, 50)}`, 
            description: event.data.message.text, 
            teamId: process.env.LINEAR_SUPPORT_TEAM_ID!, 
            priority: 2, 
        }); 
    }
);

Example 2: Sync Issue Updates to Slack

corsair.ts
import { inngest } from "./inngest";
import { createCorsair } from "corsair";
import { slack } from "@corsair-dev/slack";
import { linear } from "@corsair-dev/linear";

export const corsair = createCorsair({
    plugins: [
        linear({
            webhookHooks: {
                issues: {
                    update: {
                        after: async (ctx, result) => {
                            // Only notify if issue was completed
                            if (result.data.state?.type === "completed") {
                                await inngest.send({ 
                                    name: "linear/issue-completed", 
                                    data: { 
                                        tenantId: ctx.tenantId, 
                                        issue: result.data, 
                                    }, 
                                }); 
                            }
                        },
                    },
                },
            },
        }),
        slack(),
    ],
});
inngest/functions.ts
export const notifyIssueCompleted = inngest.createFunction(
    { id: "notify-issue-completed" },
    { event: "linear/issue-completed" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);
        
        await tenant.slack.api.chat.postMessage({
            channel: "engineering",
            text: `✅ Issue completed: ${event.data.issue.title}`,
            blocks: [
                {
                    type: "section",
                    text: {
                        type: "mrkdwn",
                        text: `*${event.data.issue.title}*\n${event.data.issue.identifier}`,
                    },
                },
            ],
        });
    }
);

Example 3: Auto-Assign Issues by Priority

corsair.ts
linear({
    hooks: {
        issues: {
            create: {
                before: async (ctx, input) => {
                    // Auto-assign high priority issues to team lead
                    if (input.priority === 1 || input.priority === 2) {
                        input.assigneeId = process.env.LINEAR_TEAM_LEAD_ID; 
                    }
                    return { ctx, input };
                },
            },
        },
    },
})