Creating Stories with the Jira CLI
Creating stories manually in Jira’s web UI can be painful. If you have a large set of notes that can be turned into stories, this page has a helper script that will read a text file of stories and create them in bulk using the Jira CLI.
|
This script intentionally does not set a Fix Version, Sprint, Size, and Time Estimate. Those are best set after the stories are created while planning for upcoming sprints. |
What You Need
-
The Jira CLI installed and initialized (
jira initcompleted). -
Your
JIRA_API_TOKENexported in your shell and sourced. -
The script and a stories file (links below).
|
If you have not set up the Jira CLI yet, follow the Jira CLI guide
first. The script is just a thin wrapper around |
Download
-
Script: create-jira-stories.sh
-
Sample stories file: stories.example.txt
Save both to the same directory and make the script executable:
chmod +x create-jira-stories.sh
The Stories File Format
Stories live in a text file that follows a simple format.
-
Lines starting with
#are comments. -
Stories are separated by a line that is just
---. -
Sticky single-line fields — set them once, and they apply to every following story until you change them:
Field Meaning PROJECT:Jira project key (e.g.
CKM)COMPONENT:Component name (e.g.
privacy-policy-administration-service)TYPE:Issue type (e.g.
Story,Technical Debt,Task)PRIORITY:Priority (e.g.
Standard)EPIC:Key of an existing epic to attach each story to (e.g.
CKM-40000). See Attaching stories to an epic. -
Per-story fields — reset for each story:
Field Meaning TITLE:One-line summary (required)
VALUE:The value statement (multi-line)
NOTES:Notes / fix details that provide context (multi-line)
ACCEPTANCE:Acceptance criteria (multi-line)
-
Optional per-story fields — also reset for each story; omit them to leave the field unset in Jira:
Field Meaning FIX_VERSION:Release / fix version name (e.g.
privacy-policy-administration-service: Release A)STORY_POINTS:Story points estimate (a number, e.g.
3)ORIGINAL_ESTIMATE:Original time estimate (e.g.
2d,4h,30m)ID:A local alias for this story that other stories can depend on (e.g.
date-roundtrip). See Ordering stories with dependencies.AFTER:Alias(es) and/or issue key(s) this story must be done after (e.g.
date-roundtrip, CKM-39999). See Ordering stories with dependencies.
The VALUE, NOTES, and ACCEPTANCE sections continue across multiple lines until the next field
or the --- separator. The description sent to Jira is assembled from them like this:
<VALUE>
Notes / Fix:
<NOTES>
Acceptance Criteria:
<ACCEPTANCE>
Any empty section is omitted. Likewise, any optional field you leave out is simply not sent to Jira.
Example stories file
PROJECT: CKM
COMPONENT: privacy-policy-administration-service
TYPE: Technical Debt
PRIORITY: Standard
---
TITLE: Remove redundant/incorrect date round-trip in UserAgreementMapper
FIX_VERSION: privacy-policy-administration-service: Release A
STORY_POINTS: 2
ORIGINAL_ESTIMATE: 4h
VALUE:
As a maintainer of the privacy-policy-administration-service, I want the service to be
as efficient as possible so that I am able to use it better.
NOTES:
getUserAgreementMapper(...) parses an OffsetDateTime that is already an OffsetDateTime.
The parse/format round-trip is wasteful and can throw on locale/format edge cases.
Use the value directly.
ACCEPTANCE:
- mapper uses getCreatedDate() directly; tests unchanged/green.
---
Usage
Run the script with the path to your stories file:
./create-jira-stories.sh stories.txt
If you omit the argument it defaults to stories.txt in the current directory.
Do a dry run first
Always preview before creating real tickets. Set DRY_RUN=1 to print exactly what would be
created without touching Jira:
DRY_RUN=1 ./create-jira-stories.sh stories.txt
The output shows the project, type, priority, component, summary, and the fully assembled
description for each story, followed by a count such as Done. 3 ticket(s) previewed.
|
A dry run also validates your file. If any story is missing a required field ( |
Create the tickets
When the dry run looks right, run it:
./create-jira-stories.sh stories.txt
Each created ticket prints its new key (e.g. CKM-31299).
Re-running safely (duplicate protection)
After a ticket is created, the script writes its key back into your stories file as a
TICKET NUMBER: line directly above that story’s TITLE:
TICKET NUMBER: CKM-31299
TITLE: Add metrics via Micrometer
NOTES:
...
On any later run, every story that already has a TICKET NUMBER is skipped, so you can safely
re-run the same file — for example after appending a few new stories — without creating duplicates:
-- skipping (already created as CKM-31299): Add metrics via Micrometer
>> creating: A newly added story
Done. 1 ticket(s) created, 1 skipped (already created).
|
You never type |
Overriding Fields Per Story
Because the single-line fields are sticky, you set common values once at the top and only specify what differs. To change a field for a specific story, just set it again inside that story’s block.
|
A sticky override persists for all stories that follow it — it does not automatically revert to the previous value. If you only want the change to apply to one story, set the field back to the original value in the next story (or reorder so the odd-one-out comes last). |
In the example below, most stories target privacy-policy-administration-service, but the middle
story is filed against callback-service, and the component is then reset:
PROJECT: CKM
COMPONENT: privacy-policy-administration-service
TYPE: Technical Debt
PRIORITY: Standard
---
TITLE: First PPAS cleanup item
VALUE:
...
---
# Override component for just this one story
COMPONENT: callback-service
TITLE: A callback-service item
VALUE:
...
---
# Reset back to PPAS for the remaining stories
COMPONENT: privacy-policy-administration-service
TITLE: Another PPAS item
VALUE:
...
---
You can override TYPE, PRIORITY, and PROJECT the same way — for example, setting
TYPE: Story for one ticket among a batch of Technical Debt items.
Attaching stories to an epic
Set EPIC: to the key of an already-existing epic and every story created afterwards is attached
to it (the script passes it to the Jira CLI as --parent). The script does not create the epic —
make it in Jira first, then reference its key.
EPIC is sticky, so a single line near the top links the whole batch:
PROJECT: CKM
COMPONENT: privacy-policy-administration-service
TYPE: Story
PRIORITY: Standard
EPIC: CKM-40000
---
TITLE: First story under the epic
VALUE:
...
---
TITLE: Second story under the same epic
VALUE:
...
Because it is sticky, you can switch epics partway through by setting EPIC: again, or stop
attaching stories by setting it to an empty value (EPIC: with nothing after it).
Ordering stories with dependencies
Sometimes one story must be done after another, but when you are writing the file you do not yet have the real ticket numbers. You bridge that gap with two per-story fields:
-
ID:gives a story a local alias (any short name you choose, e.g.date-roundtrip). -
AFTER:lists the alias(es) — and/or real issue key(s) — that a story must come after.
After all stories are created, the script resolves each AFTER reference to the real key and creates
a Jira link so the relationship reads "this story is blocked by that story" (link type
Blocks by default; override with the LINK_TYPE environment variable).
---
ID: date-roundtrip
TITLE: Remove redundant date round-trip in UserAgreementMapper
VALUE:
...
---
# Must be done after the story above. "date-roundtrip" is the alias, no ticket
# number needed yet.
TITLE: Add regression tests for the date handling
AFTER: date-roundtrip
VALUE:
...
---
# AFTER can list several prerequisites and can mix a local alias with a real,
# already-existing issue key.
TITLE: Document the change in the SRVDD
AFTER: date-roundtrip, CKM-39999
VALUE:
...
|
Notes and Caveats
-
TYPEandPRIORITYvalues must match exactly what is configured in your Jira project (including spaces and capitalization). If a value is wrong,jira issue createwill fail for that ticket. -
EPICmust be an existing epic key; the script attaches stories to it but never creates it. A wrong key will makejira issue createfail for that ticket. -
FIX_VERSIONmust match an existing release name in the project (these typically look like<component>: Release X, e.g.privacy-policy-administration-service: Release A), otherwise the create fails. -
STORY_POINTSis sent as thestory-pointscustom field (--custom story-points=<n>). If your project uses a differently named field, adjust the script’s--customkey. -
ORIGINAL_ESTIMATEaccepts Jira time syntax such as2d,4h, or30m. -
FIX_VERSION,STORY_POINTS,ORIGINAL_ESTIMATE,ID, andAFTERare optional and reset per story — omit them to leave the field unset. (Ticket size is otherwise not set by this script.) -
Dependency links use the
Blockslink type by default; override it withLINK_TYPE(for exampleLINK_TYPE="Depends") if your Jira uses a different name. The type must exist in your Jira. -
The script stamps a
TICKET NUMBER:line into your file after each successful create and skips any story that already has one (see Re-running safely (duplicate protection)). Keep the stamped file if you intend to re-run it; deleting theTICKET NUMBERlines will cause those stories to be created again.