Forums

Articles
Create
cancel
Showing results for 
Search instead for 
Did you mean: 

Which users are working in which Jira projects?

Daniel Pfeffer
Contributor
July 28, 2026

Hi Jira Community

I need a simple overview that shows me which active users (internal and external) have access to which Jira spaces. How do I do that? Even after trying various methods with “Rovo,” I haven't been able to achieve the desired result.
I look forward to your suggestions.
Thank you very much
CRB, Daniel Pfeffer

2 answers

3 votes
Nikola Perisic
Community Champion
July 28, 2026

Hi @Daniel Pfeffer 

You can use Python for this where it will export users and projects they belong to into CSV.

You will need your API token that you can get through Account Settings -> Security -> API

Script:

import csv

import sys

import requests

from requests.auth import HTTPBasicAuth

# ==========================================

# CONFIGURATION

# ==========================================

JIRA_URL = "" # Replace with your Jira domain

EMAIL = "" # Your Jira login email

API_TOKEN = "" # Generated from https://id.atlassian.com/manage-profile/security/api-tokens

def get_all_projects(auth):

"""Fetches all visible projects in Jira."""

url = f"{JIRA_URL}/rest/api/3/project"

response = requests.get(url, auth=auth, headers={"Accept": "application/json"})

if response.status_code != 200:

print(f"Error fetching projects: {response.status_code} - {response.text}")

sys.exit(1)

return response.json()

def get_project_access_map(auth):

"""

Iterates through projects and roles to build a mapping of:

Active User -> List of Accessible Projects

"""

projects = get_all_projects(auth)

print(f"Found {len(projects)} projects. Mapping user permissions...\n")

# Mapping structure: { "User Display Name (email)": set("Project Key 1", "Project Key 2") }

user_project_map = {}

for project in projects:

project_key = project.get("key")

project_name = project.get("name")

print(f"Processing project: {project_key} ({project_name})...")

# Get all defined roles for this project

roles_url = f"{JIRA_URL}/rest/api/3/project/{project_key}/role"

roles_resp = requests.get(roles_url, auth=auth, headers={"Accept": "application/json"})

if roles_resp.status_code != 200:

continue

roles = roles_resp.json()

for role_name, role_url in roles.items():

# Get detailed membership data for each role

role_detail_resp = requests.get(role_url, auth=auth, headers={"Accept": "application/json"})

if role_detail_resp.status_code != 200:

continue

role_data = role_detail_resp.json()

# Inspect actors (users/groups) assigned to this role

for actor in role_data.get("actors", []):

# Direct user assignment

if actor.get("type") == "atlassian-user-role-actor":

user_data = actor.get("actorUser", {})

# Only include active users

if user_data.get("active", False):

user_id = f"{user_data.get('displayName')} ({user_data.get('emailAddress', 'No Public Email')})"

user_project_map.setdefault(user_id, set()).add(f"{project_name} [{project_key}]")

# Group assignment (resolves group members)

elif actor.get("type") == "atlassian-group-role-actor":

group_name = actor.get("name")

group_users_url = f"{JIRA_URL}/rest/api/3/group/member?groupname={group_name}"

group_resp = requests.get(group_users_url, auth=auth, headers={"Accept": "application/json"})

if group_resp.status_code == 200:

for user in group_resp.json().get("values", []):

if user.get("active", False):

user_id = f"{user.get('displayName')} ({user.get('emailAddress', 'No Public Email')})"

user_project_map.setdefault(user_id, set()).add(f"{project_name} [{project_key}]")

return user_project_map

def main():

auth = HTTPBasicAuth(EMAIL, API_TOKEN)

access_data = get_project_access_map(auth)

print("\n" + "=" * 60)

print("ACTIVE USER PROJECT ACCESS REPORT")

print("=" * 60)

# Print to console

for user, projects in sorted(access_data.items()):

print(f"\nUser: {user}")

print("Accessible Projects:")

for proj in sorted(projects):

print(f" - {proj}")

# Export to CSV

csv_file = "jira_user_project_access.csv"

with open(csv_file, mode="w", newline="", encoding="utf-8") as f:

writer = csv.writer(f)

writer.writerow(["User Info", "Project Count", "Projects"])

for user, projects in sorted(access_data.items()):

writer.writerow([user, len(projects), ", ".join(sorted(projects))])

print(f"\nReport exported successfully to '{csv_file}'!")

if __name__ == "__main__":

main()
0 votes
Dejan V _DVLS Labs_
Atlassian Partner
August 7, 2026

Hi Daniel,

Before building the report, it is worth separating two questions, because they
produce very different lists:

1. Who has access to a project - this is a configuration question.
2. Who is actually working in a project - this is a data question.

Nikola's script answers the first one. If you need the second one, the reliable
source is the issue data itself. Run a search per project over a time window and
collect the actors from the changelog - the issue search endpoint accepts
expand=changelog, and every history entry carries an author, so you get everyone
who really touched the project, not everyone who could have. A lighter version:
assignee, reporter and worklogAuthor over the last 90 days already covers most
real activity.

If you do need the access list, the script is a good base, but walking project
roles covers only one of the places access comes from. These are the gaps that
usually make such a list wrong:

- Permission scheme grants that are not roles. Browse Projects can be granted to
a group directly, to "Any logged in user", to an application role, or through a
user/group picker custom field. Any of those effectively means "everyone with a
license", and it silently makes the role list meaningless. Worth checking
GET /rest/api/3/permissionscheme/{id}/permission?expand=all per project.

- Team-managed projects never appear under Jira admin > Project roles - that
screen is company-managed only. They use a separate model (Open / Limited /
Private, with Administrator / Member / Viewer) and need their own pass.

- Product access. Membership in the Jira product access group
(Admin > Products > Jira > User access) is exactly what makes an "Any logged in
user" grant dangerous, so without it the picture is incomplete.

- Issue security schemes narrow visibility below Browse Projects, so a
project-level list can overstate what a person can actually see.

- Service project customers are unlicensed and do not appear in roles at all. If
"external" in your question means portal customers, they live in Project
settings > Customer permissions, not in the role model.

One practical detail for the script: /rest/api/3/group/member is paginated and
includes inactive users by default, so pass includeInactiveUsers=false if you
want an active-only list - otherwise deactivated accounts inflate the numbers.

Which of the two do you actually need, access or activity? The approach is quite
different, and I can be more specific.

Suggest an answer

Log in or Sign up to answer
DEPLOYMENT TYPE
CLOUD
PRODUCT PLAN
PREMIUM
PERMISSIONS LEVEL
Product Admin
TAGS
AUG Leaders

Atlassian Community Events