Let players add Jira tickets for you

Jira
Jira is Atlassian's project management and issue tracking platform that's become pretty much the standard for software development teams. There are a lot of opinions about Jira, but it's really good at organizing work into tickets, tracking bugs, managing sprints, and giving you visibility into how your project is progressing through dashboards and reports. Most game dev companies use Jira to keep programmers, artists, designers, and QA teams on the same page.
While Jira might look like way too much software for indie developers, it's actually free for teams up to 10 users, which makes it a decent option for small studios and solo devs. It's got a ton of features and can at times feel overwhelming, but you don't have to use all of it. You can start simple with basic issue tracking and add more stuff like custom workflows or automation as your project gets bigger. The trick is thinking of it as something that grows with you instead of trying to figure out every feature right away. It's not unlike Visual Studio in this way.

Architecture
In this article I'm going to show you how I implemented a system in my game Terralysia that allows players to send feedback and bug reports directly from the game into our Jira workspace. Instead of players having to email, post on Discord, or fill out external forms, they can report issues or suggest features without ever leaving the game, and those reports automatically become properly formatted Jira tickets for the team (uh...me) to triage and address.
The backend of our system runs on Cloudflare Workers, basically serverless functions that run on Cloudflare's network around the world. Workers are perfect for this because they're fast, cheap, and handle API requests really well. Our worker gets the data from the game, cleans it up and formats it properly, then talks to Jira's API to create tickets. This way we keep our Jira login info safe on the server instead of putting it in the game where players could potentially dig it out.
export default {
async fetch(request, env) {
// accept only POST requests
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
try {
const payload = await request.json();
// validate incoming data
if (!payload.feedback || !payload.build) {
return new Response('Missing required fields', { status: 400 });
}
// create jira payload
const jiraPayload = {
fields: {
project: {
key: env.JIRA_PROJECT_KEY
},
summary: 'Terralysia feedback',
description: {
type: "doc",
version: 1,
content: [
{
type: "paragraph",
content: [
{
type: "text",
text: payload.feedback
}
]
}
]
},
labels: [
'feedback',
payload.build
],
issuetype: {
name: 'Task'
}
}
};
const jiraResponse = await fetch(`${env.JIRA_BASE_URL}/rest/api/3/issue`, {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa(`${env.JIRA_USER}:${env.JIRA_TOKEN}`),
'Content-Type': 'application/json',
},
body: JSON.stringify(jiraPayload),
});
if (!jiraResponse.ok) {
const errorText = await jiraResponse.text();
console.error('Jira API error:', errorText);
return new Response('Failed to create ticket', { status: 500 });
}
const jiraResult = await jiraResponse.json();
return new Response(JSON.stringify({
success: true
}), {
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
return new Response('Internal server error', { status: 500 });
}
},
}
On the Unreal Engine 5 side, I built a custom UI screen that grabs the player's feedback and uses UE5's HTTP module to send it to our Cloudflare Worker. The interface in Terralysia is minimal: the player just enters a message and presses send. I wanted to keep it simple and anonymous. If you'd like, you could easily extend this to a proper bug-reporting feature that includes additional info like platform, logs, reproduction steps and even a screenshot if it makes sense.
FString BuildVersion = TEXT("0.0.0.0");
GConfig->GetString(TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectVersion"), BuildVersion, GGameIni);
TSharedRef<FJsonObject> JsonRootObject = MakeShareable(new FJsonObject);
JsonRootObject->SetStringField("build", BuildVersion);
JsonRootObject->SetStringField("feedback", Feedback);
FString JsonString;
TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> JsonWriter = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&JsonString, /*Indent=*/0);
FJsonSerializer::Serialize(JsonRootObject, JsonWriter);
TSharedRef< IHttpRequest > HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetVerb("POST");
HttpRequest->SetHeader("User-Agent", FGenericPlatformHttp::GetDefaultUserAgent());
HttpRequest->SetURL(Url);
HttpRequest->SetHeader("Content-Type", "application/json");
HttpRequest->SetContentAsString(JsonString);
HttpRequest->ProcessRequest();
The whole flow is pretty simple: a player hits F9 anywhere in the game to bring up the feedback UI and fills out form, a request is sent to our Cloudflare Worker, which then forwards the data to Jira and creates a properly formatted ticket in our project. The whole thing takes just a few seconds, and players get confirmation right away that we got their feedback.
This system has been awesome for collecting feedback. I'm seeing a ton of really helpful reports and I imagine (and hope) that players feel like they're being heard because they can see their input goes straight to our dev process.
