Python script to bulk attach files to Jira Issues in Jira Cloud

Hi Atlassian Community!

It's me again.

I'm sharing a helpful Python script to streamline attaching multiple files to Jira Cloud issues. This script automates the process of uploading files, saving you time and effort, especially when dealing with many attachments.

The solution

The script uses the Jira Cloud REST API (this endpoint specifically) and reads data from a CSV file to attach files to specific Jira issues. The CSV file should contain two columns: "Issue Key" and "Filename". The "Filename" column should contain the paths to your attachment files (either relative paths from where you run the script or absolute paths).

Example:

Issue Key,Filename
PROJ-001,/Users/user1/Desktop/attachments/attachment1.png
PROJ-002,/Users/user1/Desktop/attachments/attachmend2.txt
PROJ-003,/Users/user1/Desktop/attachments/attachment3.pdf

Key features

  • Attach multiple files to multiple issues in one run
  • Uses a CSV file to specify issue keys and file paths, making it easy to manage attachments
  • Includes robust error handling to catch issues like invalid file paths, missing CSV columns, or authentication problems
  • Provides informative logging messages to track the progress and identify any errors
  • Prompts you for your Jira Cloud domain, email, and API token for secure authentication

Preparation

1) Make sure that the user acting on this script has the necessary project permissions to "Browse Projects", "Edit Issues", and "Create Attachments".

2) Ensure the destination issues (issue types) have the "Attachments Field" in their edit screen

3) Considerations: There's a character limit of 256 for file names, also check your attachment size limit to avoid related errors

4) Install the necessary Python libraries by running:

pip install requests

Prepare your Jira Cloud site URL (your-domain.atlassian.net), email address, and API token. You can generate an API token from Atlassian's account security page.

The script

import requests
from requests.auth import HTTPBasicAuth
from getpass import getpass
import logging
import os
import csv
import json

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def get_jira_auth():
    jira_domain = input("Enter your Jira Cloud domain (e.g., your-domain.atlassian.net): ")
    email = input("Enter your Jira email: ")
    api_token = getpass("Enter your Jira API token: ")
    return jira_domain, HTTPBasicAuth(email, api_token)


def attach_files_to_issues(jira_domain, auth, csv_filepath):
    """Attaches files to Jira issues based on data from a CSV file."""

    base_url = f"https://{jira_domain}/rest/api/2/issue/"

    try:
        with open(csv_filepath, 'r', newline='', encoding='utf-8-sig') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                issue_key = row['Issue Key']
                filepath = row['Filename'] 

                url = f"{base_url}{issue_key}/attachments"
                headers = {
                    "Accept": "application/json",
                    "X-Atlassian-Token": "no-check"
                }

                try:
                    with open(filepath, 'rb') as file_content:
                        files = {"file": (os.path.basename(filepath), file_content)}
                        response = requests.post(url, headers=headers, auth=auth, files=files)
                        response.raise_for_status()

                        logging.info(f"Attachment {filepath} added to issue {issue_key} successfully:")


                except requests.exceptions.RequestException as e:
                    logging.error(f"Error attaching {filepath} to {issue_key}: {e}")
                    if response.status_code == 404: 
                        logging.error(f"Issue {issue_key} might not exist or you might not have permission.")
                    elif response.status_code == 403:
                         logging.error(f"Authentication failed, please verify your email and API token.")

                except FileNotFoundError:
                    logging.error(f"Error: File '{filepath}' not found. Please double-check the file path.")
                except KeyError as e:
                    logging.error(f"Error: CSV file is missing a required column: {e}")
                except Exception as e:
                    logging.exception(f"An unexpected error occurred during attachment upload: {e}")


    except FileNotFoundError:
        logging.error(f"Error: CSV file '{csv_filepath}' not found. Please double-check the file path.")
    except Exception as e:
        logging.exception(f"An unexpected error occurred during CSV processing: {e}")



def main():
    jira_domain, auth = get_jira_auth()

    while True:
        csv_filepath = input("""
Enter the path to your CSV file.  The CSV must have columns named "Issue Key" and "Filename".
The "Filename" column must contain valid paths to your attachment files (either relative or absolute).

Examples:

1. CSV in the same directory as the script:
   attachments.csv

2. CSV in a subdirectory called "data":
   data/attachments.csv

3. Full path to the CSV:
   /path/to/your/attachments.csv

CSV File Path: """)
        if csv_filepath.lower().endswith(".csv"):
            break
        else:
            print("Invalid file type. Please enter a CSV file.")
            

    attach_files_to_issues(jira_domain, auth, csv_filepath)




if __name__ == "__main__":
    main()

 

Disclaimer:

While this script is designed to facilitate certain interactions with JIRA Software Cloud as a convenience, it is essential to understand that its functionality is subject to change due to updates to JIRA Software Cloud’s API or other conditions that could affect its operation.

Please note that this script is provided on an "as is" and "as available" basis without any warranties of any kind. This script is not officially supported or endorsed by Atlassian, and its use is at your own discretion and risk.

Cheers!

2 comments

Matt Doar _Adaptavist_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 26, 2025

Nice, thank you!

Might also be worth checking that each attachment name is not too long? (255 chars IIRC). And sadly I've never found a way to set the user name of the person who is supposed to have uploaded the attachment. That info doesn't seem to be displayed any more so it probably no longer matters

Like Delfino Rosales likes this
Delfino Rosales
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
February 28, 2025

@Matt Doar _Adaptavist_ 
That was a good catch, thanks! 
Will update the post with this consideration

Best regards,

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events