Forums

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

How to add a Scope for Specific project using forge API's

Vitheya Monikha July 3, 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
Mohanraj Thangamuthu
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
July 13, 2025

Hello, Good day. You can raise your query in Atlassian developer community at https://community.developer.atlassian.com/

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