🚀 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
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.
- Log into your Linear account.
- Navigate to Settings > API.
- Under “Personal API Keys”, create a new key. Give it a descriptive label like “CustomReportingScript”.
- 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 ourconfig.envfile, 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 (liketitle,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 namedlinear_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
pageInfoandaftercursor 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.envis incorrect, has been revoked, or you forgot to add theAuthorizationheader. 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
🤖 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