Forums

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

How to Create a Project-Scoped Custom Field Context Programmatically in Forge

Vitheya Monikha June 27, 2025

Hi,

I am working on a Forge app where I need to add options to a custom field (single-select) such that the options (folders) are visible only in the specific project from which they are created, not globally.

What I am doing:

Fetching existing contexts for the field using:


GET /rest/api/3/field/{fieldId}/context

 

If there is no existing context for the project, I attempt to create a new project-scoped context using:


POST /rest/api/3/field/{fieldId}/context
with:

json

{
"name": "Context for Project {projectId}",
"description": "Auto-created context for project {projectId}",
"projectIds": [projectId]
}
The issue:
When making the above POST request, I get:

json

{
"errorMessages": ["These projects were not found: {projectId}"],
"errors": {}
}
even though:
The projectId is correct and accessible via:

GET /rest/api/3/project/{projectId}
The Forge app has read:jira-work and manage:jira-configuration scopes.
The app is installed in the site and the project.

My goal: Create a project-scoped context for a custom field via Forge so that added options are visible only in that project, not globally.

Questions:
1. Is there an additional permission or step required to allow the Forge app to create project-scoped contexts?
2. Are there known limitations on creating projectIds scoped contexts via the REST API in Forge?
3. Has anyone successfully created project-scoped custom field contexts programmatically? If so, could you share the exact steps or working example?

Code snippet

resolver.define("addOptionToCustomField", async ({ payload }) => {
console.log("addOptionToCustomField triggered...");

const { fieldId, name, projectId } = payload;
const numericProjectId = parseInt(projectId, 10);

try {
console.log("Payload received:", { fieldId, name, numericProjectId });

// 1️⃣ Verify the project exists and is accessible
const projectResponse = await api.asApp().requestJira(
route`/rest/api/3/project/${numericProjectId}`,
{
method: "GET",
headers: { "Accept": "application/json" },
}
);

if (!projectResponse.ok) {
console.error(`❌ Project ${numericProjectId} not accessible or does not exist.`);
return { code: 2001, message: `Project ${numericProjectId} not accessible or does not exist.` };
}

const projectData = await projectResponse.json();
console.log(`✅ Project verified: ${projectData.name} (${numericProjectId})`);

// 2️⃣ Fetch existing contexts
const contextResponse = await api.asApp().requestJira(route`/rest/api/3/field/${fieldId}/context`, {
method: "GET",
headers: { "Accept": "application/json" },
});
const contextData = await contextResponse.json();
console.log("Fetched contextData:", contextData);

// 3️⃣ Check if a project-scoped context already exists
let projectScopedContextId = null;

for (const ctx of contextData.values) {
const ctxProjectRes = await api.asApp().requestJira(
route`/rest/api/3/field/${fieldId}/context/${ctx.id}/project`,
{ method: "GET", headers: { "Accept": "application/json" } }
);
const ctxProjectData = await ctxProjectRes.json();
const hasProject = ctxProjectData?.projectIds?.includes(numericProjectId);

if (hasProject) {
projectScopedContextId = ctx.id;
console.log(`✅ Found existing context (${ctx.id}) scoped to project ${numericProjectId}`);
break;
}
}

// 4️⃣ If no context found, create a new project-scoped context
if (!projectScopedContextId) {
console.log(`No context found for project ${numericProjectId}. Creating a new project-scoped context...`);

const createContextResponse = await api.asApp().requestJira(
route`/rest/api/3/field/${fieldId}/context`,
{
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: `Context for Project ${numericProjectId}`,
description: `Auto-created context for project ${numericProjectId}`,
projectIds: [numericProjectId],
}),
}
);

const createdContextData = await createContextResponse.json();
console.log("Created Context Data:", createdContextData);

if (createdContextData.errorMessages) {
console.error("❌ Error creating context:", createdContextData.errorMessages);
return {
code: 2002,
message: `Error creating context: ${createdContextData.errorMessages.join(", ")}`,
};
}

projectScopedContextId =
createdContextData.id ||
createdContextData.values?.[0]?.id ||
createdContextData.values?.id;

if (!projectScopedContextId) {
console.error("❌ Failed to retrieve created context ID from Jira response.");
return { code: 2003, message: "Unable to create project-scoped context." };
}

console.log(`✅ Created new project-scoped context with ID: ${projectScopedContextId}`);
}

// 5️⃣ Add the folder (option) to the project-scoped context
const optionData = [{ value: name }];
console.log("Adding Option Data:", optionData);

const addOptionResponse = await api.asApp().requestJira(
route`/rest/api/3/field/${fieldId}/context/${projectScopedContextId}/option`,
{
method: "POST",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ options: optionData }),
}
);

const addedOptionData = await addOptionResponse.json();
console.log(`✅ Successfully added option "${name}" to field ${fieldId} under project ${numericProjectId}`, addedOptionData);

return addedOptionData;

} catch (error) {
console.error("❌ Error while creating project-scoped option:", error);
return { code: 2000, message: "Error while creating project-scoped option." };
}
});



1 answer

0 votes
Ahmed Arslan
Contributor
June 27, 2025

Hi @Vitheya Monikha

The issues seems to be permission. I see you're using read:jira-work. Have you tried using write:jira-work also. For more details, you can check this doc

Let me know how it goes.

Cheers,
Ahmed

Suggest an answer

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

Atlassian Community Events