🚀 Executive Summary

TL;DR: Manually pulling data from Linear for reporting is tedious and error-prone. This guide provides a Python script to automate exporting Linear issues to a CSV file, saving time and ensuring accurate custom reports.

🎯 Key Takeaways

  • Linear API keys are essential for authentication and must be securely stored, ideally in a `config.env` file, not hardcoded.
  • The Python script utilizes `requests` for GraphQL API calls and `python-dotenv` for secure environment variable loading.
  • GraphQL queries allow precise selection and filtering of Linear issue data, including fields like `id`, `title`, `state`, `assignee`, and `labels`.
  • The `csv` module is used to write fetched data into a CSV file, requiring flattening of nested data structures (e.g., assignee names, multiple labels).
  • For large datasets, Linear’s API requires pagination handling using `pageInfo` and `after` cursor fields in the GraphQL query.
  • The entire workflow can be automated using cron jobs to generate reports at scheduled intervals, such as weekly.

Exporting Linear Issues to CSV for Custom Reporting

Exporting Linear Issues to CSV for Custom Reporting

Hey team, Darian here. Let’s talk about reporting. For a while, I was spending a couple of hours every other week manually pulling data from Linear for our sprint retros and quarterly reviews. It was tedious, error-prone, and frankly, a poor use of my time. That’s when I built a simple Python script to automate the whole process. Now, I have a perfectly formatted CSV waiting for me every Monday morning. It’s a small change that has saved me a ton of headache.

This guide will walk you through setting up that exact workflow. It’s designed to be quick, get you the data you need, and let you get back to more important things.

Prerequisites

Before we dive in, make sure you have the following ready:

  • Python 3 installed on your machine.
  • A Linear Account: You’ll need access to your team’s Linear workspace.
  • API Key Permissions: You must be able to generate a personal API key within Linear.

The Guide: From API Key to Automated CSV

Step 1: Generate Your Linear API Key

First things first, we need to authenticate with Linear’s API. A personal API key is the way to do it.

  1. Log into your Linear account.
  2. Navigate to Settings > API.
  3. Under “Personal API Keys”, create a new key. Give it a descriptive label like “CustomReportingScript”.
  4. Crucially: Copy this key immediately and store it somewhere safe. You won’t be able to see it again after you close the modal. We’ll be putting this in a configuration file, not hardcoding it in our script.

Step 2: Prepare Your Project Environment

I’ll skip the standard virtual environment setup since you likely have your own workflow for that. Let’s jump straight to the dependencies and file structure.

In your project directory, you’ll need two files:

  • config.env: This will securely store our API key.
  • export_linear_issues.py: This is where our Python logic will live.

Next, you’ll need to install a couple of Python packages. In your activated environment’s terminal, you’ll want to install requests for making HTTP calls and python-dotenv to handle our configuration file. You can do this with a standard pip install command for those packages.

Step 3: Write the Python Script

Now for the core of the operation. First, let’s set up our config.env file. It’s just one line:

LINEAR_API_KEY="your_api_key_here"

Replace your_api_key_here with the key you generated in Step 1. Now, let’s write our Python script, export_linear_issues.py. I’ll provide the full code first, then break down what each part does.

import os
import csv
import requests
from dotenv import load_dotenv

def fetch_linear_issues():
    load_dotenv('config.env')
    api_key = os.getenv('LINEAR_API_KEY')
    if not api_key:
        print("Error: LINEAR_API_KEY not found in config.env")
        return

    api_url = 'https://api.linear.app/graphql'
    headers = {
        'Authorization': api_key,
        'Content-Type': 'application/json'
    }

    # This GraphQL query fetches the first 100 issues.
    # You can customize the fields you need here.
    query = """
    query {
      issues(first: 100, filter: { team: { name: { eq: "TechResolve" } } }) {
        nodes {
          id
          title
          state { name }
          priorityLabel
          createdAt
          assignee { name }
          labels { nodes { name } }
        }
      }
    }
    """

    try:
        response = requests.post(api_url, json={'query': query}, headers=headers)
        response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
    except requests.exceptions.RequestException as e:
        print(f"API request failed: {e}")
        return

    data = response.json()
    issues = data.get('data', {}).get('issues', {}).get('nodes', [])
    
    if not issues:
        print("No issues found or failed to parse data.")
        return

    return issues

def write_to_csv(issues):
    if not issues:
        print("No issues to write.")
        return

    file_name = 'linear_issues.csv'
    # Define the headers for your CSV file
    headers = ['ID', 'Title', 'Status', 'Priority', 'Created At', 'Assignee', 'Labels']
    
    with open(file_name, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.writer(csvfile)
        writer.writerow(headers)
        
        for issue in issues:
            # Flatten nested data for CSV
            status = issue.get('state', {}).get('name', 'N/A')
            assignee = issue.get('assignee', {}).get('name', 'Unassigned')
            # Join multiple labels with a comma
            labels = ', '.join([label['name'] for label in issue.get('labels', {}).get('nodes', [])])
            
            writer.writerow([
                issue.get('id', ''),
                issue.get('title', ''),
                status,
                issue.get('priorityLabel', 'None'),
                issue.get('createdAt', ''),
                assignee,
                labels
            ])
            
    print(f"Successfully exported {len(issues)} issues to {file_name}")

if __name__ == "__main__":
    all_issues = fetch_linear_issues()
    if all_issues:
        write_to_csv(all_issues)

Breaking Down the Logic:

  • fetch_linear_issues(): This function handles the connection to Linear. It loads the API key from our config.env file, sets up the request headers, and defines the GraphQL query. The query is where the magic happens—you can specify exactly which fields you want (like title, state, assignee) and even filter for a specific team. I’ve set it to filter for the “TechResolve” team as an example.
  • write_to_csv(issues): This function takes the list of issues we fetched and writes it into a file named linear_issues.csv. It first writes a header row and then iterates through each issue, cleaning up nested data (like the assignee’s name or a list of labels) to fit nicely into a flat CSV format.
  • if __name__ == "__main__":: This standard Python construct ensures the code inside it only runs when the script is executed directly. It orchestrates the process: fetch the data, then write it.

Pro Tip: Linear’s API uses pagination. The query above fetches the first 100 issues. For teams with thousands of issues, you’ll need to modify the GraphQL query to handle pagination using the pageInfo and after cursor fields. It’s a bit more complex but essential for full-scale exports.

Step 4: Running the Script

Executing the script is simple. Open your terminal in the project directory and run:

python3 export_linear_issues.py

If everything is configured correctly, you’ll see a message saying “Successfully exported X issues to linear_issues.csv”, and that file will appear in your directory. Open it up, and you should see all your data, ready for a pivot table or custom chart.

Step 5: Automate It (The DevOps Way)

Running this manually is fine, but the real power comes from automation. I use a simple cron job to run this for me every Monday at 2 AM.

You can set up a similar job. The command would look something like this, assuming your script is in your user’s project folder:

0 2 * * 1 python3 export_linear_issues.py

This tells the system to execute our Python script every Monday at 2:00 AM. Now you have a fresh report ready for the week without lifting a finger.

Common Pitfalls (Where I Usually Mess Up)

  • Invalid API Key: The most common error is a 401 Unauthorized. This almost always means the API key in your config.env is incorrect, has been revoked, or you forgot to add the Authorization header. Double-check it.
  • GraphQL Query Errors: Linear’s API will return an error if your GraphQL query has a typo or asks for a field that doesn’t exist. I recommend testing complex queries in Linear’s API playground first (you can find it in your API settings).
  • Forgetting to Filter: If you’re on a large team and you forget to add a filter (like I did for the “TechResolve” team), you might pull down thousands of issues from the entire workspace. Always start with a narrow filter and expand as needed.

Conclusion

And that’s it. You now have a reusable, automated way to pull Linear data for any custom reporting need. This script is a starting point—you can customize the GraphQL query to pull cycle time data, specific labels, or anything else the Linear API exposes. The goal is to spend less time gathering data and more time acting on it. Hope this helps streamline your workflow.

Cheers,
Darian Vance

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How do I export Linear issues to CSV for custom reporting?

You can export Linear issues to CSV by creating a Python script that authenticates with a Linear personal API key, sends GraphQL queries to the Linear API using the `requests` library, and then processes the JSON response into a CSV file using Python’s `csv` module.

âť“ How does this custom script compare to Linear’s built-in export options?

This custom Python script offers superior flexibility compared to built-in options by allowing highly specific data selection and filtering through GraphQL queries, enabling custom formatting for CSV output, and providing full automation capabilities via cron jobs for recurring reports.

âť“ What are common pitfalls when implementing this Linear CSV export script?

Common pitfalls include using an invalid or expired API key resulting in 401 Unauthorized errors, GraphQL query errors due to typos or requesting non-existent fields, and forgetting to apply filters which can lead to unintentionally pulling vast amounts of data from the entire workspace.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading