Backup and restore individual Jira Cloud projects via REST API — issues, comments, worklogs, attachments, boards, and sprints. Fully resumable, with an interactive menu and CLI mode.
Jira Cloud has no built-in per-project backup/restore. The only native option was the full-instance Backup Manager, which Atlassian deprecated in March 2026. This tool fills the gap using standard REST API v3 endpoints.
| Feature | Description |
|---|---|
| Full project backup | Metadata, components, versions, roles, issues (all fields + changelog), worklogs, attachments, agile boards, sprints |
| 5-phase restore | Issues (epics first, subtasks last), links, comments, worklogs, attachments |
| Multi-project | Backup dozens of projects in a single run |
| Skip existing | --skip-existing skips projects that already have a complete backup |
| Auto-cleanup | Incomplete/partial backup folders are automatically removed before each run |
| Resumable | Safely re-run after interruption — already-processed items are skipped |
| Memory efficient | Issues stream directly to disk — backup 18 000+ issues on a 1 GB host without OOM |
| Verified backups | Detects silent truncation (count check), records SHA-256 checksums, and validates on demand |
| Dry-run mode | Preview all restore actions without making any API calls |
| Rate-limit aware | Exponential backoff with 429 / Retry-After detection |
| CSV export | Export backup data to CSV files for reporting, auditing, and sharing |
| Backup inspection | Detailed breakdown of issue types, statuses, priorities, and assignees |
| Connection test | Pre-flight check: authentication, project access, and server info |
| Interactive menu | Guided workflow with organised sections and post-operation summaries |
| CLI mode | --backup / --restore / --export-csv flags for scripted or cron use |
Via PyPI (recommended):
pip install jira-project-backup-restoreOr clone for development:
git clone https://github.com/davidmalko87/jira-project-backup-restore.git
cd jira-project-backup-restore
pip install -r requirements.txtcp .env.example .envEdit .env with your Jira Cloud credentials:
JIRA_URL=https://your-domain.atlassian.net
JIRA_EMAIL=you@example.com
JIRA_API_TOKEN=your-api-tokenGenerate an API token at id.atlassian.com/manage-api-tokens
Interactive menu:
python main.py==================================================
Jira Backup & Restore Tool v1.5.0
==================================================
Instance : https://your-domain.atlassian.net
Auth : API Token
Backups : ./backups
--- Backup & Restore ---
1) Backup project(s)
2) Restore project from backup
--- Browse & Analyze ---
3) List existing backups
4) Validate backup integrity
5) Upload attachments only
6) Export backup to CSV
7) Inspect backup details
--- Settings & Tools ---
8) Test Jira connection
9) Show current configuration
10) Cleanup incomplete backups
0) Exit
CLI — backup:
python main.py --backup PROJ
python main.py --backup PROJ1,PROJ2
python main.py --backup PROJ1,PROJ2 --skip-existingCLI — restore:
python main.py --restore backups/PROJ_20260322_143000 --target NEWPROJ
python main.py --restore backups/PROJ_20260322_143000 --target NEWPROJ --dry-runCLI — export to CSV:
python main.py --export-csv backups/PROJ_20260322_143000
python main.py --export-csv backups/PROJ_20260322_143000 --output-dir /tmp/reportCLI — validate a backup (exits non-zero on problems, for scripts/cron):
python main.py --validate backups/PROJ_20260322_143000| File | Contents |
|---|---|
project_meta.json |
Project config — name, lead, category, permission scheme |
components.json |
All project components |
versions.json |
All fix versions |
roles.json |
Role-to-member mappings |
issues.json |
All issues — every field, changelog history |
worklogs/worklogs.json |
Time tracking entries per issue |
attachments/<KEY>/ |
Binary attachment files, streamed to disk |
boards.json |
Agile board list |
board_<id>_config.json |
Board columns and swimlanes |
board_<id>_sprints.json |
Sprint history |
manifest.json |
File index, SHA-256 checksums, completeness counts, and a complete flag |
Each phase can be toggled individually and is fully resumable via restore_progress.json:
| Phase | What happens | Endpoint |
|---|---|---|
| 1 | Create issues — epics first, then regular, then subtasks | POST /rest/api/3/issue |
| 2 | Restore issue links (outward only, no duplicates) | POST /rest/api/3/issueLink |
| 3 | Add comments — original author and date prepended as text | POST /rest/api/3/issue/{key}/comment |
| 4 | Add worklogs — original author prepended as text | POST /rest/api/3/issue/{key}/worklog |
| 5 | Upload attachments — skips duplicates by filename | POST /rest/api/3/issue/{key}/attachments |
| 6 | Restore statuses (opt-in, best-effort) — single workflow transition to the original status | POST /rest/api/3/issue/{key}/transitions |
Phases 1–5 run by default. Phase 6 is opt-in (it fires workflow rules and notifications): enable it with --with-statuses on the CLI, or by selecting 6 in the interactive phase prompt.
Issue key mapping between source and target is saved in key_mapping.json inside the backup directory.
The backup→restore round-trip has been proven end-to-end against a live Jira Cloud site: a project was backed up, restored into a fresh project, and diffed via the API — issue count, types, hierarchy (epics/subtasks), ADF bodies, labels, links (no duplicates), comments, worklogs, and attachment bytes (SHA-256) all matched. A backup is only proven once it has been restored end-to-end and verified; structural checks alone are necessary but not sufficient.
These are Jira Cloud REST API constraints — not tool limitations:
| Data | Status | Notes |
|---|---|---|
| Timestamps (created/updated) | Not restorable | Cloud API blocks setting these fields |
| Changelog / history | Backup only | No write endpoint exists |
| Comment / worklog author | Text attribution | [Originally by Name on Date] prepended; original ADF body preserved |
| Reporter / Assignee | Conditional | Restored only if the user's email exists in the target instance |
| Issue status | Best-effort (opt-in) | Phase 6 restores it via a single workflow transition; statuses needing a multi-step path or a required-field screen stay at the project default |
Issue keys (e.g. KEY-123) |
Reassigned | Cloud assigns new keys; old→new mapping is saved |
jira-project-backup-restore/
├── main.py # Entry point — interactive menu + CLI flags
├── .env.example # Configuration template
├── requirements.txt # Python dependencies
│
├── jira_tool/
│ ├── config.py # .env loader and validation
│ ├── auth.py # Session builder (API token / cookie auth)
│ ├── api_client.py # HTTP client with retry and rate-limit handling
│ ├── backup.py # BackupManager — orchestrates full project backup
│ ├── restore.py # RestoreManager — 5-phase restore
│ ├── export.py # CSV export and backup statistics
│ ├── attachments.py # Standalone attachment uploader
│ ├── adf.py # Atlassian Document Format helpers
│ ├── progress.py # Resumability tracker
│ ├── utils.py # Logging, JSON I/O, utilities
│ ├── cli.py # Console-script entry point
│ └── menu.py # Interactive CLI menu
│
└── backups/ # Backup output directory (gitignored)
└── PROJ_20260322_143000/
├── manifest.json # Completion marker + file index
├── issues.json
├── attachments/
└── ...
All settings live in .env (copy from .env.example):
| Variable | Required | Default | Description |
|---|---|---|---|
JIRA_URL |
Yes | — | Jira Cloud base URL (no trailing slash) |
JIRA_EMAIL |
Yes* | — | Account email for API token auth |
JIRA_API_TOKEN |
Yes* | — | API token — generate here |
JIRA_COOKIE_HEADER |
Alt* | — | Full Cookie: header value for SSO auth |
JIRA_VERIFY_SSL |
No | true |
Set false to skip SSL verification |
BACKUP_ROOT |
No | ./backups |
Directory where backups are written |
PAGE_SIZE |
No | 100 |
Issues per API page (max 100) |
MAX_RETRIES |
No | 3 |
Retry count on transient failures |
READ_TIMEOUT |
No | 30 |
HTTP read timeout in seconds |
API_DELAY |
No | 0.2 |
Seconds to wait between API calls |
INCLUDE_ATTACHMENTS |
No | true |
Download attachment binary files |
INCLUDE_CHANGELOG |
No | true |
Include field change history in issues |
INCLUDE_WORKLOGS |
No | true |
Include time tracking entries |
LEGACY_KEY_JQL_TEMPLATE |
No | — | JQL template for standalone attachment upload |
* Either
JIRA_EMAIL+JIRA_API_TOKENorJIRA_COOKIE_HEADERis required.
- Python 3.10+
requests>= 2.28python-dotenv>= 1.0
See CHANGELOG.md for the full version history.
See CONTRIBUTING.md for the versioning policy and how to bump the version when making changes.