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." };
}
});
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
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.