How to migrate Original Story Points in Jira Data Center

Platform Notice: Data Center Only - This article only applies to Atlassian products on the Data Center platform.

Note that this KB was created for the Data Center version of the product. Data Center KBs for non-Data-Center-specific features may also work for Server versions of the product, however they have not been tested. Support for Server* products ended on February 15th 2024. If you are running a Server product, you can visit the Atlassian Server end of support announcement to review your migration options.

*Except Fisheye and Crucible

Summary

Jira Cloud does not contain the Original Story Points fields. If you are planning a migration to Jira Cloud these values will be lost on migration. This article will outline how to do this.

Background

Original story points were created as part of Advanced Roadmaps and have long since been deprecated.

See the following Atlassian Community post for further details.

While the Community post indicates that Original Story Points are not visible directly on Jira issues, these values have appeared on Jira issues since then.

Solution

To be able to migrate this information to Jira Cloud, you can create a custom field and copy the existing data to the new field.

Create a custom field

  • Navigate to Administration ⚙ > Issues > Custom Fields.

  • Create a Number field as outlined in our documentation.

Copy the field data

You have two options to copy the data.

Option 1: Create an automation rule

  • Navigate to Administration ⚙ > System > Automation rules.

  • Create a rule with the trigger component Scheduled.

  • Use the JQL "Original story points" is not EMPTY for the section When rule executes...

  • Add the Edit Issue action

  • Use smart values in the Additional fields section under More Options.

    Example

    {"fields": {"StoryOrigin": "{{triggerIssue.Original story points}}"}}

    Replace 'StoryOrigin' with the name of your custom field

  • Save and manually run the rule against your projects.

Option 2: Use a Python script to copy

Use the following Python script that will copy values from the Original Story Points field to the new numeric custom field

import requests import json # Jira configuration JIRA_BASE_URL = "https://your-jira-instance.com" API_TOKEN = "your-pat-token" # Personal Access Token # Custom field names and IDs ORIGINAL_POINTS_NAME = "Original story points" # Custom field name as shown in Jira ORIGINAL_POINTS_FIELD = "customfield_11203" # Custom field ID for updates PLANNING_ESTIMATE_FIELD = "customfield_10002" # Planning Estimate custom field ID # Headers with Bearer token authentication headers = { "Accept": "application/json", "Content-Type": "application/json", "Authorization": f"Bearer {API_TOKEN}" } def get_issues_with_original_points(): """ Search for all issues that have a value in Original story points field """ # Use the custom field name in the JQL query jql = f'"{ORIGINAL_POINTS_NAME}" is not EMPTY' start_at = 0 max_results = 100 all_issues = [] while True: search_url = f"{JIRA_BASE_URL}/rest/api/2/search" payload = { "jql": jql, "startAt": start_at, "maxResults": max_results, "fields": [ORIGINAL_POINTS_FIELD, PLANNING_ESTIMATE_FIELD] } try: response = requests.post( search_url, headers=headers, json=payload ) response.raise_for_status() # Raise an exception for bad status codes except requests.exceptions.RequestException as e: print(f"Error searching issues: {e}") print(f"Response status code: {response.status_code}") print(f"Response text: {response.text}") return [] data = response.json() issues = data.get('issues', []) all_issues.extend(issues) if len(issues) < max_results: break start_at += max_results return all_issues def update_planning_estimate(issue_key, points): """ Update the Planning Estimate field for a given issue """ update_url = f"{JIRA_BASE_URL}/rest/api/2/issue/{issue_key}" payload = { "fields": { PLANNING_ESTIMATE_FIELD: points } } try: response = requests.put( update_url, headers=headers, json=payload ) response.raise_for_status() return True except requests.exceptions.RequestException as e: print(f"Error updating issue {issue_key}: {e}") print(f"Response status code: {response.status_code}") print(f"Response text: {response.text}") return False def main(): # Get all issues with Original story points print("Fetching issues with Original story points...") issues = get_issues_with_original_points() print(f"Found {len(issues)} issues to process") # Process each issue success_count = 0 error_count = 0 for issue in issues: issue_key = issue['key'] original_points = issue['fields'].get(ORIGINAL_POINTS_FIELD) current_planning_estimate = issue['fields'].get(PLANNING_ESTIMATE_FIELD) # Skip if Original points is None or if Planning Estimate already has the same value if original_points is None or original_points == current_planning_estimate: continue print(f"Processing {issue_key}: Copying {original_points} to Planning Estimate") if update_planning_estimate(issue_key, original_points): success_count += 1 else: error_count += 1 print("\nSummary:") print(f"Successfully updated: {success_count}") print(f"Errors: {error_count}") if __name__ == "__main__": main()

Remove the Original Story Points from any screen

You can easily prevent users from adding or modifying the field further by removing it from all available screens via Administration ⚙ > Issues

  • Under Fields (the left-side panel), select Field configurations.

  • Select the field configuration.

  • Find the Original Story Points in the list and select Screens.

  • Change the associations and select Update.

Always back up your data before making any database modifications. If possible, test any alter, insert, update, or delete SQL commands on a staging server first.

  • Optional: Unlock the Original Story Points field to include a message not to use it

    # replace customfield_11203 with the ID of the Original Story Points custom field ID UPDATE managedconfigurationitem set managed='false' where item_id = 'customfield_11203';
    • Modify the description of the Original Story Points with something like:

      *DO NOT USE* - Replaced with (new numeric) custom field
    • Lock the Original Story Points field again

      # replace customfield_11203 with the ID of the Original Story Points custom field ID UPDATE managedconfigurationitem set managed='true' where item_id = 'customfield_11203';

Updated on June 26, 2025

Still need help?

The Atlassian Community is here for you.