enigmare/v2-crawler
1904
1{"id":"doc-multitenancy-480080f3","source":"documentation","title":"Multitenancy","url":"https://developer.atlassian.com/developer-guide/multitenancy/","text":"Example:\n```text\n// app-descriptor.json\n{\n ...\n \"lifecycle\": {\n \"installed\": \"/installed\"\n }\n ...\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://docs.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"eventType\": \"installed\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.158Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":36,"estimatedTokens":159}}2{"id":"doc-interpret_the_generate_license_api_output-93bc03a7","source":"documentation","title":"Interpret the generate License API output","url":"https://developer.atlassian.com/platform/marketplace/understanding-the-generated-license-api-support/","text":"Example:\n```text\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-lib</artifactId>\n <version>${upm.license.compatibility.version}</version>\n <scope>compile</scope> <!-- intentionally compile scoped -->\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-plugin</artifactId>\n <version>${upm.license.compatibility.version}</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>licensing-api</artifactId>\n <version>${upm.license.compatibility.version}</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>upm-api</artifactId>\n <version>${upm.license.compatibility.version}</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.sal</groupId>\n <artifactId>sal-api</artifactId>\n <version>2.4.0</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>org.springframework.osgi</groupId>\n <artifactId>spring-osgi-core</artifactId>\n <version>1.1.3</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>commons-lang</groupId>\n <artifactId>commons-lang</artifactId>\n <version>2.4</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.templaterenderer</groupId>\n <artifactId>atlassian-template-renderer-api</artifactId>\n <version>1.0.5</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>maven-jira-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n <instructions>\n <Private-Package>\n com.atlassian.upm.license.storage.lib*\n </Private-Package>\n <DynamicImport-Package>\n com.atlassian.upm.api.license;version=\"2.0.1\",\n com.atlassian.upm.api.license.entity;version=\"2.0.1\",\n com.atlassian.upm.api.util;version=\"2.0.1\",\n com.atlassian.upm.license.storage.plugin;version=\"${upm.license.compatibility.version}\"\n </DynamicImport-Package>\n </instructions>\n <productVersion>${jira.version}</productVersion>\n <productDataVersion>${jira.version}</productDataVersion>\n </configuration>\n</plugin>\n```\n\nExample:\n```text\n<plugin>\n <!-- Include the License Storage app artifact such that it can be found on the classpath when we need to install it -->\n <artifactId>maven-dependency-plugin</artifactId>\n <executions>\n <execution>\n <id>copy-storage-plugin</id>\n <phase>process-resources</phase>\n <goals>\n <goal>copy-dependencies</goal>\n </goals>\n <configuration>\n <outputDirectory>${project.build.outputDirectory}</outputDirectory>\n <includeArtifactIds>plugin-license-storage-plugin</includeArtifactIds>\n <stripVersion>true</stripVersion>\n </configuration>\n </execution>\n </executions>\n</plugin>\n```\n\nExample:\n```text\n<plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>maven-jira-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n <instructions>\n ...\n </instructions>\n <productVersion>${jira.version}</productVersion>\n <productDataVersion>${jira.version}</productDataVersion>\n <bundledArtifacts>\n <bundledArtifact>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>atlassian-universal-plugin-manager-plugin</artifactId>\n <version>1.6.3</version>\n </bundledArtifact>\n <bundledArtifact>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-plugin</artifactId>\n <version>${upm.license.compatibility.version}</version>\n </bundledArtifact>\n </bundledArtifacts>\n </configuration>\n</plugin>\n```\n\nExample:\n```text\n<param name=\"atlassian-licensing-enabled\">true</param>\n```\n\nExample:\n```text\n<component-import key=\"pluginAccessor\" interface=\"com.atlassian.plugin.PluginAccessor\"/>\n<component-import key=\"pluginController\" interface=\"com.atlassian.plugin.PluginController\"/>\n<component-import key=\"txTemplate\" interface=\"com.atlassian.sal.api.transaction.TransactionTemplate\"/>\n<component-import key=\"applicationProperties\" interface=\"com.atlassian.sal.api.ApplicationProperties\"/>\n<component-import key=\"templateRenderer\" interface=\"com.atlassian.templaterenderer.TemplateRenderer\"/>\n<component-import key=\"pluginSettingsFactory\" interface=\"com.atlassian.sal.api.pluginsettings.PluginSettingsFactory\"/>\n<component-import key=\"loginUriProvider\" interface=\"com.atlassian.sal.api.auth.LoginUriProvider\"/>\n<component-import key=\"userManager\" interface=\"com.atlassian.sal.api.user.UserManager\"/>\n<component-import key=\"i18nResolver\" interface=\"com.atlassian.sal.api.message.I18nResolver\"/>\n<component key=\"pluginLicenseStoragePluginInstaller\" class=\"com.atlassian.upm.license.storage.lib.PluginLicenseStoragePluginInstaller\"/>\n<component key=\"thirdPartyPluginLicenseStorageManager\" class=\"com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManagerImpl\"/>\n<component key=\"atlassianMarketplaceUriFactory\" class=\"com.atlassian.upm.license.storage.lib.AtlassianMarketplaceUriFactoryImpl\"/>\n```\n\nExample:\n```text\n<servlet name=\"License Servlet\" i18n-name-key=\"license-servlet.name\" key=\"license-servlet\" class=\"com.example.plugins.tutorial.servlet.LicenseServlet\">\n <description key=\"license-servlet.description\">The License Servlet Plugin</description>\n <url-pattern>/com.example.plugins.tutorial.plugin-license-compatibility-tutorial/license</url-pattern>\n</servlet>\n<servlet name=\"License Hello World Servlet\" i18n-name-key=\"license-hello-world-servlet.name\" key=\"license-hello-world-servlet\" class=\"com.example.plugins.tutorial.servlet.LicenseHelloWorldServlet\">\n <description key=\"license-hello-world-servlet.description\">The License Hello World Servlet Plugin</description>\n <url-pattern>/com.example.plugins.tutorial.plugin-license-compatibility-tutorial/licensehelloworld</url-pattern>\n</servlet>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.160Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":168,"estimatedTokens":1620}}3{"id":"doc-forge_cli-c1d597d0","source":"documentation","title":"Forge CLI","url":"https://developer.atlassian.com/platform/forge/cli-reference/","text":"Example:\n```text\nnpm i -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nforge login\n```\n\nExample:\n```text\nnpm uninstall -g @forge/cli\nnpm i -g @forge/cli@latest\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.160Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":23,"estimatedTokens":50}}4{"id":"doc-atlassian_developer_stories_atlassian-e5caf6b1","source":"documentation","title":"Atlassian Developer Stories | Atlassian","url":"https://developer.atlassian.com/grow-your-business/","text":"Close View this page in your language? All languages Choose your language 中文 Deutsch English Español Français Italiano 한국어 Nederlands 日本語 Português Pусский Polski\n\nGet it free Products Featured Developers Product Managers IT professionals Business Teams Leadership Teams Featured Developers Product Managers IT professionals Business Teams Leadership Teams See all apps Featured FEATURED APPS Jira Flexible project management Confluence Collaborative knowledge workspace Jira Service Management High-velocity service delivery ATLASSIAN COLLECTIONS Supercharge teamwork seamlessly Jira • Confluence • Loom • Rovo Optimize strategy and outcomes confidently Focus • Talent • Align Deliver service at high-velocity Jira Service Management • Customer Service Management • Assets • Rovo Ship high-quality software fast Rovo Dev • DX • Pipelines • Bitbucket Build products with confidence Jira Product Discovery • Feedback • Rovo POWERED BY Rovo AI-powered apps – driven by your team's knowledge. Developers Jira Flexible project management Bitbucket Source code and CI/CD Rovo Dev Agentic AI for developers Pipelines Scalable CI/CD automation DX Measure productivity and AI impact Ship high-quality software fast Rovo Dev • DX • Pipelines • Bitbucket Rovo AI-powered apps – driven by your team's knowledge. Product Managers Jira Product Discovery Capture & prioritize ideas Jira Flexible project management Confluence Knowledge, all in one place Supercharge teamwork seamlessly Jira • Confluence • Loom • Rovo Build products with confidence Jira Product Discovery • Feedback • Rovo Rovo AI-powered apps – driven by your team's knowledge. IT professionals Jira Service Management High-velocity service delivery Guard Enhanced cloud security Rovo AI-powered apps – driven by your team's knowledge. Business Teams Jira Flexible project management Confluence Knowledge, all in one place Trello Capture and organize your tasks Loom Quick, async video updates Jira Service Management High-velocity service delivery Customer Service Management Customer experiences reimagined Supercharge teamwork seamlessly Jira • Confluence • Loom • Rovo Rovo AI-powered apps – driven by your team's knowledge. Leadership Teams Focus Enterprise-scale strategic planning Talent Knowledge workforce planning Jira Align Enterprise-wide work planning & value Optimize strategy and outcomes confidently Focus • Talent • Align Rovo AI-powered apps – driven by your team's knowledge. Back Solutions Solutions By use case Team collaboration Strategy and planning Service management Software development By team Software Marketing IT Product By size Enterprise Small business Startup Non-profit By industry Retail Telecommunications Professional services Government Rovo AI-powered apps – driven by your team's knowledge. Back Why Atlassian Why Atlassian System of Work Atlassian's blueprint for how teams work together Ecosystem New Connect tools, teams, and agents into one intelligent system of work Marketplace Connect thousands of apps to your Atlassian products Customers Case studies & stories powered by teamwork FedRAMP Compliant solutions for the public sector Resilience Enterprise-grade & highly performant infrastructure Platform Our deeply integrated, reliable & secure platform Trust center Ensure your data’s security, compliance & availability Back Resources Resources Customer Support Ask questions, report bugs & give us feedback Templates New Ready-made workflows for every team. Find Partners Consulting, training & product customization support Atlassian Ascend Resources and support for your transformation Community Learn, connect and grow with the Atlassian Community support General inquiries Product Advice Partner support Enterprise support Technical Support Pricing and Billing Developer support Purchasing & Licensing Resources Project Management Agile Atlassian Learning Get Started Project Collaboration Team Playbook Product Documentation Back Enterprise More + Less - Get it free Jira Flexible project management Jira Service Management High-velocity service delivery Confluence Knowledge, all in one place See all products Get it free Back Get it free\n\nAtlassian Developer Open and close the navigation menu Developer blog Developer community Get started Developer blog Developer community You're in good company with Atlassian developers We’re 25,000 + developers strong. Join us. See how we’ve helped businesses grow Cloud Find out how Elements won first place in Codegeist in 2021, and tips for efficiency in using constraints to your advantage. Read the story Cloud Read how the new UI Modifications feature unblocked Deviniti’s journey to Cloud. Read the story Oliver Rolle Cloud Learn how Oliver’s experience gives him a unique edge when it comes to taking on a role as an Atlassian Marketplace Partner. Read the story Cloud Learn about Emre’s experience on the Marketplace, and understand why he made the decision to to move Time in Status from Server to Cloud. Read the story Cloud Learn how Amaresh launched his business on the Atlassian Marketplace with the support of Atlassian Ventures. Read the story Cloud Zephyr’s agile test management products use the Atlassian Marketplace to scale. Read the story Cloud Learn how Tempo built an ecosystem within the Atlassian ecosystem. Read the story Server From $30K to $1M in 18 months, Xpand IT continues to do more on Atlassian. Read the story Server Read about Oliver’s recent experience transitioning his Connect app to leverage Atlassian’s newest app development platform, Forge. Read the story Cloud Learn how the Atlassian Cloud helped Adaptavist become profitable, sooner. Read the story Cloud eazyBI scales their business intelligence application with a Marketplace app. Read the story\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.161Z","totalSectionsIncluded":3,"totalCodeBlocksIncluded":0,"totalLines":7,"estimatedTokens":1440}}5{"id":"doc-functions-3c0de1dd","source":"documentation","title":"Functions","url":"https://developer.atlassian.com/platform/forge/runtime-reference/","text":"Example:\n```text\napp:\n id: <app-id>\n runtime:\n name: nodejs24.x | nodejs22.x | nodejs20.x\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.162Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":15,"estimatedTokens":35}}6{"id":"doc-functions_introduction-545e70ae","source":"documentation","title":"Functions introduction","url":"https://developer.atlassian.com/platform/forge/function-reference/index/","text":"Example:\n```bash\nnpm i @forge/api@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n/src\n /functions\n /index.js\n```\n\nExample:\n```javascript\nexport const handler = (...args) => {\n console.log(args);\n // Do something\n}\n```\n\nExample:\n```text\nmodules:\n function:\n - key: my-function\n handler: index.handler\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: example\n function: my-function # References the function with key: my-function\n interval: hour # Runs hourly\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.163Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":44,"estimatedTokens":131}}7{"id":"doc-web_triggers-06f36b98","source":"documentation","title":"Web triggers","url":"https://developer.atlassian.com/platform/forge/runtime-reference/web-trigger/","text":"Example:\n```javascript\nexport const trigger = (payload, context) => {\n return {\n statusCode: 200,\n headers: {},\n body: \"Hello, world!\",\n };\n};\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: example\n function: my-function\n urlFormat: v2\n response:\n type: dynamic\n function:\n - key: my-function\n handler: index.trigger\n```\n\nExample:\n```json\n{\n \"method\": \"POST\",\n \"headers\": {\n \"Accept\": [\"*/*\"],\n \"accept-encoding\": [\"gzip, deflate\"],\n \"content-length\": [\"71\"],\n \"Connection\": [\"keep-alive\"],\n \"Host\": [\"localhost:8080\"],\n \"Cache-Control\": [\"no-cache\"],\n \"Content-Type\": [\"text/plain\"]\n },\n \"body\": \"{\\n\\t\\\"hello\\\": 1,\\n\\t\\\"test\\\": [\\\"foo\\\", \\\"bar\\\"],\\n\\t\\\"foo\\\": {\\n\\t\\t\\\"bar\\\": \\\"hello\\\"\\n\\t}\\n}\",\n \"path\": \"/x1/XUBR5WnG2Hk2V52APDdGaRSDm/hello/world\",\n \"userPath\": \"/hello/world\",\n \"queryParameters\": {\n \"apples\": [\"red\", \"green\"],\n \"grapes\": [\"green\"]\n }\n}\n```\n\nExample:\n```json\n{\n \"method\": \"POST\",\n \"headers\": {\n \"Accept\": [\"*/*\"],\n \"accept-encoding\": [\"gzip, deflate\"],\n \"content-length\": [\"71\"],\n \"Connection\": [\"keep-alive\"],\n \"Host\": [\"localhost:8080\"],\n \"Cache-Control\": [\"no-cache\"],\n \"Content-Type\": [\"text/plain\"]\n },\n \"body\": \"{\\n\\t\\\"hello\\\": 1,\\n\\t\\\"test\\\": [\\\"foo\\\", \\\"bar\\\"],\\n\\t\\\"foo\\\": {\\n\\t\\t\\\"bar\\\": \\\"hello\\\"\\n\\t}\\n}\",\n \"path\": \"/x1/XUBR5WnG2Hk2V52APDdGaRSDm\",\n \"userPath\": \"\",\n \"queryParameters\": {\n \"apples\": [\"red\", \"green\"],\n \"grapes\": [\"green\"]\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.163Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":78,"estimatedTokens":385}}8{"id":"doc-evaluation_insights-1c01175c","source":"documentation","title":"Evaluation insights","url":"https://developer.atlassian.com/platform/marketplace/vendor-reports-data-insights/","text":"Example:\n```json\n{\n \"_links\": {},\n \"licenses\": [\n {\n \"addonLicenseId\": \"121222\",\n \"hostLicenseId\": \"112434\",\n \"licenseId\": \"SEN-112434\",\n \"addonKey\": \"com.atlassian.marketplace.app\",\n \"addonName\": \"Marketplace App\",\n \"hosting\": \"Cloud\",\n \"lastUpdated\": \"2019-10-01\",\n \"licenseType\": \"COMMERCIAL\",\n \"maintenanceStartDate\": \"2020-09-22\",\n \"maintenanceEndDate\": \"2020-12-18\",\n \"status\": \"active\",\n \"tier\": \"Subscription\",\n \"contactDetails\": {\n \"company\": \"S.T.A.R. Labs\",\n \"country\": \"United States of America\",\n \"region\": \"Americas\",\n \"technicalContact\": {\n \"email\": \"barryallen@starlabs.com\",\n \"name\": \"Barry Allen\"\n }\n },\n \"evaluationOpportunitySize\": \"NA\",\n \"evaluationLicense\": \"18762269\",\n \"daysToConvertEval\": \"0\",\n \"evaluationStartDate\": \"2020-09-22\",\n \"evaluationEndDate\": \"2020-11-19\",\n \"evaluationSaleDate\": \"2020-11-19\"\n }\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.169Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":45,"estimatedTokens":261}}9{"id":"doc-sales_promotions-a2807056","source":"documentation","title":"Sales promotions","url":"https://developer.atlassian.com/platform/marketplace/sales-promotions/","text":"Example:\n```bash\nhttps://promo.atlassian.com/9SO6RI\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n9SO6RI\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.173Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":31}}10{"id":"doc-penetration_test_scoping_template_for_marketplac-4844ae56","source":"documentation","title":"Penetration Test Scoping Template for Marketplace Apps","url":"https://developer.atlassian.com/platform/marketplace/pentest-scoping-template/","text":"Example:\n```text\n===============================================================================\n PENETRATION TEST SCOPING TEMPLATE\n===============================================================================\n\n** CONTACT INFORMATION **\n\n1. Best point(s) of contact for technical information about the product?\n Name: [ENTER NAME HERE]\n Role: [ENTER ROLE HERE]\n \n2. What is the best way to contact them?\n Email: [ENTER EMAIL HERE]\n Phone: [ENTER PHONE HERE]\n Preferred method: [EMAIL/PHONE/SLACK/OTHER]\n\n** TIMING AND SCHEDULING **\n\n3. Do you have any timing requests for when we shouldn't do the pentest?\n Off-limit date ranges: [ENTER DATES OR \"NONE\"]\n \n4. Is there a specific date you would like the testing completed by?\n Target completion date: [ENTER DATE OR \"FLEXIBLE\"]\n Hard deadline (if any): [ENTER DATE OR \"NONE\"]\n\n** TECHNICAL DETAILS **\n\n5. What framework does this app utilize?\n [DELETE OTHERS: Forge / Connect / Connect on Forge]\n \n Additional details: [ENTER FRAMEWORK DETAILS OR \"NONE\"]\n\n** APPLICATION OVERVIEW **\n\n6. Please provide:\n\n • High-level summary of app's purpose and core functions:\n [DESCRIBE YOUR APP'S MAIN PURPOSE AND KEY FEATURES]\n \n • User guides, API documentation, or other helpful resources:\n [PROVIDE LINKS TO DOCUMENTATION OR \"NOT AVAILABLE\"]\n \n • App features and expected workflows:\n [DESCRIBE HOW USERS INTERACT WITH YOUR APP]\n \n • Public-facing URLs (if applicable):\n [ENTER URLS OR \"NOT APPLICABLE\"]\n \n • Level of data the app processes or stores:\n [DESCRIBE DATA TYPES AND SENSITIVITY LEVEL]\n \n • AI functionality description (if applicable):\n [DESCRIBE AI FEATURES OR \"NO AI FUNCTIONALITY\"]\n\n** EXTERNAL INTEGRATIONS **\n\n7. Any custom web triggers, outgoing webhook URLs, or external integrations?\n [DESCRIBE INTEGRATIONS OR \"NONE\"]\n\n8. List of external domains the app interacts with:\n [LIST EXTERNAL DOMAINS OR \"NONE\"]\n\n** SECURITY HISTORY **\n\n9. Has a security review/penetration test been performed before?\n [DELETE ONE: YES / NO]\n \n If yes, when: [ENTER DATE OR \"NOT APPLICABLE\"]\n\n10. Provide a link or embed the most recent documents if possible:\n [ENTER LINK OR \"NOT AVAILABLE\"]\n\n11. Previous findings for this App:\n [DESCRIBE PREVIOUS FINDINGS OR \"NOT APPLICABLE\"]\n\n12. If previously tested, what major changes/features developed since then?\n [DESCRIBE CHANGES OR \"NOT APPLICABLE\"]\n\n13. If not previously tested, what would a hacker be interested in targeting?\n [DESCRIBE POTENTIAL TARGETS OR \"NOT APPLICABLE\"]\n\n** RISK ASSESSMENT **\n\n14. Are there particular areas you think are risky or need deep-diving?\n [DESCRIBE RISKY AREAS OR \"NO SPECIFIC CONCERNS\"]\n\n** TECHNICAL ACCESS AND TESTING **\n\n15. Application routes list (or link to code mappings):\n [PROVIDE ROUTES LIST OR \"WILL BE PROVIDED SEPARATELY\"]\n\n16. Are there multiple user privilege levels, roles, or paywall levels?\n [DELETE ONE: YES / NO]\n \n If yes, detail functionality differences between levels:\n [DESCRIBE DIFFERENT ACCESS LEVELS OR \"NOT APPLICABLE\"]\n\n17. How can we get a non-prod environment for testing and log access?\n [DESCRIBE ACCESS PROCESS]\n\n18. Are there any access prerequisites or restrictions?\n [DESCRIBE RESTRICTIONS OR \"NONE\"]\n\n** ADDITIONAL NOTES **\n\n19. Any further notes or special considerations?\n [ENTER ADDITIONAL NOTES OR \"NONE\"]\n\n===============================================================================\nTemplate completed by: [ENTER YOUR NAME] Date: [ENTER DATE]\n===============================================================================\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.177Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":121,"estimatedTokens":919}}11{"id":"doc-marketplace_segment_integration-4db36264","source":"documentation","title":"Marketplace segment integration","url":"https://developer.atlassian.com/platform/marketplace/marketplace-integration-with-segment/","text":"Example:\n```text\n{\n \"context\": {\n \"library\": {\n \"name\": \"analytics-java\",\n \"version\": \"3.1.3\"\n }\n },\n \"event\": \"MPAC User event\",\n \"integrations\": {},\n \"messageId\": \"99e491a1-d2c7-4b68-ab97-cc7c3497e011\",\n \"originalTimestamp\": \"2022-03-29T09:39:55.093Z\",\n \"properties\": {\n \"action\": \"clicked\",\n \"actionSubjectId\": \"https://rieckermann.atlassian.net/\",\n \"anonymousId\": \"89b60ff131fa6d32b85f0893a8670fbc2a9608d0\",\n \"eventTime\": \"2022-03-29T09:11:31.147Z\",\n \"eventType\": \"Track\",\n \"label\": \"Start Free Trial\",\n \"partnerId\": \"1211971\",\n \"pluginId\": \"1219125\",\n \"partnerAnonymousId\": \"87s12b3431fa6d32b85f0893a8670f8a88s2235dd0\"\n \"pluginKey\": \"com.infosysta.jira.mobile-for-jira-service-desk-portal\",\n \"queryParamsJson\": {\n \"hosting\": \"cloud\",\n \"tab\": \"overview\"\n },\n \"referrerUrl\": \"https://www.google.com/\",\n \"url\": \"https://marketplace.atlassian.com/apps/1219125/jsmobile-service-management-portal?hosting=cloud&tab=overview\",\n \"userId\": \"0efffdf9a0db5f80d0927f9c0d269e8089c72df8\"\n },\n \"receivedAt\": \"2022-03-29T09:39:58.641Z\",\n \"sentAt\": \"2022-03-29T09:39:57.093Z\",\n \"timestamp\": \"2022-03-29T09:39:56.641Z\",\n \"type\": \"track\",\n \"userId\": \"89b60ff131fa6d32b85f0893a8670fbc2a9608d0\",\n \"writeKey\": \"5MveUGRM2pTALAYGCNygBu8Klw9UFRBP\"\n }\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"context\": {\n \"library\": {\n \"name\": \"analytics-java\",\n \"version\": \"3.1.3\"\n }\n },\n \"event\": \"MPAC Install event\",\n \"integrations\": {},\n \"messageId\": \"32e377f9-d6c0-44d2-a986-7b2c12498558\",\n \"originalTimestamp\": \"2022-03-29T09:35:10.582Z\",\n \"properties\": {\n \"anonymousId\": \"cf9692e7-83b4-4f41-967c-a5b3fa988c27\",\n \"eventType\": \"Track\",\n \"partnerId\": \"1213750\",\n \"pluginId\": \"1216784\",\n \"pluginKey\": \"dropbox-jira\"\n },\n \"receivedAt\": \"2022-03-29T09:35:15.377Z\",\n \"sentAt\": \"2022-03-29T09:35:12.581Z\",\n \"timestamp\": \"2022-03-29T09:35:13.378Z\",\n \"type\": \"track\",\n \"userId\": \"cf9692e7-83b4-4f41-967c-a5b3fa988c27\",\n \"writeKey\": \"5MveUGRM2pTALAYGCNygBu8Klw9UFRBP\"\n }\n```\n\nExample:\n```text\n{\n \"context\": {\n \"library\": {\n \"name\": \"analytics-java\",\n \"version\": \"3.1.3\"\n }\n },\n \"event\": \"MPAC App request event\",\n \"integrations\": {},\n \"messageId\": \"903b89f3-f5de-48bf-8c08-db02b035f2db\",\n \"originalTimestamp\": \"2022-03-29T09:35:52.997Z\",\n \"properties\": {\n \"anonymousId\": \"c2b5e0e5-c919-4330-9cb6-c1db82cf87e1\",\n \"eventType\": \"Track\",\n \"partnerId\": \"350257\",\n \"pluginId\": \"350288\",\n \"pluginKey\": \"com.lucidchart.confluence.plugins.lucid-confluence\"\n },\n \"receivedAt\": \"2022-03-29T09:35:56.509Z\",\n \"sentAt\": \"2022-03-29T09:35:54.997Z\",\n \"timestamp\": \"2022-03-29T09:35:54.509Z\",\n \"type\": \"track\",\n \"userId\": \"c2b5e0e5-c919-4330-9cb6-c1db82cf87e1\",\n \"writeKey\": \"5MveUGRM2pTALAYGCNygBu8Klw9UFRBP\"\n }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.179Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":106,"estimatedTokens":710}}12{"id":"doc-ecoscanner-3476f242","source":"documentation","title":"Ecoscanner","url":"https://developer.atlassian.com/platform/marketplace/ecoscanner/","text":"Example:\n```text\n100.25.61.160\n3.214.98.112 \n35.172.132.4 \n3.221.51.2 \n3.217.215.43 \n35.174.235.12\n34.211.197.111/32\n50.112.197.134/32\n52.42.55.113/32\n54.68.221.42/32\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.197Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":21,"estimatedTokens":54}}13{"id":"doc-submit_your_data_center_app_to_atlassian_marketp-052afcb6","source":"documentation","title":"Submit your Data Center app to Atlassian Marketplace","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-submitting-your-app/","text":"Example:\n```text\n<param name=\"atlassian-data-center-status\">compatible</param>\n<param name=\"atlassian-data-center-compatible\">true</param>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"atlassian-data-center-status\">compatible</param>\n <param name=\"atlassian-data-center-compatible\">true</param>\n</plugin-info>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.217Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":24,"estimatedTokens":136}}14{"id":"doc-atlassian_rest_api_policy-db3fe099","source":"documentation","title":"Atlassian REST API policy","url":"https://developer.atlassian.com/platform/marketplace/atlassian-rest-api-policy/","text":"Example:\n```text\nDeprecation: @1769990400\nSunset: Mon, 2 Feb 2026 00:00:00 GMT\nLink: <https://developer.atlassian.com/cloud/jira/platform/changelog/#CHANGE-2567>; rel=\"deprecation\"; type=\"text/html\"\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.219Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":14,"estimatedTokens":61}}15{"id":"doc-prepare_your_data_center_app_to_comply_with_secu-24442885","source":"documentation","title":"Prepare your Data Center app to comply with secure endpoint defaults","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-platform-7-preparing-for-secure-endpoints/","text":"Example:\n```text\n<servlet key=\"exampleServlet\" class=\"com.atlassian.plugins.servlet.scope.ExampleServlet\">\n <url-pattern>/example</url-pattern>\n</servlet>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npublic class ExampleServlet extends HttpServlet {\n @Override\n protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {\n ...\n }\n}\n```\n\nExample:\n```text\n@AnonymousSiteAccess\npublic class ExampleServlet extends HttpServlet {\n @Override\n protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {\n ...\n }\n \n @Override\n @SystemAdminOnly\n protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {\n ...\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.219Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":41,"estimatedTokens":205}}16{"id":"doc-alternate_deployment_model_for_server_apps-5d0b7e96","source":"documentation","title":"Alternate deployment model for server apps","url":"https://developer.atlassian.com/platform/marketplace/alternate-deployment-model-for-server-apps/","text":"Example:\n```text\n<component key=\"pluginLicenseStoragePluginInstaller\" class=\"com.atlassian.upm.license.storage.lib.PluginLicenseStoragePluginInstaller\"/>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<bundledArtifact>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-plugin</artifactId>\n <version>${upm.license.compatibility.version}</version>\n</bundledArtifact>\n```\n\nExample:\n```text\n<plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>maven-jira-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n\n ...\n\n <!-- Specify what to bundle in the OBR -->\n <pluginDependencies>\n <pluginDependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-plugin</artifactId>\n </pluginDependency>\n </pluginDependencies>\n\n ...\n\n </configuration>\n</plugin>\n```\n\nExample:\n```text\ncom.atlassian.upm.api.license;version=\"2.0.1\",\ncom.atlassian.upm.api.license.entity;version=\"2.0.1\",\ncom.atlassian.upm.api.util;version=\"2.0.1\",\ncom.atlassian.upm.license.storage.plugin;version=\"${upm.license.compatibility.version}\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.220Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":54,"estimatedTokens":313}}17{"id":"doc-automating_part_of_your_platform_7_upgrades_with-4e813691","source":"documentation","title":"Automating part of your Platform 7 upgrades with Openrewrite","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-platform-7-openrewrite/","text":"Example:\n```text\n<profile>\n <id>openrewrite</id>\n <build>\n <plugins>\n <plugin>\n <groupId>org.openrewrite.maven</groupId>\n <artifactId>rewrite-maven-plugin</artifactId>\n <version>5.15.4</version> <!-- This version can be newer if you want -->\n <configuration>\n <!-- This tag ensures that we first do a dry run on all modules, before doing a real run. -->\n <failOnDryRunResults>true</failOnDryRunResults>\n </configuration>\n <dependencies>\n <dependency>\n <groupId>org.openrewrite.recipe</groupId>\n <artifactId>rewrite-migrate-java</artifactId>\n <!-- Ensure this uses the latest version, especially when running standard recipes -->\n <version>2.8.0</version>\n </dependency>\n <dependency>\n <groupId>com.atlassian.platform</groupId>\n <artifactId>openrewrite-recipes</artifactId>\n <!-- Replace this with the latest version of the openrewrite-recipes dependency -->\n <version>${platform.version}</version>\n </dependency>\n </dependencies>\n </plugin>\n </plugins>\n </build>\n</profile>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nmvn clean install -DskipTests\n```\n\nExample:\n```bash\nmvn -Popenrewrite rewrite:run -Drewrite.activeRecipes=com.package.Recipe\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.220Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":51,"estimatedTokens":344}}18{"id":"doc-add_watching_and_rating_controls_to_server_apps-1c96fd0e","source":"documentation","title":"Add watching and rating controls to server apps","url":"https://developer.atlassian.com/platform/marketplace/adding-watching-and-rating-controls-to-server-apps/","text":"Example:\n```text\n<dependencies>\n ...\n <dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>plugin-license-storage-lib</artifactId>\n <version>${upm.license.compatibility.version}</version>\n </dependency>\n ...\n</dependencies>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport com.atlassian.upm.license.storage.lib.AtlassianMarketplaceUriFactory;\n```\n\nExample:\n```text\n...\nif (uriFactory.canReviewPlugin())\n{\n // make sure review form is only shown when uriFactory.canReviewPlugin() returns true\n ...\n URI reviewUri = uriFactory.getReviewPluginUri(4 /*stars*/, some(\"This is my review message\"));\n PostMethod post = new PostMethod(reviewUri);\n post.setRequestHeader(\"Content-Type\",\"application/vnd.atl.plugins.available.plugin+json\")\n httpClient.execute(post);\n ...\n}\n...\n```\n\nExample:\n```text\n...\nif (uriFactory.canWatchPlugin())\n{\n // make sure watch link is only shown when uriFactory.canWatchPlugin() returns true\n ...\n // to watch an app\n URI watchUri = uriFactory.getWatchPluginUri();\n PostMethod post = new PostMethod(watchUri);\n post.setRequestHeader(\"Content-Type\",\"application/vnd.atl.plugins.available.plugin+json\")\n httpClient.execute(post);\n ...\n \n // to unwatch an app\n URI unwatchUri = uriFactory.getWatchPluginUri();\n DeleteMethod delete = new DeleteMethod(unwatchUri);\n httpClient.execute(delete);\n}\n...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.221Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":63,"estimatedTokens":361}}19{"id":"doc-add_licensing_support_to_server_apps-7849cb9c","source":"documentation","title":"Add licensing support to server apps","url":"https://developer.atlassian.com/platform/marketplace/adding-licensing-support-to-server-apps/","text":"Example:\n```bash\ncd atlastutorial/\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\natlas-create-jira-plugin\n```\n\nExample:\n```bash\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 21.863 s\n[INFO] Finished at: 2016-11-17T15:18:35+10:00\n[INFO] Final Memory: 22M/314M\n[INFO] ------------------------------------------------------------------------\n```\n\nExample:\n```bash\ncd jiraLicensedPlugin/\n```\n\nExample:\n```bash\natlas-run\n```\n\nExample:\n```text\n``` bash\n[INFO] jira started successfully in 82s at http://localhost:2990/jira\n[INFO] Type Ctrl-D to shutdown gracefully\n[INFO] Type Ctrl-C to exit\n```\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>licensing-api</artifactId>\n <version>2.21.4</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>upm-api</artifactId>\n <version>2.21</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>javax.servlet</groupId>\n <artifactId>javax.servlet-api</artifactId>\n <version>3.0.1</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n<param name=\"atlassian-licensing-enabled\">true</param>\n```\n\nExample:\n```text\n<servlet name=\"License Servlet\" class=\"com.atlassian.tutorial.LicenseServlet\" key=\"license-servlet\">\n <description>Servlet which serves the raw license string</description>\n <url-pattern>/my/license</url-pattern>\n</servlet>\n```\n\nExample:\n```text\npackage com.atlassian.tutorial;\n\nimport com.atlassian.plugin.spring.scanner.annotation.component.Scanned;\nimport com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;\nimport com.atlassian.upm.api.license.PluginLicenseManager;\n\nimport javax.inject.Inject;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n@Scanned\npublic class LicenseServlet extends HttpServlet {\n @ComponentImport\n private final PluginLicenseManager pluginLicenseManager;\n\n @Inject\n public LicenseServlet(PluginLicenseManager pluginLicenseManager) {\n this.pluginLicenseManager = pluginLicenseManager;\n }\n\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n PrintWriter w = resp.getWriter();\n if (pluginLicenseManager.getLicense().isDefined()) {\n w.println(pluginLicenseManager.getLicense().get().getRawLicense());\n } else {\n w.println(\"License missing!\");\n }\n w.close();\n }\n}\n```\n\nExample:\n```javascript\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.222Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":131,"estimatedTokens":826}}20{"id":"doc-prepare_your_data_center_app_for_2025_security_a-62c1313f","source":"documentation","title":"Prepare your Data Center app for 2025 security and usability updates","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-platform-8/","text":"Example:\n```text\n@POST\n@ScopesAllowed(requiredScope = \"WRITE\")\npublic void createEntity(...) {}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.223Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":14,"estimatedTokens":35}}21{"id":"doc-data_center_app_performance_toolkit_user_guide_f-82bada35","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Crowd","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-crowd/","text":"Example:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\napplication_hostname: test_crowd_instance.atlassian.com # Crowd DC hostname without protocol and port e.g. test-crowd.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 4990, etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /crowd # Default postfix value for TerraForm deployment url like `http://a1234-54321.us-east-2.elb.amazonaws.com/crowd`\n admin_login: admin\n admin_password: admin\n application_name: crowd\n application_password: 1111\n load_executor: jmeter \n concurrency: 1000 # number of concurrent threads to authenticate random users\n test_duration: 45m\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh crowd.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py performance_profile.yml\n```\n\nExample:\n```text\n# 1 node scenario parameters\n# ramp-up: 20s # time to spin all concurrent threads\n# total_actions_per_hour: 180000 # number of total JMeter actions per hour\n\n# 2 nodes scenario parameters\n ramp-up: 10s # time to spin all concurrent threads\n total_actions_per_hour: 360000 # number of total JMeter actions per hour\n\n# 4 nodes scenario parameters\n# ramp-up: 5s # time to spin all concurrent threads\n# total_actions_per_hour: 720000 # number of total JMeter actions per hour\n```\n\nExample:\n```text\n# 1 node scenario parameters\n# ramp-up: 20s # time to spin all concurrent threads\n# total_actions_per_hour: 180000 # number of total JMeter actions per hour\n\n# 2 nodes scenario parameters\n# ramp-up: 10s # time to spin all concurrent threads\n# total_actions_per_hour: 360000 # number of total JMeter actions per hour\n\n# 4 nodes scenario parameters\nramp-up: 5s # time to spin all concurrent threads\ntotal_actions_per_hour: 720000 # number of total JMeter actions per hour\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py scale_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.225Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":95,"estimatedTokens":790}}22{"id":"doc-server_app_licensing_validation_rules-4d6a59b3","source":"documentation","title":"Server app licensing validation rules","url":"https://developer.atlassian.com/platform/marketplace/server-app-license-validation-rules/","text":"Example:\n```text\nif (licenseManager.getLicense().isDefined())\n{\n PluginLicense license = licenseManager.getLicense().get();\n if (license.getError().isDefined())\n {\n // handle license error scenario\n // (e.g., expiration or user mismatch) \n }\n else\n {\n // handle valid license scenario\n }\n} \nelse\n{\n // handle unlicensed scenario\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.225Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":28,"estimatedTokens":104}}23{"id":"doc-data_center_app_performance_toolkit_user_guide_f-9a296926","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Jira","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-jira/","text":"Example:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nbzt jira.yml\n```\n\nExample:\n```text\n# def test_1_selenium_custom_action(webdriver, datasets, screen_shots):\n# app_specific_action(webdriver, datasets)\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\napplication_hostname: test_jira_instance.atlassian.com # Jira DC hostname without protocol and port e.g. test-jira.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 2990, etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /jira # e.g. /jira for TerraForm deployment url like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. Leave this value blank for url without postfix.\n admin_login: admin\n admin_password: admin\n load_executor: jmeter # jmeter and locust are supported. jmeter by default.\n concurrency: 200 # number of concurrent virtual users for jmeter or locust scenario\n test_duration: 45m\n ramp-up: 3m # time to spin all concurrent users\n total_actions_per_hour: 54500 # number of total JMeter/Locust actions per hour\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh jira.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py performance_profile.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py scale_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.227Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":85,"estimatedTokens":693}}24{"id":"doc-data_center_app_performance_toolkit_user_guide_f-f8e3d2b0","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Bitbucket","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-bitbucket/","text":"Example:\n```bash\ndocker run --env-file aws_envs \\\n-v \"/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nbzt bitbucket.yml\n```\n\nExample:\n```text\n# def test_1_selenium_custom_action(webdriver, datasets, screen_shots):\n# app_specific_action(webdriver, datasets)\n```\n\nExample:\n```bash\ndocker run --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\napplication_hostname: test_bitbucket_instance.atlassian.com # Bitbucket DC hostname without protocol and port e.g. test-bitbucket.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 7990 etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /bitbucket # e.g. /bitbucket for TerraForm deployment url like `http://a1234-54321.us-east-2.elb.amazonaws.com/bitbucket`. Leave this value blank for url without postfix.\n admin_login: admin\n admin_password: admin\n load_executor: jmeter # only jmeter executor is supported\n concurrency: 20 # number of concurrent virtual users for jmeter scenario\n test_duration: 50m\n ramp-up: 10m # time to spin all concurrent users\n total_actions_per_hour: 32700 # number of total JMeter actions per hour\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh bitbucket.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py performance_profile.yml\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py scale_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.229Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":94,"estimatedTokens":769}}25{"id":"doc-prepare_your_data_center_app_for_the_dark_theme-b537d4a4","source":"documentation","title":"Prepare your Data Center app for the dark theme","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-preparing-for-dark-theme/","text":"Example:\n```text\n.info-field {\n color: var(--ds-text, black);\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { token } from '@atlaskit/tokens';\nconst infoField = css({\n color: token('color.text', 'black')\n})\n```\n\nExample:\n```text\n/* Original and Light */\n.panel {\n background-image: url(images/bg-light.png);\n}\n/* Dark */\nhtml[data-color-mode='dark'] .panel {\n background-image: url(images/bg-dark.png);\n}\n```\n\nExample:\n```javascript\nconst colorMode = document.documentElement.getAttribute('data-color-mode')\nif (colorMode === 'light') {\n // Light mode\n} else if (colorMode === 'dark') {\n // Dark mode\n}\n```\n\nExample:\n```javascript\nimport { ThemeMutationObserver } from '@atlaskit/tokens';\n\nconst observer = new ThemeMutationObserver((newTheme) => {\n console.log(newTheme.colorMode);\n});\n\nobserver.observe();\nobserver.disconnect();\n```\n\nExample:\n```text\nnpm install @atlaskit/tokens\n```\n\nExample:\n```javascript\n// Make sure you import the token function\nimport { token } from '@atlaskit/tokens';\nconst infoField = css({\n color: token('color.text', 'black')\n});\n```\n\nExample:\n```text\n# process the CSS-syntax files\nnpx @atlaskit/codemod-cli -n css-to-design-tokens -e css,scss,less .\n\n# process the JavaScript-syntax files\nnpx @atlaskit/codemod-cli -n theme-to-design-tokens -p tsx -e ts,tsx,js,jsx .\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.232Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":79,"estimatedTokens":336}}26{"id":"doc-data_center_app_performance_toolkit_user_guide_f-20834c59","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Bamboo","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-bamboo/","text":"Example:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nexport BAMBOO_TOKEN=newly_generarted_token # for MacOS and Linux\n```\n\nExample:\n```bash\nset BAMBOO_TOKEN=newly_generarted_token # for Windows\n```\n\nExample:\n```bash\n./run.sh # for MacOS and Linux\n```\n\nExample:\n```bash\nrun # for Windows\n```\n\nExample:\n```text\n# def test_1_selenium_custom_action(webdriver, datasets, screen_shots):\n# app_specific_action(webdriver, datasets)\n```\n\nExample:\n```text\napplication_hostname: bamboo_host_name or public_ip # Bamboo DC hostname without protocol and port e.g. test-bamboo.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 8085, etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /bamboo # e.g. /babmoo in case of url like http://localhost:8085/bamboo\n admin_login: admin\n admin_password: admin\n load_executor: jmeter \n concurrency: 200 # number of concurrent threads to authenticate random users\n test_duration: 45m\n ramp-up: 3m\n total_actions_per_hour: 2000 # number of total JMeter actions per hour\n number_of_agents: 50 # number of available remote agents\n parallel_plans_count: 40 # number of parallel plans execution\n start_plan_timeout: 60 # maximum timeout of plan to start\n default_dataset_plan_duration: 60 # expected plan execution duration\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh bamboo.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py bamboo_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.234Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":86,"estimatedTokens":630}}27{"id":"doc-data_center_app_performance_toolkit_user_guide_f-2ef85d2b","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Confluence","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-confluence/","text":"Example:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nbzt confluence.yml\n```\n\nExample:\n```text\n# def test_1_selenium_custom_action(confluence_webdriver, confluence_datasets, confluence_screen_shots):\n# extension_ui.app_specific_action(confluence_webdriver, confluence_datasets)\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\napplication_hostname: test_confluence_instance.atlassian.com # Confluence DC hostname without protocol and port e.g. test-confluence.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 2990, etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /confluence # e.g. /confluence for TerraForm deployment url like `http://a1234-54321.us-east-2.elb.amazonaws.com/confluence`\n admin_login: admin\n admin_password: admin\n load_executor: jmeter # jmeter and locust are supported. jmeter by default.\n concurrency: 200 # number of concurrent virtual users for jmeter or locust scenario\n test_duration: 45m\n ramp-up: 5m # time to spin all concurrent users\n total_actions_per_hour: 20000 # number of total JMeter/Locust actions per hour.\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh confluence.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py performance_profile.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py scale_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.236Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":85,"estimatedTokens":713}}28{"id":"doc-data_center_app_performance_toolkit_user_guide_f-dd180206","source":"documentation","title":"Data Center App Performance Toolkit User Guide For Jira Service Management","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-performance-toolkit-user-guide-jsm/","text":"Example:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt-small.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nbzt jsm.yml\n```\n\nExample:\n```text\n# def test_1_selenium_agent_custom_action(jsm_webdriver, jsm_datasets, jsm_screen_shots):\n# extension_ui_agents.app_specific_action(jsm_webdriver, jsm_datasets)\n```\n\nExample:\n```text\n# def test_1_selenium_customer_custom_action(jsm_webdriver, jsm_datasets, jsm_screen_shots):\n# extension_ui_customers.app_specific_action(jsm_webdriver, jsm_datasets)\n```\n\nExample:\n```bash\npython util/jmeter/start_jmeter_ui.py --app jsm --type agents\n# or\npython util/jmeter/start_jmeter_ui.py --app jsm --type customers\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file aws_envs \\\n-v \"/$PWD/dcapt.tfvars:/data-center-terraform/conf.tfvars\" \\\n-v \"/$PWD/dcapt-snapshots.json:/data-center-terraform/dcapt-snapshots.json\" \\\n-v \"/$PWD/logs:/data-center-terraform/logs\" \\\n-it atlassianlabs/terraform:2.9.23 ./install.sh -c conf.tfvars\n```\n\nExample:\n```text\napplication_hostname: test_jsm_instance.atlassian.com # Jira Service Management DC hostname without protocol and port e.g. test-jsm.atlassian.com or localhost\n application_protocol: http # http or https\n application_port: 80 # 80, 443, 8080, 2990, etc\n secure: True # Set False to allow insecure connections, e.g. when using self-signed SSL certificate\n application_postfix: /jira # e.g. /jira for TerraForm deployment url like `http://a1234-54321.us-east-2.elb.amazonaws.com/jira`. Leave this value blank for url without postfix.\n admin_login: admin\n admin_password: admin\n load_executor: jmeter # jmeter and locust are supported. jmeter by default.\n concurrency_agents: 50 # number of concurrent virtual agents for jmeter or locust scenario\n concurrency_customers: 150 # number of concurrent virtual customers for jmeter or locust scenario\n test_duration: 45m\n ramp-up: 3m # time to spin all concurrent users\n total_actions_per_hour_agents: 5000 # number of total JMeter/Locust actions per hour\n total_actions_per_hour_customers: 15000 # number of total JMeter/Locust actions per hour\n insight: False # Set True to enable Insight specific tests\n```\n\nExample:\n```bash\nexport ENVIRONMENT_NAME=your_environment_name\n```\n\nExample:\n```bash\ndocker run --pull=always --env-file ./app/util/k8s/aws_envs \\\n-e REGION=us-east-2 \\\n-e ENVIRONMENT_NAME=$ENVIRONMENT_NAME \\\n-v \"/$PWD:/data-center-terraform/dc-app-performance-toolkit\" \\\n-v \"/$PWD/app/util/k8s/bzt_on_pod.sh:/data-center-terraform/bzt_on_pod.sh\" \\\n-it atlassianlabs/terraform:2.9.23 bash bzt_on_pod.sh jsm.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py performance_profile.yml\n```\n\nExample:\n```bash\ndocker run --pull=always \\\n-v \"/$PWD:/dc-app-performance-toolkit\" \\\n--workdir=\"//dc-app-performance-toolkit/app/reports_generation\" \\\n--entrypoint=\"python\" \\\n-it atlassian/dcapt csv_chart_generator.py scale_profile.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.239Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":101,"estimatedTokens":891}}29{"id":"doc-timebomb_licenses_for_testing_server_apps_on_the-24a55aad","source":"documentation","title":"Timebomb licenses for testing server apps on the Atlassian Marketplace","url":"https://developer.atlassian.com/platform/marketplace/timebomb-licenses-for-testing-server-apps/","text":"Example:\n```bash\nAAABCA0ODAoPeNpdj01PwkAURffzKyZxZ1IyUzARkllQ24gRaQMtGnaP8VEmtjPNfFT59yJVFyzfu\nbkn796Ux0Bz6SmbUM5nbDzj97RISxozHpMUnbSq88poUaLztFEStUN6MJZ2TaiVpu/YY2M6tI6sQ\nrtHmx8qd74EZ+TBIvyUU/AoYs7jiE0jzknWQxMuifA2IBlUbnQ7AulVjwN9AaU9atASs69O2dNFU\n4wXJLc1aOUGw9w34JwCTTZoe7RPqUgep2X0Vm0n0fNut4gSxl/Jcnj9nFb6Q5tP/Ueu3L+0PHW4g\nhZFmm2zZV5k6/95CbR7Y9bYGo/zGrV3Ir4jRbDyCA6vt34DO8p3SDAsAhQnJjLD5k9Fr3uaIzkXK\nf83o5vDdQIUe4XequNCC3D+9ht9ZYhNZFKmnhc=X02dh\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\nExample:\n```bash\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uoYeScChC1FhLU6Gaxsvr+ko3wi7ZD7T/XoTqwYPv9\nmbezGTeVXnytBCOsohyHrN5HHG6yUoaMh6SDK0wsnNSq6RE62gjBSqL9KgN7RpfS0XfsMdGd2gsW\nfv2gKY4VnbYEs7IjUH4FmfgMAk5DwO2CDgneQ+NH5nEGY9ksrKz6xkIJ3uc0EeQyqECJTD/7KQ5j\nzaZN6PyNeZsGFKYGpS0k9vSNWCtBEWe0PRo7rMkvV2UwUu1i4KH/f4uSBl/JqupxsBW6l3pD/WD/\nJNzuSjPHa6hHfh8l6+KTb79rZ1Ce9B6i612uKxROZuEc7LxRpzA4t8ffAHYfn9KMCwCFEErfsC77\n7XOAsdjoKVRM24pJL3+AhRbORJTyFn7+5BUotohYeGfCqYgkA==X02dt\n```\n\nExample:\n```bash\nAAAA9A0ODAoPeNpdj0FPwkAQhe/7KzbxZlLSXTEGkj2AbYBoSxO2SriNdcCNZdrMbqv8e5HqheP7X\nubLvJusIZkBSzWRSk3VwzQeyyKxUsdKiwR9xa4NriFj0QdZuwrJo9w3LNu6OziS79hj3bTIXuTd8\nQ15vS/9ORkVi0dG+D1OIKDRSuko1pG+F2kPdXdpTOAOxaDyo9sRVMH1ONAMHAUkoArT79bx6aIp7\npZizQcg5wfDLNTgvQMSG+QeeZWY+WJio235Mo6edrtlNI/Vq3geXj+3JX1S80X/5Mr9R+2pxRyOa\nGy6sat8IYqOqw/weL3lB7zIa8swLAIULU/BlyZoOVbLPEVGAVAio9Yh3t4CFFBJJd00fLnkkzgex\nV+T6NsiPVpgX02ck\n```\n\nExample:\n```bash\nAAAA9A0ODAoPeNpdj0FrwkAQhe/7KxZ6K0SySyso7KGaoNImBty0xduYjnZpnITZTVr/fa2xF4/ve\n8zHvLusIZkBSzWRSk3VeKrHskis1LHSIkFfsWuDa8hY9EHWrkLyKPcNy7buDo7kB/ZYNy2yF3l33\nCGv96U/J6NiMWeEv+MEAhqtlI5iHelHkfZQd5fGBO5QDCo/uh9BFVyPA83AUUACqjD9aR2fLpoiX\noo1H4CcHwxPoQbvHZDYIPfIq8TMFhMbvZevD9HzdruMZrF6Ey/D6+e2pC9qvumf3Liv1J5azOGIx\nqYbu8oXoui4+gSPt1t+Ab4Oa8gwLAIUbz98vdLFn/2d+epiSRUkE1spDVICFGIao7aIxBdxQdEoM\no/wg3GIMhntX02ck\n```\n\nExample:\n```bash\nAAABCQ0ODAoPeNpdj8FPgzAYxe/9K5p4M2FpcYtuSQ9DyLaoQFxRs9s3/GCN0JK2oPvvnUM97Pjey\n/vlvSt56GlWesqmlLPF7HbB7mgeSxoyHpIYXWlV55XRQqLztFElaoe0MpZ2TV8rTd9xwMZ0aB1J+\n3aPNqsKd1KCM3JvEX7KMXgUIedhwOYB5yQZoOnPiaigcUhGlptcT6D0akDhbY/kCZT2qEGXmHx1y\nh7PnPxmTTJbg1ZuRCx9A84p0GSLdkC7iUW0msvgrXiZBg+73TqIGH8lj+P2U1roD20+9Z9zwf515\nbHDFFoUMtnKTbr6fxdBuzfmGVvjcVmj9k6EM5L3tjyAw8ur38lTdxIwLAIUOawOiPVfH6zNRA/o/\nSZKylVzOfgCFG0JSQn0He9aMn3zrNE/fg625LgFX02dh\n```\n\nExample:\n```bash\nAAABBw0ODAoPeNpdjzFvgzAUhHf/CkvdKhHZkA6J5CEUlERtCSqmrbK90Ae1CjayDW3+fdMglox3p\n/t0dycHpJkZKQ8pW62X0ZozmheShoxHJEFXWdV7ZbSQ6DxtVYXaIa2NpX07NErTTxyxNT1aR7KhO\n6E91KW7KMFD8mgR/ssJeBQh51HAWRAyko7QDtdE1NA6JBPLLe4XUHk1ovB2QPICSnvUoCtMf3tlz\n1dOznbkYBvQyk2IjW/BOQWaFGhHtPtExNuVDD7Kt2XwdDzugpjxd/I8bb+kpf7W5kfPzg17fhFDd\nzLmFTvjcdOg9k6ED3NHnnvMoEMh00Lusy3JB1t9gcPbq3/qq3cVMCwCFEJl98Z/glYXAJfalw64u\nyFp+3zWAhRYkVu511dtvnuBWgWES2DhAbIkpg==X02dh\n```\n\nExample:\n```bash\nAAABCA0ODAoPeNpdj8FrwjAYxe/5KwK7DSpJK4hCDnYtKttame0c3j7j1y6sTUqSdvO/n7PbDh7fe\n7wf793t8URz6SmLKJ8totmCTek2KWjIeEgSdNKqziujRYHO00ZJ1A5pZSztmr5Wmp5wwMZ0aB3J+\nvaINq9Kd1EiJA8W4aebgEcRch4GbB5wRtIBmv6aiAoah2REucn9BKRXAwpveyTPoLRHDVpi+tUpe\n75yttGa5LYGrdyIWPoGnFOgyQ7tgHaTiHg1L4K38nUaPB4O6yBmfE+exumXtNQf2nzqP+eG/esW5\nw4zaFEU6a7YZKv/czG0R2NesDUelzVq7wQn297Kd3B4+/Qb7dx2mjAtAhUAkCqinKy6DbTosPd7S\nmDta0BxH8ECFBU82orr2+LLHzvHmq2Xr3pvQc8mX02dh\n```\n\nExample:\n```bash\nAAABDQ0ODAoPeNpdj01Lw0AQhu/7Kxa8CVt204KmsIfG1rZoP7CJSm/TOK2LyW6Y3UT7740JFRHmM\nu/LPMNzdU+Gr4C4vOUyHo/UeBTz7S7lkVRDNkWfk6mCcVan6AMvTI7WIz864lVRn4zlb9hg4Sokz\n9Z1eUDaHDPfblpJdkcIP8dTCKgjpYZCRqLlzhoo6q7RgWpkPcoPrgeQB9Ngn67A2IAWbI6zr8rQu\ncOo+EYKqdphGzqBNb4HTUIB3huwbIfUIC2nOpnHqXjNnkfiYb9fiESqF/bYG7RtZj+s+7SX5M+L7\nXBxSdNzhWsoUaezXbpcz38dEygPzj1h6QJOTmhDJ7ytKX8Hj/+FvwHN/XffMCwCFHRCjzxhfiWzk\npYAfiOmIZ8IxGP2AhRErmAkWvxlDZEljComfwIA/wscjg==X02dp\n```\n\nExample:\n```bash\nAAABiQ0ODAoPeNp1kk9TwjAQxe/9FJnxXKYpeoCZHqCtgsqfgaIO4yWELURD0tm0KN/eWOjYdvD68\nvbtb3dzM9GKTBgS2iOU9n3a7/pkHiXE96jvbNhho3XnWXBQBuKtyIVWQTxN4sV8MV7GTirMHk5QO\nZJTBsG91eITvPdJBEeQOgN0uNRHwIYtLKWGa1ocNoCzdGUATUA9h2uVdhjPxRGCHAtw5gXyPTMQs\nRwCn1Lf9XzXv3NqwVN2gGCZDBYWstLj70zgqSyad0fVWPXgJaClGUfB8KGXuG+rl1v3ab0euUOPv\njofAlmD/XG8GJBY5YAZCtMa9Ze5MagVZAGKX/FVE4eyMDZtqrdgAq+19zJlWEr/Na0TXjkTx4KLj\nWzeKbyIjaAJE7aDYpa2tTSO+mvbCrBKo/ryate4Up9KfylnhjumhGEl0SCXzBjB1B9Q/QYhQulrH\n/fcue6svl1di8BwFFnZKAGTE3mGIalGksliJxTZVqTmvLF6fXxksjhzpkwaqP5s3fMDBMYhRDAtA\nhUAhcR3uL05YCxbclq7h1dNa+Nc+j4CFBrdN005oVlMN9yBlWeM4TlnrOhqX02j3\n```\n\nExample:\n```bash\nAAABCQ0ODAoPeNpdjzFPwzAUhHf/CktsSKnsUJBayUNDorYC0og6gLq9mpfUIrEj2wn031MaYOh4d\n7pPd1fy0NONCpRNKedzxub8jhappDHjMUnRK6e7oK0REn2gjVZoPNLKOto1fa0NfccBG9uh8yTv2\nz26TVX6kxKckXuH8FNOIaCIOY8jNos4J9kATX9ORAWNRzKy/OR6AiroAUVwPZIn0CagAaMw++q0O\n545xc2KbFwNRvsRsQgNeK/BkC26Ad06FclyJqO38mUaPex2qyhh/JU8jttPaWk+jP00f84F+9eVx\nw5zaFHIbCvX+fL/XQLt3tpnbG3ARY0meBHfkqJ36gAeL69+A7mJdwYwLAIUOvp5LMC8ygBti6ue+\nae0PwoyX8QCFBfM5Yqqy2BCrwExOXHECJskOitAX02dh\n```\n\nExample:\n```bash\nAAAB8w0ODAoPeNp9Uk2P2jAQvedXWOoNydmELVKLFKlL4u7SLglKQj+27cEkA3gb7GjssMu/rwnQl\ns9DDvHMvPfmvXmTN0BGfE08n3jdftfv927J/SgnXc9/58wRQC5UXQO6j6IAqYGVwgglAxbnLB2nw\n4w5cbOcAiaziQbUge85oZKGFybmSwjKmiMKvfjATcW1Fly6hVo64waLBdcQcQPBhot6Per5zo4lX\n9fQjofJaMTScHj3uC+x11rgup0b3z7sudiIi+oSWQa4AhxGweD+fU6/Tb68pZ+fnh7owPO/Os8Cu\nVujKpvCuJsfqtXMvHAE1+KKFQQGG3A+2cp412XJeQjSHLVkzVQXKOrWn/bljH/nNmslXPa30+nES\nU4/Jikdp0k0CfNhEtNJxmwhCBGsFSWZrolZANmhECYLVQISu9gzFIb8WBhT/+zf3MyVe2DOTbWdo\nLCd+OWSSBGpDCmFNiimjQGLLDQxihSNNmppU3Yd67c0ILksjhOxqsKU3eUsooPvG4kXUrli/MlF7\ndayEU7kb6lepJOxOLAf7XneFmkfCuCp95nh+LdwhfegL8E5l0LzNo4IVlApi0Vy0GZvs9O6b+vHZ\nxzBv0toB3Yuk5lCwuualHs8fSD0/3NqdZ48nBd+5bjYilfNdokZr6zmP7TmY5YwLAIUNq8MbmR8G\nfaV9ulfLz1K+3g9j1YCFDeq7aYROMQbwMIvHimNt7/bJCCIX02nj\n```\n\nExample:\n```bash\nAAAB3w0ODAoPeNqFUttu0zAYvs9TWOKuktOkbBJUisSamK2wHJQDhwEXrvN39Zbake109O1xk0ZjQ\ny0XvrD/w3fym7IDFNM98nzkzeaz2dy/QNdxiWae/8554Iq6rZJ1x4x7uGANascZ1KAfXcoM30FgV\nAdO0m1XoNJ1ZRt04HtOKIWxDQndQlC3VCmuNx+oaajWnAqXya1jj/v8cmpz1im2oRoiaiA4sMLeJ\nfZ859Y2Cw3lvoUeJEzjmOTh8up2LJHfLVf7fi57ezMyIjHlzSlKhSUBahkFi+v3Jf5WfbnAn+/ub\nvDC8786n6wB2dEMu5WGIIyCF2RPSzpD97zLZwaLbqWZ4q3hUgz4/apRfs37AklKkmf5siD/Qfonw\n8lkkqQl/pjmOMvTqArLZZrgqiC2EIQKrLM1Wu2R2QA6giIimKxBIYvyAMygHxtj2p/z6fReui+8n\njbDBIZh4peLIomENKjm2ii+6gzYzVwjIxHrtJFbS8t1bHzCgKCCvQ7YsgpzclWSCC++HyieCPlI1\naZciUchn4RTkCSwB1963uDRGC+owddU3VPBNe0NjWAHjWytyBK0GZU7vSG2/vqjRvAcUj9wFI7WU\niHatqge9+kB/O8PRXa06QbYNW0syh8ZAEsLMCwCFGL057N7jgFszjOK1RP20LSXbPDkAhR/Kf0Zh\nAzWSddIoCukypG76CReHQ==X02mq\n```\n\nExample:\n```bash\nAAABtQ0ODAoPeNp9kV9v0zAUxd/9Ka7EWyWnTmESqxSJNQlbxdJUTbLBgAfXuV0NqR3ZTqHfHjdpY\nVSCB7/4/jm/e86rR6wh4wdgE2Bsyq6nLITbrIQJC9+SRbdbo8k3lUVjo5CRWCvHhVvwHUZ1y42Rd\nvuOu4ZbK7kKhN4RodUm8D1yj5EzHZJlZ8SWW0y4w+i4lrIrykJyLwUqi+WhxX5fnGdZuornN/fnU\nvqzlebQzy1f353F04zL5l/qBZo9mnkSzW6vS/qxenhDPzw93dEZCx8HtBeyLyX7mpfiMSqHZkAvu\nrUVRrZOajX8jEajRV7S9/mKLld5UsXlPF/Qqkh9IYoNetYa1gdwW4STEqRK6BoNtEZ/Q+Hg89a59\nst0PH7WwV/042aYoDhMfA0g0aC0g1paZ+S6c+g3SwtOg+is0zufS0C8IZ5ZcSUuLfNU8Sq9KdOEz\nj4dEf8XWuG4+X36Cd47WanvSv9QpEgXkX/0ijGSm2eupOW9MQnusdGtv7BE685nk94NX7/M/TKFy\n/BPJjz4047bJyTBPyH0CqcO2GgDvG2hPgNYku550w1YG954il/X0fxXMC0CFQCRUd9kwqDYeFIFJ\nyQmlQPeMMYDLQIUYpH3kyyXea6e1PzAN2rpSuuUl4M=X02l1\n```\n\nExample:\n```bash\nAAABrQ0ODAoPeNp9kVFvmzAQx9/9KSztLZIJZIu0RkJqA6yNViAK0G3d+uDApXgjNrKPbPn2dYG0a\n6fuwS8+393v//O7vAMa8yN1PerOFrPZYn5GL+OczlzvI0m6/RZ0uisMaON7LgmURF5iwvfgVy3XW\npj6nGPDjRFcOqXaE4Pc1M61KEEayI8t9I+DNI6jTbC6uP73wd/FdafLmhsIOYL/yMDcOXM98p95Y\nyn60wp97PvW769OpFHMRfMWagb6AHoV+svLs5x9LW4+sM+3t1ds6XpfRkw7jwcgEbSPugOSdVtTa\ntGiUHK4mUwmSZqzT+mGrTdpWAT5Kk1YkUW24AcaLFBFt0eKNdARlUayVBVo2mr1E0qk32vE9sdiO\nr1XzgvEaTN0MBg67hwaKioV0koY1GLbIdjJwlBUtOwMqr39KYfY1JZZclm+9jLEsmbEAZ4CBJvoI\no9Ctvz2CP2GrRHe6irkL6l+S5JFiW8Pm7suSfU9l8LwXkwIB2hUaxPmYPAUm/Q2bP315w5MGXL95\nDmEZ839jFEE3SlNedvS6rTCkOjAm25YvOON3fMAVTj4nTAtAhRH4o+fI5MQ7xSh2mtA1bPJrq0WA\ngIVAIGperR8m2N0fl/GfUUJfQnd+T1aX02kk\n```\n\nExample:\n```bash\nAAABZw0ODAoPeNp9UV1PwjAUfe+vaOLzyDbBRJMlwlYjCY5lDN7v5gUbunZpOxR/vWUMPxC9adP23\nvac03OvihbpA5Y0GNHg5u46vBve0pgVNPTDkMx4hdJgsW8whRqjhK3YbJ6xnMQawXIlE7AYHe56f\nuAGKaEulRpAZfkOI6tbPKXSti5Rz9eT7jhTFYjxBqU1kReQU3FpUHeJnpm9NVzvO5IsHD6SRVuaS\nvPmQP0D/W+lbAei7bRGaxDm84UDhdjxoz4C9QjTJFrKrVSvkixQ71C7xNiFN3HhxS68xAXJWl29g\nMFzAxYsjdz0Rr5PEvwSW6CxVBw56FppeuCnRwHkCbhbJcjq148vupcJkJ1NF6s51spib27gk5515\naw9KAnJXG9AcnM0JcEdCtWgpp3E3oYLvn7vvpLWtZg53SJqzJajrt730m3uwQowhoMcVKo+Q2HPv\nKNkacHyLJ8u2Amp69l/QB/3JOtvMCwCFA0F6wrOfvxukthYbD4doBefiu/mAhQVD9z9LF1gkNh6j\nBnqKXDvBjxVog==X02hl\n```\n\nExample:\n```bash\nAAABrw0ODAoPeNp9kl9vmzAUxd/9KSztLZIJpKvURUJaA7SNViAKsD9d++DATeKN2JZ9yZZvPxeSZ\na3UPfjFx/f4d479ruyApvxA/YD6k+nkYjq5ordpSSd+cEWybrcCk68rC8aGgU8iJZHXmPEdhI3mx\ngi7/cix5dYKLr1a7Yhuu42Q1ht57qDYQ4imA7LoTL3lFmKOED57M/+S+QG5FzVIC+VBQ28a5WmaL\nKP59f1JSn5rYQ793OLi7kSQpFy0byEUYPZg5nE4u/1Qsq/V5/fs08PDHZv5wRdSdCtbG6FRKDmwn\nYkL5AbBDNuj0SjLS3aTL9limcdVVM7zjFVF4oQwMuCIGro6UNwCPbLSRNaqAUO1UT+gRvp9i6gfp\n+PxRnkvGMftMMFgmHjyaKyoVEgbYdGIVYfgnIWlqGjdWVQ79wQecbElguSyfl2Mo4qWyXWZxGz27\nRnxjXKOqK6dSv6U6pckRZKFbrFL3ye52XApLO/biWEPrdIuTwkWTyFJn93pr9/y3KNTeATyb5Uxn\nCvvrY7p6VoZyrWmzekm+4/Lf/5GsudtN0CueeuY/gBrQ/OdMC0CFQCBdUokGEPDQeApZwmK47VRc\n7hv9wIUHoAl+9VxpP1eW0/g1SHbc5S4p5M=X02ko\n```\n\nExample:\n```bash\nAAABtQ0ODAoPeNp9kl9v2jAUxd/zKSztDckhQevEkCK1JFmH1iQVSfan6x5McgFvwbbsazq+/UwCZ\nZvUWvKLj+/x757rN5UFkrEDCUISTGaT6SycktusIpMgnHq53a1AF+vagDZRGHixFMgazNkOolYxr\nbnZXjPsmDGcCb+RO091dsOF8Ue+u8j3EKG24N1b3WyZgYQhREdvGlzRIPTueAPCQHVQ0JvGRZaly\n3hxc3eW0t+K60Nfl1jNkEvxOHsXuHWmSTPGu5dwStB70Iskmt++r+jX+vNb+unh4SOdB+EXr7Qr0\n2iujqYD54W+RKYR9HA8Go3yoqIfiiW9XxZJHVeLIqd1mTohijU4upasDgS3QE7cJBWNbEETpeVPa\nJB83yKqx9l4vJH+P4zjbqigMFT88EkiiZBIWm5Q85VFcM7cEJSksQblzo3D91zbAkEw0bwWkiOMl\n+lNlSZ0/u2I+0JQJ2yXVC1+CfkkvDLNI7fplXMp9IYJbnrnKIE9dFK53ioweG7Y63Nw+v8zvmTqF\nBaDeI41gUv8vdUpCbKWmjClSHt+yfzl8sqfSfesswPkmnWO6Q9infuvMCwCFBh3Bec/J0qyMOEW5\n11M1DJ8Dy3JAhQmDJx2Rpx4bJdsyMk2A1S5fY3FVQ==X02l1\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.240Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":19,"totalLines":211,"estimatedTokens":2500}}30{"id":"doc-omit_web_resources_for_unlicensed_atlassian_erve-01813494","source":"documentation","title":"Omit web-resources for unlicensed Atlassian erver apps","url":"https://developer.atlassian.com/platform/marketplace/ommitting-web-resources-for-unlicensed-server-apps/","text":"Example:\n```bash\n$ git clone git@bitbucket.org:atlassian_tutorial/tutorial-licensed-conditions.git\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd tutorial-licensed-conditions/\n```\n\nExample:\n```bash\natlas-run --product jira --version 6.4\n```\n\nExample:\n```bash\n[INFO] jira started successfully in 42s at http://localhost:2990/jira\n[INFO] Type Ctrl-D to shutdown gracefully\n[INFO] Type Ctrl-C to exit\n```\n\nExample:\n```text\n<web-resource key=\"tutorial-view-issue-resources\" name=\"tutorial-licensing Web Resources for the View work item page\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n <resource type=\"download\" name=\"issue.css\" location=\"/css/issue.css\"/>\n <resource type=\"download\" name=\"issue.js\" location=\"/js/issue.js\"/>\n <context>jira.view.issue</context>\n</web-resource>\n```\n\nExample:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"plugin-icon\">images/pluginIcon.png</param>\n <param name=\"plugin-logo\">images/pluginLogo.png</param>\n <param name=\"atlassian-licensing-enabled\">true</param>\n </plugin-info>\n <!-- add our i18n resource -->\n <resource type=\"i18n\" name=\"i18n\" location=\"tutorial-licensing\"/>\n \n <!-- add our web resources -->\n <web-resource key=\"tutorial-licensing-resources\" name=\"tutorial-licensing Web Resources\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n \n <resource type=\"download\" name=\"tutorial-licensing.css\" location=\"/css/tutorial-licensing.css\"/>\n <resource type=\"download\" name=\"tutorial-licensing.js\" location=\"/js/tutorial-licensing.js\"/>\n <resource type=\"download\" name=\"images/\" location=\"/images\"/>\n <context>tutorial-licensing</context>\n </web-resource>\n\n <web-resource key=\"tutorial-view-issue-resources\" name=\"tutorial-licensing Web Resources for the View Issue page\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n\n <resource type=\"download\" name=\"issue.css\" location=\"/css/issue.css\"/>\n <resource type=\"download\" name=\"issue.js\" location=\"/js/issue.js\"/>\n <context>jira.view.issue</context>\n </web-resource>\n <!-- publish our component -->\n <component key=\"myPluginComponent\" class=\"com.example.plugins.tutorial.MyPluginComponentImpl\" public=\"true\">\n <interface>com.example.plugins.tutorial.MyPluginComponent</interface>\n </component>\n\n <!-- import from the product container -->\n <component-import key=\"applicationProperties\" interface=\"com.atlassian.sal.api.ApplicationProperties\" />\n <component-import key=\"pluginLicenseManager\" interface=\"com.atlassian.upm.api.license.PluginLicenseManager\"/>\n\n</atlassian-plugin>\n```\n\nExample:\n```javascript\n// You can read about how AMD works here: https://github.com/amdjs/amdjs-api/wiki/AMD\nrequire([\n 'jira/flag',\n 'jquery'\n], function(\n flag,\n $\n) {\n var titleText = \"MyPlugin is licensed!\";\n var messageText = \"I just thought you'd like to know that.\";\n $(function() {\n flag.showInfoMsg(titleText, messageText);\n });\n});\n```\n\nExample:\n```text\npackage com.example.plugins.conditions;\n\npublic interface ConditionEvaluator\n{\n public boolean evaluate(ConditionType type);\n}\n```\n\nExample:\n```text\npackage com.example.plugins.conditions;\n \npublic enum ConditionType\n{\n LICENSED;\n}\n```\n\nExample:\n```text\npackage com.example.plugins.conditions;\n \nimport com.example.plugins.conditions.ConditionEvaluator;\n\npublic class ConditionEvaluatorImpl implements ConditionEvaluator\n{\n @Override\n public boolean evaluate(ConditionType type)\n {\n switch (type)\n {\n case LICENSED:\n return isLicenseValid();\n default:\n return false;\n }\n }\n private boolean isLicenseValid()\n {\n // Implement your logic for determining if\n // your app has a valid, active license.\n }\n}\n```\n\nExample:\n```text\n<web-resource key=\"tutorial-view-issue-resources\" name=\"tutorial-licensing Web Resources for the View Issue page\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n <resource type=\"download\" name=\"issue.css\" location=\"/css/issue.css\"/>\n <resource type=\"download\" name=\"issue.js\" location=\"/js/issue.js\"/>\n <context>jira.view.issue</context>\n</web-resource>\n```\n\nExample:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"plugin-icon\">images/pluginIcon.png</param>\n <param name=\"plugin-logo\">images/pluginLogo.png</param>\n <param name=\"atlassian-licensing-enabled\">true</param>\n </plugin-info>\n <!-- add our i18n resource -->\n <resource type=\"i18n\" name=\"i18n\" location=\"tutorial-licensing\"/>\n \n <!-- add our web resources -->\n <web-resource key=\"tutorial-licensing-resources\" name=\"tutorial-licensing Web Resources\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n \n <resource type=\"download\" name=\"tutorial-licensing.css\" location=\"/css/tutorial-licensing.css\"/>\n <resource type=\"download\" name=\"tutorial-licensing.js\" location=\"/js/tutorial-licensing.js\"/>\n <resource type=\"download\" name=\"images/\" location=\"/images\"/>\n <context>tutorial-licensing</context>\n </web-resource>\n\n <web-resource key=\"tutorial-view-issue-resources\" name=\"tutorial-licensing Web Resources for the View Issue page\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n <resource type=\"download\" name=\"issue.css\" location=\"/css/issue.css\"/>\n <resource type=\"download\" name=\"issue.js\" location=\"/js/issue.js\"/>\n <context>jira.view.issue</context>\n </web-resource>\n \n <!-- publish our components -->\n <component key=\"myPluginComponent\" class=\"com.example.plugins.tutorial.MyPluginComponentImpl\" public=\"true\">\n <interface>com.example.plugins.tutorial.MyPluginComponent</interface>\n </component>\n <component key=\"myPluginConditionEvaluator\" class=\"com.example.plugins.conditions.ConditionEvaluatorImpl\" public=\"false\">\n <interface>com.example.plugins.conditions.ConditionEvaluator</interface>\n </component>\n\n <!-- import from the product container -->\n <component-import key=\"applicationProperties\" interface=\"com.atlassian.sal.api.ApplicationProperties\" />\n <component-import key=\"pluginLicenseManager\" interface=\"com.atlassian.upm.api.license.PluginLicenseManager\"/>\n\n</atlassian-plugin>\n```\n\nExample:\n```text\npackage com.example.plugins.conditions;\n\nimport com.atlassian.plugin.webresource.condition.SimpleUrlReadingCondition;\n\npublic class IsPluginLicensedCondition extends SimpleUrlReadingCondition\n{\n private ConditionEvaluator conditionEvaluator;\n\n public IsPluginLicensedCondition(final ConditionEvaluator conditionEvaluator)\n {\n this.conditionEvaluator = conditionEvaluator;\n }\n\n @Override\n protected boolean isConditionTrue()\n {\n return conditionEvaluator.evaluate(ConditionType.LICENSED);\n }\n\n @Override\n protected String queryKey()\n {\n // This string will be appended to URLs as a GET parameter\n // whenever 'isConditionTrue' returns true.\n // You should make the string short, but unique to your app.\n return \"myPluginName\";\n }\n}\n```\n\nExample:\n```text\n<condition class=\"com.example.plugins.conditions.IsPluginLicensedCondition\" />\n```\n\nExample:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"plugin-icon\">images/pluginIcon.png</param>\n <param name=\"plugin-logo\">images/pluginLogo.png</param>\n <param name=\"atlassian-licensing-enabled\">true</param>\n </plugin-info>\n <!-- add our i18n resource -->\n <resource type=\"i18n\" name=\"i18n\" location=\"tutorial-licensing\"/>\n \n <!-- add our web resources -->\n <web-resource key=\"tutorial-licensing-resources\" name=\"tutorial-licensing Web Resources\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n <resource type=\"download\" name=\"tutorial-licensing.css\" location=\"/css/tutorial-licensing.css\"/>\n <resource type=\"download\" name=\"tutorial-licensing.js\" location=\"/js/tutorial-licensing.js\"/>\n <resource type=\"download\" name=\"images/\" location=\"/images\"/>\n <context>tutorial-licensing</context>\n </web-resource>\n\n <web-resource key=\"tutorial-view-issue-resources\" name=\"tutorial-licensing Web Resources for the View Issue page\">\n <dependency>com.atlassian.auiplugin:ajs</dependency>\n <resource type=\"download\" name=\"issue.css\" location=\"/css/issue.css\"/>\n <resource type=\"download\" name=\"issue.js\" location=\"/js/issue.js\"/>\n <context>jira.view.issue</context>\n <condition class=\"com.example.plugins.conditions.IsLicensedCondition\" />\n </web-resource>\n\n <!-- publish our component -->\n <component key=\"myPluginComponent\" class=\"com.example.plugins.tutorial.MyPluginComponentImpl\" public=\"true\">\n <interface>com.example.plugins.tutorial.MyPluginComponent</interface>\n </component>\n <component key=\"myPluginConditionEvaluator\" class=\"com.example.plugins.conditions.ConditionEvaluatorImpl\" public=\"false\">\n <interface>com.example.plugins.conditions.ConditionEvaluator</interface>\n </component>\n\n <!-- import from the product container -->\n <component-import key=\"applicationProperties\" interface=\"com.atlassian.sal.api.ApplicationProperties\" />\n <component-import key=\"pluginLicenseManager\" interface=\"com.atlassian.upm.api.license.PluginLicenseManager\"/>\n</atlassian-plugin>\n```\n\nExample:\n```bash\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.242Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":17,"totalLines":295,"estimatedTokens":2692}}31{"id":"doc-function_arguments-83b1c1c8","source":"documentation","title":"Function Arguments","url":"https://developer.atlassian.com/platform/forge/function-reference/arguments/","text":"Example:\n```javascript\nexport const handler = (payload, context) => {\n // Do something\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nexport type Context = {\n installContext: string;\n principal?: Principal;\n license?: License;\n installation?: Installation;\n workspaceId?: string;\n}\n\nexport type Principal = {\n accountId: string;\n}\n\nexport type License = {\n active: boolean;\n /** @deprecated Use `active` instead. */\n isActive: boolean;\n billingPeriod?: string | null;\n capabilitySet?: string | null;\n ccpEntitlementId?: string | null;\n ccpEntitlementSlug?: string | null;\n isEvaluation?: boolean | null;\n subscriptionEndDate?: string | null;\n supportEntitlementNumber?: string | null;\n trialEndDate?: string | null;\n type?: string | null;\n};\n\nexport type Installation = {\n ari: InstallationAri;\n contexts: ContextAri[];\n}\n\nexport type InstallationAri = {\n installationId: string;\n toString: () => string;\n};\n\nexport type ContextAri = {\n cloudId?: string;\n workspaceId?: string;\n toString: () => string;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.244Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":60,"estimatedTokens":266}}32{"id":"doc-overview-3cfa6747","source":"documentation","title":"Overview","url":"https://developer.atlassian.com/platform/forge/ui-kit/overview/","text":"Example:\n```text\nnpm i react@18\nnpm i @forge/react@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n/src\n /frontend\n /index.jsx\n```\n\nExample:\n```text\n/static\n /src\n /index.js\n```\n\nExample:\n```javascript\nimport React, { useEffect } from 'react';\nimport ForgeReconciler, { Box, Text } from '@forge/react';\n\nconst App = () => (\n <Box>\n <Text>Hello, world!</Text>\n </Box>\n)\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ReactDOM from 'react-dom';\n\nconst App = () => (\n <div>\n Hello, world!\n </div>\n)\n\nReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById('root')\n);\n```\n\nExample:\n```text\nresources:\n- key: frontend\n path: src/frontend/index.tsx\n```\n\nExample:\n```text\nnpm i @forge/resolver@latest\n```\n\nExample:\n```text\n/src\n /backend\n /index.js\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nconst resolver = new Resolver();\n\nresolver.define('my-example', async (req) => {\n return {\n data: 'Hello, world',\n }\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```text\nmodules:\n function:\n - key: backend\n handler: index.handler\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: example\n title: Example Macro\n resource: frontend\n resolver:\n function: backend\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.244Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":116,"estimatedTokens":354}}33{"id":"doc-scheduled_triggers-d4cdc796","source":"documentation","title":"Scheduled triggers","url":"https://developer.atlassian.com/platform/forge/function-reference/scheduled-trigger/","text":"Example:\n```javascript\nexport const trigger = ({ context }) => {\n console.log(context);\n // Do something\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: example\n function: my-function\n interval: hour # Runs hourly\n function:\n - key: my-function\n handler: index.trigger\n```\n\nExample:\n```json\n{\n context: {\n cloudId: '4f6d9508-93c9-4c2e-abd7-916c34920012',\n moduleKey: 'my -scheduled-trigger-example'\n },\n contextToken: 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImZvcmdlL2NvbnRleHQtdG9rZW4vMTcyYWE2NDItNDY1OS00OWRlLTk0YTYtYjVhYTZjODdlYTliIn0.eyJjb250ZXh0Ijp7ImNsb3VkSWQiOiI0ZjJkMjUwOC05YWM5LTRiZGUtYWQ5Ny0uCCy7qpzzMjkwOTAiLCJtb2R1bGVLZXkiOiJoZWxlbi1zY2hlZHVsZWQtdHJpZ2dlci10cmlnZ2VyLWV4YW1wbGUifSwiYWNjb3VudElkIjoiNzEyMDIwOjQzYmVmMjU2LWQyMGUtNGU5YS1iMTI3LTE1OTM1YjUzZmU3MCIsImV4dGVuc2lvbklkIjoiYXJpOmNsb3VkOmVjb3N5c3RlbTo6ZXh0ZW5zaW9uLzA3ZDg4NWNlLWVkYjItNGQxMC04M2MzLTc5Y2NkODU3OTcyMy81OTcyMmM1OS05ZjY4LTRlMzItYTNkMi1mNDhjOWU4OWM4MDgvc3RhdGljL2hlbGVuLXNjaGVkdWxlZC10cmlnZ2VyLXRyaWdnZXItZXhhbXBsZSIsImNvbnRleHRJZHMiOlsiYXJpOmNsb3VkOmppcmE6OnNpdGUvNGYyZDI1MDgtOWFjOS00YmRlLWFkOTctOTE2YzM3MzI5MDkwIl0sImFwcElkIjoiMDdkODg1Y2UtZWRiMi00ZDEwLTgzYzMtNzljY2Q4NTc5NzIzIiwiYXBwVmVyc2lvbiI6IjIuMTIuMCIsImV4dGVuc2lvblR5cGUiOiJjb3JlOnNjaGVkdWxlZFRyaWdnZXIiLCJ1bmxpY2Vuc2VkIjpmYWxzZSwiaXNzIjoiZm9yZ2UvY29udGV4dC10b2tlbiIsImF1ZCI6ImZvcmdlIiwiaWF0IjoxNzA2MjU1MTgwLCJuYmYiOjE3MDYyNTUxODAsImV4cCI6MTcwNjI1NjA4MCwianRpIjoiYmQ0ZGMxNWY0ODhjYTdhOTRlOGY5NzBkNDZlNWZmNTg4OWRiNTM4YSJ9.Jaf5XHhzKseGx2qxNrAYRK1hKRdrXxMvdIbfmIGFqNh_P9MWVECyB59_1gPxbLhu_WbIwEM-sQxKtWlLLkMrgXNPbX2hraO6w7p6yOaLgitaPizSbAt6JCLYN5ulEpRMRjMAkG4xT-fKYuARBc_bd7WlP4Z8SZrqiZwkModWtHVsXD6OnsYpITJwP9nNoUIG_6WaBZelkyoCpomjj3zXRy5_b5MNI61rJrRGt_R3EZ9h2Xb5OwZ9OUve2SeelrcBBxJblg3tBQ9IFS_828Az6CELbCbf5h7Wz8aXvd3Q_Mrh3GpDHk4sP6bUsjkfhmtT9d292aEehl4BCZHFVQZieQ'\n}\n```\n\nExample:\n```json\n{\n principal: undefined,\n installContext: 'ari:cloud:jira::site/4f275028-9ac9-4a4e-ad97-91667d422097'\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.245Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":46,"estimatedTokens":502}}34{"id":"doc-forge_resolver-04871207","source":"documentation","title":"Forge resolver","url":"https://developer.atlassian.com/platform/forge/runtime-reference/forge-resolver/","text":"Example:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n resource: example-resource\n resolver:\n function: issue-panel-resolver\n render: native\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n function:\n - key: issue-panel-resolver\n handler: index.handler\nresources:\n - key: example-resource\n path: src/frontend/index.jsx\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', (req) => {\n console.log(req);\n return 'Hello, world!';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const [data, setData] = useState(null);\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n return (\n <>\n <Text>Hello world!</Text>\n <Text>{data ? data : 'Loading...'}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n resource: example-resource\n resolver:\n function: issue-panel-resolver\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n function:\n - key: issue-panel-resolver\n handler: index.handler\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```javascript\nimport Resolver from \"@forge/resolver\";\n\nconst resolver = new Resolver();\n\nresolver.define(\"exampleFunctionKey\", ({ payload, context }) => {\n return { example: `Hello, ${payload.name}!` };\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nimport { invoke } from \"@forge/bridge\";\n\ninvoke(\"exampleFunctionKey\", { name: \"World\" }).then((returnedData) =>\n console.log(returnedData.example);\n);\n```\n\nExample:\n```typescript\nexport type Defs = {\n getPrice: (args: { product: string; }) => { price: number };\n setPrice: (args: { product: string; price: number; }) => void;\n listProducts: () => { products: string[] };\n};\n```\n\nExample:\n```typescript\nimport { makeResolver } from '@forge/resolver';\n\nimport { Defs } from './shared/types';\n\nexport const handler = makeResolver<Defs>({\n async getPrice(request) {\n const { product } = request.payload;\n return { price: 100 };\n },\n async setPrice(request) {\n const { product, price } = request.payload;\n },\n async listProducts() {\n return { products: ['product1', 'product2'] };\n }\n});\n```\n\nExample:\n```typescript\ntype Handler<Args, Return> = (request: { payload: Args; context: Context }) => Promise<Return>;\n```\n\nExample:\n```typescript\nimport { makeInvoke } from '@forge/bridge';\n\nimport { Defs } from '../shared/types';\n\nconst invoke = makeInvoke<Defs>();\n\nconst { products } = await invoke('listProducts');\nconst result = await invoke('getPrice', { product: products[0]});\nconsole.log(result.price);\n```\n\nExample:\n```typescript\ntype Context = {\n accountId?: string;\n accountType?: 'licensed' | 'unlicensed' | 'customer' | 'anonymous';\n cloudId?: string;\n workspaceId?: string;\n localId: string;\n installContext: string;\n environmentId: string;\n environmentType: string;\n extension: {\n config?: { [key: string]: any } // defined for macro extensions\n [key: string]: any;\n },\n installation?: {\n ari: {\n installationId: string;\n toString: () => string;\n },\n contexts: [\n {\n cloudId?: string,\n workspaceId?: string,\n toString: () => string\n }\n ]\n },\n license?: LicenseDetails;\n};\n\ntype LicenseDetails = {\n active?: boolean;\n billingPeriod?: string;\n ccpEntitlementId?: string;\n ccpEntitlementSlug?: string;\n isEvaluation?: boolean;\n subscriptionEndDate?: string;\n supportEntitlementNumber?: string | null;\n trialEndDate?: string;\n type?: string;\n};\n\nfunction define(\n functionKey: string,\n cb: (request: {\n payload: { [key in number | string]: any;},\n context: Context\n }) => Promise<{ [key: string]: any } | string | void> | { [key: string]: any } | string | void,\n): this\n```\n\nExample:\n```javascript\nfunction getDefinitions(): InvocationHandler\n```\n\nExample:\n```typescript\nfunction makeResolver<D extends Definitions>(handlers: Handlers<D>): DefinitionsHandler;\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.246Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":14,"totalLines":215,"estimatedTokens":1140}}35{"id":"doc-realtime_events_api-565728d4","source":"documentation","title":"Realtime events API","url":"https://developer.atlassian.com/platform/forge/runtime-reference/realtime-events-api/","text":"Example:\n```javascript\nimport { publish } from '@forge/realtime';\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { publishGlobal } from '@forge/realtime';\n```\n\nExample:\n```javascript\nconst publish = (\n channel: string,\n payload: string | Record<string, unknown>,\n options?: PublishOptions\n): Promise<PublishResult>\n\nconst publishGlobal = (\n channel: string,\n payload: string | Record<string, unknown>,\n options?: PublishOptions\n): Promise<PublishResult>\n\nexport enum Jira {\n Board = 'board',\n Issue = 'issue',\n Project = 'project'\n}\n\nexport enum Confluence {\n Content = 'content',\n Space = 'space'\n}\n\nexport enum Bitbucket {\n PullRequest = 'pullRequest',\n Repository = 'repository'\n}\n\ntype ProductContext = Jira | Confluence | Bitbucket;\n\ninterface PublishOptions {\n token?: string;\n contextOverrides?: ProductContext[];\n}\n\ninterface PublishResult {\n eventId: string | null;\n eventTimestamp: string | null;\n errors?: any[];\n}\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport { publish } from '@forge/realtime';\n\nconst resolver = new Resolver();\n\nresolver.define(\"publishRealtimeEvent\", ({ payload }) => {\n const channel = 'my-example-channel';\n publish(channel, payload);\n return {\n example: `Published event with payload: ${JSON.stringify(payload)}`\n };\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nimport { publishGlobal, signRealtimeToken } from '@forge/realtime';\n```\n\nExample:\n```javascript\nconst signRealtimeToken = (\n channelName: string,\n claims: object,\n permissions?: [RealtimeTokenPermission, ...RealtimeTokenPermission[]]\n): Promise<TokenResult>\n\ntype RealtimeTokenPermission = 'subscribe' | 'publish';\n\ninterface TokenResult {\n token: string | null;\n expiresAt: number | null;\n errors?: any[];\n}\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport { publish, signRealtimeToken } from '@forge/realtime';\n\nconst resolver = new Resolver();\nconst TOKEN_EXPIRY_BUFFER = 5000; // 5 seconds\n\nresolver.define('publishEvent', async () => {\n const customClaims = {\n allowedUsers: ['accountId-1', 'accountId-2'],\n };\n \n const { token, expiresAt } = await signRealtimeToken('my-test-channel', customClaims);\n\n // expiresAt is an epoch timestamp expressed in seconds (in accordance with the JWT \n // standard for the exp field), so it needs to be converted to milliseconds before \n // comparing against a Date timestamp.\n if (Date.now() - TOKEN_EXPIRY_BUFFER < expiresAt * 1000) {\n return publish('my-test-channel', 'This is an event payload', { token }); \n }\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.246Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":127,"estimatedTokens":676}}36{"id":"doc-list_a_forge_successor_to_a_connect_app-99a6893c","source":"documentation","title":"List a Forge successor to a Connect app","url":"https://developer.atlassian.com/platform/marketplace/listing-forge-successor-to-connect-apps","text":"Example:\n```text\napp:\n id: ari:cloud:ecosystem::app/cxxxxxx-xxxxx-xxxx-xxxxx-xxxxxxxxx\n connect:\n key: com.example.yourappkey\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy -e production\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.247Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":20,"estimatedTokens":56}}37{"id":"doc-marketplace_api_java_client-dd9db524","source":"documentation","title":"Marketplace API Java Client","url":"https://developer.atlassian.com/platform/marketplace/marketplace-api-java-client","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.marketplace</groupId>\n <artifactId>marketplace-client-java</artifactId>\n <version>2.0.0</version> <!-- replace with latest release version -->\n</dependency>\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.marketplace</groupId>\n <artifactId>marketplace-client-java</artifactId>\n <version>2.0.0</version> <!-- replace with latest release version -->\n <classifier>dependencies</classifier>\n</dependency>\n```\n\nExample:\n```text\nMarketplaceClient client = MarketplaceClientFactory.createMarketplaceClient();\n```\n\nExample:\n```text\nHttpConfiguration config = HttpConfiguration.builder()\n .credentials(Option.some(new HttpConfiguration.Credentials(\"my.username@example.com\", \"my.password\")))\n .build();\nMarketplaceClient client = MarketplaceClientFactory.createMarketplaceClient(config);\n```\n\nExample:\n```text\nModelBuilders.SomeObjectBuilder builder = ModelBuilders.someObject();\nbuilder.name(\"name of my object\")\n .someOtherProperty(\"some other property\");\nSomeObject myObject = builder.build();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.248Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":57,"estimatedTokens":354}}38{"id":"doc-license_api_for_cloud_apps-415d899d","source":"documentation","title":"License API for cloud apps","url":"https://developer.atlassian.com/platform/marketplace/license-api-for-cloud-apps","text":"Example:\n```json\n{\n \"key\": \"example-app\",\n \"version\": \"1.0\",\n \"state\": \"ENABLED\",\n \"host\": {\n \"product\": \"Jira\",\n \"contacts\": [\n {\n \"name\": \"Example Contact\",\n \"email\": \"contact@example.com\"\n }\n ]\n },\n \"license\": {\n \"active\": true,\n \"type\": \"COMMERCIAL\",\n \"evaluation\": false,\n \"supportEntitlementNumber\": \"SEN-###\"\n },\n \"links\": {\n \"marketplace\": [\n {\n \"href\": \"http:// marketplace.atlassian.com/plugins/example-app\"\n }\n ],\n \"self\": [\n {\n \"href\": \"http:// example.com/rest/atlassian-connect/latest/example-app\"\n }\n ]\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"key\": \"example-app\",\n \"version\": \"1.0\",\n \"state\": \"ENABLED\",\n \"host\": {\n \"product\": \"Jira\",\n \"contacts\": [\n {\n \"name\": \"Example Contact\",\n \"email\": \"contact@example.com\"\n }\n ]\n },\n \"links\": {\n \"marketplace\": [\n {\n \"href\": \"http:// marketplace.atlassian.com/plugins/example-app\"\n }\n ],\n \"self\": [\n {\n \"href\": \"http:// example.com/rest/atlassian-connect/latest/example-app\"\n }\n ]\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.248Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":73,"estimatedTokens":326}}39{"id":"doc-getting_started_with_forge-beda4099","source":"documentation","title":"Getting started with Forge","url":"https://developer.atlassian.com/platform/forge/getting-started-learn/","text":"Example:\n```bash\nnvm install --lts\nnvm use --lts\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnode --version\n```\n\nExample:\n```bash\nnpm config set unsafe-perm true\n```\n\nExample:\n```bash\nsudo rm -rf ~/Library/Preferences/@forge\n```\n\nExample:\n```bash\nnpm install -g @forge/cli\n```\n\nExample:\n```bash\nforge --version\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd <your-app-dir>\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nAllow Forge to collect CLI usage and error reporting information?\n```\n\nExample:\n```bash\n✔ Logged in as Mia Krystof\n```\n\nExample:\n```bash\nread FORGE_EMAIL\n# Enter email\nread -s FORGE_API_TOKEN\n# Enter your Atlassian API scoped token (will not be displayed)\nexport FORGE_EMAIL FORGE_API_TOKEN\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.252Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":83,"estimatedTokens":210}}40{"id":"doc-cloud_app_licensing-1948a233","source":"documentation","title":"Cloud app licensing","url":"https://developer.atlassian.com/platform/marketplace/cloud-app-licensing/","text":"Example:\n```json\n{\n \"name\": \"Hello World\",\n \"key\": \"hello-world\",\n \"description\": \"Atlassian Connect app\",\n \"baseUrl\": \"http://www.example.com\",\n \"enableLicensing\": true\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nhttp://....?lic=active\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.261Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":23,"estimatedTokens":69}}41{"id":"doc-declare_marketing_assets_for_server_apps-45b55a7b","source":"documentation","title":"Declare marketing assets for server apps","url":"https://developer.atlassian.com/platform/marketplace/declaring-marketing-assets-for-server-apps/","text":"Example:\n```text\n<logo image=\"images/01_logo.jpg\"/>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<banner image=\"images/02_banner.jpg\"/>\n```\n\nExample:\n```text\n<highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n</highlights>\n```\n\nExample:\n```text\n<screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n</screenshots>\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<atlassian-plugin-marketing>\n <!--Describe names and versions of compatible products -->\n <compatibility>\n <product name=\"jira\" min=\"4.0\" max=\"4.4.1\"/>\n <product name=\"bamboo\" min=\"3.0\" max=\"3.1\"/>\n </compatibility>\n <!-- Describe your app logo and banner. The banner is only displayed in the UPM. -->\n <logo image=\"images/01_logo.jpg\"/>\n <banner image=\"images/02_banner.jpg\"/>\n <!-- Describe highlight and cropped highlight image assets, in order. -->\n <highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n </highlights>\n <!-- Describe additional screenshots you'd like listed on your app listing. -->\n <screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n </screenshots>\n</atlassian-plugin-marketing>\n```\n\nExample:\n```javascript\n<vendor name=\"Atlassian Plugin Generator\" url=\"http://example.com\"/>\n```\n\nExample:\n```javascript\n<param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"vendor-icon\">images/vendorIcon.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<plugin-info>\n <description>Description of Example Marketing for Confluence</description>\n <version>1.0</version>\n <vendor name=\"Your Name Here\" url=\"http://example.com\"/>\n <param name=\"atlassian-licensing-enabled\">true</param>\n <param name=\"plugin-icon\">images/pluginIcon.jpg</param>\n <param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n <param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n <param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n</plugin-info>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.267Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":102,"estimatedTokens":721}}42{"id":"doc-list_a_forge_app_on_the_atlassian_marketplace-eb6a9d49","source":"documentation","title":"List a Forge app on the Atlassian Marketplace","url":"https://developer.atlassian.com/platform/marketplace/listing-forge-apps/","text":"Example:\n```text\napp:\n id: ari:cloud:ecosystem::app/12345678-90ab-cdef-1234-2840465f3b84\n name: Forge Demo App\n licensing:\n enabled: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { view } from @forge/bridge;\n const context = await view.getContext();\n\n if (context.license.active !== true) {\n console.log(\"App is not licensed\");\n return;\n }\n\n // App is licensed ... continue with app functionality\n // ...\n```\n\nExample:\n```javascript\nimport Resolver from \"@forge/resolver\";\n const resolver = new Resolver;\n\n resolver.define(\"checkLicense\", ({ context }) => {\n return context.license && context.license.active;\n }\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: my-trigger\n function: handler\n events:\n - avi:jira:commented:issue\n filter:\n appIsLicensed: true\n```\n\nExample:\n```text\nforge install --environment development --license <license value>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.271Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":57,"estimatedTokens":237}}43{"id":"doc-build_editions_for_forge_apps-060f2118","source":"documentation","title":"Build editions for Forge apps","url":"https://developer.atlassian.com/platform/marketplace/app-editions-forge-apps/","text":"Example:\n```typescript\ninterface Context {\n accountId?: string;\n cloudId?: string;\n extension: ExtensionData;\n license?: LicenseDetails;\n localId: string;\n locale: string;\n moduleKey: string;\n siteUrl: string;\n timezone: string;\n}\n\ninterface ExtensionData {\n [k: string]: any;\n}\n\ninterface LicenseDetails {\n active: boolean;\n billingPeriod: string;\n capabilitySet: 'capabilitystandard ' | 'capabilityadvanced' | null;\n ccpEntitlementId: string;\n ccpEntitlementSlug: string;\n isEvaluation: boolean;\n subscriptionEndDate: string | null;\n supportEntitlementNumber: string | null;\n trialEndDate: string | null;\n type: string;\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"localId\": \"localId\",\n \"cloudId\": \"abcd1234-1111-1111-1111-abcdef123456\",\n \"environmentId\": \"11111111-1111-1111-0111-111111111111\",\n \"environmentType\": \"DEVELOPMENT\",\n \"moduleKey\": \"module-key-name\",\n \"siteUrl\": \"https://atlassian.net\",\n \"extension\": {\n \"type\": \"jira:globalPage\"\n },\n \"accountId\": \"abcde123456\",\n \"license\": {\n \"active\": true,\n \"type\": \"commercial\",\n \"supportEntitlementNumber\": null,\n \"trialEndDate\": null,\n \"subscriptionEndDate\": null,\n \"isEvaluation\": null,\n \"billingPeriod\": \"MONTHLY\",\n \"ccpEntitlementId\": null,\n \"ccpEntitlementSlug\": null,\n \"capabilitySet\": \"capabilityadvanced\"\n },\n \"timezone\": \"Pacific/Auckland\",\n \"locale\": \"en_GB\"\n}\n```\n\nExample:\n```bash\nimport { view } from \"@forge/bridge\";\n\nconst context = await view.getContext();\nif (context.license.capabilitySet !== \"capabilityadvanced\") {\n console.log(\"App is not licensed for Advanced features\");\n return;\n}\n// App is licensed for Advanced features... continue to render...\n```\n\nExample:\n```bash\nimport { useProductContext } from \"@forge/react\";\n\nconst context = useProductContext();\nif (context.license.capabilitySet !== \"capabilityadvanced\") {\n console.log(\"App is not licensed for Advanced features\");\n return;\n}\n// App is licensed for Advanced features... continue to render...\n```\n\nExample:\n```bash\nimport { getAppContext } from \"@forge/api\";\n\nconst { appAri, appVersion, environmentAri, environmentType, invocationId, installationAri, moduleKey, license } = getAppContext();\n\nconsole.log(appAri.toString());\n// 'ari:cloud:ecosystem::app/00000000-0000-0000-0000-000000000000'\n\nconsole.log(appAri.appId);\n// '00000000-0000-0000-0000-000000000000'\n\nconsole.log(appVersion);\n// '1.0.0'\n\nconsole.log(environmentAri.toString());\n// 'ari:cloud:ecosystem::environment/00000000-0000-0000-0000-000000000000/11111111-1111-1111-0111-111111111111'\n\nconsole.log(environmentAri.environmentId);\n// '11111111-1111-1111-0111-111111111111'\n\nconsole.log(environmentType);\n// 'DEVELOPMENT'\n\nconsole.log(invocationId);\n// '33333333-3333-3333-0333-333333333333'\n\nconsole.log(installationAri.toString());\n// 'ari:cloud:ecosystem::installation/22222222-2222-2222-0222-222222222222'\n\nconsole.log(installationAri.installationId);\n// '22222222-2222-2222-0222-222222222222'\n\nconsole.log(moduleKey);\n// 'hello-world'\n\nconsole.log(JSON.stringify(license));\n//{\"active\":true,\"billingPeriod\":\"MONTHLY\",\"ccpEntitlementId\":\"NULL\",\"ccpEntitlementSlug\":\"NULL\",\"isEvaluation\":\"NULL\",\"subscriptionEndDate\":\"NULL\",\"supportEntitlementNumber\":\"NULL\",\"trialEndDate\":\"NULL\",\"type\":\"commercial\"}\n```\n\nExample:\n```bash\nimport { getAppContext } from \"@forge/api\";\n\nconst { appAri, appVersion, environmentAri, environmentType, invocationId, installationAri, moduleKey, license } = getAppContext();\nif (license.capabilitySet !== \"capabilityadvanced\") {\n console.log(\"App is not licensed for Advanced features\");\n return;\n}\n// App is licensed for Advanced features... continue...\n```\n\nExample:\n```bash\nexport const validateContextToken = async (invocationToken, appId) => {\n const jwksUrl = 'https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json';\n const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));\n\n const payload = await jose.jwtVerify(invocationToken, JWKS, {audience: appId});\n return payload;\n}\n\nconst payload = validateContextToken(invocationToken, appId)\nif(payload.app.license.capabilitySet !== \"capabilityadvanced\") {\n console.log(\"App is not licensed for Advanced features\");\n return;\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install --environment development --license Advanced\n```\n\nExample:\n```bash\nforge install --environment development --license Standard\n```\n\nExample:\n```bash\nlicensing:\n enabled: true\n editionsEnabled: true\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.273Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":184,"estimatedTokens":1124}}44{"id":"doc-create_a_server_app_package-3371b326","source":"documentation","title":"Create a server app package","url":"https://developer.atlassian.com/platform/marketplace/creating-a-server-app-package/","text":"Example:\n```text\n<modelVersion>4.0.0</modelVersion>\n<groupId>com.example.plugins.tutorial</groupId>\n<artifactId>tutorial-plugin-marketing</artifactId>\n<version>1.0-SNAPSHOT</version>\n\n<organization>\n <name>Example Company</name>\n <url>http://www.example.com/</url>\n</organization>\n\n<name>tutorial-plugin-marketing</name>\n<description>This is the com.example.plugins.tutorial:tutorial-plugin-marketing plugin for Atlassian Jira.</description>\n<packaging>atlassian-plugin</packaging>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<properties>\n <amps.version>3.7.2</amps.version>\n <confluence.data.version>3.5</confluence.data.version>\n <confluence.version>4.0</confluence.version>\n</properties>\n```\n\nExample:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n\n <param name=\"plugin-icon\">images/pluginIcon.jpg</param>\n <param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n <param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n <param name=\"vendor-icon\">images/vendorIcon.jpg</param>\n <param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n </plugin-info>\n ...\n\n</atlassian-plugin>\n```\n\nExample:\n```text\n<atlassian-plugin-marketing>\n \n <!--Describe names and versions of compatible products -->\n <compatibility>\n <product name=\"jira\" min=\"4.0\" max=\"4.4.1\"/>\n <product name=\"bamboo\" min=\"3.0\" max=\"3.1\"/>\n </compatibility>\n \n <!-- Describe your app logo and banner. The banner is only displayed in the UPM. -->\n <logo image=\"images/01_logo.jpg\"/>\n <banner image=\"images/02_banner.jpg\"/>\n \n <!-- Describe highlight and cropped highlight image assets, in order. -->\n <highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n </highlights>\n \n <!-- Describe additional screenshots you'd like listed on your app listing. -->\n <screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n </screenshots>\n \n</atlassian-plugin-marketing>\n```\n\nExample:\n```bash\nManifest-Version: 1.0\nArchiver-Version: Plexus Archiver\nCreated-By: Apache Maven\nBuilt-By: manthony\nBuild-Jdk: 1.6.0_31\nAtlassian-Build-Date: 2012-05-08T17:01:14-0700\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.275Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":93,"estimatedTokens":712}}45{"id":"doc-publish_and_list_your_app_for_isolated_cloud-e5069ca7","source":"documentation","title":"Publish and list your app for Isolated Cloud","url":"https://developer.atlassian.com/platform/marketplace/publish-and-list-ic-apps/","text":"Example:\n```bash\nforge deploy -e production\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.276Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":12,"estimatedTokens":22}}46{"id":"doc-parent_insights_and_discount_data-626956fa","source":"documentation","title":"Parent insights and discount data","url":"https://developer.atlassian.com/platform/marketplace/parent-insights-and-discounts-data/","text":"Example:\n```text\n{\n \"licenses\": [\n {\n ...\n ...\n ...\n \"evaluationOpportunitySize\": \"NA\",\n \"evaluationLicense\": \"2205182\",\n \"daysToConvertEval\": \"-59\",\n \"attribution\": {\n \"channel\": \"Paid Search\",\n \"referrerDomain\": \"www.google.com\",\n \"campaignMedium\": \"cpc\",\n \"campaignName\":\n \"P:opsgenie|O:ppm|V:google|G:de|L:de|F:consider|S:brand-trademark|M:exact|A:text|D:desktop\",\n \"campaignSource\": \"google\"\n },\n \"evaluationStartDate\": \"2021-03-25\",\n \"evaluationEndDate\": \"2021-05-24\",\n \"evaluationSaleDate\": \"2021-03-26\",\n \"parentProductBillingCycle\": \"ANNUAL\",\n \"parentProductName\": \"Jira\",\n \"installedOnSandbox\": \"Yes\",\n \"parentProductEdition\": \"Premium\"\n },\n ...\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"type\": (MANUAL|EXPERT|MARKETPLACE_PROMOTION|LOYALTY),\n \"amount\": Double\n}\n```\n\nExample:\n```text\n{\n \"transactions\": [\n {\n ...\n ...\n \"licenseId\": \"SEN-22034XXX\",\n \"addonKey\": \"com.innovalog.jmwe.jira-misc-workflow-extensions\",\n \"purchaseDetails\": {\n \"saleDate\": \"2021-03-26\",\n \"licenseType\": \"COMMERCIAL\",\n ...\n ...\n \"tier\": \"10 Users\",\n \"parentProductName\": \"Jira\",\n \"changeInParentProductEdition\": \"No\",\n \"parentProductEdition\": \"Standard\",\n \"purchasePrice\": 40.0,\n \"vendorAmount\": 34.0,\n \"partnerDiscountAmount\": 10.0,\n \"discounts\": [\n {\n \"type\": \"EXPERT\",\n \"amount\": 10.0\n }\n ],\n \"saleType\": \"New\",\n \"maintenanceStartDate\": \"2021-04-24\",\n \"maintenanceEndDate\": \"2022-04-24\"\n },\n ...\n ...\n }\n ...\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.277Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":86,"estimatedTokens":406}}47{"id":"doc-list_an_app_version_from_java-2db2bc92","source":"documentation","title":"List an app version from Java","url":"https://developer.atlassian.com/platform/marketplace/listing-an-app-version-from-java/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nFile file = new File(\"/path/to/my-app-1.0.jar\");\nArtifactId artifactId = client.assets().uploadArtifact(file);\n```\n\nExample:\n```text\nArtifactId artifactId = ArtifactId.fromUri(URI.create(\"http://my-server.com/url/to/download/my-app-1.0.jar\"));\n```\n\nExample:\n```text\nModelBuilders.AddonVersionBuilder versionBuilder = ModelBuilders.addonVersion();\n\n// These properties must always be specified for every new version:\nversionBuilder.buildNumber(100) // build numbers are unique values you choose to determine version ordering\n .paymentModel(PaymentModel.FREE) // is this version of the app free, paid via vendor, or paid via Atlassian?\n .status(AddonVersionStatus.PUBLIC) // you can make a version public or private\n .releaseDate(new LocalDate(\"2016-03-17\")) // this date will appear in the app's version history\n .releaseSummary(Option.some(\"this version fixes some bugs\")); // a brief description of the release\n\n// If you have an ArtifactId (see step 1), attach it to the version like this:\nversionBuilder.artifact(Option.some(artifactId))\n\n// Or, if there is no ArtifactId because the app is not directly installable, you will normally\n// provide an external download URI, like this:\nversionBuilder.externalLinkUri(AddonVersionExternalLinkType.BINARY, URI.create(\"http://my-vendor.com/page-for-getting-the-addon\"));\nversionBuilder.name(\"1.0\"); // Marketplace can detect the version string automatically for installable apps; but\n // if there is no installable artifact, you'll need to specify the version string yourself.\n\n// You do not need to specify compatibility details if they are the same as the previous version;\n// but if they have changed, you can set them for the new version like this (the numeric values\n// are application build numbers):\nversionBuilder.compatibilities(ImmutableList.of(\n ModelBuilders.versionCompatibilityForServer(ApplicationKey.JIRA, 70107, 71003)\n));\n\n// Finally, call build() to produce an AddonVersion object that has all of the specified properties.\nAddonVersion version = versionBuilder.build();\n```\n\nExample:\n```text\nAddonVersion createdVersion = client.addons().createVersion(\"my.app.key\", version);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.277Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":64,"estimatedTokens":642}}48{"id":"doc-marketing_funnel_insights-f0702a0e","source":"documentation","title":"Marketing funnel insights","url":"https://developer.atlassian.com/platform/marketplace/marketing-funnel-insights/","text":"Example:\n```json\n{\n \"_links\": {\n\n },\n \"licenses\": [\n {\n \"addonLicenseId\": \"123456\",\n \"licenseId\": \"SEN-123456\",\n \"addonKey\": \"com.atlassian.marketplace.app\",\n \"addonName\": \"Marketplace App\",\n \"hosting\": \"Cloud\",\n \"lastUpdated\": \"2019-10-01\",\n \"licenseType\": \"EVALUATION\",\n \"maintenanceStartDate\": \"2019-11-01\",\n \"maintenanceEndDate\": \"2022-11-01\",\n \"status\": \"active\",\n \"tier\": \"Evaluation\",\n \"contactDetails\": {\n \"company\": \"S.T.A.R. Labs\",\n \"country\": \"United States of America\",\n \"region\": \"Americas\",\n \"technicalContact\": {\n \"email\": \"barryallen@starlabs.com\",\n \"name\": \"Barry Allen\"\n }\n },\n \"attribution\": {\n \"channel\": \"Paid Search\",\n \"campaignName\": \"Marketplace App Campaign\",\n \"campaignSource\": \"google\",\n \"campaignMedium\": \"cpc\",\n \"campaignContent\": \"Marketplace App Campaign\",\n \"referrerDomain\": \"google.com\"\n }\n }\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.278Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":48,"estimatedTokens":264}}49{"id":"doc-list_an_app_version_using_rest-3e8f8e82","source":"documentation","title":"List an app version using REST","url":"https://developer.atlassian.com/platform/marketplace/listing-an-app-version-using-rest/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"assets\": {\n \"href\": \"/rest/2/assets\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/assets\"\n },\n \"artifact\": {\n \"href\": \"/rest/2/assets/artifact\"\n }\n}\n```\n\nExample:\n```bash\n$ curl --basic --user MY_USERNAME \\\n -H \"Content-Type: application/octet-stream\" \\\n --data-binary @/LOCATION_OF_MY_FILE/my-app-1.0.jar \\\n https://marketplace.atlassian.com/rest/2/assets/artifact?file=my-app-1.0.jar\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/assets/artifact%2F1324354154%2Fmy-app-1.0.jar\"\n }\n } [...you can ignore the rest of the JSON properties...]\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"artifact\": {\n \"href\": \"/rest/2/assets/artifact%2F1324354154%2Fmy-app-1.0.jar\"\n }\n },\n \"name\": \"1.0\",\n \"buildNumber\": 100,\n \"status\": \"private\",\n \"paymentModel\": \"free\",\n \"compatibilities\": [\n {\n \"application\": \"jira\",\n \"server\": {\n \"min\": {\n \"build\": 70107\n },\n \"max\": {\n \"build\": 71003\n }\n }\n }\n ],\n \"release\": {\n \"date\": \"2016-03-17\",\n \"beta\": false,\n \"supported\": false\n },\n \"text\": {\n \"releaseSummary\": \"here is a new version\"\n },\n \"vendorLinks\": {\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"addons\": {\n \"href\": \"/rest/2/addons\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons\"\n }, [...some more links that you can ignore...]\n \"byKey\": {\n \"href\": \"/rest/2/addons/{addonKey}\",\n \"templated\": true\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons/my.excellent.app\"\n },\n \"versions\": {\n \"href\": \"/rest/2/addons/my.excellent.app/versions\"\n }\n}\n```\n\nExample:\n```bash\n$ curl --basic --user MY_USERNAME \\\n -H \"Content-Type: application/json\" \\\n --data-binary @/path/to/my-json-document.json \\\n https://marketplace.atlassian.com/rest/2/addons/my.excellent.app/versions\n```\n\nExample:\n```bash\nLocation: /rest/2/addons/my.excellent.app/versions/build/100\n```\n\nExample:\n```bash\n{\n \"errors\": [\n {\n \"path\": \"/buildNumber\",\n \"message\": \"Must be unique\"\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.279Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":169,"estimatedTokens":676}}50{"id":"doc-invoke_remote_api-0b81644d","source":"documentation","title":"Invoke Remote API","url":"https://developer.atlassian.com/platform/forge/runtime-reference/invoke-remote-api/","text":"Example:\n```typescript\nimport { RequestInit, Response } from 'undici';\n\ntype InvokeRemoteOptions = {\n path: string;\n};\n\ntype APIResponse = Pick<Response, 'json' | 'text' | 'arrayBuffer' | 'ok' | 'status' | 'statusText' | 'headers'>\n\nexport async function invokeRemote(\n remoteKey: string,\n options: RequestInit & InvokeRemoteOptions\n): Promise<APIResponse>;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { invokeRemote } from '@forge/api';\n\nconst res = await invokeRemote('my-remote-key', {\n path: `/tasks/`,\n method: 'POST',\n headers: {\n 'x-header-key': 'x-header-value'\n },\n body: JSON.stringify({\n department: 'Ecosystem',\n team: 'Forge',\n description: 'Write docs'\n })\n});\n\nif (!res.ok) {\n throw new Error(`invokeRemote failed: ${res.status}`);\n}\n\nconst json = await res.json();\nconsole.log(`Created task: ${JSON.stringify(json.task)}`);\n```\n\nExample:\n```javascript\nimport { invokeRemote } from '@forge/api';\n\nconst res = await invokeRemote('my-remote-key', {\n path: `/tasks/?team=Forge`,\n method: 'GET'\n});\n\nif (!res.ok) {\n throw new Error(`invokeRemote failed: ${res.status}`);\n}\n\nconst json = await res.json();\nconsole.log(`Tasks: ${JSON.stringify(json)}`);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.280Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":65,"estimatedTokens":306}}51{"id":"doc-handling_streaming_errors-51bc8efe","source":"documentation","title":"Handling streaming errors","url":"https://developer.atlassian.com/platform/forge/runtime-reference/forge-llms-api-errors/","text":"Example:\n```javascript\n`You were interrupted in your previous attempt.\nYour original instruction was \"${originalUserPrompt}\".\nContinue from the following interrupted output: ${storedOutput}`\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nlet isStreamComplete = false;\nlet response;\nconst checkIfFinishReasonExists = (chunk) =>\n !!chunk.choices.find(({ finish_reason }) => finish_reason !== undefined);\n\ntry {\n response = await stream(myPrompt);\n\n for await (const chunk of response) {\n if (checkIfFinishReasonExists(chunk)) {\n isStreamComplete = true;\n }\n }\n} catch (e) {\n // Exceptions are not thrown for finishing streams with incomplete responses.\n} finally {\n response?.close();\n}\n\nconsole.log(`Is the stream complete? ${isStreamComplete}`);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.280Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":38,"estimatedTokens":197}}52{"id":"doc-appcontext_api-282ac709","source":"documentation","title":"AppContext API","url":"https://developer.atlassian.com/platform/forge/runtime-reference/app-context-api/","text":"Example:\n```typescript\nexport declare function getAppContext(): AppContext;\n\nexport type AppContext = {\n appAri: AppAri;\n appVersion: string;\n environmentAri: EnvironmentAri;\n environmentType: string;\n invocationId: string;\n invocationRemainingTimeInMillis(): number;\n installationAri: InstallationAri;\n moduleKey: string;\n license?: License;\n installation?: Installation;\n};\n\nexport type AppAri = {\n appId: string;\n toString: () => string;\n};\nexport type EnvironmentAri = {\n environmentId: string;\n toString: () => string;\n};\nexport type InstallationAri = {\n installationId: string;\n toString: () => string;\n};\n\nexport type License = {\n active?: boolean;\n /** @deprecated Use `active` instead. */\n isActive?: boolean;\n billingPeriod?: string;\n capabilitySet?: CapabilitySet;\n ccpEntitlementId?: string;\n ccpEntitlementSlug?: string;\n isEvaluation?: boolean;\n subscriptionEndDate?: string;\n supportEntitlementNumber?: string;\n trialEndDate?: string;\n type?: string;\n};\n\nexport interface ContextAri {\n cloudId?: string;\n workspaceId?: string;\n toString: () => string;\n}\n\nexport interface Installation {\n ari: InstallationAri;\n contexts: ContextAri[];\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nimport { getAppContext } from \"@forge/api\";\n\nconst { appAri, appVersion, environmentAri, environmentType, invocationId, installationAri, moduleKey, license } = getAppContext();\n\nconsole.log(appAri.toString());\n// 'ari:cloud:ecosystem::app/00000000-0000-0000-0000-000000000000'\n\nconsole.log(appAri.appId);\n// '00000000-0000-0000-0000-000000000000'\n\nconsole.log(appVersion);\n// '1.0.0'\n\nconsole.log(environmentAri.toString());\n// 'ari:cloud:ecosystem::environment/00000000-0000-0000-0000-000000000000/11111111-1111-1111-0111-111111111111'\n\nconsole.log(environmentAri.environmentId);\n// '11111111-1111-1111-0111-111111111111'\n\nconsole.log(environmentType);\n// 'DEVELOPMENT'\n\nconsole.log(invocationId);\n// '33333333-3333-3333-0333-333333333333'\n\nconsole.log(installationAri.toString());\n// 'ari:cloud:ecosystem::installation/22222222-2222-2222-0222-222222222222'\n\nconsole.log(installationAri.installationId);\n// '22222222-2222-2222-0222-222222222222'\n\nconsole.log(moduleKey);\n// 'hello-world'\n\nconsole.log(JSON.stringify(license));\n//{\"active\":true,\"billingPeriod\":\"MONTHLY\",\"ccpEntitlementId\":\"NULL\",\"ccpEntitlementSlug\":\"NULL\",\"isEvaluation\":\"NULL\",\"subscriptionEndDate\":\"NULL\",\"supportEntitlementNumber\":\"NULL\",\"trialEndDate\":\"NULL\",\"type\":\"commercial\"}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.280Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":104,"estimatedTokens":636}}53{"id":"doc-manifest_forge_apps-e138be3f","source":"documentation","title":"Manifest – Forge apps","url":"https://developer.atlassian.com/platform/forge/manifest-reference/","text":"Example:\n```text\napp:\n id: \"ari:cloud:ecosystem::app/406d303d-0393-4ec4-ad7c-1435be94583a\"\n licensing:\n enabled: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { readFileSync } from 'node:fs';\n\nconst data = readFileSync('./file.txt', 'utf8');\n```\n\nExample:\n```javascript\nimport { execFileSync } from 'node:child_process';\n\nconst stdout = execFileSync('./binary', ['--args'], {\n stdio: 'pipe',\n encoding: 'utf8',\n});\n```\n\nExample:\n```text\nremotes:\n - key: connect-app-server\n baseUrl: https://hello-world-app.example.com\napp:\n connect:\n key: hello-world\n remote: connect-app-server\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.281Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":43,"estimatedTokens":159}}54{"id":"doc-async_events_api-402eff98","source":"documentation","title":"Async Events API","url":"https://developer.atlassian.com/platform/forge/runtime-reference/async-events-api/","text":"Example:\n```javascript\nimport { Queue } from '@forge/events';\n\nconst queue = new Queue({ key: 'queue-name' });\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nimport { Queue } from '@forge/events';\n\ninterface IssueEventBody {\n issueKey: string;\n}\n\nconst queue = new Queue<IssueEventBody>({ key: 'queue-name' });\n\nawait queue.push({ body: { issueKey: 'ABC-123' } }); // Allowed\n// await queue.push({ body: { foo: 'bar' } }); // Type error\n```\n\nExample:\n```typescript\nexport type Body = Record<string, unknown>;\nexport interface Concurrency {\n key: string;\n limit: number;\n}\nexport interface PushEvent {\n body: Body;\n delayInSeconds?: number;\n concurrency?: Concurrency;\n}\nexport interface PushResult {\n jobId: string;\n}\n\nconst result: PushResult = await queue.push(PushEvent | PushEvent[]);\n```\n\nExample:\n```javascript\n// Push a single event\nawait queue.push({ body: { hello: 'world' } });\n\n// Push multiple events\nawait queue.push([\n { body: { greeting: 'hello' } },\n { body: { farewell: 'goodbye' } }\n]);\n\n// Delay the processing of the event by 5 seconds\nawait queue.push({\n body: { hello: 'world' },\n delayInSeconds: 5\n});\n```\n\nExample:\n```text\nmodules:\n consumer:\n - key: queue-consumer\n # Name of the queue for which this consumer will be invoked\n queue: queue-name\n # Function to be called with payload\n function: consumer-function\n function:\n - key: consumer-function\n handler: consumer.handler\n timeoutSeconds: 600\n```\n\nExample:\n```typescript\nimport { AsyncEvent } from '@forge/events';\n\nexport async function handler(event: AsyncEvent, context) {\n // Access the event body\n const data = event.body;\n // Process the event\n}\n```\n\nExample:\n```typescript\nimport { AsyncEvent } from '@forge/events';\n\ninterface IssueEventBody {\n issueKey: string;\n}\n\nexport async function handler(event: AsyncEvent<IssueEventBody>, context) {\n // event.body is typed as IssueEventBody\n const { issueKey } = event.body;\n // Process the event\n}\n```\n\nExample:\n```javascript\nexport async function handler(event, context) {\n // Access the event body\n const data = event.body;\n // Process the event\n}\n```\n\nExample:\n```javascript\n// Get the job ID\nconst { jobId } = await queue.push([\n { body: { event: 'event1' } },\n { body: { event: 'event2' } }\n]);\n\n// Get the JobProgress object\nconst jobProgress = queue.getJob(jobId);\n\n// Get stats of a particular job\nconst { success, inProgress, failed } = await jobProgress.getStats();\n```\n\nExample:\n```typescript\nimport { Queue, AsyncEvent } from '@forge/events';\n\nconst queue = new Queue({ key: 'queue-name' });\n\nexport async function handler(event: AsyncEvent, context) {\n const jobProgress = queue.getJob(event.jobId);\n\n try {\n // process the event\n } catch (error) {\n // You can cancel the job when an error happens\n await jobProgress.cancel();\n }\n}\n```\n\nExample:\n```javascript\nawait queue.push({\n body: { ... },\n concurrency: {\n key: 'my-key',\n limit: 1\n }\n});\n```\n\nExample:\n```typescript\ninterface RetryContext {\n retryCount: number;\n retryReason: string;\n retryData: any;\n retentionWindow?: RetentionWindow;\n}\n\ninterface RetentionWindow {\n startTime: string;\n remainingTimeMs: number;\n}\n```\n\nExample:\n```typescript\nimport { AsyncEvent } from '@forge/events';\n\nexport async function handler(event: AsyncEvent) {\n // retryContext is only present on retries\n if (event.retryContext) {\n const {\n retryCount,\n retryData,\n retryReason,\n retentionWindow: {\n startTime,\n remainingTimeMs\n }\n } = event.retryContext;\n //...\n }\n}\n```\n\nExample:\n```typescript\ninterface RetryOptions {\n retryAfter: number;\n retryReason: InvocationErrorCode;\n retryData?: any;\n}\n```\n\nExample:\n```typescript\nimport { AsyncEvent, InvocationError, InvocationErrorCode } from '@forge/events';\n\nexport async function handler(event: AsyncEvent) {\n const userName = event.body.userName;\n const response = await callExternalApi(userName);\n\n if (response.headers.has('Retry-After')) {\n return new InvocationError({\n retryAfter: parseInt(response.headers.get('Retry-After')),\n retryReason: InvocationErrorCode.FUNCTION_UPSTREAM_RATE_LIMITED,\n retryData: {\n userName: userName\n }\n });\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.282Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":224,"estimatedTokens":1103}}55{"id":"doc-i18n_api_for_forge_functions-b1aa0bbd","source":"documentation","title":"i18n API for Forge functions","url":"https://developer.atlassian.com/platform/forge/runtime-reference/i18n/","text":"Example:\n```typescript\nconst createTranslationFunction = (\n locale: ForgeSupportedLocaleCode\n): Promise<TranslationFunction>\n\ntype TranslationFunction = (i18nKey: string, defaultValue?: string) => string;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport api, { i18n } from \"@forge/api\";\n\nconst resolver = new Resolver();\n\nresolver.define(\"getText\", async (req) => {\n const t = await i18n.createTranslationFunction('en-US');\n return JSON.stringify({\n text: t(\"page.content\"),\n });\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```typescript\nconst getTranslations = (\n locale: ForgeSupportedLocaleCode,\n options: GetTranslationsOptions = { fallback: true }\n): Promise<GetTranslationsResult>\n\ninterface GetTranslationsOptions {\n fallback: boolean;\n}\n\ninterface GetTranslationsResult {\n locale: ForgeSupportedLocaleCode;\n translations: TranslationResourceContent | null;\n}\n\ninterface TranslationResourceContent {\n [key: string]: string | TranslationResourceContent;\n}\n```\n\nExample:\n```javascript\nimport api, { i18n } from \"@forge/api\";\n\nconst resolver = new Resolver();\n\nresolver.define(\"getText\", async (req) => {\n const { translations, locale } = await i18n.getTranslations(\"en-US\");\n return JSON.stringify({\n translations,\n locale,\n });\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nconst getConfluenceLocale = async () => {\n const response = await api\n .asUser()\n .requestConfluence(route`/wiki/rest/api/user/current`, {\n headers: {\n Accept: \"application/json\",\n },\n });\n const confluence = await response.json();\n\n return confluence.locale;\n};\n```\n\nExample:\n```javascript\nconst getJiraLocale = async () => {\n const response = await api\n .asUser()\n .requestJira(route`/rest/api/3/mypreferences/locale`, {\n headers: {\n Accept: \"application/json\",\n },\n });\n const jira = await response.json();\n\n return jira.locale;\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.283Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":102,"estimatedTokens":497}}56{"id":"doc-forge_llms_api_reference-0677c88e","source":"documentation","title":"Forge LLMs API reference","url":"https://developer.atlassian.com/platform/forge/runtime-reference/forge-llms-api-reference/","text":"Example:\n```text\nError: LLM package is used but 'llm' module is not defined in the manifest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n llm:\n - key: llm-app\n model:\n - claude\n```\n\nExample:\n```javascript\nlist() => Promise<ModelListResponse>\nchat(Prompt) => Promise<LlmResponse>\nstream(Prompt) => Promise<StreamResponse>\n```\n\nExample:\n```typescript\ntype Prompt = LlmRequest & {\n model: string;\n};\n\ninterface LlmRequest {\n messages: Message[];\n temperature?: number;\n max_completion_tokens?: number;\n top_p?: number;\n tools?: Tool[];\n tool_choice?: ToolChoice;\n}\ntype Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage;\n\ninterface SystemMessage {\n content: Content;\n role: \"system\";\n}\ninterface UserMessage {\n content: Content;\n role: \"user\";\n}\ninterface ToolMessage {\n content: Content;\n role: \"tool\";\n tool_call_id?: string;\n name?: string;\n}\ninterface AssistantMessage {\n content: Content;\n role: \"assistant\";\n tool_calls?: ToolCall[];\n}\n\ninterface ToolCall {\n id: string;\n type: \"function\";\n index: number;\n function: {\n name: string;\n arguments: object;\n };\n}\n\ntype ToolChoice =\n | \"auto\"\n | \"none\"\n | \"required\"\n | {\n type: \"function\";\n function: {\n name: string;\n };\n };\n\ninterface Tool {\n type: \"function\";\n function: {\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n };\n}\n\ntype Content = string | ContentPart[];\n\ntype ContentPart = TextPart;\n\ninterface TextPart {\n type: \"text\";\n text: string;\n}\n```\n\nExample:\n```javascript\ninterface LlmResponse {\n choices: Choice[];\n usage?: Usage;\n}\n\ninterface Choice {\n finish_reason: string;\n index?: number;\n message: AssistantMessage;\n}\n\ninterface Usage {\n input_tokens?: number;\n output_tokens?: number;\n total_tokens?: number;\n}\n\ninterface AssistantMessage {\n content: Content;\n role: \"assistant\";\n tool_calls?: ToolCall[];\n}\n\ninterface StreamResponse extends AsyncIterable<LlmResponse> {\n close(): Promise<void> | undefined;\n}\n\ninterface ModelListResponse {\n models: {\n model: string,\n status: \"active\" | \"deprecated\",\n }[];\n}\n```\n\nExample:\n```javascript\nimport { chat } from \"@forge/llm\";\ntry {\n const response = await chat({\n model: \"claude-opus-4-6\",\n messages: [\n {\n role: \"user\",\n content: \"Write a short poem about Forge LLMs.\",\n },\n ],\n });\n\n console.log(\"#### LLM response:\", JSON.stringify(response));\n} catch (err) {\n console.error(\"#### LLM request failed:\", {\n error: err.context?.responseText,\n });\n throw err;\n}\n```\n\nExample:\n```javascript\nimport { stream } from \"@forge/llm\";\ntry {\n const response = await stream({\n model: \"claude-opus-4-6\",\n messages: [\n {\n role: \"user\",\n content: \"Write a short poem about Forge LLMs.\",\n },\n ],\n });\n\n for await (const chunk of response) {\n console.log(\"#### LLM response:\", JSON.stringify(chunk));\n }\n\n response.close();\n} catch (err) {\n console.error(\"#### LLM request failed:\", {\n error: err.context?.responseText,\n });\n throw err;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.283Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":191,"estimatedTokens":779}}57{"id":"doc-license_api-3be9dd65","source":"documentation","title":"License API","url":"https://developer.atlassian.com/platform/forge/apis-reference/license-api/","text":"Example:\n```javascript\nimport { asApp } from '@forge/api';\n\n// Self-query: retrieve the calling app's own license\nconst response = await asApp().requestAtlassian(`/forge/app/v1/license`, {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'GET'\n});\nconst body = await response.json();\nconsole.log(`Response: ${response.status}`, body);\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"results\": [\n {\n \"appId\": \"35559c21-6120-406b-b7cd-d87f468f6d32\",\n \"data\": {\n \"active\": true,\n \"type\": \"commercial\",\n \"isEvaluation\": false,\n \"trialEndDate\": null,\n \"subscriptionEndDate\": \"2027-01-01T00:00:00.000Z\",\n \"billingPeriod\": \"ANNUAL\",\n \"supportEntitlementNumber\": \"SEN-12345678\",\n \"ccpEntitlementId\": \"ent-abc123\",\n \"ccpEntitlementSlug\": \"my-app-commercial\",\n \"capabilitySet\": \"capabilityAdvanced\"\n }\n },\n {\n \"appId\": \"11259c21-6120-406b-b7cd-d87f468f6d99\",\n \"error\": {\n \"type\": \"NOT_FOUND\",\n \"message\": \"No license found for this app on the current site\"\n }\n }\n ]\n}\n```\n\nExample:\n```json\n{\n \"code\": 400,\n \"message\": \"Invalid appId format\"\n}\n```\n\nExample:\n```json\n{\n \"code\": 500,\n \"message\": \"An internal error occurred\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.283Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":68,"estimatedTokens":322}}58{"id":"doc-installation_details_api-8b7348dc","source":"documentation","title":"Installation Details API","url":"https://developer.atlassian.com/platform/forge/apis-reference/installation-details-api/","text":"Example:\n```javascript\nimport { asApp } from '@forge/api';\n\nconst appId = '35559c21-6120-406b-b7cd-d87f468f6d32';\nconst envId = '3aaa01b0-02cc-1d00-3eee-1f01e001d1c0';\nconst installationId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';\n\ntry {\n const response = await asApp().requestAtlassian(\n `/forge/installation/v1/${appId}/${envId}/installation/${installationId}`,\n {\n method: 'GET'\n }\n );\n\n if (!response.ok) {\n throw new Error(`Request failed with status ${response.status}`);\n }\n\n const body = await response.json();\n console.log(`Response: ${response.status}`, body);\n} catch (error) {\n console.error('Failed to retrieve installation details', error);\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"version\": \"12.0.3\",\n \"installationContext\": {\n \"id\": \"ari:cloud:confluence::site/a12bc345-678d-9e1f-ab10-1bcd112131e4\"\n },\n \"secondaryInstallationContexts\": [\n {\n \"id\": \"ari:cloud:jira::site/b23cd456-789e-1f20-bc21-2cde223242f5\"\n }\n ],\n \"permissions\": {\n \"scopes\": [\"read:jira-user\", \"storage:app\"],\n \"external\": {\n \"fetch\": {\n \"backend\": [\"https://api.example.com\"],\n \"client\": [\"https://cdn.example.com\"]\n },\n \"images\": [\"https://images.example.com\"],\n \"media\": [\"https://media.example.com\"],\n \"styles\": [\"https://styles.example.com\"],\n \"scripts\": [\"https://scripts.example.com\"],\n \"frames\": [\"https://frames.example.com\"],\n \"fonts\": [\"https://fonts.example.com\"]\n }\n },\n \"isSandbox\": false\n}\n```\n\nExample:\n```json\n{\n \"type\": \"AuthorizationError\",\n \"code\": 401,\n \"message\": \"Authorization failed\"\n}\n```\n\nExample:\n```json\n{\n \"type\": \"InternalError\",\n \"code\": 500,\n \"message\": \"An internal error occurred\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.284Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":83,"estimatedTokens":438}}59{"id":"doc-customer_managed_egress_and_remotes_preview-3337ed39","source":"documentation","title":"Customer-managed egress and remotes (Preview)","url":"https://developer.atlassian.com/platform/forge/customer-managed-egress-and-remotes/","text":"Example:\n```json\n{\n \"key\": \"egress-group\",\n \"description\": \"Access to example.com\",\n \"configured\": [\n {\n \"domain\": \"https://api.example.com\",\n \"type\": [\"FETCH_BACKEND_SIDE\", \"FETCH_CLIENT_SIDE\"]\n },\n {\n \"domain\": \"https://media.example.com\",\n \"type\": [\"IMAGES\", \"MEDIA\"]\n },\n {\n \"domain\": \"https://cdn.example.com\",\n \"type\": [\"FONTS\", \"SCRIPTS\", \"STYLES\"]\n }\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"key\": \"external-api\",\n \"configured\": {\n \"endpoint\": \"https://api.external.com\"\n }\n}\n```\n\nExample:\n```text\npermissions:\n external:\n configurable:\n enabled: true\n```\n\nExample:\n```text\nremotes:\n - key: my-site-1\n configurable:\n name: \"My site\"\n description: \"This will be used to make connections to My Site\"\n supportedPatterns:\n - \"*.example.com\"\n```\n\nExample:\n```text\nremotes:\n - key: my-site-1\n baseUrl: \"https://default.example.com\"\n configurable:\n name: \"My site\"\n description: \"This will be used to make connections to My Site\"\n supportedPatterns:\n - \"*.example.com\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.284Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":70,"estimatedTokens":282}}60{"id":"doc-create_an_agentic_llm_web_trigger_using_forge_ll-ff3dec57","source":"documentation","title":"Create an agentic LLM web trigger using Forge LLMs","url":"https://developer.atlassian.com/platform/forge/create-an-agentic-llm-webtrigger-app/","text":"Example:\n```javascript\nimport { chat } from \"@forge/llm\";\n\nexports.runSync = async (event, context) => {\n let finalResponse={};\n\n try {\n /* 1. Build and request the initial prompt. */\n const llmPrompt = JSON.parse(event.body);\n finalResponse = await chat(llmPrompt);\n\n /* 2. Handle tool_use: executed required \"tool\", and share the response with LLM */\n const firstChoice = finalResponse.choices[0];\n if (firstChoice?.message?.role === 'assistant' && !!firstChoice.message?.tool_calls) {\n const promptWithToolResult = await executeToolAndBuildToolUsePrompt(\n {\n toolUseAssistantChoice: firstChoice,\n initialPrompt: llmPrompt\n });\n\n /* Update finalResponse */\n finalResponse = await chat(promptWithToolResult);\n }\n\n return buildOutput(\n JSON.stringify({\n success: true,\n choices: finalResponse.choices,\n }),\n 200,\n \"OK\"\n );\n } catch (err) {\n return buildOutput(\n JSON.stringify({\n success: false,\n error: err?.message || err,\n errorDetails: err.context?.responseText\n }),\n\n err.statusCode ?? 500,\n err.statusText ?? \"Unknown Error\"\n );\n }\n};\n\n/* 3. Execute the \"tool\" and build a new prompt */\nasync function executeToolAndBuildToolUsePrompt({ toolUseAssistantChoice, initialPrompt }) {\n const toolCall = toolUseAssistantChoice?.message.tool_calls?.[0];\n if (toolUseAssistantChoice.finish_reason !== 'tool_use' || !toolCall || toolCall.function?.name !== 'get_current_weather') return null;\n\n let args = {};\n try { args = typeof toolCall.function.arguments === 'string' ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments; } catch {}\n\n const result = await get_current_weather(args.location, args.unit);\n const requiredToolUseAssistantMessage = toolUseAssistantChoice.message; // Add proper validation when needed\n\n /* The prompt must include three types of messages:\n * 1. The original user and/or system messages from the initial prompt.\n * 2. The assistant's message indicating a tool should be used (`tool_use`).\n * 3. A tool message containing the function's result and context for the assistant.\n */\n return {\n messages: [\n ...initialPrompt.messages,\n requiredToolUseAssistantMessage,\n {\n role: \"tool\",\n content: [{ type: \"text\", text: JSON.stringify(result), tool_use: toolCall.id }],\n tool_call_id: toolCall.id,\n name: toolCall.function.name\n },\n ],\n model: initialPrompt.model,\n };\n}\n\n/* 4. A mock implementation of a weather tool */\nasync function get_current_weather(location, unit = 'celsius') {\n const temperature = Math.floor(Math.random() * 16) + 15; // 15°C to 30°C\n const conditions = ['clear skies', 'cloudy', 'rainy', 'sunny', 'windy', 'foggy'];\n const condition = conditions[Math.floor(Math.random() * conditions.length)];\n return { location, unit, temperature, condition };\n}\n\nconst buildOutput = (body, statusCode, statusText) => ({\n body,\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${Math.random()}`]\n },\n statusCode,\n statusText,\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy\n# ✔ Deployed\nforge install # OR 'forge install upgrade'\n# ✔ Install in <Atlassian app> complete!\n```\n\nExample:\n```bash\nforge webtrigger\n```\n\nExample:\n```bash\n# ? Select an installation: (Use the Enter key to select)\n# ┌───────────────┬────────────────────┬────────────────┬───────────────┐\n# │ Environment │ Site │ Atlassian apps │ Major Version │\n# ├───────────────┼────────────────────┼────────────────┼───────────────┤\n# │ ❯ environment │ <your-site> │ <App> │ 2 (Latest) │\n# └───────────────┴────────────────────┴────────────────┴───────────────┘\n# ? Select a web trigger: llm-webtrigger-app-webtrigger-sync\n# Copy your web trigger URL below to start using it:\n# https://<WEBTRIGGER_URL>\n```\n\nExample:\n```json\n{\n \"model\": \"claude-haiku-4-5-20251001\",\n \"messages\": [\n { \"role\": \"user\", \"content\": \"What is the weather like in Melbourne?\" },\n {\n \"role\": \"system\",\n \"content\": \"Your role is to support our AI weather tool.\"\n }\n ],\n \"tools\": [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"description\": \"Get the current weather in a given location\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": { \"type\": \"string\", \"description\": \"The city and state, e.g. Sydney, CA\" },\n \"unit\": { \"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"] }\n },\n \"required\": [\"location\"]\n }\n }\n }],\n \"tool_choice\": \"auto\"\n}\n```\n\nExample:\n```bash\ncurl -X POST https://<WEBTRIGGER_URL> \\\n -H \"Content-Type: application/json\" \\\n -d @request.json | jq \".\"\n```\n\nExample:\n```json\n{\n \"success\": true,\n \"choices\": [\n {\n \"finish_reason\": \"end_turn\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Currently in Melbourne, it's 22°C and rainy. I recommend bringing an umbrella or raincoat if you're heading out.\"\n }\n ]\n }\n }\n ]\n}\n```\n\nExample:\n```json\n{\n \"messages\": [\n {\n \"role\": \"system | user | assistant | tool\",\n \"content\": \"string | ContentPart[]\"\n }\n ],\n \"temperature\": number,\n \"max_completion_tokens\": number,\n \"top_p\": number,\n \"tools\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"string\",\n \"description\": \"string\",\n \"parameters\": \"object\"\n }\n }\n ],\n \"tool_choice\": \"auto | none | required | { type: 'function', function: { name: string } }\"\n}\n```\n\nExample:\n```text\n- messages: The conversation content, including system, user, assistant, or tool messages.\n```\n\nExample:\n```text\n- temperature: Controls randomness in responses; higher values produce more creative output.\n- max_completion_tokens: Limits the maximum number of tokens in the generated response.\n- top_p: Controls diversity via nucleus sampling; lower values make output more focused.\n- tools: List of function tools available for the LLM to use during generation.\n- tool_choice: Specifies which tool (if any) the LLM should use for the current prompt.\n```\n\nExample:\n```javascript\n{\n \"choices\": [\n {\n \"finish_reason\": string, // \"tool_use\", \"end_turn\", \"max_tokens\", etc.\n \"index\": 0,\n \"message\": {\n \"role\": string, // \"assistant\"\n \"content\": string | ContentPart[],\n \"tool_calls\": [\n {\n \"id\": \"<tool_call_id>\",\n \"type\": \"function\",\n \"index\": number,\n \"function\": {\n \"name\": \"<function_name>\",\n \"arguments\": { /* function arguments */ }\n }\n }\n ]\n }\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.286Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":255,"estimatedTokens":1742}}61{"id":"doc-create_an_llm_web_trigger_app_using_forge_llm-58059f97","source":"documentation","title":"Create an LLM web trigger app using Forge LLM","url":"https://developer.atlassian.com/platform/forge/create-an-llm-webtrigger-app/","text":"Example:\n```bash\n# First, authenticate with Forge if you haven’t already:\nforge login\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n# ? Enter a name for your app: llm-webtrigger-app\n# ? Select an Atlassian app or platform tool: Show All\n# ? Select a category: Show All\n# ? Select a template: webtrigger\n```\n\nExample:\n```bash\ncd llm-webtrigger-app\n```\n\nExample:\n```text\nmodules:\n # 'llm' module is required for LLM functionalities\n llm:\n - key: my-first-llm-app\n model:\n - claude\n # webtrigger module added automatically when you create a \"webtrigger\" app\n webtrigger:\n - key: llm-webtrigger-app-webtrigger-sync\n function: sync\n urlFormat: v2\n response:\n type: dynamic\n function:\n - key: sync\n handler: index.runSync\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/<your-unique-app-uuid>\n```\n\nExample:\n```bash\nyarn add @forge/llm\n```\n\nExample:\n```json\n{\n \"dependencies\": {\n \"@forge/llm\": \"^0.0.15\"\n }\n}\n```\n\nExample:\n```text\n.\n├── AGENTS.md\n├── manifest.yml\n├── package.json\n├── src\n│ └── index.js\n└── yarn.lock\n└── node_modules\n```\n\nExample:\n```javascript\n// 1. Install and import forge-llm-sdk\nimport { chat } from '@forge/llm'\n\nexports.runSync = async (event, context) => {\n let finalResponse={};\n\n try {\n // 2. Parse the incoming JSON body as LLM prompt\n const llmPrompt = JSON.parse(event.body);\n\n // 3. Call the Forge LLMs SDK's chat function\n finalResponse = await chat(llmPrompt);\n\n // 4. Return the LLM response with status code 200\n console.log(\"#### LLM response:\", JSON.stringify(finalResponse, null, 2 ));\n\n return buildOutput(\n JSON.stringify({\n success: true,\n choices: finalResponse.choices,\n }),\n 200,\n \"OK\");\n\n } catch(err) {\n // 5. Handle errors\n console.error(\"Error during LLM chat:\", err);\n return buildOutput({\n success: false,\n error: err?.message || err,\n errorDetails: err.context?.responseText\n },\n err.statusCode ?? 500,\n err.statusText ?? \"Unknown Error\"\n );\n }\n};\n\nconst buildOutput = (body, statusCode, statusText) => ({\n body,\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${Math.random()}`]\n },\n statusCode,\n statusText,\n});\n```\n\nExample:\n```bash\nforge deploy\n# ✔ Deployed\n```\n\nExample:\n```bash\nforge install\n# ? Select an Atlassian app or platform tool: Jira\n# ? Enter the site URL: <your-site-url>\n# ? Do you want to continue? Yes\n# ✔ Install in Jira complete!\n```\n\nExample:\n```bash\nforge webtrigger\n```\n\nExample:\n```text\n# ? Select an installation: (Use the Enter key to select)\n# ┌───────────────┬────────────────────┬────────────────┬───────────────┐\n# │ Environment │ Site │ Atlassian apps │ Major Version │\n# ├───────────────┼────────────────────┼────────────────┼───────────────┤\n# │ ❯ environment │ <your-site> │ <App> │ 2 (Latest) │\n# └───────────────┴────────────────────┴────────────────┴───────────────┘\n# ? Select a web trigger: llm-webtrigger-app-webtrigger-sync\n# Copy your web trigger URL below to start using it:\n# https://<WEBTRIGGER_URL>\n```\n\nExample:\n```json\n{\n \"model\": \"claude-haiku-4-5-20251001\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a meticulous problem-solver. Solve the following problem. Think step by step, showing each intermediate calculation and the rationale behind it. After reasoning, clearly state the final answer on a new line prefixed with 'Final answer:'.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Problem: A shop sells pens at $2 each or 3 for $5. If I need 17 pens, what's the minimum cost? \"\n }\n ]\n}\n```\n\nExample:\n```bash\ncurl -X POST https://<WEBTRIGGER_URL> \\\n -H \"Content-Type: application/json\" \\\n -d @request.json | jq \".\"\n```\n\nExample:\n```json\n{\n \"success\": true,\n \"choices\": [\n {\n \"finish_reason\": \"end_turn\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Let's solve this step by step:\\n\\n1. Let's first look at the available pricing options:\\n• Individual pen price: $2 per pen\\n• Bulk pricing: 3 pens for $5\\n\\n2. To minimize cost, we want to use the bulk pricing as much as possible\\n\\n3. Calculate how many sets of 3 pens we can buy:\\n• 17 ÷ 3 = 5 remainder 2\\n• This means we can buy 5 sets of 3 pens (15 pens total)\\n\\n4. Calculate cost of bulk sets:\\n• 5 sets × $5 per set = $25\\n\\n5. For the remaining 2 pens:\\n• 2 pens at individual price of $2 each\\n• 2 × $2 = $4\\n\\n6. Total minimum cost:\\n• Bulk set cost + Individual pen cost\\n• $25 + $4 = $29\\n\\nFinal answer: $29\\n\\nLet's verify:\\n✓ 5 sets of 3 pens (15 pens): $25\\n✓ 2 individual pens: $4\\n✓ Total pens: 17\\n✓ Total cost: $29\" \n }\n ]\n }\n }\n ]\n}\n```\n\nExample:\n```json\n{\n \"messages\": [\n {\n \"role\": \"system | user | assistant | tool\",\n \"content\": \"string | ContentPart[]\"\n }\n ],\n \"temperature\": number,\n \"max_completion_tokens\": number,\n \"top_p\": number,\n \"tools\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"string\",\n \"description\": \"string\",\n \"parameters\": \"object\"\n }\n }\n ],\n \"tool_choice\": \"auto | none | required | { type: 'function', function: { name: string } }\"\n}\n```\n\nExample:\n```text\n- messages: The conversation content, including system, user, assistant, or tool messages.\n```\n\nExample:\n```text\n- temperature: Controls randomness in responses; higher values produce more creative output.\n- max_completion_tokens: Limits the maximum number of tokens in the generated response.\n- top_p: Controls diversity via nucleus sampling; lower values make output more focused.\n- tools: List of function tools available for the LLM to use during generation.\n- tool_choice: Specifies which tool (if any) the LLM should use for the current prompt.\n```\n\nExample:\n```json\n{\n \"choices\": [\n {\n \"finish_reason\": \"string\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"string | ContentPart[]\"\n }\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.287Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":20,"totalLines":263,"estimatedTokens":1568}}62{"id":"doc-handling_long_running_llm_processes_with_forge_r-63b3cba9","source":"documentation","title":"Handling long-running LLM processes with Forge Realtime","url":"https://developer.atlassian.com/platform/forge/llm-long-running-process-with-forge-realtime/","text":"Example:\n```text\nUI subscribes → resolver enqueues → consumer runs LLM → publishes → UI renders\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n<project-directory>\n ├── manifest.yml\n ├── package-lock.json\n ├── package.json\n ├── README.md\n └── src\n ├── frontend\n │ └── index.jsx\n ├── consumers\n │ └── index.js\n ├── resolvers\n │ └── index.js\n ├── index.js\n```\n\nExample:\n```text\nmodules:\n llm:\n - key: llm-with-realtime-app\n model: \n - claude\n consumer:\n - key: llm-prompt-consumer\n queue: llm-prompt-consumer-queue\n function: consumer\n macro:\n - key: llm-with-forge-realtime-hello-world-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: llm-with-forge-realtime\n function:\n - key: resolver\n handler: index.resolverHandler\n - key: consumer\n handler: index.consumerHandler\n timeoutSeconds: 900 # 15 minutes (queue consumer max)\n```\n\nExample:\n```javascript\n{\n // ...\n \"dependencies\": {\n // ...\n \"@forge/bridge\": \"5.8.0\",\n \"@forge/events\": \"^2.0.10\",\n \"@forge/llm\": \"^0.3.0\",\n \"@forge/realtime\": \"^0.3.0\",\n }\n}\n```\n\nExample:\n```javascript\n//<project-directory>/src/frontend/index.jsx\nimport { invoke, realtime } from '@forge/bridge';\n\nconst CHANNEL_NAME = \"my-llm-realtime-channel\";\n\nconst [token, setToken] = useState(null);\nconst [customClaims, setCustomClaims] = useState({});\n// ... other state variables like message, result, etc.\n\n// Request token with custom claims\nuseEffect(() => {\n const fetchToken = async () => {\n const { token, customClaims } = await invoke('buildToken', { channelName: CHANNEL_NAME });\n setToken(token);\n setCustomClaims(customClaims);\n };\n fetchToken();\n}, []);\n\n// Subscribe with token\nuseEffect(() => {\n if (!token) return;\n let subscription;\n\n realtime.subscribeGlobal(\n CHANNEL_NAME,\n (payload) => setResult(payload),\n { token }\n ).then(sub => { subscription = sub; });\n\n return () => {\n if (subscription) subscription.unsubscribe();\n };\n}, [token]);\n\nconst handleSubmit = () => {\n setResult(null);\n invoke(\"sendLLMPrompt\", {\n customClaims,\n prompt: {\n model: 'claude-haiku-4-5-20251001',\n messages: [{ role: \"user\", content: message.trim() }]\n }\n });\n};\n```\n\nExample:\n```javascript\n//<project-directory>/src/resolvers/index.js\nimport Resolver from '@forge/resolver';\nimport { Queue } from '@forge/events';\nimport { signRealtimeToken } from '@forge/realtime';\n\nconst resolver = new Resolver();\nconst queue = new Queue({ key: \"llm-prompt-consumer-queue\" });\n\nresolver.define('buildToken', async ({ payload, context }) => {\n const customClaims = {\n channelName: payload.channelName,\n accountId: context.accountId,\n };\n\n const { token } = await signRealtimeToken(payload.channelName, customClaims);\n return { token, customClaims };\n});\n\nresolver.define(\"sendLLMPrompt\", async ({ payload }) => {\n await queue.push([{ body: payload }]);\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n//<project-directory>/src/consumers/index.js\nimport { publishGlobal, signRealtimeToken } from '@forge/realtime';\nimport { chat } from '@forge/llm';\n\nexport const handler = async (event) => {\n const { customClaims, prompt } = event.body;\n const channelName = customClaims.channelName;\n let result;\n \n try {\n const chatResult = await chat(prompt);\n result = { status: \"done\", ...chatResult };\n } catch (err) {\n result = {\n status: \"error\",\n error: err?.context?.responseText || \"Unknown error occurred\"\n };\n }\n\n // Sign token with same custom claims as subscriber\n const { token } = await signRealtimeToken(channelName, customClaims);\n\n await publishGlobal(\n channelName,\n result,\n { token }\n );\n};\n```\n\nExample:\n```javascript\n// <project-directory>/index.js\nimport { handler as consumerHandler } from './consumers/index.js';\nimport { handler as resolverHandler } from './resolvers/index.js';\n```\n\nExample:\n```bash\nforge deploy\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.288Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":190,"estimatedTokens":1022}}63{"id":"doc-integrating_containerised_services-3c6bc7ca","source":"documentation","title":"Integrating containerised services","url":"https://developer.atlassian.com/platform/forge/containers-reference/integrating-service/","text":"Example:\n```text\nservices:\n - key: java-service\n containers:\n - key: java-container\n tag: ${TAG}\n resources:\n cpu: \"1\"\n memory: \"2Gi\"\n health:\n type: http\n route:\n path: /health\n\nmodules:\n endpoint:\n - key: event-invocation\n service: java-service\n route:\n path: /event-invocation\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.288Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":30,"estimatedTokens":106}}64{"id":"doc-managing_containerised_services-a0ba9917","source":"documentation","title":"Managing containerised services","url":"https://developer.atlassian.com/platform/forge/containers-reference/managing-service/","text":"Example:\n```text\nRUN useradd -u 1000 -r -s /bin/false appuser\n[...]\nUSER appuser\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nexport TAG=$(date +%s)\n```\n\nExample:\n```text\nexport TAG=${BITBUCKET_COMMIT:0:8}\n```\n\nExample:\n```text\nforge containers create\nCreate a new container.\n? Enter a name for your container: java-service\nCreating new container with key 'java-service'.\nPress Ctrl+C to cancel.\n✔ Container created.\nNew repository URI: forge-registry.services.atlassian.com/forge/5175c066-7f18-474e-9137-19e6718a8c99/java-service\n```\n\nExample:\n```text\nservices:\n - key: java-service\n containers:\n - key: java-service\n tag: latest\n resources:\n cpu: \"1\"\n memory: \"2Gi\"\n health:\n type: http\n route:\n path: \"/healthcheck\"\n scaling: \n min: 1\n max: 1\n```\n\nExample:\n```text\ndocker build . -t \"forge-registry.services.atlassian.com/forge/5175c066-7f18-474e-9137-19e6718a8c99/java-service:latest\" --platform linux/amd64\n```\n\nExample:\n```text\ndocker push \"forge-registry.services.atlassian.com/forge/5175c066-7f18-474e-9137-19e6718a8c99/java-service:latest\"\n```\n\nExample:\n```text\nforge logs -c | grep -o '{.*}' | jq '.'\n{\n \"@timestamp\": \"2025-06-09T04:46:59.196716991Z\",\n \"@version\": \"1\",\n \"message\": \"Received Get Kvs response: <200 OK OK,{\\\"value\\\":\\\"test-value\\\",\\\"key\\\":\\\"test-key\\\"},[:status:\\\"200\\\", content-length:\\\"39\\\", content-type:\\\"application/json; charset=utf-8\\\", date:\\\"Mon, 09 Jun 2025 04:46:59 GMT\\\", forge-proxy-upstream-latency:\\\"88\\\", server:\\\"Caddy\\\", \\\"envoy\\\", strict-transport-security:\\\"max-age=31536000\\\", x-envoy-upstream-service-time:\\\"99\\\"]>\",\n \"logger_name\": \"com.atlassian.container.WebTriggerEndpoint\",\n \"thread_name\": \"http-nio-8080-exec-3\",\n \"level\": \"INFO\",\n \"level_value\": 20000,\n \"invocationId\": \"804edb684056c101eeab78c7e194258d7d0b6b6f\"\n}\n{\n \"@timestamp\": \"2025-06-09T04:46:59.197144784Z\",\n \"@version\": \"1\",\n \"message\": \"Fetching book by title\",\n \"logger_name\": \"com.atlassian.container.db.BookRepository\",\n \"thread_name\": \"http-nio-8080-exec-3\",\n \"level\": \"INFO\",\n \"level_value\": 20000,\n \"invocationId\": \"804edb684056c101eeab78c7e194258d7d0b6b6f\"\n}\n[...]\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.289Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":90,"estimatedTokens":554}}65{"id":"doc-testing_a_containerised_service_locally_preview-6c4f15aa","source":"documentation","title":"Testing a containerised service locally (Preview)","url":"https://developer.atlassian.com/platform/forge/containers-reference/test-service-locally/","text":"Example:\n```text\ntunnel:\n docker:\n build:\n context: ./services/java-spring-server\n dockerfile: Dockerfile\n ports:\n - '8080:8080'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ntunnel:\n docker:\n image: java-service:${TAG}\n ports:\n - '8080:8080'\n```\n\nExample:\n```bash\nforge containers docker-login\ndocker pull forge-registry.services.atlassian.com/forge-platform/proxy-sidecar:latest\n```\n\nExample:\n```bash\ndocker compose up --build -d\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.289Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":38,"estimatedTokens":122}}66{"id":"doc-call_a_containerised_service_without_an_invocati-a3930b69","source":"documentation","title":"Call a containerised service without an invocationId (Preview)","url":"https://developer.atlassian.com/platform/forge/containers-reference/int-installbased/","text":"Example:\n```text\n# Returns up to the first 100 installations (default page size)\nGET <FORGE_EGRESS_PROXY_URL>/v1/installations\n\n# Returns up to the first 50 installations\nGET <FORGE_EGRESS_PROXY_URL>/v1/installations?pageSize=50\n\n# Returns up to the next 50 installations after the given cursor\nGET <FORGE_EGRESS_PROXY_URL>/v1/installations?pageSize=50&cursor=eyJhbGciOi...\n\n# Returns up to the next 100 installations after the given cursor (default page size)\nGET <FORGE_EGRESS_PROXY_URL>/v1/installations?cursor=eyJhbGciOi...\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"installations\": [\n {\n \"id\": \"<installation-1-id-uuid>\",\n \"installationContext\": \"ari:cloud:confluence::site/<cloudId>\",\n \"version\": \"12.0.3\",\n \"secondaryInstallationContexts\": [\"<secondary-installation-context-1>\", \"<secondary-installation-context-2>\"]\n },\n {\n \"id\": \"<installation-2-id-uuid>\",\n \"installationContext\": \"ari:cloud:jira::site/<cloudId>\",\n \"version\": \"11.0.0\",\n \"secondaryInstallationContexts\": []\n },\n {\n \"id\": \"<installation-3-id-uuid>\",\n \"installationContext\": \"ari:cloud:jira::site/<cloudId>\",\n \"version\": \"11.0.0\",\n \"secondaryInstallationContexts\": []\n }\n ],\n \"cursor\": null\n}\n```\n\nExample:\n```text\nGET <FORGE_EGRESS_PROXY_URL>/v1/installation/<installationId>\n```\n\nExample:\n```json\n{\n \"id\": \"<installation-1-id-uuid>\",\n \"version\": \"12.0.3\",\n \"installationContext\": \"ari:cloud:confluence::site/<cloudId>\",\n \"secondaryInstallationContexts\": [\"<secondary-installation-context-1>\"],\n \"scopes\": [\"read:jira-user\", \"storage:app\"],\n \"external\": {\n \"fetch\": {\n \"backend\": [\"<backend-url>\"],\n \"client\": [\"<client-url>\"]\n },\n \"images\": [\"<image-url>\"],\n \"media\": [\"<media-url>\"],\n \"scripts\": [\"<script-url>\"],\n \"styles\": [\"<stylesheet-url>\"],\n \"fonts\": [\"<font-url>\"],\n \"frames\": [\"<frame-url>\"]\n }\n}\n```\n\nExample:\n```text\nGET <FORGE_EGRESS_PROXY_URL>/jira/<path>\n -H forge-proxy-authorization: Forge as=app,installationId=<installationId>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.289Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":83,"estimatedTokens":518}}67{"id":"doc-examples_of_api_usage_through_json_requests-2b18ac31","source":"documentation","title":"Examples of API usage through JSON requests","url":"https://developer.atlassian.com/platform/marketplace/examples-of-api-usage-through-json-requests/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/resource\"\n }\n },\n \"name\": \"gold\",\n \"details\": {\n \"atomicNumber\": 79\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.290Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":32,"estimatedTokens":120}}68{"id":"doc-get_app_details_from_java-666eca7b","source":"documentation","title":"Get app details from Java","url":"https://developer.atlassian.com/platform/marketplace/getting-app-details-from-java/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", AddonQuery.any());\n// Option implements Iterable, so we can use \"for\" to execute this code only if it has a value \nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n}\nif (!result.isDefined()) {\n System.out.println(\"not found\");\n}\n```\n\nExample:\n```text\nAddonQuery query = AddonQuery.builder().\n withVersion(true).\n build();\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", query);\nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n for (AddonVersion v: a.getVersion()) {\n System.out.println(\"And the latest version is \" + v.getName());\n }\n}\n```\n\nExample:\n```text\nAddonQuery query = AddonQuery.builder().\n hosting(Option.some(HostingType.CLOUD)).\n withVersion(true).\n build();\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", query);\nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n for (AddonVersion v: a.getVersion()) {\n System.out.println(\"And the latest Cloud version is \" + v.getName());\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.290Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":58,"estimatedTokens":383}}69{"id":"doc-get_app_details_using_json-06d6ba37","source":"documentation","title":"Get app details using JSON","url":"https://developer.atlassian.com/platform/marketplace/getting-app-details-using-json/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"addons\": {\n \"href\": \"/rest/2/addons\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons\"\n }, [...some more links that you can ignore...]\n \"byKey\": {\n \"href\": \"/rest/2/addons/{addonKey}\",\n \"templated\": true\n }\n }\n}\n```\n\nExample:\n```bash\n$ curl \"https://marketplace.atlassian.com/rest/2/addons/my.excellent.app?withVersion=true\"\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons/my.excellent.app\"\n },\n \"alternate\": {\n \"href\": \"/plugins/my.excellent.app\"\n }\n },\n \"_embedded\": {\n \"logo\": {\n \"_links\": {\n \"image\": \"https://marketplace-cdn.atlassian.com/files/my.excellent.app/icons/default/e1692eef-eda8-4508-909a.png\"\n }\n },\n \"version\": {\n \"_links\": {\n \"href\": \"/rest/2/addons/my.excellent.app/build/100\"\n },\n \"buildNumber\": \"100\",\n \"name\": \"1.0.0\",\n [...other version properties...]\n }\n },\n \"key\": \"my.excellent.app\",\n \"name\": \"My Excellent App\",\n [...other app properties...]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.291Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":84,"estimatedTokens":373}}70{"id":"doc-marketplace_api_java_client-e89ff4ce","source":"documentation","title":"Marketplace API Java Client","url":"https://developer.atlassian.com/platform/marketplace/marketplace-api-java-client/","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.marketplace</groupId>\n <artifactId>marketplace-client-java</artifactId>\n <version>2.0.0</version> <!-- replace with latest release version -->\n</dependency>\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.marketplace</groupId>\n <artifactId>marketplace-client-java</artifactId>\n <version>2.0.0</version> <!-- replace with latest release version -->\n <classifier>dependencies</classifier>\n</dependency>\n```\n\nExample:\n```text\nMarketplaceClient client = MarketplaceClientFactory.createMarketplaceClient();\n```\n\nExample:\n```text\nHttpConfiguration config = HttpConfiguration.builder()\n .credentials(Option.some(new HttpConfiguration.Credentials(\"my.username@example.com\", \"my.password\")))\n .build();\nMarketplaceClient client = MarketplaceClientFactory.createMarketplaceClient(config);\n```\n\nExample:\n```text\nModelBuilders.SomeObjectBuilder builder = ModelBuilders.someObject();\nbuilder.name(\"name of my object\")\n .someOtherProperty(\"some other property\");\nSomeObject myObject = builder.build();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.291Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":57,"estimatedTokens":354}}71{"id":"doc-examples_of_api_usage_through_json_requests-178b0ef2","source":"documentation","title":"Examples of API usage through JSON requests","url":"https://developer.atlassian.com/platform/marketplace/examples-of-api-usage-through-json-requests","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/resource\"\n }\n },\n \"name\": \"gold\",\n \"details\": {\n \"atomicNumber\": 79\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.292Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":32,"estimatedTokens":120}}72{"id":"doc-user_guide_for_commerce_pricing_apis_to_update_a-2a607b77","source":"documentation","title":"User guide for Commerce Pricing APIs to update and fetch app pricing","url":"https://developer.atlassian.com/platform/marketplace/marketplace-app-pricing-api/","text":"Example:\n```text\nUser tier fetched: \n {\n \"floor\": 11,\n \"ceiling\": 11,\n \"unitAmount\": --,\n \"flatAmount\": --\n },\n {\n \"floor\": 12,\n \"ceiling\": 15,\n \"unitAmount\": null,\n \"flatAmount\": --\n },\n\nUser tier while creating draft Pricing Plan:\n {\n \"floor\": 11,\n \"ceiling\": 15,\n \"unitAmount\": --,\n \"flatAmount\": --\n },\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ncurl --location 'https://{{host}}/api/v2/pricing-plans' \\\n--header 'x-catalog-account: {{catalog_account}}' \\\n--header 'Authorization: {{API_token}}'\\\n--header 'Content-Type: application/json' \\\n--data '{\n \"id\": \"{{guid}}\",\n \"offeringId\": \"{{offeringId}}\",\n \"primaryCycleId\": \"ANNUAL\",\n \"currency\": \"USD\",\n \"items\": [\n {\n \"cycleId\": \"ANNUAL\",\n \"starter\": false,\n \"chargeType\": \"LICENSED\",\n \"chargeElement\": \"user\",\n \"tiersMode\": \"VOLUME\",\n \"chargeElementAggregationType\": \"LATEST\",\n \"transformQuantity\": null,\n \"prorateOnUsageChange\": \"NONE\",\n \"tiers\": [\n {\n \"floor\": 1,\n \"ceiling\": 10,\n \"unitAmount\": null,\n \"flatAmount\": 0\n },\n {\n \"floor\": 11,\n \"ceiling\": 15,\n \"unitAmount\": null,\n \"flatAmount\": 61200\n },\n {\n \"floor\": 16,\n \"ceiling\": 25,\n \"unitAmount\": null,\n \"flatAmount\": 102000\n },\n {\n \"floor\": 26,\n \"ceiling\": 50,\n \"unitAmount\": null,\n \"flatAmount\": 204000\n },\n {\n \"floor\": 51,\n \"ceiling\": 100,\n \"unitAmount\": null,\n \"flatAmount\": 408000\n },\n {\n \"floor\": 101,\n \"ceiling\": 200,\n \"unitAmount\": null,\n \"flatAmount\": 756000\n },\n {\n \"floor\": 201,\n \"ceiling\": 300,\n \"unitAmount\": null,\n \"flatAmount\": 1075000\n },\n {\n \"floor\": 301,\n \"ceiling\": 400,\n \"unitAmount\": null,\n \"flatAmount\": 1365000\n },\n {\n \"floor\": 401,\n \"ceiling\": 500,\n \"unitAmount\": null,\n \"flatAmount\": 1655000\n },\n {\n \"floor\": 501,\n \"ceiling\": 600,\n \"unitAmount\": null,\n \"flatAmount\": 1945000\n },\n {\n \"floor\": 601,\n \"ceiling\": 800,\n \"unitAmount\": null,\n \"flatAmount\": 2525000\n },\n {\n \"floor\": 801,\n \"ceiling\": 1000,\n \"unitAmount\": null,\n \"flatAmount\": 3105000\n },\n {\n \"floor\": 1001,\n \"ceiling\": 1200,\n \"unitAmount\": null,\n \"flatAmount\": 3631000\n },\n {\n \"floor\": 1201,\n \"ceiling\": 1400,\n \"unitAmount\": null,\n \"flatAmount\": 4157000\n },\n {\n \"floor\": 1401,\n \"ceiling\": 1600,\n \"unitAmount\": null,\n \"flatAmount\": 4683000\n },\n {\n \"floor\": 1601,\n \"ceiling\": 1800,\n \"unitAmount\": null,\n \"flatAmount\": 5209000\n },\n {\n \"floor\": 1801,\n \"ceiling\": 2000,\n \"unitAmount\": null,\n \"flatAmount\": 5735000\n },\n {\n \"floor\": 2001,\n \"ceiling\": 2250,\n \"unitAmount\": null,\n \"flatAmount\": 6392500\n },\n {\n \"floor\": 2251,\n \"ceiling\": 2500,\n \"unitAmount\": null,\n \"flatAmount\": 7050000\n },\n {\n \"floor\": 2501,\n \"ceiling\": 2750,\n \"unitAmount\": null,\n \"flatAmount\": 7662500\n },\n {\n \"floor\": 2751,\n \"ceiling\": 3000,\n \"unitAmount\": null,\n \"flatAmount\": 8275000\n },\n {\n \"floor\": 3001,\n \"ceiling\": 3250,\n \"unitAmount\": null,\n \"flatAmount\": 8887500\n },\n {\n \"floor\": 3251,\n \"ceiling\": 3500,\n \"unitAmount\": null,\n \"flatAmount\": 9500000\n },\n {\n \"floor\": 3501,\n \"ceiling\": 3750,\n \"unitAmount\": null,\n \"flatAmount\": 10112500\n },\n {\n \"floor\": 3751,\n \"ceiling\": 4000,\n \"unitAmount\": null,\n \"flatAmount\": 10725000\n },\n {\n \"floor\": 4001,\n \"ceiling\": 4250,\n \"unitAmount\": null,\n \"flatAmount\": 11337500\n },\n {\n \"floor\": 4251,\n \"ceiling\": 4500,\n \"unitAmount\": null,\n \"flatAmount\": 11950000\n },\n {\n \"floor\": 4501,\n \"ceiling\": 4750,\n \"unitAmount\": null,\n \"flatAmount\": 12562500\n },\n {\n \"floor\": 4751,\n \"ceiling\": 5000,\n \"unitAmount\": null,\n \"flatAmount\": 13175000\n },\n {\n \"floor\": 5001,\n \"ceiling\": 5500,\n \"unitAmount\": null,\n \"flatAmount\": 14325000\n },\n {\n \"floor\": 5501,\n \"ceiling\": 6000,\n \"unitAmount\": null,\n \"flatAmount\": 15475000\n },\n {\n \"floor\": 6001,\n \"ceiling\": 6500,\n \"unitAmount\": null,\n \"flatAmount\": 16625000\n },\n {\n \"floor\": 6501,\n \"ceiling\": 7000,\n \"unitAmount\": null,\n \"flatAmount\": 17775000\n },\n {\n \"floor\": 7001,\n \"ceiling\": 7500,\n \"unitAmount\": null,\n \"flatAmount\": 18925000\n },\n {\n \"floor\": 7501,\n \"ceiling\": 8000,\n \"unitAmount\": null,\n \"flatAmount\": 20010000\n },\n {\n \"floor\": 8001,\n \"ceiling\": 8500,\n \"unitAmount\": null,\n \"flatAmount\": 21095000\n },\n {\n \"floor\": 8501,\n \"ceiling\": 9000,\n \"unitAmount\": null,\n \"flatAmount\": 22180000\n },\n {\n \"floor\": 9001,\n \"ceiling\": 9500,\n \"unitAmount\": null,\n \"flatAmount\": 23265000\n },\n {\n \"floor\": 9501,\n \"ceiling\": 10000,\n \"unitAmount\": null,\n \"flatAmount\": 24350000\n },\n {\n \"floor\": 10001,\n \"ceiling\": 11000,\n \"unitAmount\": null,\n \"flatAmount\": 26230000\n },\n {\n \"floor\": 11001,\n \"ceiling\": 12000,\n \"unitAmount\": null,\n \"flatAmount\": 28110000\n },\n {\n \"floor\": 12001,\n \"ceiling\": 13000,\n \"unitAmount\": null,\n \"flatAmount\": 29990000\n },\n {\n \"floor\": 13001,\n \"ceiling\": 14000,\n \"unitAmount\": null,\n \"flatAmount\": 31870000\n },\n {\n \"floor\": 14001,\n \"ceiling\": 15000,\n \"unitAmount\": null,\n \"flatAmount\": 33750000\n },\n {\n \"floor\": 15001,\n \"ceiling\": 16000,\n \"unitAmount\": null,\n \"flatAmount\": 35480000\n },\n {\n \"floor\": 16001,\n \"ceiling\": 17000,\n \"unitAmount\": null,\n \"flatAmount\": 37210000\n },\n {\n \"floor\": 17001,\n \"ceiling\": 18000,\n \"unitAmount\": null,\n \"flatAmount\": 38940000\n },\n {\n \"floor\": 18001,\n \"ceiling\": 19000,\n \"unitAmount\": null,\n \"flatAmount\": 40670000\n },\n {\n \"floor\": 19001,\n \"ceiling\": 20000,\n \"unitAmount\": null,\n \"flatAmount\": 42400000\n },\n {\n \"floor\": 20001,\n \"ceiling\": 21000,\n \"unitAmount\": null,\n \"flatAmount\": 44030000\n },\n {\n \"floor\": 21001,\n \"ceiling\": 22000,\n \"unitAmount\": null,\n \"flatAmount\": 45660000\n },\n {\n \"floor\": 22001,\n \"ceiling\": 23000,\n \"unitAmount\": null,\n \"flatAmount\": 47290000\n },\n {\n \"floor\": 23001,\n \"ceiling\": 24000,\n \"unitAmount\": null,\n \"flatAmount\": 48920000\n },\n {\n \"floor\": 24001,\n \"ceiling\": 25000,\n \"unitAmount\": null,\n \"flatAmount\": 50550000\n },\n {\n \"floor\": 25001,\n \"ceiling\": 26000,\n \"unitAmount\": null,\n \"flatAmount\": 52070000\n },\n {\n \"floor\": 26001,\n \"ceiling\": 27000,\n \"unitAmount\": null,\n \"flatAmount\": 53590000\n },\n {\n \"floor\": 27001,\n \"ceiling\": 28000,\n \"unitAmount\": null,\n \"flatAmount\": 55110000\n },\n {\n \"floor\": 28001,\n \"ceiling\": 29000,\n \"unitAmount\": null,\n \"flatAmount\": 56630000\n },\n {\n \"floor\": 29001,\n \"ceiling\": 30000,\n \"unitAmount\": null,\n \"flatAmount\": 58150000\n },\n {\n \"floor\": 30001,\n \"ceiling\": 31000,\n \"unitAmount\": null,\n \"flatAmount\": 59350000\n },\n {\n \"floor\": 31001,\n \"ceiling\": 32000,\n \"unitAmount\": null,\n \"flatAmount\": 60550000\n },\n {\n \"floor\": 32001,\n \"ceiling\": 33000,\n \"unitAmount\": null,\n \"flatAmount\": 61750000\n },\n {\n \"floor\": 33001,\n \"ceiling\": 34000,\n \"unitAmount\": null,\n \"flatAmount\": 62950000\n },\n {\n \"floor\": 34001,\n \"ceiling\": 35000,\n \"unitAmount\": null,\n \"flatAmount\": 64150000\n },\n {\n \"floor\": 35001,\n \"ceiling\": 36000,\n \"unitAmount\": null,\n \"flatAmount\": 65330000\n },\n {\n \"floor\": 36001,\n \"ceiling\": 37000,\n \"unitAmount\": null,\n \"flatAmount\": 66510000\n },\n {\n \"floor\": 37001,\n \"ceiling\": 38000,\n \"unitAmount\": null,\n \"flatAmount\": 67690000\n },\n {\n \"floor\": 38001,\n \"ceiling\": 39000,\n \"unitAmount\": null,\n \"flatAmount\": 68870000\n },\n {\n \"floor\": 39001,\n \"ceiling\": 40000,\n \"unitAmount\": null,\n \"flatAmount\": 70050000\n },\n {\n \"floor\": 40001,\n \"ceiling\": 41000,\n \"unitAmount\": null,\n \"flatAmount\": 71200000\n },\n {\n \"floor\": 41001,\n \"ceiling\": 42000,\n \"unitAmount\": null,\n \"flatAmount\": 72350000\n },\n {\n \"floor\": 42001,\n \"ceiling\": 43000,\n \"unitAmount\": null,\n \"flatAmount\": 73500000\n },\n {\n \"floor\": 43001,\n \"ceiling\": 44000,\n \"unitAmount\": null,\n \"flatAmount\": 74650000\n },\n {\n \"floor\": 44001,\n \"ceiling\": 45000,\n \"unitAmount\": null,\n \"flatAmount\": 75800000\n },\n {\n \"floor\": 45001,\n \"ceiling\": 46000,\n \"unitAmount\": null,\n \"flatAmount\": 76930000\n },\n {\n \"floor\": 46001,\n \"ceiling\": 47000,\n \"unitAmount\": null,\n \"flatAmount\": 78060000\n },\n {\n \"floor\": 47001,\n \"ceiling\": 48000,\n \"unitAmount\": null,\n \"flatAmount\": 79190000\n },\n {\n \"floor\": 48001,\n \"ceiling\": 49000,\n \"unitAmount\": null,\n \"flatAmount\": 80320000\n },\n {\n \"floor\": 49001,\n \"ceiling\": 50000,\n \"unitAmount\": null,\n \"flatAmount\": 81450000\n },\n {\n \"floor\": 50001,\n \"ceiling\": 60000,\n \"unitAmount\": null,\n \"flatAmount\": 92750000\n },\n {\n \"floor\": 60001,\n \"ceiling\": 70000,\n \"unitAmount\": null,\n \"flatAmount\": 104050000\n },\n {\n \"floor\": 70001,\n \"ceiling\": 80000,\n \"unitAmount\": null,\n \"flatAmount\": 115350000\n },\n {\n \"floor\": 80001,\n \"ceiling\": 90000,\n \"unitAmount\": null,\n \"flatAmount\": 126650000\n },\n {\n \"floor\": 90001,\n \"ceiling\": 100000,\n \"unitAmount\": null,\n \"flatAmount\": 137950000\n }\n ]\n }\n ]\n}\n'\n```\n\nExample:\n```text\ncurl --location 'https://offering.staging.atl-paas.net/api/v2/pricing-changes' \\\n--header 'x-catalog-account: {{catalog_account}}' \\\n--header 'Authorization: {{API_token}}'\\\n--header 'Content-Type: application/json' \\\n--data '{\n \"id\": \"{{guid}}\",\n \"offeringId\": \"{{offeringId}}\",\n \"description\": \"string\",\n \"changes\": [\n {\n \"activate\": {\n \"pricingPlanId\": \"{{draftPricingPlanId}}\",\n \"activationReasonCode\": \"DEFAULT_PRICING\"\n },\n \"deactivate\": {\n \"pricingPlanId\": \"{{activePricingPlanIdToBeDeactivated}}\",\n \"maxNewQuoteDate\": 1767591039000\n },\n \"migrate\": {\n \"fromPricingPlanId\": \"{{activePricingPlanIdToBeDeactivated}}\",\n \"destinations\": [\n {\n \"toPricingPlanId\": \"{{draftPricingPlanId}}\",\n \"cohort\": {\n \"type\": \"DEFAULT\",\n \"chargeElement\": \"string\",\n \"tierFloor\": 0,\n \"tierCeiling\": 0\n }\n }\n ]\n }\n }\n ]\n}'\n```\n\nExample:\n```text\ncurl --location --request PUT 'https://{{host}}/api/v2/pricing-changes/{pricingChangeId}/execute' \\\n--header 'x-catalog-account: {{catalog_account}}' \\\n--header 'Authorization: {{API_token}}'\\\n--header 'Content-Type: application/json' \\\n--data '{\n}'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.293Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":614,"estimatedTokens":3475}}73{"id":"doc-get_app_details_from_java-b2016dcf","source":"documentation","title":"Get app details from Java","url":"https://developer.atlassian.com/platform/marketplace/getting-app-details-from-java","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", AddonQuery.any());\n// Option implements Iterable, so we can use \"for\" to execute this code only if it has a value \nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n}\nif (!result.isDefined()) {\n System.out.println(\"not found\");\n}\n```\n\nExample:\n```text\nAddonQuery query = AddonQuery.builder().\n withVersion(true).\n build();\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", query);\nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n for (AddonVersion v: a.getVersion()) {\n System.out.println(\"And the latest version is \" + v.getName());\n }\n}\n```\n\nExample:\n```text\nAddonQuery query = AddonQuery.builder().\n hosting(Option.some(HostingType.CLOUD)).\n withVersion(true).\n build();\nOption<Addon> result = client.addons().getByKey(\"my.excellent.app\", query);\nfor (Addon a: result) {\n System.out.println(\"I found the app; its name is \" + a.getName());\n for (AddonVersion v: a.getVersion()) {\n System.out.println(\"And the latest Cloud version is \" + v.getName());\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.293Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":58,"estimatedTokens":383}}74{"id":"doc-functions_introduction-a5fa49da","source":"documentation","title":"Functions introduction","url":"https://developer.atlassian.com/platform/forge/function-reference/","text":"Example:\n```bash\nnpm i @forge/api@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n/src\n /functions\n /index.js\n```\n\nExample:\n```javascript\nexport const handler = (...args) => {\n console.log(args);\n // Do something\n}\n```\n\nExample:\n```text\nmodules:\n function:\n - key: my-function\n handler: index.handler\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: example\n function: my-function # References the function with key: my-function\n interval: hour # Runs hourly\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.294Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":44,"estimatedTokens":131}}75{"id":"doc-list_a_forge_successor_to_a_connect_app-d9cd4aeb","source":"documentation","title":"List a Forge successor to a Connect app","url":"https://developer.atlassian.com/platform/marketplace/listing-forge-successor-to-connect-apps/","text":"Example:\n```text\napp:\n id: ari:cloud:ecosystem::app/cxxxxxx-xxxxx-xxxx-xxxxx-xxxxxxxxx\n connect:\n key: com.example.yourappkey\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy -e production\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.296Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":20,"estimatedTokens":56}}76{"id":"doc-reliability_connect_metrics-ebca4943","source":"documentation","title":"Reliability: Connect metrics","url":"https://developer.atlassian.com/platform/marketplace/cloud-fortified-apps-program-connect-metrics/","text":"Example:\n```text\n<script type=\"application/json\" id=\"incident-data\">\n{\n \"appKey\": \"com.example.appkey\",\n \"appDetails\": {\n \"name\": \"AppName\",\n \"partnerName\": \"Atlassian\"\n },\n \"incidentId\": \"FOotlQrY5ZQ\",\n \"incidentStatus\": \"ANOMALOUS\",\n \"time\": \"2022-03-24T15:40:00Z\",\n \"metricName\": \"Jira Installation Callback Success Rate\",\n \"metricUnit\": \"PERCENT\",\n \"measurementDurationSeconds\": 86400,\n \"sliValue\": 60.0,\n \"sloValue\": 99.0\n}\n</script>\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.296Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":28,"estimatedTokens":125}}77{"id":"doc-list_an_app_version_from_java-769122d0","source":"documentation","title":"List an app version from Java","url":"https://developer.atlassian.com/platform/marketplace/listing-an-app-version-from-java","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nFile file = new File(\"/path/to/my-app-1.0.jar\");\nArtifactId artifactId = client.assets().uploadArtifact(file);\n```\n\nExample:\n```text\nArtifactId artifactId = ArtifactId.fromUri(URI.create(\"http://my-server.com/url/to/download/my-app-1.0.jar\"));\n```\n\nExample:\n```text\nModelBuilders.AddonVersionBuilder versionBuilder = ModelBuilders.addonVersion();\n\n// These properties must always be specified for every new version:\nversionBuilder.buildNumber(100) // build numbers are unique values you choose to determine version ordering\n .paymentModel(PaymentModel.FREE) // is this version of the app free, paid via vendor, or paid via Atlassian?\n .status(AddonVersionStatus.PUBLIC) // you can make a version public or private\n .releaseDate(new LocalDate(\"2016-03-17\")) // this date will appear in the app's version history\n .releaseSummary(Option.some(\"this version fixes some bugs\")); // a brief description of the release\n\n// If you have an ArtifactId (see step 1), attach it to the version like this:\nversionBuilder.artifact(Option.some(artifactId))\n\n// Or, if there is no ArtifactId because the app is not directly installable, you will normally\n// provide an external download URI, like this:\nversionBuilder.externalLinkUri(AddonVersionExternalLinkType.BINARY, URI.create(\"http://my-vendor.com/page-for-getting-the-addon\"));\nversionBuilder.name(\"1.0\"); // Marketplace can detect the version string automatically for installable apps; but\n // if there is no installable artifact, you'll need to specify the version string yourself.\n\n// You do not need to specify compatibility details if they are the same as the previous version;\n// but if they have changed, you can set them for the new version like this (the numeric values\n// are application build numbers):\nversionBuilder.compatibilities(ImmutableList.of(\n ModelBuilders.versionCompatibilityForServer(ApplicationKey.JIRA, 70107, 71003)\n));\n\n// Finally, call build() to produce an AddonVersion object that has all of the specified properties.\nAddonVersion version = versionBuilder.build();\n```\n\nExample:\n```text\nAddonVersion createdVersion = client.addons().createVersion(\"my.app.key\", version);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.298Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":64,"estimatedTokens":642}}78{"id":"doc-get_app_details_using_json-414b487b","source":"documentation","title":"Get app details using JSON","url":"https://developer.atlassian.com/platform/marketplace/getting-app-details-using-json","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"addons\": {\n \"href\": \"/rest/2/addons\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons\"\n }, [...some more links that you can ignore...]\n \"byKey\": {\n \"href\": \"/rest/2/addons/{addonKey}\",\n \"templated\": true\n }\n }\n}\n```\n\nExample:\n```bash\n$ curl \"https://marketplace.atlassian.com/rest/2/addons/my.excellent.app?withVersion=true\"\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons/my.excellent.app\"\n },\n \"alternate\": {\n \"href\": \"/plugins/my.excellent.app\"\n }\n },\n \"_embedded\": {\n \"logo\": {\n \"_links\": {\n \"image\": \"https://marketplace-cdn.atlassian.com/files/my.excellent.app/icons/default/e1692eef-eda8-4508-909a.png\"\n }\n },\n \"version\": {\n \"_links\": {\n \"href\": \"/rest/2/addons/my.excellent.app/build/100\"\n },\n \"buildNumber\": \"100\",\n \"name\": \"1.0.0\",\n [...other version properties...]\n }\n },\n \"key\": \"my.excellent.app\",\n \"name\": \"My Excellent App\",\n [...other app properties...]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.298Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":84,"estimatedTokens":373}}79{"id":"doc-export_app_metrics-07552dd7","source":"documentation","title":"Export app metrics","url":"https://developer.atlassian.com/platform/forge/export-app-metrics/","text":"Example:\n```text\nquery Ecosystem($appId: ID!, $query: ForgeMetricsOtlpQueryInput!) {\n ecosystem {\n forgeMetrics(appId: $appId) {\n appMetrics(query: $query) {\n ... on ForgeMetricsOtlpData {\n resourceMetrics\n }\n ... on QueryError {\n message\n identifier\n extensions {\n statusCode\n errorType\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"appId\": \"ari:cloud:ecosystem::app/8ce114f4-d82c-45e2-b4fb-c6a0751d7d57\",\n \"query\": {\n \"filters\": {\n \"environments\": [\"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"],\n \"interval\": {\n \"start\": \"2023-06-18T02:55:00.000Z\",\n \"end\": \"2023-06-18T02:57:00.000Z\"\n },\n \"metrics\": [\n \"FORGE_API_REQUEST_COUNT\", \n \"FORGE_API_REQUEST_LATENCY\", \n \"FORGE_BACKEND_INVOCATION_LATENCY\", \n \"FORGE_BACKEND_INVOCATION_COUNT\", \n \"FORGE_BACKEND_INVOCATION_ERRORS\", \n \"CONTAINER_CPU_USAGE_PERCENTAGE\", \n \"CONTAINER_MEMORY_USAGE_PERCENTAGE\", \n \"CONTAINER_UPTIME_SECONDS\",\n \"CONTAINER_STATUS_RESTARTS_TOTAL\",\n \"SERVICE_INSTANCE_COUNT\"\n ]\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"Authorization\": \"Basic base64<email:token>\",\n \"User-Agent\": \"ForgeMetricsExportServer/1.0.0\",\n \"X-ExperimentalApi\": \"ForgeMetricsQuery\"\n}\n```\n\nExample:\n```text\n{\n \"data\": {\n \"ecosystem\": {\n \"forgeMetrics\": {\n \"appMetrics\": {\n \"resourceMetrics\": [\n {\n \"resource\": {},\n \"schemaUrl\": \"https://opentelemetry.io/schemas/1.9.0\",\n \"scopeMetrics\": [\n {\n \"metrics\": [\n {\n \"name\": \"forge_api_request_count\",\n \"description\": \"\",\n \"sum\": {\n \"aggregationTemporality\": 1,\n \"dataPoints\": [\n {\n \"asInt\": 8,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"contextAri\",\n \"value\": {\n \"stringValue\": \"ari:cloud:compass::site/04c5a385-0899-4edc-93a8-ada653b7c534\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"6f5f56e9-55c0-4551-9247-ee1484340f64\"\n }\n },\n {\n \"key\": \"provider\",\n \"value\": {\n \"stringValue\": \"app\"\n }\n },\n {\n \"key\": \"remote\",\n \"value\": {\n \"stringValue\": \"stargate\"\n }\n },\n {\n \"key\": \"status\",\n \"value\": {\n \"stringValue\": \"2xx\"\n }\n },\n {\n \"key\": \"url\",\n \"value\": {\n \"stringValue\": \"/forge/entities/graphql\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1698720840000000000\",\n \"timeUnixNano\": \"1698720900000000000\"\n }\n ]\n },\n \"unit\": \"s\"\n },\n {\n \"name\": \"forge_backend_invocation_count\",\n \"description\": \"\",\n \"sum\": {\n \"aggregationTemporality\": 1,\n \"dataPoints\": [\n {\n \"asInt\": 70,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"8ce114f4-d82c-45e2-b4fb-c6a0751d7d57\"\n }\n },\n {\n \"key\": \"appVersion\",\n \"value\": {\n \"stringValue\": \"4.64.0\"\n }\n },\n {\n \"key\": \"contextAri\",\n \"value\": {\n \"stringValue\": \"ari:cloud:confluence::site/13095d29-407d-47ec-aa57-76764a470f36\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"functionKey\",\n \"value\": {\n \"stringValue\": \"updateStatusTitle\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1687497375656000000\",\n \"timeUnixNano\": \"1687497375662000000\"\n }\n ]\n },\n \"unit\": \"s\"\n },\n {\n \"name\": \"forge_backend_invocation_errors\",\n \"description\": \"\",\n \"sum\": {\n \"aggregationTemporality\": 1,\n \"dataPoints\": [\n {\n \"asInt\": 0,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"8ce114f4-d82c-45e2-b4fb-c6a0751d7d57\"\n }\n },\n {\n \"key\": \"appVersion\",\n \"value\": {\n \"stringValue\": \"5.1.0\"\n }\n },\n {\n \"key\": \"contextAri\",\n \"value\": {\n \"stringValue\": \"ari:cloud:compass::site/6a9ea14f-759d-4f4a-b3ac-11395d8bf519\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"errorType\",\n \"value\": {\n \"stringValue\": \"UNHANDLED_EXCEPTION\"\n }\n },\n {\n \"key\": \"functionKey\",\n \"value\": {\n \"stringValue\": \"process-app-event\"\n }\n },\n {\n \"key\": \"moduleKey\",\n \"value\": {\n \"stringValue\": \"app-event-webtrigger\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1687488960000000000\",\n \"timeUnixNano\": \"1687489020000000000\"\n }\n ]\n },\n \"unit\": \"s\"\n },\n {\n \"name\": \"container_cpu_usage_percentage\",\n \"description\": \"\",\n \"gauge\": {\n \"dataPoints\": [\n {\n \"asDouble\": 1.121,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-8vg5v\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1687488960000000000\",\n \"timeUnixNano\": \"1687489020000000000\"\n }\n ]\n },\n \"unit\": \"%\"\n },\n {\n \"name\": \"container_memory_usage_percentage\",\n \"description\": \"\",\n \"gauge\": {\n \"dataPoints\": [\n {\n \"asDouble\": 21.7125,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-8vg5v\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1687488960000000000\",\n \"timeUnixNano\": \"1687489020000000000\"\n }\n ]\n },\n \"unit\": \"%\"\n },\n {\n \"name\": \"service_instance_count\",\n \"description\": \"\",\n \"gauge\": {\n \"dataPoints\": [\n {\n \"asDouble\": 2,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1687488960000000000\",\n \"timeUnixNano\": \"1687489020000000000\"\n }\n ]\n },\n \"unit\": \"1\"\n },\n {\n \"name\": \"container_uptime_seconds\",\n \"description\": \"\",\n \"gauge\": {\n \"dataPoints\": [\n {\n \"asDouble\": 388626.6037724018,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785240860000000000\",\n \"timeUnixNano\": \"1785240920000000000\"\n },\n {\n \"asDouble\": 388626.60381031036,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-rhvk8\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785240860000000000\",\n \"timeUnixNano\": \"1785240920000000000\"\n }\n ]\n },\n \"unit\": \"s\"\n },\n {\n \"name\": \"container_status_restarts_total\",\n \"description\": \"\",\n \"sum\": {\n \"aggregationTemporality\": 2,\n \"isMonotonic\": true,\n \"dataPoints\": [\n {\n \"asInt\": 0,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785409740000000000\",\n \"timeUnixNano\": \"1785409800000000000\"\n },\n {\n \"asInt\": 0,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785409800000000000\",\n \"timeUnixNano\": \"1785409860000000000\"\n },\n {\n \"asInt\": 1,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785409800000000000\",\n \"timeUnixNano\": \"1785409860000000000\"\n },\n {\n \"asInt\": 1,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785409800000000000\",\n \"timeUnixNano\": \"1785409860000000000\"\n },\n {\n \"asInt\": 2,\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"a11dfa0b-cf2c-44d1-9080-5c3944961223\"\n }\n },\n {\n \"key\": \"cluster_uid\",\n \"value\": {\n \"stringValue\": \"8ffs\"\n }\n },\n {\n \"key\": \"container\",\n \"value\": {\n \"stringValue\": \"java-service-container-001\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"8cb293d5-be08-47ae-a75c-95b89da5ad1d\"\n }\n },\n {\n \"key\": \"pod\",\n \"value\": {\n \"stringValue\": \"eco-deployment-65db68664c-g5m2g\"\n }\n },\n {\n \"key\": \"region\",\n \"value\": {\n \"stringValue\": \"us-west-2\"\n }\n },\n {\n \"key\": \"serviceKey\",\n \"value\": {\n \"stringValue\": \"java-service\"\n }\n }\n ],\n \"startTimeUnixNano\": \"1785409800000000000\",\n \"timeUnixNano\": \"1785409860000000000\"\n }\n ]\n },\n \"unit\": \"1\"\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n```\nMyLambdaFunction:\n Type: AWS::Lambda::Function\n Properties:\n FunctionName: MyLambdaFunction\n Runtime: nodejs14.x\n Handler: index.handler\n Code:\n S3Bucket: my-function-bucket\n S3Key: my-function-package.zip\n Layers:\n - !Ref OTelLambdaLayer\n Environment:\n Variables:\n OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/config.yml\nMyScheduledRule:\n Type: AWS::Events::Rule\n Properties:\n Description: My scheduled rule\n ScheduleExpression: rate(3 minutes)\n State: ENABLED\n Targets:\n - Arn: !GetAtt MyLambdaFunction.Arn\n Id: MyLambdaTarget\n```\n```\n\nExample:\n```text\n```\nResources:\nOTelLambdaLayer:\n Type: AWS::Lambda::LayerVersion\n Properties:\n LayerName: OTelLambdaLayer\n Description: My OTEL Lambda layer\n Content:\n S3Bucket: my-layer-bucket\n S3Key: my-layer-package.zip\n CompatibleRuntimes:\n - nodejs14.x\nMyLambdaFunction:\n Type: AWS::Lambda::Function\n Properties:\n FunctionName: MyLambdaFunction\n Runtime: nodejs14.x\n Handler: index.handler\n Code:\n S3Bucket: my-function-bucket\n S3Key: my-function-package.zip\n Layers:\n - !Ref OTelLambdaLayer\n Environment:\n Variables:\n OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/config.yml\nMyScheduledRule:\n Type: AWS::Events::Rule\n Properties:\n Description: My scheduled rule\n ScheduleExpression: rate(3 minutes)\n State: ENABLED\n Targets:\n - Arn: !GetAtt MyLambdaFunction.Arn\n Id: MyLambdaTarget\n```\n```\n\nExample:\n```text\nreceivers:\n otlp:\n protocols:\n http:\n \nexporters:\n signalfx:\n # Access token to send data to SignalFx.\n access_token: <access_token>\n # SignalFx realm where the data will be received.\n realm: us1\n # Timeout for the send operations.\n timeout: 30s \n\nprocessors:\n batch:\n\nservice:\n pipelines:\n metrics:\n receivers: [otlp]\n processors: [batch]\n exporters: [signalfx]\n```\n\nExample:\n```text\nFROM otel/opentelemetry-collector-contrib:latest\n\n# Copy the collector configuration file into the container\nCOPY otel-collector-config.yaml /etc/otel-collector-config.yaml\n\n# Start the collector with the specified configuration file\nCMD [\"--config=/etc/otel-collector-config.yaml\"]\n```\n\nExample:\n```text\ncurl --location --request POST 'localhost:4318/v1/metrics' \\\n--header 'Content-Type: application/json' \\\n--data-raw '<response.data.ecosystem.forgeMetrics.appMetrics>'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.300Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":897,"estimatedTokens":8612}}80{"id":"doc-list_an_app_version_using_rest-b4476f44","source":"documentation","title":"List an app version using REST","url":"https://developer.atlassian.com/platform/marketplace/listing-an-app-version-using-rest","text":"Example:\n```text\n{\n\"message\": \"This V2 API has been deprecated and disabled for your account. Please migrate to V3 Marketplace APIs.\",\n\"error\": \"API_DEPRECATED\",\n\"statusCode\": 410,\n\"migrationGuide\": \"https://developer.atlassian.com/platform/marketplace/changelog/#CHANGE-3257\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"assets\": {\n \"href\": \"/rest/2/assets\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/assets\"\n },\n \"artifact\": {\n \"href\": \"/rest/2/assets/artifact\"\n }\n}\n```\n\nExample:\n```bash\n$ curl --basic --user MY_USERNAME \\\n -H \"Content-Type: application/octet-stream\" \\\n --data-binary @/LOCATION_OF_MY_FILE/my-app-1.0.jar \\\n https://marketplace.atlassian.com/rest/2/assets/artifact?file=my-app-1.0.jar\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/assets/artifact%2F1324354154%2Fmy-app-1.0.jar\"\n }\n } [...you can ignore the rest of the JSON properties...]\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"artifact\": {\n \"href\": \"/rest/2/assets/artifact%2F1324354154%2Fmy-app-1.0.jar\"\n }\n },\n \"name\": \"1.0\",\n \"buildNumber\": 100,\n \"status\": \"private\",\n \"paymentModel\": \"free\",\n \"compatibilities\": [\n {\n \"application\": \"jira\",\n \"server\": {\n \"min\": {\n \"build\": 70107\n },\n \"max\": {\n \"build\": 71003\n }\n }\n }\n ],\n \"release\": {\n \"date\": \"2016-03-17\",\n \"beta\": false,\n \"supported\": false\n },\n \"text\": {\n \"releaseSummary\": \"here is a new version\"\n },\n \"vendorLinks\": {\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2\"\n },\n [some more links...]\n \"addons\": {\n \"href\": \"/rest/2/addons\"\n }\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons\"\n }, [...some more links that you can ignore...]\n \"byKey\": {\n \"href\": \"/rest/2/addons/{addonKey}\",\n \"templated\": true\n }\n}\n```\n\nExample:\n```bash\n{\n \"_links\": {\n \"self\": {\n \"href\": \"/rest/2/addons/my.excellent.app\"\n },\n \"versions\": {\n \"href\": \"/rest/2/addons/my.excellent.app/versions\"\n }\n}\n```\n\nExample:\n```bash\n$ curl --basic --user MY_USERNAME \\\n -H \"Content-Type: application/json\" \\\n --data-binary @/path/to/my-json-document.json \\\n https://marketplace.atlassian.com/rest/2/addons/my.excellent.app/versions\n```\n\nExample:\n```bash\nLocation: /rest/2/addons/my.excellent.app/versions/build/100\n```\n\nExample:\n```bash\n{\n \"errors\": [\n {\n \"path\": \"/buildNumber\",\n \"message\": \"Must be unique\"\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.301Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":169,"estimatedTokens":676}}81{"id":"doc-license_api_for_cloud_apps-b09ceb16","source":"documentation","title":"License API for cloud apps","url":"https://developer.atlassian.com/platform/marketplace/license-api-for-cloud-apps/","text":"Example:\n```json\n{\n \"key\": \"example-app\",\n \"version\": \"1.0\",\n \"state\": \"ENABLED\",\n \"host\": {\n \"product\": \"Jira\",\n \"contacts\": [\n {\n \"name\": \"Example Contact\",\n \"email\": \"contact@example.com\"\n }\n ]\n },\n \"license\": {\n \"active\": true,\n \"type\": \"COMMERCIAL\",\n \"evaluation\": false,\n \"supportEntitlementNumber\": \"SEN-###\"\n },\n \"links\": {\n \"marketplace\": [\n {\n \"href\": \"http:// marketplace.atlassian.com/plugins/example-app\"\n }\n ],\n \"self\": [\n {\n \"href\": \"http:// example.com/rest/atlassian-connect/latest/example-app\"\n }\n ]\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"key\": \"example-app\",\n \"version\": \"1.0\",\n \"state\": \"ENABLED\",\n \"host\": {\n \"product\": \"Jira\",\n \"contacts\": [\n {\n \"name\": \"Example Contact\",\n \"email\": \"contact@example.com\"\n }\n ]\n },\n \"links\": {\n \"marketplace\": [\n {\n \"href\": \"http:// marketplace.atlassian.com/plugins/example-app\"\n }\n ],\n \"self\": [\n {\n \"href\": \"http:// example.com/rest/atlassian-connect/latest/example-app\"\n }\n ]\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.301Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":73,"estimatedTokens":326}}82{"id":"doc-license_api_for_server_apps-7ecc2173","source":"documentation","title":"License API for server apps","url":"https://developer.atlassian.com/platform/marketplace/license-api-for-server-apps/","text":"Example:\n```text\nif (licenseManager.getLicense().isDefined())\n{\n PluginLicense license = licenseManager.getLicense().get();\n if (license.getError().isDefined())\n {\n // handle license error scenario\n // (e.g., expiration or user mismatch) \n }\n else\n {\n // handle valid license scenario\n }\n} \nelse\n{\n // handle unlicensed scenario\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nPluginLicense license = licenseManager.getLicense().get();\nif (license.isDataCenter())\n{\n // handle Data Center scenario\n // (e.g., functionality specific to Data Center) \n}\nelse\n{\n // handle server (non-Data Center) scenario\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.301Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":42,"estimatedTokens":170}}83{"id":"doc-getting_started_with_forge-c46a26bd","source":"documentation","title":"Getting started with Forge","url":"https://developer.atlassian.com/platform/forge/getting-started/","text":"Example:\n```bash\nnvm install --lts\nnvm use --lts\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnode --version\n```\n\nExample:\n```bash\nnpm config set unsafe-perm true\n```\n\nExample:\n```bash\nsudo rm -rf ~/Library/Preferences/@forge\n```\n\nExample:\n```bash\nnpm install -g @forge/cli\n```\n\nExample:\n```bash\nforge --version\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd <your-app-dir>\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nAllow Forge to collect CLI usage and error reporting information?\n```\n\nExample:\n```bash\n✔ Logged in as Mia Krystof\n```\n\nExample:\n```bash\nread FORGE_EMAIL\n# Enter email\nread -s FORGE_API_TOKEN\n# Enter your Atlassian API scoped token (will not be displayed)\nexport FORGE_EMAIL FORGE_API_TOKEN\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.303Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":83,"estimatedTokens":210}}84{"id":"doc-environments-e31d4823","source":"documentation","title":"Environments","url":"https://developer.atlassian.com/platform/forge/environments/","text":"Example:\n```bash\nforge variables list\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge variables set MY_KEY my-value\n```\n\nExample:\n```bash\nforge variables set --encrypt MY_KEY my-value\n```\n\nExample:\n```bash\nforge variables unset MY_KEY\n```\n\nExample:\n```javascript\nprocess.env.MY_KEY\n```\n\nExample:\n```bash\nforge variables set --environment production --encrypt MY_KEY my-value\n```\n\nExample:\n```bash\nexport FORGE_USER_VAR_MY_KEY=test\n```\n\nExample:\n```javascript\nconst myVar = process.env.MY_KEY // MY_KEY will be \"test\"\n```\n\nExample:\n```text\nforge environments create\n```\n\nExample:\n```text\nCreate a new development environment.\n\n? Enter a name for your environment: my-dev-environment\n\nCreating environment...\n\nCreated DEV my-dev-environment\n\nYou can now tunnel or deploy to this new development environment.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.304Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":65,"estimatedTokens":208}}85{"id":"doc-build_editions_for_existing_connect_apps-bbd78cc7","source":"documentation","title":"Build editions for existing Connect apps","url":"https://developer.atlassian.com/platform/marketplace/app-editions-connect-apps/","text":"Example:\n```text\nhttp://....?lic=active\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nhttp://....?lic=active&capSet=capabilityadvanced\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://issues.example.com\",\n \"displayUrlServicedeskHelpCenter\": \"https://support.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"capabilitySet\": \"capabilityadvanced\"\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```javascript\nlicense:\n active: true\n capabilitySet: \"capabilityadvanced\"\n[[Prototype]]: Object\n```\n\nExample:\n```javascript\n{\n\"modules\": {\n \"jiraProjectPages\": [\n {\n \"conditions\": [\n {\n \"condition\": \"capability_set\",\n \"params\": {\n \"value\": \"capabilityadvanced\"\n }\n }\n ]\n }\n ]\n}\n```\n\nExample:\n```text\n``` shell\n\"editionsEnabled\": true\n```\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.304Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":73,"estimatedTokens":358}}86{"id":"doc-user_privacy_guide_for_forge_app_developers-798dab1e","source":"documentation","title":"User privacy guide for Forge app developers","url":"https://developer.atlassian.com/platform/forge/user-privacy-guidelines","text":"Example:\n```json\n{\n\"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"updatedAt\": \"2018-10-25T23:08:51.382Z\"\n }, {\n \"accountId\": \"account-id-b\",\n \"updatedAt\": \"2018-10-25T23:14:44.231Z\"\n }, {\n \"accountId\": \"account-id-c\",\n \"updatedAt\": \"2018-12-01T02:44:21.020Z\"\n }]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"status\": \"closed\"\n }, {\n \"accountId\": \"account-id-c\",\n \"status\": \"updated\"\n }]\n}\n```\n\nExample:\n```json\n{\n \"errorType\": \"string\",\n \"errorMessage\": \"string\"\n}\n```\n\nExample:\n```typescript\ninterface Account {\n references: string[];\n accountId: string;\n displayName: string;\n emailAddress: string;\n updatedAt?: string;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.305Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":55,"estimatedTokens":188}}87{"id":"doc-get_started_with_the_forge_mcp_server-8ffe075d","source":"documentation","title":"Get started with the Forge MCP Server","url":"https://developer.atlassian.com/platform/forge/ai-development-toolkit/forge-mcp/","text":"Example:\n```json\n{\n \"mcpServers\": {\n \"forge\": {\n \"url\": \"https://mcp.atlassian.com/v1/forge/mcp\",\n \"transport\": \"http\"\n }\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.307Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":19,"estimatedTokens":47}}88{"id":"doc-install_the_forge_ai_plugin-ec1ef7f6","source":"documentation","title":"Install the Forge AI Plugin","url":"https://developer.atlassian.com/platform/forge/ai-development-toolkit/forge-ai-plugin/","text":"Example:\n```bash\n/plugin install forge-skills@atlassian-forge-skills\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nBuild me a Jira issue panel that shows customer support tickets.\n```\n\nExample:\n```text\nWhat Forge templates are available for Confluence macros?\n```\n\nExample:\n```text\nWhat Atlaskit components should I use for a data table?\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.307Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":27,"estimatedTokens":90}}89{"id":"doc-install_forge_skills-f75afd2f","source":"documentation","title":"Install Forge skills","url":"https://developer.atlassian.com/platform/forge/ai-development-toolkit/forge-developer-skills/","text":"Example:\n```bash\nnpx skills add atlassian/forge-skills\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnpx skills add atlassian/forge-skills --skill forge-app-builder\n```\n\nExample:\n```text\nCreate a Jira issue panel that shows related support tickets from an external API.\n```\n\nExample:\n```text\nBuild a Confluence macro that embeds an interactive chart with bar, line, and pie options.\n```\n\nExample:\n```text\nWhat scopes do I need for a Confluence app that also reads Jira data?\n```\n\nExample:\n```text\nReview my Forge app before release and tell me whether it is ready to ship.\n```\n\nExample:\n```text\nCheck my manifest, resolver wiring, dependencies, and tests before I deploy.\n```\n\nExample:\n```text\nAudit my Forge app for unnecessary invocations, storage writes, logs, and memory usage.\n```\n\nExample:\n```text\nOptimize this app's scheduled triggers and resolver calls to reduce Forge platform consumption.\n```\n\nExample:\n```text\nMy Forge issue panel is blank after deploy — help me trace it.\n```\n\nExample:\n```text\nMy app stopped appearing in Jira after a recent deploy. What could have gone wrong?\n```\n\nExample:\n```text\nBuild a Forge connector that ingests external project data into Rovo Search.\n```\n\nExample:\n```text\nWhere should I start if I want to surface ServiceNow knowledge articles in Rovo Chat?\n```\n\nExample:\n```text\nRun a white-box security review on this Forge app and include CVSS-scored findings.\n```\n\nExample:\n```text\nReview this Forge web trigger for authentication, authorization, and tenant isolation issues.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.308Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":82,"estimatedTokens":385}}90{"id":"doc-connect_app_descriptor-9fbb7a1d","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/confluence/app-descriptor/","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://docs.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.308Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":98,"estimatedTokens":449}}91{"id":"doc-migrating_a_forge_app_to_support_multiple_atlass-397dea2f","source":"documentation","title":"Migrating a Forge app to support multiple Atlassian apps","url":"https://developer.atlassian.com/platform/forge/migrating-a-forge-app-to-support-multiple-atlassian-apps/","text":"Example:\n```text\ncompatibility:\n jira:\n required: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ncompatibility:\n confluence:\n required: false\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n - read:app-system-token\n```\n\nExample:\n```text\ncompatibility:\n compass:\n required: false\npermissions:\n scopes:\n - read:event:compass\n```\n\nExample:\n```text\ncompatibility:\n confluence: (removed)\n```\n\nExample:\n```text\ncompatibility:\n jira:\n required: true\n confluence:\n required: false\n```\n\nExample:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\napp:\n id: '<app id>'\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<app id>'\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n jira:issuePanel:\n - key: hello-world-hello-world-issue-panel\n resource: main\n resolver:\n function: resolver\n render: native\n title: Forge app for Mia\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: <your app id>\n # required and optional Atlassian apps added here:\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.309Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":136,"estimatedTokens":491}}92{"id":"doc-building_automations_with_forge-b540a420","source":"documentation","title":"Building automations with Forge","url":"https://developer.atlassian.com/platform/forge/building-automations/","text":"Example:\n```text\nconst headers = webtriggerPayload.headers;\nconst headerSecretObject = headers['shared-secret'];\nconst headerSecret = headerSecretObject ? headerSecretObject[0] : undefined;\nconst validated = headerSecret && headerSecret === process.env.SHARED_SECRET;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - 'https://automation.atlassian.com'\n```\n\nExample:\n```text\nimport api, { route } from '@forge/api';\n```\n\nExample:\n```text\nconst result = await api\n .asApp()\n .[requestConfluence | requestJira](\n route`/rest/api/etc`\n );\nconst status = result.status;\n```\n\nExample:\n```text\nimport api from '@forge/api';\n\nconst response = await api.asUser()\n .withProvider('google', 'google-apis')\n .fetch('/userinfo/v2/me');\n```\n\nExample:\n```text\nconst payload = {\n ...\n}\nconst options = {\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(payload)\n}\n// Set this environment variable with forge variables set MY_WEBHOOK_URL https://automation.atlassian.com/[remainder of url of incoming webhook component]\nconst url = process.env.MYL_WEBHOOK_URL;\nconst response = await fetch(url, options);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.311Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":66,"estimatedTokens":314}}93{"id":"doc-upgrading_from_legacy_runtime-27386864","source":"documentation","title":"Upgrading from legacy runtime","url":"https://developer.atlassian.com/platform/forge/runtime-reference/legacy-runtime-migrating/","text":"Example:\n```javascript\nresolver.define(\"example\", () => {\n setTimeout(() => {\n fetch(\"...\");\n }, 5000);\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\napp:\n id: \"ari:cloud:ecosystem::app/406d303d-0393-4ec4-ad7c-1435be94583a\"\n runtime:\n name: sandbox\n```\n\nExample:\n```text\napp:\n id: \"ari:cloud:ecosystem::app/406d303d-0393-4ec4-ad7c-1435be94583a\"\n runtime:\n name: nodejs24.x\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.311Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":32,"estimatedTokens":103}}94{"id":"doc-migrating_storage_functions_to_forge_kvs_from_fo-92f8e836","source":"documentation","title":"Migrating storage functions to @forge/kvs from @forge/api","url":"https://developer.atlassian.com/platform/forge/storage-reference/kvs-migration-from-legacy/","text":"Example:\n```javascript\nawait storage.query().\n where(\"key\", startsWith(\"aa\")).\n getMany();\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nawait kvs.query().\n where(\"key\", WhereConditions.beginsWith(\"aa\")).\n getMany();\n```\n\nExample:\n```typescript\nexport enum SortOrder { ASC = 'ASC', DESC = 'DESC' }\n```\n\nExample:\n```typescript\nexport enum Sort { ASC = 'ASC', DESC = 'DESC'}\n```\n\nExample:\n```typescript\nconst andFilterChain = new Filter<Employee>()\n .and('employmentYear', FilterConditions.equalTo(2025))\n .and(...);\nconst orFilterChain = new Filter<Employee>()\n .or('employmentYear', FilterConditions.equalTo(2025))\n .or(...);\nawait kvs\n .entity<Employee>('employee')\n .query()\n .index('by-age')\n .filters(filter) // add filterChain here\n .sort(Sort.DESC)\n .getMany();\n```\n\nExample:\n```typescript\nexport const WhereConditions = {\n beginsWith,\n between,\n equalTo,\n greaterThan,\n greaterThanEqualTo,\n lessThan,\n lessThanEqualTo\n};\n\nexport const FilterConditions = {\n beginsWith,\n between,\n contains,\n notContains,\n equalTo,\n notEqualTo,\n exists,\n notExists,\n greaterThan,\n greaterThanEqualTo,\n lessThan,\n lessThanEqualTo\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.312Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":76,"estimatedTokens":295}}95{"id":"doc-migrating_data_to_forge_sql_from_an_external_sou-7ff9d737","source":"documentation","title":"Migrating data to Forge SQL from an external source","url":"https://developer.atlassian.com/platform/forge/storage-reference/sql-migration-guide/","text":"Example:\n```text\nmodules:\n function:\n - key: data-migrations-function\n handler: migration.handler\n # This enables the maximum runtime of an async events call\n timeoutSeconds: 900\n consumer:\n - key: forge-data-migration-queue-consumer\n queue: forge-data-migration-queue # is the key you will use in your code\n resolver:\n function: data-migrations-function\n method: dataMigrationQueueListener\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport { Queue } from '@forge/events';\nimport { dataMigrationDB } from \"./data-migration/sql\";\n\n// key matches what is defined in the manifest.yml\nconst dataMigrationQueue = new Queue({ key: \"forge-data-migration-queue\" });\n\n// You can use a single event to do the data migration\nexport async function startMigrationJob() {\n const { jobId } = await dataMigrationQueue.push({ body: {} });\n await dataMigrationDB.insertJob(jobId);\n}\n\n// OR fan out to multiple ones\nconst DATA_TYPES = ['users', 'stuff', 'things', 'related_things']\nexport async function startMigrationJobs() {\n\n // this will span a new event job for each data type above\n for(const dataType in DATA_TYPES) {\n const { jobId } = await dataMigrationQueue.push({ body: { dataType } });\n await dataMigrationDB.insertJob(jobId);\n }\n}\n```\n\nExample:\n```text\nCREATE TABLE IF NOT EXISTS ForgeDataMigrations (\n id BINARY(16) DEFAULT (UUID_TO_BIN(UUID())),\n jobId VARCHAR(256) UNIQUE NOT NULL ,\n status VARCHAR(20) NOT NULL,\n metadata VARCHAR(256),\n created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n finished_at DATETIME\n)\n```\n\nExample:\n```text\nexport const DATA_MIGRATION_TRACKING_STATE_ID = \"__DATA_MIGRATION__\";\n```\n\nExample:\n```text\nawait sql\n .prepare<{status: String}>(\"SELECT status FROM ForgeDataMigrations WHERE jobId = ?\")\n .bindParams(DATA_MIGRATION_TRACKING_STATE_ID)\n .execute();\n```\n\nExample:\n```text\nimport { invokeRemote } from '@forge/api';\nimport { dataMigrationDB, DATA_MIGRATION_TRACKING_STATE_ID } from './data-migration/sql';\n\n// This function is called by a scheduled trigger, webhook, or even a user via the UI\n// Will be used to start the data migration process\nexport const migrate = async (req) => {\n const migrationJob = await dataMigrationDB.getJob(DATA_MIGRATION_TRACKING_STATE_ID)\n\n // Short-circuit if the migration is already completed, don't call remote service\n switch (migrationJob.status) {\n case dataMigrationDB.status.PENDING:\n console.log(\"Migration is pending, not starting migration\");\n // now we can check the remote status\n break;\n case dataMigrationDB.status.COMPLETED:\n console.log(\"Migration is completed, no action needed\");\n return;\n case dataMigrationDB.status.MIGRATING:\n console.log(\"Migration is in progress, checking status and continuing if needed\");\n await checkMigrationJobs();\n return;\n }\n\n // Check-in to remote service to get migration status\n // This can return a status, or any migration data you need to get started\n const remoteMigrationState = await invokeRemote('get-migration-status')\n\n switch (remoteMigrationState.status) {\n case dataMigrationDB.status.PENDING:\n console.log(\"Migration is pending, not starting migration\");\n return;\n case dataMigrationDB.status.COMPLETED:\n // update the local state to match\n await dataMigrationDB.updateJob(\n DATA_MIGRATION_TRACKING_STATE_ID,\n dataMigrationDB.status.COMPLETED\n );\n return;\n case dataMigrationDB.status.MIGRATING:\n console.log(\"Migration is in progress, checking status and continuing if needed\");\n await checkMigrationJobs();\n return;\n case dataMigrationDB.status.READY:\n console.log(\"Migration ready to start, starting migration\");\n await dataMigrationDB.updateJob(\n DATA_MIGRATION_TRACKING_STATE_ID,\n dataMigrationDB.status.MIGRATING\n );\n await startMigrationJobs();\n return;\n default:\n console.log(`Unknown migration status ${remoteMigrationState.status}, not starting migration`);\n return;\n }\n};\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: data-migration-trigger\n function: data-migration-function\n interval: hour # Runs hourly\n function:\n - key: data-migration-function\n handler: index.migrate\n```\n\nExample:\n```text\nasync function getRemoteData<RowData>(dataType: String, after: String): Promise<MigrationData<RowData>> {\n const res = await invokeRemote('my-remote-key', {\n path: `/data-migration/${dataType}/?limit=500&after=${lastSyncId}`,\n method: 'GET'\n });\n\n if (!res.ok) {\n console.error(\"Failed to invoke remote\", { \n status: response.status,\n statusText: response.statusText,\n headers: response.headers.raw(),\n body: await res.text(),\n dataType,\n after\n })\n throw new Error(\"Failed to invoke remote\")\n }\n\n return await res.json();\n};\n```\n\nExample:\n```text\ninterface MigrationData<RowData> {\n /** Array matching order of DataRow that indicates the columns **/\n headers: Array<String>;\n /** Array of DataRow indicates the columns **/\n data: Array<RowData>;\n /** Quick info to include in getting next page\n * Useful if you use some other kind of pagination key.\n **/\n pagination: {\n afterId: String | Number;\n }\n}\n```\n\nExample:\n```text\nimport Resolver from '@forge/resolver';\nimport { invokeRemote } from '@forge/api';\nimport { Queue } from '@forge/events';\nimport { dataMigrationDB } from './data-migration/sql';\nimport { dataHandlers } from './data-migration/data-handlers';\n\n// key matches what is defined in the manifest.yml\nconst dataMigrationQueue = new Queue({ key: 'forge-data-migration-queue' });\n\n// The limit of the async event run time, in milliseconds to match the performance API\nconst ASYNC_RUNTIME = 900000; // 900 seconds (15 minutes)\nconst ASYNC_RUNTIME_BUFFER = 20000; // 20 seconds buffer to do start and cleanup\nconst ASYNC_RUNTIME_LIMIT = ASYNC_RUNTIME - ASYNC_RUNTIME_BUFFER;\n\nconst resolver = new Resolver();\n\n// The function that is called by the async event queue, which handles the actual data migration\nresolver.define('dataMigrationQueueListener', async ({ payload, context }) => {\n // start timer\n const startTime = performance.now();\n\n const { pagination, dataType } = payload;\n console.log({ jobId: context.jobId, pagination, dataType }, \"Data migration job started\");\n\n // Check if the job is already in progress, and update it if needed\n const currentJob = await dataMigrationDB.getJob(context.jobId);\n if(currentJob.status !== dataMigrationDB.status.MIGRATING) {\n await dataMigrationDB.updateJob(context.jobId, dataMigrationDB.status.MIGRATING);\n }\n\n // Use a specific handler for each data type for your migration\n const handler = dataHandlers[dataType];\n\n let nextPage = pagination;\n let done = false;\n do {\n // pull data from remote\n const remoteData = await handler.getRemoteData(nextPage)\n // set the next page details\n nextPage = handler.nextPage(remoteData);\n // determine if we are done or not\n if(handler.done(remoteData)) {\n done = true;\n }\n // convert data to forge sql format\n const forgeSqlParams = handler.toForgeSQL(remoteData);\n // store data in forge sql\n await handler.pushToSQL(forgeSqlParams);\n } while (!done || performance.now() - startTime < ASYNC_RUNTIME_LIMIT);\n\n if(!done) {\n const { jobId } = await dataMigrationQueue.push({ body: { dataType, pagination } });\n console.log(\"Next job started\", jobId);\n // update all job statuses\n await Promise.allSettled([\n dataMigrationDB.updateJob(context.jobId, dataMigrationDB.status.COMPLETED),\n // this can include the same context information, incase we hit the limit\n dataMigrationDB.insertJob(job.id, dataMigrationDB.status.PENDING, dataType, {\n dataType,\n pagination,\n }),\n ])\n } else {\n // otherwise mark the job as completed!\n await dataMigrationDB.updateJob(context.jobId, dataMigrationDB.status.COMPLETED);\n await markDataMigrationCompleted(dataType);\n console.log(\"Data migration completed\", {dataType, jobId: context.jobId});\n }\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```text\n/**\n * Example usage of insertMultipleRows function.\n */\nexport async function exampleInsert(rows) {\n const tableName = \"example_table\";\n const columns = [\"id\", \"name\", \"age\"];\n\n await insertMultipleRows(tableName, columns, rows);\n}\n\n// This will generate and execute the following SQL query:\n// INSERT INTO `example_table` (`id`, `name`, `age`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?);\n// Args: [1, 'Alice', 30, 2, 'Bob', 25, 3, 'Charlie', 35];\nawait exampleInsert([\n [1, \"Alice\", 30],\n [2, \"Bob\", 25],\n [3, \"Charlie\", 35],\n ])\n```\n\nExample:\n```text\nimport { sql } from '@forge/sql';\n\n/**\n * Generates and executes a SQL INSERT query for multiple rows of data.\n *\n * @param {string} tableName - The name of the table to insert data into.\n * @param {string[]} columns - An array of column names for the table.\n * @param {Array<Array<any>>} rows - An array of arrays, where each inner array represents a row of data.\n */\nexport async function insertMultipleRows(tableName, columns, rows) {\n if (!rows || rows.length === 0) {\n console.log(\"No rows to insert\");\n return;\n }\n\n // Construct the base SQL query\n const columnList = columns.map((col) => `\\`${col}\\``).join(\", \");\n // Precompute the placeholder for a single row\n const singleRowPlaceholder = `(${columns.map(() => \"?\").join(\", \")})`;\n const query = `INSERT INTO \\`${tableName}\\` (${columnList}) VALUES ${new Array(\n rows.length\n )\n .fill(singleRowPlaceholder)\n .join(\", \")}`;\n\n // Prepare and execute the SQL query\n await sql.prepare(query).bindParams(rows.flat()).execute();\n\n console.log(`Inserted ${rows.length} rows into table ${tableName}`);\n}\n```\n\nExample:\n```text\n/** Checks existing data migration jobs, and starts them again as needed */\nexport async function checkMigrationJobs() {\n const jobs = await dataMigrationDB.getJobsByStatus(dataMigrationDB.status.MIGRATING);\n\n if (jobs.length === 0) {\n console.log(\"No migration jobs in progress, starting them again\");\n await startMigrationJobs();\n return;\n }\n\n const missingDataTypes = getMissingDataTypes(jobs);\n if (missingDataTypes.size > 0) {\n console.log(\"Missing data types in migration jobs, starting them again\", { missingDataTypes: [...missingDataTypes] });\n await startMigrationJobs(missingDataTypes);\n }\n\n const jobTasks = jobs.map((job) => processJob(job));\n await Promise.allSettled(jobTasks);\n}\n\n/** Identifies missing data types that need to be restarted */\nfunction getMissingDataTypes(jobs) {\n const activeDataTypes = new Set(jobs.map((job) => job.dataType));\n const allDataTypes = new Set(DATA_TYPES);\n return new Set([...allDataTypes].filter((dataType) => !activeDataTypes.has(dataType)));\n}\n\n\n/** Processes an individual job and restarts it if necessary */\nasync function processJob(job) {\n console.log(`Checking migration job ${job.id} status`);\n const queueJob = dataMigrationQueue.getJob(job.jobId);\n const { success, inProgress, failed } = await jobProgress.getStats();\n\n if (inProgress) {\n console.log(\"Job is still in progress\", { jobId: job.jobId, dataType: job.dataType });\n return;\n } else if (success) {\n console.log(\n \"Job completed, but still marked as migrating. Restarting at last know point\",\n { jobId: job.jobId, dataType: job.dataType, pagination: job.pagination }\n );\n await dataMigrationDB.updateJob(job.id, dataMigrationDB.status.COMPLETED);\n await restartJob(job);\n } else if (failed) {\n console.log(\"Job previously failed, restarting from last known point\", {\n jobId: job.jobId,\n dataType: job.dataType,\n pagination: job.pagination,\n });\n await dataMigrationDB.updateJob(job.id, dataMigrationDB.status.FAILED);\n await restartJob(job);\n }\n}\n\n/** Restarts a job with the same data type and pagination */\nasync function restartJob(job) {\n const { newJobId } = await dataMigrationQueue.push({ body: { dataType: job.dataType, pagination: job.pagination } });\n await dataMigrationDB.insertJob(newJobId);\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.315Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":14,"totalLines":381,"estimatedTokens":3083}}96{"id":"doc-optimise_forge_platform_costs-0c3d1af3","source":"documentation","title":"Optimise Forge platform costs","url":"https://developer.atlassian.com/platform/forge/optimise-forge-costs/","text":"Example:\n```javascript\n// Both UI Kit and Custom UI — import from @forge/bridge\nimport { requestJira } from '@forge/bridge';\n\n// ✅ Fetch issue data directly from the browser — no function invocation needed\nasync function loadIssue(issueKey) {\n const response = await requestJira(`/rest/api/3/issue/${issueKey}?fields=summary,status,assignee`);\n return response.json();\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// ❌ Anti-pattern: resolver called on every render\nfunction MyPanel() {\n const result = useAction(() => invoke('getConfig'));\n return <Text>{result.data?.value}</Text>;\n}\n\n// ✅ Better: fetch once, store in state\nfunction MyPanel() {\n const [config, setConfig] = useState(null);\n useEffect(() => {\n invoke('getConfig').then(setConfig);\n }, []); // empty dependency array = runs once on mount\n return <Text>{config?.value}</Text>;\n}\n```\n\nExample:\n```javascript\nimport { view } from '@forge/bridge';\n\nasync function getIssueKey() {\n const context = await view.getContext();\n return context.extension.issue.key;\n}\n```\n\nExample:\n```javascript\nimport { useProductContext } from '@forge/react';\n\nfunction MyPanel() {\n const context = useProductContext();\n\n // context.extension.issue.key, context.extension.project.key, etc.\n // are all available here — no resolver call needed\n const issueKey = context?.extension?.issue?.key;\n\n return <Text>Issue: {issueKey}</Text>;\n}\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: my-scheduled-trigger\n function: my-function\n interval: day # Options: fiveMinutes, hour, day, week\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: issue-updated-trigger\n function: my-function\n events:\n - avi:jira:updated:issue # fires only when an issue is updated\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n // Read a lightweight flag from storage before doing expensive work\n const lastProcessedAt = await storage.get('lastProcessedAt');\n const dataChangedAt = await getDataLastChangedTimestamp();\n\n if (dataChangedAt <= lastProcessedAt) {\n // Nothing has changed since last run — exit early\n return;\n }\n\n // Only now do the expensive processing\n await processData();\n await storage.set('lastProcessedAt', Date.now());\n}\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: issue-trigger\n function: handle-issue-event\n events:\n - avi:jira:updated:issue\n filter:\n # Only invoke the function for issues in a specific project\n expression: \"event.issue.fields.project.key == 'MYPROJ'\"\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n const { issue } = event;\n\n // Exit early if this isn't an issue type we care about\n if (issue.fields.issuetype.name !== 'Bug') return;\n\n // Exit early if the priority isn't high enough\n if (!['Critical', 'Blocker'].includes(issue.fields.priority.name)) return;\n\n // Only now do expensive work\n await processHighPriorityBug(issue);\n}\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: issue-updated-trigger\n function: handle-issue-update\n events:\n - avi:jira:updated:issue\n filter:\n ignoreSelf: true # Don't invoke this function for updates made by our own app\n expression: \"event.issue.fields.project.key == 'MYPROJ'\"\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: my-inbound-webhook\n function: handle-webhook\n\nfunctions:\n - key: handle-webhook\n handler: index.handleWebhook\n```\n\nExample:\n```javascript\n// Your web trigger handler receives the inbound HTTP request\nexport async function handleWebhook(request) {\n const payload = JSON.parse(request.body);\n\n // Process the event from the external service\n await processExternalEvent(payload);\n\n return { statusCode: 200, body: 'OK' };\n}\n```\n\nExample:\n```javascript\nimport { storage } from '@forge/api';\n\n// Cache an expensive API result for 1 hour\nconst CACHE_TTL_MS = 60 * 60 * 1000;\n\nexport async function getCachedData(key) {\n const cached = await storage.get(`cache:${key}`);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.value; // serve from cache — no extra API call needed\n }\n\n const freshData = await fetchExpensiveData(key);\n await storage.set(`cache:${key}`, { value: freshData, timestamp: Date.now() });\n return freshData;\n}\n```\n\nExample:\n```javascript\nimport { asApp, route } from '@forge/api';\n\n// Store metadata against a Jira issue — uses product storage, not Forge storage\nawait asApp().requestJira(route`/rest/api/3/issue/${issueId}/properties/my-app-data`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ processedAt: Date.now(), status: 'done' }),\n});\n```\n\nExample:\n```javascript\nimport { storage } from '@forge/api';\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\nasync function getCustomFieldId(fieldName) {\n const cacheKey = `field-id:${fieldName}`;\n const cached = await storage.get(cacheKey);\n\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.fieldId; // serve from cache — no API call needed\n }\n\n // Cache miss: fetch from API and store\n const fields = await fetchAllFields();\n const field = fields.find(f => f.name === fieldName);\n\n await storage.set(cacheKey, { fieldId: field.id, cachedAt: Date.now() });\n return field.id;\n}\n```\n\nExample:\n```javascript\nimport { storage, startsWith } from '@forge/api';\n\n// ❌ Anti-pattern: fetch all items and filter in code\nconst allItems = await storage.query().getMany();\nconst activeItems = allItems.results.filter(item => item.value.status === 'active');\n\n// ✅ Better: use index-based querying to fetch only what you need\n// (requires defining an index on the 'status' field in your entity schema)\nconst activeItems = await storage.query()\n .index('by-status')\n .where('status', startsWith('active'))\n .limit(50)\n .getMany();\n```\n\nExample:\n```javascript\n// ❌ Anti-pattern: N+1 API calls\nconst issues = await searchIssues('project = MYPROJ');\nfor (const issue of issues) {\n // This makes one API call per issue — very expensive!\n const details = await getIssue(issue.id);\n await process(details);\n}\n```\n\nExample:\n```javascript\n// ✅ Better: fetch all details in one or a few bulk requests\nconst issues = await searchIssues('project = MYPROJ', {\n fields: ['summary', 'status', 'assignee', 'priority', 'description'],\n // Request all the fields you need upfront\n});\n// No additional per-issue calls needed — all fields already loaded\nfor (const issue of issues) {\n await process(issue);\n}\n```\n\nExample:\n```javascript\n// ❌ Fetches every field — wastes bandwidth and memory\nconst response = await asApp().requestJira(route`/rest/api/3/search?jql=project=MYPROJ`);\n\n// ✅ Request only the fields you'll actually use\nconst response = await asApp().requestJira(\n route`/rest/api/3/search?jql=project=MYPROJ&fields=summary,status,assignee`\n);\n```\n\nExample:\n```javascript\n// ❌ Sequential: duration = sum of all call durations\nconst issueData = await getIssue(issueId);\nconst userData = await getUser(userId);\nconst projectData = await getProject(projectId);\n\n// ✅ Parallel: duration ≈ slowest single call\nconst [issueData, userData, projectData] = await Promise.all([\n getIssue(issueId),\n getUser(userId),\n getProject(projectId),\n]);\n```\n\nExample:\n```javascript\n// ❌ Anti-pattern: fetch all issues, filter in code\nconst allIssues = await searchIssues('project = MYPROJ');\nfor (const issue of allIssues) {\n if (issue.fields.status.name === 'Done' && issue.fields.assignee === null) {\n await processIssue(issue); // only a small subset actually gets processed\n }\n}\n\n// ✅ Better: express the condition in JQL so only matching issues are returned\nconst targetIssues = await searchIssues(\n 'project = MYPROJ AND status = Done AND assignee is EMPTY'\n);\nfor (const issue of targetIssues) {\n await processIssue(issue); // every issue here needs processing\n}\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n const lastRunAt = await storage.get('lastRunAt') || '2000-01-01';\n\n // Only fetch issues updated since the last run\n const issues = await searchIssues(\n `project = MYPROJ AND updated >= \"${lastRunAt}\" ORDER BY updated ASC`\n );\n\n for (const issue of issues) {\n await processIssue(issue);\n }\n\n await storage.set('lastRunAt', new Date().toISOString().split('T')[0]);\n}\n```\n\nExample:\n```javascript\n// ❌ Fetches up to 50 issues by default — wasteful if you only need 5\nconst response = await asApp().requestJira(\n route`/rest/api/3/search?jql=project=MYPROJ&orderby=created DESC`\n);\n\n// ✅ Fetch only as many items as you actually need\nconst response = await asApp().requestJira(\n route`/rest/api/3/search?jql=project=MYPROJ&orderby=created DESC&maxResults=5`\n);\n```\n\nExample:\n```javascript\n// ✅ Use maximum page size when fetching all results to minimise round trips\nconst response = await asApp().requestJira(\n route`/rest/api/3/search?jql=project=MYPROJ&maxResults=100`\n);\n```\n\nExample:\n```text\nfunctions:\n - key: my-lightweight-function\n handler: index.run\n memoryMiB: 128 # Reduce for simple, low-memory functions\n\n - key: my-data-processing-function\n handler: processor.run\n memoryMiB: 1024 # Increase if the function processes large payloads\n```\n\nExample:\n```javascript\nconst DEBUG = process.env.DEBUG_LOGGING === 'true';\n\nexport async function run(event, context) {\n if (DEBUG) console.log('Event received:', JSON.stringify(event));\n\n // ... function logic ...\n\n if (DEBUG) console.log('Processing complete');\n}\n```\n\nExample:\n```javascript\n// ❌ Logs the full event payload on every invocation — expensive at scale\nconsole.log('Processing event:', JSON.stringify(event));\n\n// ✅ Log only what you need for debugging production issues\nconsole.log(`Processing issue ${event.issue?.key} (type: ${event.issue?.fields?.issuetype?.name})`);\n```\n\nExample:\n```text\nremotes:\n - key: my-remote-backend\n baseUrl: https://my-backend.example.com\n\nmodules:\n jira:issuePanel:\n - key: my-panel\n resource: main\n resolver:\n function: remote-resolver\n endpoint: my-remote-backend\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.318Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":29,"totalLines":394,"estimatedTokens":2534}}97{"id":"doc-migrate_a_macro_module_from_connect_to_forge-7afc24b0","source":"documentation","title":"Migrate a macro module from Connect to Forge","url":"https://developer.atlassian.com/platform/forge/adopting-forge-from-connect-migrate-macro/","text":"Example:\n```text\nmodules:\n macro:\n - key: static-macro-key # Forge macro key - matches Connect macro key\n resource: main\n # ...\nremotes:\n - key: connect\n baseUrl: https://hello-world-app.example.com\napp:\n id: ari:cloud:ecosystem::app/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\n connect:\n key: my-connect-app # Target connect app key\n remote: connect\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: static-macro-key\n resource: main\n render: native\n unlicensedAccess:\n - unlicensed # logged-in users without an app license\n - anonymous # users who are not logged in (guests)\n```\n\nExample:\n```json\n{\n \"type\": \"extension\",\n \"attrs\": {\n \"extensionType\": \"com.atlassian.confluence.macro.core\",\n \"extensionKey\": \"static-macro-key\",\n \"parameters\": {\n \"macroParams\": {\n \"myConnectField\": {\n \"value\": \"some text\"\n }\n }\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"type\": \"extension\",\n \"attrs\": {\n \"extensionType\": \"com.atlassian.ecosystem\",\n \"extensionKey\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/static/static-macro-key\",\n \"parameters\": {\n \"guestParams\": {\n \"myForgeField\": \"some text\"\n },\n \"macroParams\": {\n \"myConnectField\": {\n \"value\": \"some text\"\n }\n }\n }\n }\n}\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { view } from \"@forge/bridge\";\nimport ForgeReconciler, { Textfield } from \"@forge/react\";\n\nconst Config = () => {\n const [context, setContext] = useState(undefined);\n\n useEffect(() => {\n view.getContext().then(setContext);\n }, []);\n const config = context?.extension.config;\n\n return (\n <>\n {/* Keep an existing Connect field as is.\n * This will automatically be populated with the parameter from your Connect macro, if it exists */}\n <Textfield name=\"myConnectField\" label=\"My Connect Field\" />\n {/* Add a new Forge field, with a transformed value from Connect, if it exists */}\n <Textfield\n name=\"myForgeField\"\n label=\"My Forge Field\"\n defaultValue={config?.myOldConnectField?.replace(\"old\", \"new\")}\n />\n </>\n );\n};\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```text\n<style>\n body {\n width: fit-content;\n }\n</style>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.319Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":115,"estimatedTokens":580}}98{"id":"doc-debug_forge_functions_using_vs_code-4e8fb170","source":"documentation","title":"Debug Forge functions using VS Code","url":"https://developer.atlassian.com/platform/forge/debug-functions-using-vscode/","text":"Example:\n```json\n{\n // Use IntelliSense to learn about possible attributes.\n // Hover to view descriptions of existing attributes.\n // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"name\": \"Attach Forge App Debugger\",\n \"port\": 9229,\n \"request\": \"attach\",\n \"type\": \"node\",\n \"sourceMaps\": true\n }\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge tunnel --debug --debugFunctionHandlers index.handler --debugStartingPort 9229\n```\n\nExample:\n```bash\n✔ Resources bundled.\n\nListening for requests...\n\nDebugger listening on ws://0.0.0.0:9229/8d29c246-2186-4e20-9851-4aa609a3b890\nFor help, see: https://nodejs.org/en/docs/inspector\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.320Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":41,"estimatedTokens":203}}99{"id":"doc-debug_forge_functions_using_intellij-d05410a2","source":"documentation","title":"Debug Forge functions using IntelliJ","url":"https://developer.atlassian.com/platform/forge/debug-functions-using-intellij/","text":"Example:\n```bash\nforge tunnel --debug --debugFunctionHandlers index.handler --debugStartingPort 9229\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n✔ Resources bundled.\n\nListening for requests...\n\nDebugger listening on ws://0.0.0.0:9229/8d29c246-2186-4e20-9851-4aa609a3b890\nFor help, see: https://nodejs.org/en/docs/inspector\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.320Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":22,"estimatedTokens":86}}100{"id":"doc-upgrade_to_ui_kit_latest_version-e15a3137","source":"documentation","title":"Upgrade to UI Kit latest version","url":"https://developer.atlassian.com/platform/forge/ui-kit/upgrade-to-ui-kit-latest/","text":"Example:\n```javascript\nimport ForgeUI, { useState, useEffect, useAction, Text } from '@forge/ui';\n\nconst App = () => {\n const [count, setCount] = useState(0);\n\n return (\n <Text>Count: {count}</Text>\n );\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect, useReducer } from 'react';\nimport { Text } from '@forge/react';\n\nconst App = () => {\n const [count, setCount] = useState(0);\n\n return (\n <Text>Count: {count}</Text>\n );\n};\n```\n\nExample:\n```text\nmodules:\n confluence:globalPage:\n - key: hello-world\n resource: main\n route: hello-world\n resolver:\n function: resolver\n render: native\n title: UI Kit App\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n```\n\nExample:\n```javascript\nconst CheckboxGroup = () => {\n return (\n <Label labelFor=\"products\">Products</Label>\n <CheckboxGroup name=\"products\" options={[\n { label: 'Jira', value: 'jira' },\n { label: 'Confluence', value: 'confluence' },\n ]} />\n <HelperMessage>Pick a product</HelperMessage>\n )\n}\n```\n\nExample:\n```javascript\n<Label labelFor=\"start-date\">Start date</Label>\n<DatePicker id=\"start-date\" />\n<HelperMessage>Enter a start date</HelperMessage>\n```\n\nExample:\n```javascript\n<Label labelFor=\"color\">Color</Label>\n<RadioGroup id=\"color\" options={[\n { name: 'color', value: 'red', label: 'Red' },\n { name: 'color', value: 'blue', label: 'Blue' },\n { name: 'color', value: 'yellow', label: 'Yellow' },\n]} />\n<HelperMessage>Pick a color</HelperMessage>\n```\n\nExample:\n```javascript\n<Label labelFor=\"range\">Range</Label>\n<Range id=\"range\" />\n```\n\nExample:\n```javascript\n<Label labelFor=\"fruit\">Favourite fruit</Label>\n<Select\n inputId=\"fruit\"\n options={[\n { label: 'Apple', value: 'apple' },\n { label: 'Banana', value: 'banana' },\n ]}\n/>\n<HelperMessage>Pick a fruit</HelperMessage>\n```\n\nExample:\n```javascript\n<Label labelFor=\"message\">Message</Label>\n<TextArea id=\"message\" />\n<HelperMessage>Enter a message</HelperMessage>\n```\n\nExample:\n```javascript\n<Label labelFor=\"email\">Email</Label>\n<Textfield id=\"email\" />\n<HelperMessage>Enter an email</HelperMessage>\n```\n\nExample:\n```javascript\n<Label labelFor=\"toggle\">Toggle</Label>\n<Toggle id=\"toggle\" />\n```\n\nExample:\n```javascript\nimport ForgeUI, { AdminPage, render, Text} from '@forge/ui';\n\nconst App = () => {\n return (\n <AdminPage>\n <Text>Hello, world!</Text>\n </AdminPage>\n );\n};\n\nexport const run = render(\n <App/>\n);\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\n\nconst App = () => {\n return (\n <Text>Hello, world!</Text>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: pet-facts\n function: main\n title: Pet\n description: Inserts facts about a pet\n config:\n function: config-function-key\n function:\n - key: main\n handler: index.run\n - key: config-function-key\n handler: index.config\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: pet-facts\n resource: main\n render: native\n resolver:\n function: resolver\n title: Pet\n config: true\n function:\n - key: resolver\n handler: index.handler\n```\n\nExample:\n```javascript\nimport ForgeUI, { MacroConfig, TextField, render } from '@forge/ui';\n\nconst Config = () => {\n return (\n <MacroConfig>\n <Textfield name=\"age\" label=\"Pet age\" />\n </MacroConfig>\n );\n};\n\nexport const config = render(<Config />);\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Label, Textfield } from '@forge/react';\n\nconst Config = () => {\n return (\n <>\n <Label>Pet age</Label>\n <Textfield name=\"age\" /> \n </>\n );\n};\n\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```javascript\nimport { kvs } from '@forge/kvs';\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getExampleValue', async ({ payload, context }) => {\n return await kvs.get('example-key');\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const [value, setValue] = useState(undefined);\n\n useEffect(() => {\n invoke('getExampleValue').then(setValue);\n }, []);\n\n return (\n <Text>{value}</Text>\n )\n};\n```\n\nExample:\n```bash\nui-kit-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```bash\nui-kit-1-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n └── index.jsx\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\nawait api.asUser()\n .requestJira(route`/rest/api/3/issue/${issueKey}/watchers`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\"\n },\n body: JSON.stringify(`${accountId}`)\n });\n```\n\nExample:\n```javascript\nimport { requestJira } from '@forge/bridge';\n\nawait requestJira(`/rest/api/3/issue/${issueKey}/watchers`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\"\n },\n body: JSON.stringify(`${accountId}`)\n});\n```\n\nExample:\n```text\nmodules:\n macro:\n export:\n function: exportFunction\n function:\n - key: exportFunction\n handler: index.macroExport\n```\n\nExample:\n```text\nmodules:\n macro:\n adfExport:\n function: exportFunction\n function:\n - key: exportFunction\n handler: index.macroExport\n```\n\nExample:\n```javascript\nimport ForgeUI, { render, Text } from \"@forge/ui\";\n\nexport const macroExport = render(\n <Text>`Hello world! This is the export view for my macro.`</Text>\n);\n```\n\nExample:\n```javascript\nimport { doc, p } from '@atlaskit/adf-utils/builders';\n\nexport const macroExport = async (payload) => {\n return doc(\n p(`Hello world! This is the export view for my macro.`)\n );\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.322Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":28,"totalLines":354,"estimatedTokens":1589}}101{"id":"doc-implement_a_dynamic_profile_retriever_with_exter-8c3ca94d","source":"documentation","title":"Implement a dynamic profile retriever with external authentication","url":"https://developer.atlassian.com/platform/forge/implement-a-dynamic-profile-retriever-with-external-authentication/","text":"Example:\n```text\n``` typescript\nimport { AuthProfile } from '@forge/response';\n\ninterface ProfileRetrieverParameters {\n status: number;\n body: {\n [key: string]: any;\n };\n}\n\nexport const retriever = (response: ProfileRetrieverParameters): AuthProfile => {\n const { status, body: externalProfile } = response;\n\n if (status === 200){\n return new AuthProfile({\n id: externalProfile.id,\n displayName: externalProfile.email || externalProfile.name,\n avatarUrl: externalProfile.picture,\n });\n } else {\n throw new Error(`Could not determine profile information. HTTP ${status}`);\n }\n}\n```\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n function:\n ...\n - key: google-profile\n handler: auth.retriever\n```\n\nExample:\n```text\nproviders:\n auth:\n - key: google\n ...\n actions:\n ...\n retrieveProfile:\n remote: google-apis\n path: /userinfo/v2/me\n function: google-profile\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\ncould not retrieve profile information: There was an error invoking\nthe function - Can't wrap a non-transferable value\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.322Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":74,"estimatedTokens":301}}102{"id":"doc-profile_performance_with_the_forge_tunnel_debugg-e0fecf74","source":"documentation","title":"Profile performance with the Forge tunnel debugger","url":"https://developer.atlassian.com/platform/forge/profiling-node-js-app-code-tunnel/","text":"Example:\n```bash\nforge tunnel --debug --debugFunctionHandlers index.runFirstMacro\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.323Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":12,"estimatedTokens":32}}103{"id":"doc-extending_your_app_with_a_scheduled_trigger-0b698106","source":"documentation","title":"Extending your app with a scheduled trigger","url":"https://developer.atlassian.com/platform/forge/add-scheduled-trigger/","text":"Example:\n```text\nmodules:\n webtrigger:\n - key: temporary-development-webtrigger\n function: '<your-function-key>'\n urlFormat: v2\n response:\n type: dynamic\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge webtrigger create\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: my-scheduled-trigger\n function: '<your-function-key>'\n interval: hour\n```\n\nExample:\n```text\nmodules:\n scheduledTrigger:\n - key: jira-five-minute-trigger\n function: 'your-function-key'\n interval: fiveMinute\n function:\n - key: your-function-key\n handler: index.handler\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.325Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":59,"estimatedTokens":185}}104{"id":"doc-build_a_jira_automation_action-2c56c714","source":"documentation","title":"Build a Jira Automation Action","url":"https://developer.atlassian.com/platform/forge/build-a-jira-automation-action/","text":"Example:\n```text\ncd {your-preferred-location}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"name\": \"requirements-automation-action\",\n \"version\": \"1.1.5\",\n \"main\": \"index.js\",\n \"license\": \"MIT\",\n \"private\": true,\n \"scripts\": {\n \"lint\": \"eslint src/**/*\"\n },\n \"devDependencies\": {\n \"eslint\": \"^8.56.0\",\n \"eslint-plugin-react-hooks\": \"^4.6.0\"\n },\n \"dependencies\": {\n \"@forge/api\": \"^6.4.1\",\n \"@forge/bridge\": \"5.10.0\",\n \"@forge/react\": \"11.8.2\",\n \"@forge/resolver\": \"1.7.1\",\n \"react\": \"^18.2.0\"\n }\n}\n```\n\nExample:\n```text\ncurl \"https://a94fc9cc-4ce2-49ec-b588-42e609969d26.hello.atlassian-dev.net/x1/lS731HDWJ5HleJIn3fQwR5qHHQ0?work-item-key=foo-123\"\n```\n\nExample:\n```text\n{\n \"id\": \"req-12345\",\n \"status\": \"approved\",\n \"approvalDate\": \"2025-12-12T01:11:08.845Z\",\n \"context\": {\n \"workItemKey\": [\n \"foo-123\"\n ]\n }\n}\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: https://a94fc9cc-4ce2-49ec-b588-42e609969d26.hello.atlassian-dev.net\n```\n\nExample:\n```text\nconst requirementsSystemApiEndpoint = `https://a94fc9cc-4ce2-49ec-b588-42e609969d26.hello.atlassian-dev.net/x1/lS731HDWJ5HleJIn3fQwR5qHHQ0`;\n\nconst fetchRequirementData = async (workItemKey) => {\n const url = `${requirementsSystemApiEndpoint}?work-item-key=${workItemKey}`;\n const response = await fetch(url);\n if (response.ok) {\n const requirementInfo = await response.json();\n console.log(`Fetched requirement info for work item ${workItemKey}: ${JSON.stringify(requirementInfo)}`);\n return {\n success: true,\n requirementInfo: requirementInfo,\n }\n } else {\n const errorText = await response.text();\n const errorMessage = `Failed to add comment to issueKey: ${issueKey}. Status: ${response.status}. Response: ${errorText}`;\n console.log(`Failed to fetch requirement for workItemKey: ${workItemKey}`);\n return {\n success: false,\n errorMessage: errorMessage,\n }\n }\n};\n\nexport const getRequirementInfo = async (payload) => {\n const workItemKey = payload.workItemKey;\n const requirementData = await fetchRequirementData(workItemKey);\n // Convert the requirement info into the schema that we want our action \n // output to have...\n const requirementInfoActionOutput = {\n requirementId: requirementData.requirementInfo?.id,\n requirementStatus: requirementData.requirementInfo?.status,\n }\n console.log(`Requirement info action output: ${JSON.stringify(requirementInfoActionOutput, null, 2)}`);\n return requirementInfoActionOutput;\n}\n```\n\nExample:\n```text\nimport { view } from '@forge/bridge';\nimport {\n Form,\n useForm,\n Stack,\n Text,\n ErrorMessage,\n Textfield,\n Box,\n} from '@forge/react';\n\nexport const AutomationInputsForm = ({ context, isValidating }) => {\n const formInstance = useForm({\n defaultValues: context.extension.data.inputs,\n disabled: isValidating\n });\n const { handleSubmit, register, getValues, formState } = formInstance;\n\n const onChange = (input) => {\n const updatedFormData = { ...getValues(), ...input };\n\n view.submit(updatedFormData);\n };\n\n const onSubmit = data => {\n view.submit(data);\n };\n\n const { onChange: workItemKeyOnChange, ...workItemKeyRegisterProps } = register('workItemKey', {\n required: { value: true, message: 'Work item key is required' },\n disabled: isValidating,\n });\n\n return (\n <Box>\n <Form onSubmit={handleSubmit(onSubmit)}>\n <Stack space=\"space.100\">\n <Text>Work item key</Text>\n <Textfield\n {...workItemKeyRegisterProps}\n onChange={(e) => {\n workItemKeyOnChange(e);\n onChange({ workItemKey: e.target.value });\n }}\n />\n {formState.errors.workItemKey?.message && (\n <ErrorMessage>\n {formState.errors.workItemKey?.message}\n </ErrorMessage>\n )}\n </Stack>\n </Form>\n </Box>\n );\n};\n```\n\nExample:\n```text\nimport React, { useEffect, useState } from 'react';\n\nimport { events } from '@forge/bridge';\nimport ForgeReconciler, {\n Text,\n useProductContext,\n} from '@forge/react';\nimport { AutomationInputsForm } from './AutomationInputsForm.jsx';\n\nexport const AutomationConfig = () => {\n\n const context = useProductContext();\n const [isValidating, setIsValidating] = useState(false);\n\n // This effect sets up a listener for the 'AUTOMATION_ACTION_VALIDATE_RULE_EVENT' event.\n useEffect(() => {\n const subscription = events.on('AUTOMATION_ACTION_VALIDATE_RULE_EVENT', onValidateAutomationRuleEvent);\n return () => subscription.then(sub => sub.unsubscribe());\n }, []);\n\n const onValidateAutomationRuleEvent = ({ isValidating }) => {\n setIsValidating(isValidating);\n }\n\n const render = () => {\n if (context) {\n return <AutomationInputsForm context={context} isValidating={isValidating} />;\n } else {\n return <Text>Loading...</Text>;\n }\n }\n\n return render();\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <AutomationConfig />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nexport { getRequirementInfo } from './backend/requirementRetriever';\n```\n\nExample:\n```text\nfunction:\n - key: get-requirement-info\n handler: index.getRequirementInfo\n```\n\nExample:\n```text\naction:\n - key: requirement-info-action\n name: Requirement Info Provider\n function: get-requirement-info\n description: Retrieves requirement information for a work item.\n actionVerb: GET \n config:\n resource: config-form-res\n render: native\n inputs:\n workItemKey:\n title: Work item Key\n type: string\n description: The work item key for the Jira work item that the requirement relates to\n required: true\n outputs:\n requirementId:\n description: The requirement ID that was retrieved\n type: string\n nullable: false\n requirementStatus:\n description: The status of requirement\n type: string\n nullable: false\n outputContext:\n entityName: requirement\n outputType: OBJECT\n outputDomain: jira\n```\n\nExample:\n```text\nautomation:actionProvider:\n - key: requirement-action-provider\n actions:\n - requirement-info-action\n```\n\nExample:\n```text\nresources:\n - key: config-form-res\n path: src/frontend/AutomationConfigForm.jsx\n```\n\nExample:\n```text\nmodules:\n automation:actionProvider:\n - key: requirement-action-provider\n actions:\n - requirement-info-action\n action:\n - key: requirement-info-action\n name: Requirement Info Provider\n function: get-requirement-info\n description: Retrieves requirement information for a work item.\n actionVerb: GET\n config:\n resource: config-form-res\n render: native\n inputs:\n workItemKey:\n title: Work item Key\n type: string\n description: The work item key for the Jira work item that the requirement\n relates to\n required: true\n outputs:\n requirementId:\n description: The requirement ID that was retrieved\n type: string\n nullable: false\n requirementStatus:\n description: The status of requirement\n type: string\n nullable: false\n outputContext:\n entityName: requirement\n outputType: OBJECT\n outputDomain: jira\n function:\n - key: get-requirement-info\n handler: index.getRequirementInfo\nresources:\n - key: config-form-res\n path: src/frontend/AutomationConfig.jsx\napp:\n runtime:\n name: nodejs22.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/your-app-id\npermissions:\n external:\n fetch:\n backend:\n - address: https://a94fc9cc-4ce2-49ec-b588-42e609969d26.hello.atlassian-dev.net\n```\n\nExample:\n```text\nFound requirement:\n* ID: \"{{fetchedJiraRequirement.requirementId}}\"\n* Status: \"{{fetchedJiraRequirement.requirementStatus}}\"\n```\n\nExample:\n```text\nFound requirement:\n* ID: \"req-12345\"\n* Status: \"approved\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.327Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":17,"totalLines":335,"estimatedTokens":2022}}105{"id":"doc-build_an_app_compatible_with_confluence_and_jira-0ac95f5b","source":"documentation","title":"Build an app compatible with Confluence and Jira","url":"https://developer.atlassian.com/platform/forge/build-an-app-compatible-with-confluence-and-jira/","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: hello world app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n compatibility:\n confluence:\n required: true\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n compatibility:\n confluence:\n required: true\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.328Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":115,"estimatedTokens":426}}106{"id":"doc-set_up_continuous_delivery_for_forge_apps-e410a885","source":"documentation","title":"Set up continuous delivery for Forge apps","url":"https://developer.atlassian.com/platform/forge/set-up-cicd/","text":"Example:\n```text\nimage:\n name: node:18\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npipelines:\n default:\n - step:\n name: Check Code linting\n script:\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge lint\n caches:\n - node\n```\n\nExample:\n```text\nforge settings set usage-analytics true\n```\n\nExample:\n```text\n- step:\n name: Deploy to Staging\n deployment: staging\n caches:\n - node\n script:\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge deploy -e staging\n - forge install --upgrade --site MYSITE.atlassian.com --product confluence --non-interactive -e staging\n```\n\nExample:\n```text\n- step:\n name: Deploy to Production\n trigger: manual\n deployment: production\n caches:\n - node\n script:\n - cd hello-world-app\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge deploy -e production\n```\n\nExample:\n```text\nimage:\n name: node:18\n\npipelines:\n default:\n - step:\n name: Check Code linting\n script:\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge lint\n caches:\n - node\n \n - step:\n name: Deploy to Staging\n deployment: staging\n caches:\n - node\n script:\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge deploy -e staging\n - forge install --upgrade --site MYSITE.atlassian.com --product confluence --non-interactive -e staging\n # remove this forge install step if you prefer to deploy \n # major app changes through your site's Universal Plugin Manager.\n\n - step:\n name: Deploy to Production\n trigger: manual\n deployment: production\n caches:\n - node\n script:\n - npm install\n - npm install --global @forge/cli\n - forge settings set usage-analytics true\n - forge deploy -e production\n```\n\nExample:\n```text\n- cd hello-world-app\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.329Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":113,"estimatedTokens":622}}107{"id":"doc-storage_api_limit_handling-8330203d","source":"documentation","title":"Storage API limit handling","url":"https://developer.atlassian.com/platform/forge/storage-api-limit-handling/","text":"Example:\n```text\nmodules:\n # Async events\n consumer:\n - key: queue-consumer\n # Name of the queue for which this consumer will be invoked\n queue: storage-async-queue\n # Function to be called with payload\n function: consumer-function\n function:\n - key: consumer-function\n handler: index.handler\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport {InvocationError, InvocationErrorCode, Queue} from \"@forge/events\";\n\nconst storageAsyncQueue = new Queue({ key: 'storage-async-queue' });\n\n// Product event handling\nexport async function onIssueCreated(event, context) {\n\n // A batch job that created 1000+ of issues will call\n // kvs.set 1000+ times and hit the operation limits.\n // This pushes it to the queue instead to let onIssueCreated handle it.\n\n const body = {\n \"issueKey\": event.issue.key\n }\n\n await storageAsyncQueue.push({ body, delayInSeconds: 0.5 });\n\n return true;\n}\n```\n\nExample:\n```javascript\nimport { InvocationError, InvocationErrorCode } from '@forge/events';\nimport { storage as kvs } from '@forge/api';\n\n// Async event handler\nexport async function handler(event, context) {\n let retryDelay = 0;\n\n // If `event.retryContext` exists, this event is a retry event\n if (event.retryContext) {\n const baseDelay = 20;\n const randomJitter = Math.random() * 100;\n \n // Exponential backoff with jitter\n retryDelay = (baseDelay * (2 ** event.retryContext.retryCount)) + randomJitter;\n } \n\n // Store issue key by calling a Storage API\n try {\n await kvs.set('most-recent-issue-created', event.body.issueKey);\n } catch (error) {\n // Return an InvocationError to trigger a retry\n return new InvocationError({\n retryAfter: retryDelay,\n retryReason: InvocationErrorCode.FUNCTION_RETRY_REQUEST,\n retryData: {\n issueKey: event.body.issueKey\n }\n });\n }\n}\n```\n\nExample:\n```javascript\nimport { InvocationError, InvocationErrorCode, Queue } from '@forge/events';\nimport { storage as kvs } from '@forge/api';\n\nconst storageAsyncQueue = new Queue({ key: 'storage-async-queue' });\n\n// Product event handling\nexport async function onIssueCreated(event, context) {\n\n // A batch job that created 1000+ of issues will call\n // kvs.set 1000+ times and hit the operation limits.\n // This pushes it to the queue instead to let onIssueCreated handle it.\n\n const body = {\n \"issueKey\": event.issue.key\n }\n\n await storageAsyncQueue.push({ body, delayInSeconds: 0.5 });\n\n return true;\n}\n\n// Async event handler\nexport async function handler(event, context) {\n let retryDelay = 0;\n\n // If `event.retryContext` exists, this event is a retry event\n if (event.retryContext) {\n const baseDelay = 20;\n const randomJitter = Math.random() * 100;\n \n // Exponential backoff with jitter\n retryDelay = (baseDelay * (2 ** event.retryContext.retryCount)) + randomJitter;\n } \n\n // Store issue key by calling a Storage API\n try {\n await kvs.set('most-recent-issue-created', event.body.issueKey);\n } catch (error) {\n // Return an InvocationError to trigger a retry\n return new InvocationError({\n retryAfter: retryDelay,\n retryReason: InvocationErrorCode.FUNCTION_RETRY_REQUEST,\n retryData: {\n issueKey: event.body.issueKey\n }\n });\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.330Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":131,"estimatedTokens":826}}108{"id":"doc-forge_compass_and_aws_cloudwatch-9990f394","source":"documentation","title":"Forge, Compass, and AWS CloudWatch","url":"https://developer.atlassian.com/platform/forge/forge-compass-cloudwatch/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd demo_app\ngit init\ngit add --all\ngit commit -m \"initial commit\"\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```text\nmodules:\n compass:componentPage:\n - key: demo-app-hello-world-component-page\n resource: main\n resolver:\n function: resolver\n render: native\n title: demo_app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\npermissions:\n scopes:\n - read:component:compass\n - write:component:compass\napp:\n runtime:\n name: nodejs24.x\n id: <auto generated id token>\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const [data, setData] = useState(null);\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n return (\n <>\n <Text>Hello world!</Text>\n <Text>{data ? data : 'Loading...'}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', (req) => {\n console.log(req);\n\n return 'Hello, world!';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport { invoke } from '@forge/bridge';\nimport ForgeReconciler, {\n Button,\n Label,\n RequiredAsterisk,\n Textfield,\n} from '@forge/react';\n\nconst App = () => {\n const [regionVal, setRegionVal] = useState();\n const [accessKeyIdVal, setAccessKeyIdVal] = useState();\n const [secretAccessKeyVal, setSecretAccessKeyVal] = useState();\n\n const change_aws_region = (event) => {\n setRegionVal(event.target.value);\n };\n\n const change_aws_access_key_id = (event) => {\n setAccessKeyIdVal(event.target.value);\n };\n\n const change_aws_secret_access_key = (event) => {\n setSecretAccessKeyVal(event.target.value);\n };\n\n const save = () => {\n invoke(\"save\", {\n aws_region: regionVal,\n aws_access_key_id: accessKeyIdVal,\n aws_secret_access_key: secretAccessKeyVal,\n });\n };\n\n return (\n <>\n <Label labelFor=\"aws_region_text_field\">\n AWS Region:\n <RequiredAsterisk />\n </Label>\n <Textfield\n appearance=\"standard\"\n value={regionVal}\n onChange={change_aws_region}\n />\n <Label labelFor=\"aws_access_key_id_text_field\">\n AWS Access Key Id:\n <RequiredAsterisk />\n </Label>\n <Textfield\n appearance=\"standard\"\n value={accessKeyIdVal}\n onChange={change_aws_access_key_id}\n />\n <Label labelFor=\"aws_secret_access_key_text_field\">\n AWS Secret Access Key:\n <RequiredAsterisk />\n </Label>\n <Textfield\n appearance=\"standard\"\n value={secretAccessKeyVal}\n onChange={change_aws_secret_access_key}\n />\n <Button appearance=\"primary\" type=\"submit\" onClick={save}>\n Save\n </Button>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport { kvs } from '@forge/kvs';\n```\n\nExample:\n```javascript\nresolver.define(\"save\", (req) => {\n kvs.setSecret(\"aws_region\", req.payload.aws_region);\n kvs.setSecret(\"aws_access_key_id\", req.payload.aws_access_key_id);\n kvs.setSecret(\"aws_secret_access_key\", req.payload.aws_secret_access_key);\n});\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```text\nmodules:\n compass:componentPage:\n - key: demo-app-hello-world-component-page\n resource: main\n resolver:\n function: resolver\n render: native\n title: demo_app\n compass:adminPage:\n - key: admin-page-ui\n resource: admin-main\n resolver:\n function: admin-resolver\n render: native\n title: demo_app\n function:\n - key: resolver\n handler: index.handler\n - key: admin-resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: admin-main\n path: src/frontend/admin-index.jsx\npermissions:\n scopes:\n - read:component:compass\n - write:component:compass\n - storage:app\napp:\n runtime:\n name: nodejs24.x\n id: <auto generated id token>\n```\n\nExample:\n```text\ncompass:adminPage:\n - key: admin-page-ui\n resource: admin-main\n resolver:\n function: admin-resolver\n render: native\n title: demo_app\n```\n\nExample:\n```text\n- key: admin-resolver\n handler: index.handler\n```\n\nExample:\n```text\n- key: admin-main\n path: src/frontend/admin-index.jsx\n```\n\nExample:\n```text\n- storage:app\n```\n\nExample:\n```bash\nforge deploy\nforge install --upgrade\n```\n\nExample:\n```bash\nnpm install @aws-sdk/client-cloudwatch-logs\nnpm install @aws-sdk/client-cloudwatch\n```\n\nExample:\n```javascript\nresolver.define(\"get_logs\", async (req) => {\n try {\n const aws_region = await kvs.getSecret(\"aws_region\");\n const aws_access_key_id = await kvs.getSecret(\"aws_access_key_id\");\n const aws_secret_access_key = await kvs.getSecret(\n \"aws_secret_access_key\"\n );\n\n const config = {\n region: `${aws_region}`.toString(),\n credentials: {\n accessKeyId: `${aws_access_key_id}`.toString(),\n secretAccessKey: `${aws_secret_access_key}`.toString(),\n },\n };\n\n const cwlClient = new CloudWatchLogsClient(config);\n const cwlInput = {\n logGroupIdentifier:\n \"<log group identifier>\",\n logStreamName: \"<log stream name>\",\n startFromHead: true,\n unmask: true,\n };\n\n var cwlResponse;\n const cwlCommand = new GetLogEventsCommand(cwlInput);\n try {\n cwlResponse = await cwlClient.send(cwlCommand);\n } catch (e) {\n console.log(e.$response);\n }\n const eventData = cwlResponse.events;\n\n const rows = eventData.map((event1, index) => ({\n key: `row-${index}-${event1.eventId}`,\n cells: [\n {\n key: event1.timestamp,\n content: event1.timestamp,\n },\n {\n key: event1.message,\n content: event1.message,\n },\n ],\n }));\n\n const head = {\n cells: [\n {\n key: \"timestamp\",\n content: \"timestamp\",\n isSortable: true,\n },\n {\n key: \"message\",\n content: \"message\",\n shouldTruncate: true,\n isSortable: true,\n },\n ],\n };\n\n return [head, rows];\n } catch (e) {\n console.log(\"get_logs error: \" + e);\n }\n});\n```\n\nExample:\n```javascript\nresolver.define(\"get_metrics\", async (req) => {\n try {\n const aws_region = await kvs.getSecret(\"aws_region\");\n const aws_access_key_id = await kvs.getSecret(\"aws_access_key_id\");\n const aws_secret_access_key = await kvs.getSecret(\n \"aws_secret_access_key\"\n );\n\n const config = {\n region: `${aws_region}`.toString(),\n credentials: {\n accessKeyId: `${aws_access_key_id}`.toString(),\n secretAccessKey: `${aws_secret_access_key}`.toString(),\n },\n };\n\n const cwClient = new CloudWatchClient(config);\n const cwInput = {\n Namespace: \"<namespace>\",\n MetricName: \"<metric name>\",\n Dimensions: [\n {\n Name: \"FunctionName\",\n Value:\n \"<function name>\",\n },\n ],\n IncludeLinkedAccounts: false,\n };\n\n const cwCommand = new ListMetricsCommand(cwInput);\n var cwResponse;\n try {\n cwResponse = await cwClient.send(cwCommand);\n } catch (e) {\n console.log(\"cwResponse error: \" + e);\n }\n \n const getMetricDataInput = {\n MetricDataQueries: [\n {\n Id: \"asdf123\",\n MetricStat: {\n Metric: {\n Namespace: \"<namespace>\",\n MetricName: \"<metric name>\",\n Dimensions: [\n {\n Name: cwResponse.Metrics[1].Dimensions[0].Name,\n Value: cwResponse.Metrics[1].Dimensions[0].Value,\n },\n ],\n },\n Period: 3600,\n Stat: \"Sum\",\n Unit: \"Count\",\n },\n ReturnData: true,\n },\n ],\n StartTime: new Date(2024, 0, 0),\n EndTime: new Date(2024, 10, 0),\n };\n\n const getMetricDataCommand = new GetMetricDataCommand(getMetricDataInput);\n var getMetricDataResponse;\n try {\n getMetricDataResponse = await cwClient.send(getMetricDataCommand);\n } catch (e) {\n console.log(\"getMetricDataResponse error: \" + e);\n }\n\n var timestamps = getMetricDataResponse.MetricDataResults[0].Timestamps;\n var values = getMetricDataResponse.MetricDataResults[0].Values;\n\n const returnValue = [];\n timestamps.forEach(function (timestamp, i) {\n returnValue[i] = [\n timestamp,\n values[i],\n getMetricDataResponse.MetricDataResults[0].Label,\n ];\n });\n\n return returnValue;\n } catch (e) {\n console.log(e);\n }\n});\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport { invoke } from '@forge/bridge';\nimport ForgeReconciler, { DynamicTable, LineChart } from '@forge/react';\n\nconst App = () => {\n const [metric_data, set_metric_data] = useState(null);\n useEffect(() => {\n invoke(\"get_metrics\", {}).then(set_metric_data);\n }, []);\n\n const [log_data, set_log_data] = useState(null);\n useEffect(() => {\n invoke(\"get_logs\", {}).then(set_log_data);\n }, []);\n\n return (\n <>\n <>\n {metric_data ? (\n <LineChart\n data={metric_data}\n xAccessor={0}\n yAccessor={1}\n colorAccessor={2}\n />\n ) : (\n \"Loading...\"\n )}\n </>\n <>\n {log_data ? (\n <DynamicTable\n caption=\"CloudWatch Logs\"\n head={log_data[0]}\n rows={log_data[1]}\n />\n ) : (\n \"Loading...\"\n )}\n </>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nexternal:\n fetch:\n backend:\n - \"*.us-west-2.amazonaws.com\"\n client:\n - \"*.us-west-2.amazonaws.com\"\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:component:compass\n - write:component:compass\n - storage:app\n external:\n fetch:\n backend:\n - \"*.us-west-2.amazonaws.com\"\n client:\n - \"*.us-west-2.amazonaws.com\"\n```\n\nExample:\n```bash\n(base) wmarusiak@C02F207NML7L icons % pwd\n/Users/wmarusiak/workspace/projects/forge/compass/demo2/demo_app/resources/icons\n(base) wmarusiak@C02F207NML7L icons % ls\nlogo.png\n(base) wmarusiak@C02F207NML7L icons %\n```\n\nExample:\n```text\n- key: icons\n path: resources\n```\n\nExample:\n```text\ncompass:adminPage:\n - key: admin-page-ui\n resource: admin-main\n resolver:\n function: admin-resolver\n render: native\n title: AWSCloudWatchGrabber\n icon: resource:icons;icons/logo.png\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.331Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":28,"totalLines":551,"estimatedTokens":2798}}109{"id":"doc-add_routing_to_a_custom_ui_full_page_app-eea754da","source":"documentation","title":"Add routing to a Custom UI full page app","url":"https://developer.atlassian.com/platform/forge/add-routing-to-a-full-page-app/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd custom-ui-routing-tutorial\n```\n\nExample:\n```bash\nnpm install\n```\n\nExample:\n```bash\nnpm run build\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nnpm install react-router@latest\n```\n\nExample:\n```bash\nnpm install @forge/bridge@latest\n```\n\nExample:\n```javascript\nimport React, { Fragment, useEffect, useState } from \"react\";\nimport { view } from \"@forge/bridge\";\nimport { Router, Route, Routes, useNavigate } from \"react-router\";\n\nfunction Link({ to, children }) {\n const navigate = useNavigate();\n return (\n <a\n href={to}\n onClick={(event) => {\n event.preventDefault();\n navigate(to);\n }}\n >\n {children}\n </a>\n );\n}\n\n// Starting page\nfunction Home() {\n return (\n <Fragment>\n <h2>Home</h2>\n <Link to=\"/page-with-path\">Route to page with path</Link>\n </Fragment>\n );\n}\n\n// Page that will render with /page-with-path link\nfunction PageWithPath() {\n return <h2>Page with path</h2>;\n}\n\nfunction App() {\n const [historyState, setHistoryState] = useState(null);\n const [navigator, setNavigator] = useState(null);\n const historyCleanupRef = React.useRef(null);\n\n useEffect(() => {\n // We're using an immediately invoked function expression (IIFE) to handle async code in useEffect\n (async () => {\n // When the app mounts, we use the view API to create a history \"log\"\n const history = await view.createHistory();\n setNavigator(history);\n\n // The initial values of action and location will be the app URL\n setHistoryState({\n action: history.action,\n location: history.location,\n });\n // Listen for changes in the history \"log\"\n const unsubscribe = await history.listen((location, action) => {\n setHistoryState({\n action,\n location,\n });\n });\n\n // Store a reference to the cleanup function for when the user navigates away\n historyCleanupRef.current = unsubscribe;\n })();\n }, []);\n\n const handleUnload = () => {\n if (historyCleanupRef.current) {\n historyCleanupRef.current();\n }\n };\n\n // When the user navigates away from the app and the iframe gets removed, we clean up the history listener\n useEffect(() => {\n window.addEventListener('unload', handleUnload);\n return () => {\n window.removeEventListener('unload', handleUnload);\n };\n }, []);\n\n if (!navigator || !historyState) {\n return <div>Loading...</div>;\n }\n\n return (\n <div>\n <Router\n navigator={navigator}\n navigationType={historyState.action}\n location={historyState.location}\n >\n <Routes>\n <Route path=\"/page-with-path\" element={<PageWithPath />}></Route>\n <Route path=\"/\" element={<Home />}></Route>\n </Routes>\n </Router>\n </div>\n );\n}\n\nexport default App;\n```\n\nExample:\n```bash\nforge tunnel\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.334Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":155,"estimatedTokens":744}}110{"id":"doc-use_an_external_oauth_2_0_api_with_fetch-67dcc1ad","source":"documentation","title":"Use an external OAuth 2.0 API with fetch","url":"https://developer.atlassian.com/platform/forge/use-an-external-oauth-2.0-api-with-fetch/","text":"Example:\n```text\nproviders:\n auth:\n - key: google\n name: Google\n scopes:\n - 'profile'\n - 'https://www.googleapis.com/auth/userinfo.email'\n type: oauth2\n clientId: PLACEHOLDER\n remotes:\n - google-apis\n bearerMethod: authorization-header\n actions:\n authorization:\n remote: google-account\n path: /o/oauth2/v2/auth\n exchange:\n remote: google-oauth\n path: /token\n revokeToken:\n remote: google-oauth\n path: /revoke\n retrieveProfile:\n remote: google-apis\n path: /userinfo/v2/me\n resolvers:\n id: id\n displayName: email\n avatarUrl: picture\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nremotes:\n - key: google-apis\n baseUrl: https://www.googleapis.com\n - key: google-account\n baseUrl: https://accounts.google.com\n - key: google-oauth\n baseUrl: https://oauth2.googleapis.com\npermissions:\n external:\n fetch:\n backend:\n - 'https://www.googleapis.com'\n - 'https://oauth2.googleapis.com'\n - 'https://accounts.google.com'\n```\n\nExample:\n```text\nfunction:\n - key: main\n handler: index.handler\n providers:\n auth:\n - google\n```\n\nExample:\n```typescript\n// src/frontend/index.jsx\n\nimport React, { useEffect, useState } from 'react';\n// Step 1: Import CodeBlock and remove Text\nimport ForgeReconciler, { CodeBlock } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const [data, setData] = useState({});\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n // Step 2: Remove the Text component and use CodeBlock\n return (\n <CodeBlock text={JSON.stringify(data, null, 2)} language=\"json\" showLineNumbers /> \n );\n\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```typescript\n// src/resolvers/index.js\n\nimport Resolver from '@forge/resolver';\n// Step 1: Add import\nimport api from \"@forge/api\";\n\nconst resolver = new Resolver();\n\n// Step 2: Add 'async' to be able to wait for the promises inside to resolve\nresolver.define('getText', async (req) => {\n\n // Step 3: Call the external APIs\n const google = api.asUser().withProvider('google', 'google-apis')\n\n if (!(await google.hasCredentials())) {\n await google.requestCredentials()\n }\n\n const response = await google.fetch('/userinfo/v2/me');\n\n // Step 4: Return the user's name\n if (response.ok) {\n const userInfo = await response.json();\n\n return userInfo;\n }\n\n // Step 5: Return the error response\n return {\n status: response.status,\n statusText: response.statusText,\n text: await response.text(),\n }\n\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge providers configure google\n```\n\nExample:\n```text\nactions:\n authorization:\n remote: google-account\n path: /o/oauth2/v2/auth\n queryParameters:\n access_type: offline\n```\n\nExample:\n```text\nqueryParameters:\n access_type: offline\n prompt: consent\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.334Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":166,"estimatedTokens":807}}111{"id":"doc-build_a_pull_request_title_validator_with_custom-d0076e4e","source":"documentation","title":"Build a pull request title validator with custom merge checks","url":"https://developer.atlassian.com/platform/forge/build-a-pull-request-title-validator-with-custom-merge-checks/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd pr-title-validator\n```\n\nExample:\n```text\npermissions:\nscopes:\n - read:pullrequest:bitbucket\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:pullrequest:bitbucket\nmodules:\n bitbucket:mergeCheck:\n - key: check-pr-title\n function: main\n name: Check pull request title\n description: Check pull request title does not contain 'DRAFT'\n triggers:\n - on-merge\n function:\n - key: main\n handler: index.run\napp:\n runtime:\n name: nodejs24.x\n id: '<your-app-id>'\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n```\n\nExample:\n```javascript\nconst workspaceUuid = event.workspace.uuid;\nconst repoUuid = event.repository.uuid;\nconst prId = event.pullrequest.id;\n\nconst res = await api\n .asApp()\n .requestBitbucket(\n route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`\n );\nconst pr = await res.json();\n```\n\nExample:\n```javascript\nconst success = !pr.title.includes(\"DRAFT\");\nconst message = success\n ? \"Pull request title does not contain 'DRAFT'\"\n : \"Pull request title contains 'DRAFT'\";\n```\n\nExample:\n```javascript\nreturn { success, message };\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n\nexport const run = async (event, context) => {\n const workspaceUuid = event.workspace.uuid;\n const repoUuid = event.repository.uuid;\n const prId = event.pullrequest.id;\n\n const res = await api\n .asApp()\n .requestBitbucket(\n route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`\n );\n const pr = await res.json();\n\n const success = !pr.title.includes(\"DRAFT\");\n const message = success\n ? \"Pull request title does not contain 'DRAFT'\"\n : \"Pull request title contains 'DRAFT'\";\n\n return { success, message };\n};\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.335Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":118,"estimatedTokens":495}}112{"id":"doc-orchestrate_your_builds_using_dynamic_pipelines-67cf3c94","source":"documentation","title":"Orchestrate your builds using Dynamic Pipelines","url":"https://developer.atlassian.com/platform/forge/orchestrate-your-builds-using-dynamic-pipelines/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - 'read:repository:bitbucket'\n```\n\nExample:\n```text\nmodules:\n bitbucket:dynamicPipelinesProvider:\n - key: dynamic-pipelines-provider\n function: main\n name: Example Dynamic Pipelines provider\n description: A Dynamic Pipelines provider example\n function:\n - key: main\n handler: index.main\npermissions:\n scopes:\n - 'read:repository:bitbucket'\napp:\n id: <your-app-id-here>\n```\n\nExample:\n```javascript\nexport const main = (request, context) => {\n console.log(`Handling Dynamic Pipelines request: ${JSON.stringify(request, null, 4)}`);\n return { pipelines_configuration: request.pipelines_configuration };\n};\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nSelect an Atlassian app:\n Jira\n Confluence\n Compass\n❯ Bitbucket\n```\n\nExample:\n```bash\nEnter the workspace URL: bitbucket.org/{your-workspace-id}\n```\n\nExample:\n```bash\nYour app will be installed with the following scopes:\n- read:repository:bitbucket\n\n? Do you want to continue? (y/N) y\n```\n\nExample:\n```bash\n✔ Install complete!\nYour app in the development environment is now installed in Bitbucket on bitbucket.org\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nINFO 23:55:21.092 b8a065db98924683b85b09b383c6dc9c Handling Dynamic Pipelines request: {\n \"target\": {\n \"commit\": {\n \"hash\": \"<commit-hash>\"\n },\n \"type\": \"pipeline_commit_target\"\n },\n \"trigger\": \"definitions\",\n \"pipelines_configuration\": {\n \"pipelines\": {\n \"default\": [\n {\n \"step\": {\n \"name\": \"Scan for secrets\",\n \"script\": [\n {\n \"pipe\": \"atlassian/git-secrets-scan:1.5.0\",\n \"variables\": {\n \"ANNOTATION_SUMMARY\": \"Secret found in code.\",\n \"FILES\": \"*\"\n }\n }\n ]\n }\n }\n ]\n },\n \"image\": {\n \"name\": \"node:18\"\n }\n },\n \"creator\": \"<user-uuid>\",\n \"repository\": \"<repo-uuid>\",\n \"workspace\": \"<workspace-uuid>\",\n \"context\": {\n \"cloudId\": \"<random-uuid>\",\n \"moduleKey\": \"my-bitbucket-dynamic-pipelines-provider\"\n },\n \"contextToken\": \"a-really-long-string\"\n}\n```\n\nExample:\n```javascript\nconst addStepIfDefaultPipeline = (request) => {\n // If the pipeline does not contain a 'default' definition, just return what was sent in the request.\n if (!request.pipelines_configuration?.pipelines?.default) {\n return { pipelines_configuration: request.pipelines_configuration };\n }\n\n // Otherwise prepend a step to the 'default' pipeline\n request.pipelines_configuration.pipelines.default = [\n {\n step: {\n name: \"Dynamic step\",\n script: [\"echo 'This step was added dynamically'\"],\n },\n },\n ...request.pipelines_configuration.pipelines?.default,\n ];\n\n // and return it.\n return request;\n};\n```\n\nExample:\n```javascript\nexport const main = (request, context) => {\n const updatedPipelineRequest = addStepIfDefaultPipeline(request);\n console.log(`Updated request: ${JSON.stringify(updatedPipelineRequest, null, 4)}`);\n return { pipelines_configuration: updatedPipelineRequest.pipelines_configuration };\n};\n```\n\nExample:\n```bash\nnpm install @forge/api --save\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n```\n\nExample:\n```javascript\nconst getCommitDiffStat = async (workspaceId, repositoryId, commitHash) => {\n const requestUrl = route`/2.0/repositories/${workspaceId}/${repositoryId}/diffstat/${commitHash}`;\n\n // Use the Forge Runtime API to fetch data from Bitbucket API using your app credential\n let response = await api.asApp().requestBitbucket(requestUrl);\n\n if (response.status !== 200) {\n console.log(response.status);\n const responseBody = await response.json();\n throw `Unable to get diffstat for commit ${commitHash}, status: ${\n response.status\n }, response: ${JSON.stringify(responseBody, null, 4)}`;\n }\n\n return response.json();\n};\n```\n\nExample:\n```javascript\nconst addStepIfDefaultPipeline = async (request) => {\n // code from earlier\n};\n```\n\nExample:\n```javascript\nconst addStepIfDefaultPipeline = async (request) => {\n // If not the 'default' pipeline definition matched from the static YML configuration, return unmodified configuration\n if (!request.pipelines_configuration.pipelines?.default) {\n return { pipelines_configuration: request.pipelines_configuration };\n }\n\n // All the values you need are part of the \"request\" object.\n const commitDiffStat = await getCommitDiffStat(\n request.workspace,\n request.repository,\n request.target.commit.hash,\n );\n\n // Otherwise prepend a step to the 'default' pipeline\n ...\n};\n```\n\nExample:\n```javascript\nconst addStepIfDefaultPipeline = async (request) => {\n // If the pipeline does not contain a default definition, just return what was sent in the request.\n if (!request.pipelines_configuration?.pipelines?.default) return request;\n\n // get the diff state for the commit.\n const commitDiffStat = await getCommitDiffStat(\n request.workspace,\n request.repository,\n request.target.commit.hash,\n );\n\n // get the number of files changed.\n const changedFilesNumber = commitDiffStat.size;\n\n // add a second line to the script section, injecting the number of files changed for the comit.\n request.pipelines_configuration.pipelines.default = [\n {\n step: {\n name: \"Dynamic step\",\n script: [\n \"echo 'This step was added dynamically'\",\n `echo 'The change for this request contained ${changedFilesNumber} files.'`\n ],\n },\n },\n ...request.pipelines_configuration.pipelines?.default,\n ];\n\n // and return the whole request.\n return request;\n};\n```\n\nExample:\n```javascript\nexport const main = async (request, context) => {\n const updatedPipelineRequest = await addStepIfDefaultPipeline(request);\n console.log(`Updated request: ${JSON.stringify(updatedPipelineRequest, null, 4)}`);\n return { pipelines_configuration: updatedPipelineRequest.pipelines_configuration };\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.336Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":22,"totalLines":266,"estimatedTokens":1605}}113{"id":"doc-use_custom_entities_to_store_structured_data-e97cf845","source":"documentation","title":"Use custom entities to store structured data","url":"https://developer.atlassian.com/platform/forge/custom-entities-store-structured-data/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnpm install --save @forge/kvs@latest\n```\n\nExample:\n```bash\nnpm install --save uuid\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n```\n\nExample:\n```text\napp:\n...\n storage:\n entities:\n - name: 'users'\n attributes:\n name:\n type: 'string'\n age:\n type: 'integer'\n country:\n type: 'string'\n indexes:\n - name: 'by-country'\n range:\n - 'country'\n - name: 'by-country-name'\n range:\n - 'name'\n partition:\n - 'country'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge storage entities indexes list -e development\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nimport kvs from '@forge/kvs';\nimport { v4 as uuidv4 } from 'uuid';\n\nconst validateInput = (payload) => {\n let input = {};\n try {\n input = JSON.parse(payload);\n } catch (e) {\n throw e;\n }\n if (!input.name || !input.age || !input.country) {\n throw new Error('Request body did not have necessary information');\n }\n if (typeof input.name !== 'string' || typeof input.age !== 'number' || typeof input.country !== 'string') {\n throw new Error('At least one of the request parameters is the wrong type');\n }\n if (input.country !== 'India' && input.country !== 'Australia' && input.country !== 'Indonesia') {\n throw new Error('Country must either be India, Australia or Indonesia');\n }\n return input;\n};\n\nconst createUserInStorage = async (data) => {\n try {\n await kvs\n .entity('users')\n .set(`user-${uuidv4()}`, {\n ...data,\n age: data.age,\n });\n } catch (e) {\n throw e;\n }\n}\n\nexport const createUser = async (event, context) => {\n try {\n if (event.method !== 'POST') {\n return { statusCode: 400, body: \"POST method must be provided\" };\n }\n const input = validateInput(event.body);\n await createUserInStorage(input);\n\n const result = {\n body: JSON.stringify(input),\n headers: {\n 'Content-Type': ['application/json'],\n },\n statusCode: 200,\n statusText: 'OK'\n };\n return result;\n } catch (e) {\n // Assume all errors are client errors\n return { statusCode: 400, body: e.message };\n }\n};\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n ...\n - key: user-management-via-entities-webtrigger-create-user\n function: create-user\n urlFormat: v2\n response:\n type: dynamic\n function:\n ...\n - key: create-user\n handler: create-user.createUser\n```\n\nExample:\n```bash\ncurl -v $URL -d '{\"name\": \"Mudit\", \"age\": 28, \"country\":\"India\"}'\n```\n\nExample:\n```javascript\nimport kvs, { WhereConditions } from '@forge/kvs'\n\nconst validateInput = (payload) => {\n let input = {};\n try {\n input = JSON.parse(payload);\n } catch (e) {\n throw e;\n }\n if (!input.name || !input.country) {\n throw new Error('Request body did not have necessary information');\n }\n if (typeof input.name !== 'string' || typeof input.country !== 'string') {\n throw new Error('At least one of the request parameters is the wrong type');\n }\n if (input.country !== 'India' && input.country !== 'Australia' && input.country !== 'Indonesia') {\n throw new Error('Country must either be India, Australia or Indonesia');\n }\n return input;\n};\n\nconst searchByNameInStorage = async (data) => {\n try {\n let queryBuilder = kvs\n .entity(\"users\")\n .query()\n .index('by-country-name', {\n partition: [data.country]\n })\n if (data.name) {\n queryBuilder = queryBuilder\n .where(WhereConditions.beginsWith(data.name));\n }\n const results = await queryBuilder\n .getMany();\n return results.results;\n } catch (e) {\n throw e;\n }\n}\n\nexport const queryUsers = async (event, context) => {\n try {\n if (event.method !== 'POST') {\n return { statusCode: 400, body: \"POST method must be provided\" };\n }\n const input = validateInput(event.body);\n const results = await searchByNameInStorage(input);\n\n return {\n body: JSON.stringify(results),\n headers: {\n 'Content-Type': ['application/json'],\n },\n statusCode: 200,\n statusText: 'OK'\n };\n } catch (e) {\n // Assume all errors are client errors\n return { statusCode: 400, body: e.message };\n }\n};\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n ...\n - key: user-management-via-entities-webtrigger-query-users\n function: query-users\n urlFormat: v2\n response:\n type: dynamic\n function:\n ...\n - key: query-users\n handler: query-users.queryUsers\n```\n\nExample:\n```bash\ncurl -v $URL -d '{\"name\": \"M\", \"country\":\"India\"}'\n```\n\nExample:\n```javascript\nimport kvs from '@forge/kvs';\n\nconst validateInput = (payload) => {\n let input = {};\n try {\n input = JSON.parse(payload);\n } catch (e) {\n throw e;\n }\n if (!input.key) {\n throw new Error('Request body did not have necessary information');\n }\n if (typeof input.key !== 'string') {\n throw new Error('At least one of the request parameters is the wrong type');\n }\n return input;\n};\n\nconst getUserDetail = async (userKey) => {\n return await kvs.entity('users').get(userKey);\n};\n\nconst deleteUserInStorage = async (userKey) => {\n await kvs.entity('users').delete(userKey);\n};\n\nexport const getUser = async (event, context) => {\n try {\n if (event.method !== 'POST') {\n return { statusCode: 400, body: \"POST method must be provided\" };\n }\n const input = validateInput(event.body);\n const results = await getUserDetail(input.key);\n\n return {\n body: JSON.stringify(results),\n headers: {\n 'Content-Type': ['application/json'],\n },\n statusCode: 200,\n statusText: 'OK'\n };\n } catch (e) {\n // Assume all errors are client errors\n return { statusCode: 400, body: e.message };\n }\n};\n\nexport const deleteUser = async (event, context) => {\n try {\n if (event.method !== 'POST') {\n return { statusCode: 400, body: \"POST method must be provided\" };\n }\n const input = validateInput(event.body);\n await deleteUserInStorage(input.key);\n\n return {\n statusCode: 200,\n statusText: 'OK'\n };\n } catch (e) {\n // Assume all errors are client errors\n return { statusCode: 400, body: e.message };\n }\n};\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n ...\n - key: user-management-via-entities-webtrigger-get-user\n function: get-user\n urlFormat: v2\n response:\n type: dynamic\n - key: user-management-via-entities-webtrigger-delete-user\n function: delete-user\n urlFormat: v2\n response:\n type: dynamic\n function:\n ...\n - key: get-user\n handler: get-and-delete-user.getUser\n - key: delete-user\n handler: get-and-delete-user.deleteUser\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.338Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":17,"totalLines":330,"estimatedTokens":1889}}114{"id":"doc-automate_bitbucket_using_triggers-a92f46a3","source":"documentation","title":"Automate Bitbucket using triggers","url":"https://developer.atlassian.com/platform/forge/automate-bitbucket-using-triggers/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - 'read:pullrequest:bitbucket'\nmodules:\n trigger:\n - key: pull-request-app-hello-world\n function: main\n events:\n - avi:bitbucket:created:pullrequest\n function:\n - key: main\n handler: index.run\napp:\n runtime:\n name: nodejs24.x\n id: <your_app_id>\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n\tconsole.log('Hello World!');\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install -p bitbucket\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nnpm install @forge/api --save\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n```\n\nExample:\n```javascript\nasync function addComment(workspaceId, repositoryId, pullRequestId, message) {\n const requestUrl = route`/2.0/repositories/${workspaceId}/${repositoryId}/pullrequests/${pullRequestId}/comments`;\n const body = { content: { raw: message } };\n\n // Use the Forge Runtime API to fetch data from Bitbucket API using your app credential\n let response = await api.asApp().requestBitbucket(requestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n // Error checking: a 201 is returned if the request is successful\n if (response.status !== 201) {\n console.log(response.status);\n const responseBody = await response.json();\n throw `Unable to add comment to pull request ${pullRequestId}, status: ${\n response.status\n }, response: ${JSON.stringify(responseBody, null, 2)}`;\n }\n\n return response.json();\n}\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n const response = await addComment(\n event.workspace.uuid,\n event.repository.uuid,\n event.pullrequest.id,\n \"Hello World! It's the Comment Issue app.\"\n );\n console.log(JSON.stringify(response, null, 2));\n}\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n\nasync function addComment(workspaceId, repositoryId, pullRequestId, message) {\n const requestUrl = route`/2.0/repositories/${workspaceId}/${repositoryId}/pullrequests/${pullRequestId}/comments`;\n const body = { content: { raw: message } };\n\n // Use the Forge Runtime API to fetch data from Bitbucket API using your app credential\n let response = await api.asApp().requestBitbucket(requestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n // Error checking: a 201 is returned if the request is successful\n if (response.status !== 201) {\n console.log(response.status);\n const responseBody = await response.json();\n throw `Unable to add comment to pull request ${pullRequestId}, status: ${\n response.status\n }, response: ${JSON.stringify(responseBody, null, 2)}`;\n }\n\n return response.json();\n}\n\nexport async function run(event, context) {\n const response = await addComment(\n event.workspace.uuid,\n event.repository.uuid,\n event.pullrequest.id,\n \"Hello World! It's the Comment Issue app.\"\n );\n console.log(JSON.stringify(response, null, 2));\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.339Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":145,"estimatedTokens":830}}115{"id":"doc-use_the_key_value_store_in_a_confluence_macro-19c03732","source":"documentation","title":"Use the Key-Value Store in a Confluence macro","url":"https://developer.atlassian.com/platform/forge/create-confluence-macro-with-storage-api/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnpm install --save @forge/kvs@latest\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n```\n\nExample:\n```text\nmodules:\n macro:\n ...\n config: true\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\nforge tunnel\n```\n\nExample:\n```javascript\nconst Config = () => {\n return (\n <>\n <Label labelFor=\"terms\">Terms to include (one per line)</Label>\n <TextArea name=\"terms\" id=\"terms\" defaultValue=\"\" isRequired />\n </>\n )\n};\n```\n\nExample:\n```javascript\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```javascript\nconst App = () => {\n const config = useConfig() || {};\n const terms = config.terms ? config.terms.split(\"\\n\") : [];\n\n if (terms.length === 0) {\n return (\n <Text>No Terms</Text>\n );\n }\n\n return (\n <Text>{terms.join(\",\")}</Text>\n );\n};\n```\n\nExample:\n```typescript\ninterface Term {\n definition: string;\n term: string;\n}\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport { kvs } from '@forge/kvs';\n\nconst resolver = new Resolver();\n\n// Create a key composed from the term\nfunction termKey(term) { \n return `term-${term}`;\n}\n\nasync function getTermDefinition(term) {\n const value = await kvs.get(termKey(term));\n return value ? value.definition : \"\";\n}\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nresolver.define('getTermDefinitions', async(req) => {\n const pendingDefinitions = req.payload.terms.map((term) => getTermDefinition(term));\n return await Promise.all(pendingDefinitions);\n})\n```\n\nExample:\n```javascript\nconst [definitions, setDefinitions] = useState([]);\n```\n\nExample:\n```javascript\nuseEffect(() => {\n const populateDefinitions = async() => {\n if (terms.length !== 0) {\n const data = await invoke('getTermDefinitions', { terms: terms });\n setDefinitions(data);\n }\n }\n\n populateDefinitions();\n }, [terms, invoke, setDefinitions]);\n```\n\nExample:\n```javascript\nconst App = () => {\n const [definitions, setDefinitions] = useState([]);\n const config = useConfig() || {};\n const terms = config.terms ? config.terms.split(\"\\n\") : [];\n\n useEffect(() => {\n const populateDefinitions = async() => {\n if (terms.length !== 0) {\n const data = await invoke('getTermDefinitions', { terms: terms });\n setDefinitions(data);\n }\n }\n\n populateDefinitions();\n }, [terms, invoke, setDefinitions]);\n\n return (\n <>\n <Text>{terms.join(\",\")}</Text>\n <Text>{definitions.join(\",\")}</Text>\n </>\n );\n};\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport { DynamicTable } from \"@forge/react\";\nimport { invoke } from '@forge/bridge';\n\n// Render the table headers\nconst head = {\n cells: [\n {\n key: \"term\",\n content: \"Term\",\n isSortable: true,\n },\n {\n key: \"definition\",\n content: \"Definition\",\n shouldTruncate: true,\n isSortable: true,\n },\n {\n key: \"buttons\", // Additional column for buttons on each row\n content: \"\",\n shouldTruncate: true,\n isSortable: true,\n },\n ],\n};\n\n// Render the Definition Table\nexport const DefinitionTable = ({ terms, definitions }) => {\n return (\n <DynamicTable\n head={head}\n emptyView=\"No terms provided, please press Edit on the app and add terms in the Configuration box\"\n />\n );\n};\n```\n\nExample:\n```javascript\n// create a key for each term\nconst createKey = (input) => {\n return input ? input.replace(/\\s/g, \"\") : input; // remove whitespace\n}\n\n// Render the Definition Table\nexport const DefinitionTable = ({ terms, definitions }) => {\n const rows = terms.map((term, index) => ({\n key: `row-${index}-${term}`,\n cells: [\n {\n key: createKey(term),\n content: term\n },\n {\n key: index,\n content: definitions[index],\n colSpan: 2,\n },\n ],\n }));\n\n return (\n <DynamicTable\n head={head}\n rows={rows}\n emptyView=\"No terms provided, please press Edit on the app and add terms in the Configuration box\"\n />\n );\n}\n```\n\nExample:\n```javascript\nimport { DefinitionTable } from './definition-table';\n```\n\nExample:\n```javascript\nreturn (\n <>\n <DefinitionTable terms={terms} definitions={definitions}/>\n </>\n);\n```\n\nExample:\n```javascript\nresolver.define('saveDefinition', async(req) => {\n const { term, definition } = req.payload;\n await kvs.set(termKey(term), { term, definition });\n})\n```\n\nExample:\n```javascript\nresolver.define('removeDefinition', async(req) => {\n let term = req.payload.term;\n await kvs.delete(termKey(term)); \n})\n```\n\nExample:\n```javascript\nconst { handleSubmit, register, getFieldId } = useForm();\nconst [term, setTerm] = useState('');\nconst [definition, setDefinition] = useState('');\nconst [loadingState, setLoadingState] = useState(false);\n\nconst [inputIsOpen, setInputIsOpen] = useState(false);\nconst openInputModal = () => setInputIsOpen(true);\nconst closeInputModal = () => setInputIsOpen(false);\n```\n\nExample:\n```javascript\nconst [deleteIsOpen, setDeleteIsOpen] = useState(false);\nconst openDeleteModal = () => setDeleteIsOpen(true);\nconst closeDeleteModal = () => setDeleteIsOpen(false);\n```\n\nExample:\n```javascript\nreturn (\n <>\n <DynamicTable\n head={head}\n rows={rows}\n emptyView=\"No terms provided, please press Edit on the app and add terms in the Configuration box\"\n isLoading={loadingState}\n />\n\n <ModalTransition>\n {inputIsOpen && (\n <Modal onClose={closeInputModal}>\n <Form\n onSubmit={handleSubmit(submitInput)}\n >\n <ModalHeader>\n <ModalTitle>{`Add definition for ${term}`}</ModalTitle>\n </ModalHeader>\n <ModalBody>\n <Label labelFor={getFieldId(\"definition\")}>{`Definition for ${term}`}</Label>\n <TextArea name=\"definition\" id=\"definition\" defaultValue={definition} {...register(\"definition\", {\n required: true\n })} />\n </ModalBody>\n <ModalFooter>\n <Button appearance=\"subtle\" onClick={closeInputModal}>\n Cancel\n </Button>\n <Button appearance=\"primary\" onClick={closeInputModal} type=\"submit\">\n Submit\n </Button> \n </ModalFooter>\n </Form>\n </Modal>\n )}\n </ModalTransition>\n\n <ModalTransition>\n {deleteIsOpen && (\n <Modal onClose={closeDeleteModal}>\n <Form\n onSubmit={handleSubmit(submitDelete)}\n >\n <ModalHeader>\n <ModalTitle>{`Delete definition for ${term}?`}</ModalTitle>\n </ModalHeader>\n <ModalFooter>\n <Button appearance=\"subtle\" onClick={closeDeleteModal}>\n Cancel\n </Button>\n <Button appearance=\"danger\" onClick={closeDeleteModal} type=\"submit\">\n Delete\n </Button> \n </ModalFooter>\n </Form>\n </Modal>\n )}\n </ModalTransition>\n </>\n);\n```\n\nExample:\n```javascript\nconst submitInput = async({ definition }) => {\n setLoadingState(true);\n await invoke('saveDefinition', { term: term, definition: definition })\n setLoadingState(false);\n};\n\nconst submitDelete = async() => {\n setLoadingState(true);\n await invoke('removeDefinition', { term: term })\n setLoadingState(false);\n};\n```\n\nExample:\n```javascript\nconst generateRow = useCallback(\n(index, term, generatedDefinition) => {\n let definitionContent = <Spinner />;\n if (generatedDefinition === \"\") {\n definitionContent = \n <Button appearance=\"default\" onClick={() => {\n setTerm(term);\n setDefinition(\"\");\n openInputModal();\n }}>\n Add Definition\n </Button>\n } else if (generatedDefinition) {\n definitionContent = \n <Inline spread=\"space-between\">\n <Text>{generatedDefinition}</Text>\n <ButtonGroup>\n <Button appearance=\"default\" spacing=\"compact\" onClick={() => {\n setTerm(term);\n setDefinition(generatedDefinition);\n openInputModal();\n }}>\n Edit\n </Button>\n <Button appearance=\"danger\" spacing=\"compact\" onClick={() => {\n setTerm(term);\n openDeleteModal();\n }}>\n Delete\n </Button>\n </ButtonGroup>\n </Inline>\n }\n\n return ({\n key: `row-${index}-${term}`,\n cells: [\n {\n key: createKey(term),\n content: term\n },\n {\n key: index,\n content: definitionContent,\n colSpan: 2,\n },\n ],\n })\n},\n[setTerm, setDefinition, openInputModal, openDeleteModal, createKey]);\n```\n\nExample:\n```javascript\nconst rows = terms.map((term, index) => (\n generateRow(index, term, definitions[index])\n));\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.340Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":29,"totalLines":433,"estimatedTokens":2245}}116{"id":"doc-build_a_bitbucket_hello_world_app-9aaf40da","source":"documentation","title":"Build a Bitbucket hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-bitbucket/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n bitbucket:repoCodeOverviewCard:\n - key: hello-world-app-hello-world-repository-code-overview-card\n resource: main\n resolver:\n function: resolver\n render: native\n title: Forge app for Mia\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.341Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":83,"estimatedTokens":304}}117{"id":"doc-handling_long_running_llm_processes_with_forge_r-db5063df","source":"documentation","title":"Handling long-running LLM processes with Forge Realtime","url":"https://developer.atlassian.com/platform/forge/llm-long-running-process-with-forge-realtime","text":"Example:\n```text\nUI subscribes → resolver enqueues → consumer runs LLM → publishes → UI renders\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n<project-directory>\n ├── manifest.yml\n ├── package-lock.json\n ├── package.json\n ├── README.md\n └── src\n ├── frontend\n │ └── index.jsx\n ├── consumers\n │ └── index.js\n ├── resolvers\n │ └── index.js\n ├── index.js\n```\n\nExample:\n```text\nmodules:\n llm:\n - key: llm-with-realtime-app\n model: \n - claude\n consumer:\n - key: llm-prompt-consumer\n queue: llm-prompt-consumer-queue\n function: consumer\n macro:\n - key: llm-with-forge-realtime-hello-world-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: llm-with-forge-realtime\n function:\n - key: resolver\n handler: index.resolverHandler\n - key: consumer\n handler: index.consumerHandler\n timeoutSeconds: 900 # 15 minutes (queue consumer max)\n```\n\nExample:\n```javascript\n{\n // ...\n \"dependencies\": {\n // ...\n \"@forge/bridge\": \"5.8.0\",\n \"@forge/events\": \"^2.0.10\",\n \"@forge/llm\": \"^0.3.0\",\n \"@forge/realtime\": \"^0.3.0\",\n }\n}\n```\n\nExample:\n```javascript\n//<project-directory>/src/frontend/index.jsx\nimport { invoke, realtime } from '@forge/bridge';\n\nconst CHANNEL_NAME = \"my-llm-realtime-channel\";\n\nconst [token, setToken] = useState(null);\nconst [customClaims, setCustomClaims] = useState({});\n// ... other state variables like message, result, etc.\n\n// Request token with custom claims\nuseEffect(() => {\n const fetchToken = async () => {\n const { token, customClaims } = await invoke('buildToken', { channelName: CHANNEL_NAME });\n setToken(token);\n setCustomClaims(customClaims);\n };\n fetchToken();\n}, []);\n\n// Subscribe with token\nuseEffect(() => {\n if (!token) return;\n let subscription;\n\n realtime.subscribeGlobal(\n CHANNEL_NAME,\n (payload) => setResult(payload),\n { token }\n ).then(sub => { subscription = sub; });\n\n return () => {\n if (subscription) subscription.unsubscribe();\n };\n}, [token]);\n\nconst handleSubmit = () => {\n setResult(null);\n invoke(\"sendLLMPrompt\", {\n customClaims,\n prompt: {\n model: 'claude-haiku-4-5-20251001',\n messages: [{ role: \"user\", content: message.trim() }]\n }\n });\n};\n```\n\nExample:\n```javascript\n//<project-directory>/src/resolvers/index.js\nimport Resolver from '@forge/resolver';\nimport { Queue } from '@forge/events';\nimport { signRealtimeToken } from '@forge/realtime';\n\nconst resolver = new Resolver();\nconst queue = new Queue({ key: \"llm-prompt-consumer-queue\" });\n\nresolver.define('buildToken', async ({ payload, context }) => {\n const customClaims = {\n channelName: payload.channelName,\n accountId: context.accountId,\n };\n\n const { token } = await signRealtimeToken(payload.channelName, customClaims);\n return { token, customClaims };\n});\n\nresolver.define(\"sendLLMPrompt\", async ({ payload }) => {\n await queue.push([{ body: payload }]);\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n//<project-directory>/src/consumers/index.js\nimport { publishGlobal, signRealtimeToken } from '@forge/realtime';\nimport { chat } from '@forge/llm';\n\nexport const handler = async (event) => {\n const { customClaims, prompt } = event.body;\n const channelName = customClaims.channelName;\n let result;\n \n try {\n const chatResult = await chat(prompt);\n result = { status: \"done\", ...chatResult };\n } catch (err) {\n result = {\n status: \"error\",\n error: err?.context?.responseText || \"Unknown error occurred\"\n };\n }\n\n // Sign token with same custom claims as subscriber\n const { token } = await signRealtimeToken(channelName, customClaims);\n\n await publishGlobal(\n channelName,\n result,\n { token }\n );\n};\n```\n\nExample:\n```javascript\n// <project-directory>/index.js\nimport { handler as consumerHandler } from './consumers/index.js';\nimport { handler as resolverHandler } from './resolvers/index.js';\n```\n\nExample:\n```bash\nforge deploy\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.342Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":190,"estimatedTokens":1022}}118{"id":"doc-create_a_question_generator_app_in_multiple_lang-d7600187","source":"documentation","title":"Create a question generator app in multiple languages using i18n","url":"https://developer.atlassian.com/platform/forge/create-a-question-generator-app-in-multiple-languages-using-i18n/","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge --version\n```\n\nExample:\n```bash\nnpm install @forge/bridge@latest\n```\n\nExample:\n```bash\nnpm install @forge/react@latest\n```\n\nExample:\n```text\nforge create\n```\n\nExample:\n```text\ncd forge-i18n-question-generator-app-ui-kit\n```\n\nExample:\n```text\n\"app\": {\n \"title\": \"Quizfragen-generator - i18n für UI Kit\",\n \"description\": \"Eine demo-anwendung, die quizfragen anzeigt. Die Forge-Anwendung wurde mit UI Kit erstellt, um die i18n-Unterstützung in Forge zu demonstrieren [de-DE]\"\n ...\n},\n```\n\nExample:\n```text\ntranslations:\n resources:\n - key: en-US\n path: locales/en-US.json\n - key: en-GB\n path: locales/en-GB.json\n - key: zh-CN\n path: locales/zh-CN.json\n - key: zh-TW\n path: locales/zh-TW.json\n - key: fr-FR\n path: locales/fr-FR.json\n - key: de-DE\n path: locales/de-DE.json\n - key: es-ES\n path: locales/es-ES.json\n```\n\nExample:\n```text\nfallback:\n default: en-US\n zh-TW:\n - zh-CN\n de-DE:\n - en-GB\n es-ES:\n - en-GB\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: forge-i18n-question-generator-app-ui-kit\n resource: main\n render: native\n resolver:\n function: resolver\n title:\n i18n: app.title\n```\n\nExample:\n```text\nimport { useTranslation, I18nProvider } from '@forge/react';\n```\n\nExample:\n```text\nForgeReconciler.render(\n <React.StrictMode>\n <I18nProvider>\n <App />\n </I18nProvider>\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nimport React from 'react';\nimport {\n useTranslation,\n Text,\n SectionMessage,\n} from '@forge/react';\n\nexport const QuestionContent = ({ questionKey }) => {\n const { t } = useTranslation();\n return (\n <SectionMessage appearance=\"discovery\">\n <Text weight=\"bold\" size=\"medium\">\n {t(`question.${questionKey}`)}\n </Text>\n </SectionMessage>\n );\n};\n```\n\nExample:\n```text\n\"question\": {\n \"q_0\": \"What's the best thing you've crossed off your bucket list? [en-US]\",\n ...\n},\n```\n\nExample:\n```text\nimport React, { useState, useCallback } from 'react';\nimport {\n useTranslation,\n Stack,\n Inline,\n Text,\n Button,\n Lozenge,\n Heading\n} from '@forge/react';\nimport { QuestionContent } from './QuestionContent';\n\nconst NUM_QUESTIONS = 54;\n\nexport const QuestionGeneratorPanel = () => {\n const { t, locale } = useTranslation(); // Creates the translation function and user locale from useTranslation()\n const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);\n\n return (\n <Stack space=\"space.200\">\n <Heading as=\"h1\">{t(\"ui.header\")}</Heading> // Displays the heading of the app\n <Text>{t(\"ui.text.intro\")}</Text>\n <QuestionContent questionKey={`q_${currentQuestionIndex}`} /> // The QuestionContent displays the current question\n <Inline alignBlock=\"center\" space=\"space.100\">\n <Heading as=\"h3\">{t(\"ui.text.currentLocale\")}</Heading>\n <Lozenge>{locale}</Lozenge>\n </Inline>\n <Inline alignInline=\"end\">\n <Button appearance=\"primary\">\n {t(\"ui.button.nextQuestion\")}\n </Button>\n </Inline>\n </Stack>\n );\n};\n```\n\nExample:\n```text\nconst nextQuestion = useCallback(() => {\n setCurrentQuestionIndex((prevIndex) => (prevIndex + 1) % NUM_QUESTIONS);\n }, [setCurrentQuestionIndex]);\n\n return (\n ...\n <Button appearance=\"primary\" onClick={nextQuestion}>\n {t(\"ui.button.nextQuestion\")}\n </Button>\n ...\n );\n```\n\nExample:\n```text\nconst App = () => {\n const { ready } = useTranslation();\n\n if (!ready) {\n return <Spinner size=\"large\" />;\n }\n\n return <QuestionGeneratorPanel />;\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.343Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":203,"estimatedTokens":924}}119{"id":"doc-create_an_llm_web_trigger_app_using_forge_llm-c563f823","source":"documentation","title":"Create an LLM web trigger app using Forge LLM","url":"https://developer.atlassian.com/platform/forge/create-an-llm-webtrigger-app","text":"Example:\n```bash\n# First, authenticate with Forge if you haven’t already:\nforge login\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n# ? Enter a name for your app: llm-webtrigger-app\n# ? Select an Atlassian app or platform tool: Show All\n# ? Select a category: Show All\n# ? Select a template: webtrigger\n```\n\nExample:\n```bash\ncd llm-webtrigger-app\n```\n\nExample:\n```text\nmodules:\n # 'llm' module is required for LLM functionalities\n llm:\n - key: my-first-llm-app\n model:\n - claude\n # webtrigger module added automatically when you create a \"webtrigger\" app\n webtrigger:\n - key: llm-webtrigger-app-webtrigger-sync\n function: sync\n urlFormat: v2\n response:\n type: dynamic\n function:\n - key: sync\n handler: index.runSync\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/<your-unique-app-uuid>\n```\n\nExample:\n```bash\nyarn add @forge/llm\n```\n\nExample:\n```json\n{\n \"dependencies\": {\n \"@forge/llm\": \"^0.0.15\"\n }\n}\n```\n\nExample:\n```text\n.\n├── AGENTS.md\n├── manifest.yml\n├── package.json\n├── src\n│ └── index.js\n└── yarn.lock\n└── node_modules\n```\n\nExample:\n```javascript\n// 1. Install and import forge-llm-sdk\nimport { chat } from '@forge/llm'\n\nexports.runSync = async (event, context) => {\n let finalResponse={};\n\n try {\n // 2. Parse the incoming JSON body as LLM prompt\n const llmPrompt = JSON.parse(event.body);\n\n // 3. Call the Forge LLMs SDK's chat function\n finalResponse = await chat(llmPrompt);\n\n // 4. Return the LLM response with status code 200\n console.log(\"#### LLM response:\", JSON.stringify(finalResponse, null, 2 ));\n\n return buildOutput(\n JSON.stringify({\n success: true,\n choices: finalResponse.choices,\n }),\n 200,\n \"OK\");\n\n } catch(err) {\n // 5. Handle errors\n console.error(\"Error during LLM chat:\", err);\n return buildOutput({\n success: false,\n error: err?.message || err,\n errorDetails: err.context?.responseText\n },\n err.statusCode ?? 500,\n err.statusText ?? \"Unknown Error\"\n );\n }\n};\n\nconst buildOutput = (body, statusCode, statusText) => ({\n body,\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${Math.random()}`]\n },\n statusCode,\n statusText,\n});\n```\n\nExample:\n```bash\nforge deploy\n# ✔ Deployed\n```\n\nExample:\n```bash\nforge install\n# ? Select an Atlassian app or platform tool: Jira\n# ? Enter the site URL: <your-site-url>\n# ? Do you want to continue? Yes\n# ✔ Install in Jira complete!\n```\n\nExample:\n```bash\nforge webtrigger\n```\n\nExample:\n```text\n# ? Select an installation: (Use the Enter key to select)\n# ┌───────────────┬────────────────────┬────────────────┬───────────────┐\n# │ Environment │ Site │ Atlassian apps │ Major Version │\n# ├───────────────┼────────────────────┼────────────────┼───────────────┤\n# │ ❯ environment │ <your-site> │ <App> │ 2 (Latest) │\n# └───────────────┴────────────────────┴────────────────┴───────────────┘\n# ? Select a web trigger: llm-webtrigger-app-webtrigger-sync\n# Copy your web trigger URL below to start using it:\n# https://<WEBTRIGGER_URL>\n```\n\nExample:\n```json\n{\n \"model\": \"claude-haiku-4-5-20251001\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a meticulous problem-solver. Solve the following problem. Think step by step, showing each intermediate calculation and the rationale behind it. After reasoning, clearly state the final answer on a new line prefixed with 'Final answer:'.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Problem: A shop sells pens at $2 each or 3 for $5. If I need 17 pens, what's the minimum cost? \"\n }\n ]\n}\n```\n\nExample:\n```bash\ncurl -X POST https://<WEBTRIGGER_URL> \\\n -H \"Content-Type: application/json\" \\\n -d @request.json | jq \".\"\n```\n\nExample:\n```json\n{\n \"success\": true,\n \"choices\": [\n {\n \"finish_reason\": \"end_turn\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Let's solve this step by step:\\n\\n1. Let's first look at the available pricing options:\\n• Individual pen price: $2 per pen\\n• Bulk pricing: 3 pens for $5\\n\\n2. To minimize cost, we want to use the bulk pricing as much as possible\\n\\n3. Calculate how many sets of 3 pens we can buy:\\n• 17 ÷ 3 = 5 remainder 2\\n• This means we can buy 5 sets of 3 pens (15 pens total)\\n\\n4. Calculate cost of bulk sets:\\n• 5 sets × $5 per set = $25\\n\\n5. For the remaining 2 pens:\\n• 2 pens at individual price of $2 each\\n• 2 × $2 = $4\\n\\n6. Total minimum cost:\\n• Bulk set cost + Individual pen cost\\n• $25 + $4 = $29\\n\\nFinal answer: $29\\n\\nLet's verify:\\n✓ 5 sets of 3 pens (15 pens): $25\\n✓ 2 individual pens: $4\\n✓ Total pens: 17\\n✓ Total cost: $29\" \n }\n ]\n }\n }\n ]\n}\n```\n\nExample:\n```json\n{\n \"messages\": [\n {\n \"role\": \"system | user | assistant | tool\",\n \"content\": \"string | ContentPart[]\"\n }\n ],\n \"temperature\": number,\n \"max_completion_tokens\": number,\n \"top_p\": number,\n \"tools\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"string\",\n \"description\": \"string\",\n \"parameters\": \"object\"\n }\n }\n ],\n \"tool_choice\": \"auto | none | required | { type: 'function', function: { name: string } }\"\n}\n```\n\nExample:\n```text\n- messages: The conversation content, including system, user, assistant, or tool messages.\n```\n\nExample:\n```text\n- temperature: Controls randomness in responses; higher values produce more creative output.\n- max_completion_tokens: Limits the maximum number of tokens in the generated response.\n- top_p: Controls diversity via nucleus sampling; lower values make output more focused.\n- tools: List of function tools available for the LLM to use during generation.\n- tool_choice: Specifies which tool (if any) the LLM should use for the current prompt.\n```\n\nExample:\n```json\n{\n \"choices\": [\n {\n \"finish_reason\": \"string\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": \"string | ContentPart[]\"\n }\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.344Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":20,"totalLines":263,"estimatedTokens":1568}}120{"id":"doc-create_an_agentic_llm_web_trigger_using_forge_ll-41abbf9c","source":"documentation","title":"Create an agentic LLM web trigger using Forge LLMs","url":"https://developer.atlassian.com/platform/forge/create-an-agentic-llm-webtrigger-app","text":"Example:\n```javascript\nimport { chat } from \"@forge/llm\";\n\nexports.runSync = async (event, context) => {\n let finalResponse={};\n\n try {\n /* 1. Build and request the initial prompt. */\n const llmPrompt = JSON.parse(event.body);\n finalResponse = await chat(llmPrompt);\n\n /* 2. Handle tool_use: executed required \"tool\", and share the response with LLM */\n const firstChoice = finalResponse.choices[0];\n if (firstChoice?.message?.role === 'assistant' && !!firstChoice.message?.tool_calls) {\n const promptWithToolResult = await executeToolAndBuildToolUsePrompt(\n {\n toolUseAssistantChoice: firstChoice,\n initialPrompt: llmPrompt\n });\n\n /* Update finalResponse */\n finalResponse = await chat(promptWithToolResult);\n }\n\n return buildOutput(\n JSON.stringify({\n success: true,\n choices: finalResponse.choices,\n }),\n 200,\n \"OK\"\n );\n } catch (err) {\n return buildOutput(\n JSON.stringify({\n success: false,\n error: err?.message || err,\n errorDetails: err.context?.responseText\n }),\n\n err.statusCode ?? 500,\n err.statusText ?? \"Unknown Error\"\n );\n }\n};\n\n/* 3. Execute the \"tool\" and build a new prompt */\nasync function executeToolAndBuildToolUsePrompt({ toolUseAssistantChoice, initialPrompt }) {\n const toolCall = toolUseAssistantChoice?.message.tool_calls?.[0];\n if (toolUseAssistantChoice.finish_reason !== 'tool_use' || !toolCall || toolCall.function?.name !== 'get_current_weather') return null;\n\n let args = {};\n try { args = typeof toolCall.function.arguments === 'string' ? JSON.parse(toolCall.function.arguments) : toolCall.function.arguments; } catch {}\n\n const result = await get_current_weather(args.location, args.unit);\n const requiredToolUseAssistantMessage = toolUseAssistantChoice.message; // Add proper validation when needed\n\n /* The prompt must include three types of messages:\n * 1. The original user and/or system messages from the initial prompt.\n * 2. The assistant's message indicating a tool should be used (`tool_use`).\n * 3. A tool message containing the function's result and context for the assistant.\n */\n return {\n messages: [\n ...initialPrompt.messages,\n requiredToolUseAssistantMessage,\n {\n role: \"tool\",\n content: [{ type: \"text\", text: JSON.stringify(result), tool_use: toolCall.id }],\n tool_call_id: toolCall.id,\n name: toolCall.function.name\n },\n ],\n model: initialPrompt.model,\n };\n}\n\n/* 4. A mock implementation of a weather tool */\nasync function get_current_weather(location, unit = 'celsius') {\n const temperature = Math.floor(Math.random() * 16) + 15; // 15°C to 30°C\n const conditions = ['clear skies', 'cloudy', 'rainy', 'sunny', 'windy', 'foggy'];\n const condition = conditions[Math.floor(Math.random() * conditions.length)];\n return { location, unit, temperature, condition };\n}\n\nconst buildOutput = (body, statusCode, statusText) => ({\n body,\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${Math.random()}`]\n },\n statusCode,\n statusText,\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy\n# ✔ Deployed\nforge install # OR 'forge install upgrade'\n# ✔ Install in <Atlassian app> complete!\n```\n\nExample:\n```bash\nforge webtrigger\n```\n\nExample:\n```bash\n# ? Select an installation: (Use the Enter key to select)\n# ┌───────────────┬────────────────────┬────────────────┬───────────────┐\n# │ Environment │ Site │ Atlassian apps │ Major Version │\n# ├───────────────┼────────────────────┼────────────────┼───────────────┤\n# │ ❯ environment │ <your-site> │ <App> │ 2 (Latest) │\n# └───────────────┴────────────────────┴────────────────┴───────────────┘\n# ? Select a web trigger: llm-webtrigger-app-webtrigger-sync\n# Copy your web trigger URL below to start using it:\n# https://<WEBTRIGGER_URL>\n```\n\nExample:\n```json\n{\n \"model\": \"claude-haiku-4-5-20251001\",\n \"messages\": [\n { \"role\": \"user\", \"content\": \"What is the weather like in Melbourne?\" },\n {\n \"role\": \"system\",\n \"content\": \"Your role is to support our AI weather tool.\"\n }\n ],\n \"tools\": [{\n \"type\": \"function\",\n \"function\": {\n \"name\": \"get_current_weather\",\n \"description\": \"Get the current weather in a given location\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"location\": { \"type\": \"string\", \"description\": \"The city and state, e.g. Sydney, CA\" },\n \"unit\": { \"type\": \"string\", \"enum\": [\"celsius\", \"fahrenheit\"] }\n },\n \"required\": [\"location\"]\n }\n }\n }],\n \"tool_choice\": \"auto\"\n}\n```\n\nExample:\n```bash\ncurl -X POST https://<WEBTRIGGER_URL> \\\n -H \"Content-Type: application/json\" \\\n -d @request.json | jq \".\"\n```\n\nExample:\n```json\n{\n \"success\": true,\n \"choices\": [\n {\n \"finish_reason\": \"end_turn\",\n \"message\": {\n \"role\": \"assistant\",\n \"content\": [\n {\n \"type\": \"text\",\n \"text\": \"Currently in Melbourne, it's 22°C and rainy. I recommend bringing an umbrella or raincoat if you're heading out.\"\n }\n ]\n }\n }\n ]\n}\n```\n\nExample:\n```json\n{\n \"messages\": [\n {\n \"role\": \"system | user | assistant | tool\",\n \"content\": \"string | ContentPart[]\"\n }\n ],\n \"temperature\": number,\n \"max_completion_tokens\": number,\n \"top_p\": number,\n \"tools\": [\n {\n \"type\": \"function\",\n \"function\": {\n \"name\": \"string\",\n \"description\": \"string\",\n \"parameters\": \"object\"\n }\n }\n ],\n \"tool_choice\": \"auto | none | required | { type: 'function', function: { name: string } }\"\n}\n```\n\nExample:\n```text\n- messages: The conversation content, including system, user, assistant, or tool messages.\n```\n\nExample:\n```text\n- temperature: Controls randomness in responses; higher values produce more creative output.\n- max_completion_tokens: Limits the maximum number of tokens in the generated response.\n- top_p: Controls diversity via nucleus sampling; lower values make output more focused.\n- tools: List of function tools available for the LLM to use during generation.\n- tool_choice: Specifies which tool (if any) the LLM should use for the current prompt.\n```\n\nExample:\n```javascript\n{\n \"choices\": [\n {\n \"finish_reason\": string, // \"tool_use\", \"end_turn\", \"max_tokens\", etc.\n \"index\": 0,\n \"message\": {\n \"role\": string, // \"assistant\"\n \"content\": string | ContentPart[],\n \"tool_calls\": [\n {\n \"id\": \"<tool_call_id>\",\n \"type\": \"function\",\n \"index\": number,\n \"function\": {\n \"name\": \"<function_name>\",\n \"arguments\": { /* function arguments */ }\n }\n }\n ]\n }\n }\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.346Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":255,"estimatedTokens":1742}}121{"id":"doc-use_a_long_running_function-9998ec35","source":"documentation","title":"Use a long-running function","url":"https://developer.atlassian.com/platform/forge/use-a-long-running-function","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd your-app-name\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:jira-work\n```\n\nExample:\n```text\nmodules:\n consumer:\n - key: queue-consumer-key\n # Name of the queue for which this consumer will be invoked\n queue: queue-consumer-name\n # Function to be called with payload\n function: generate-report\n function:\n - key: generate-report\n handler: generateReport.handler\n timeoutSeconds: 900\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\nexport const handler = async (event, context) => {\n try {\n console.log(\"The handler has been invoked\");\n const ret = await processGenerate(event);\n console.log(`The handler returned with: ${JSON.stringify(ret)}`);\n return ret;\n } catch (error) {\n console.error('Error in generate-report-event-listener:', error);\n throw error;\n }\n};\n\nexport const processGenerate = async (event) => {\n const { projectKey } = event.body;\n \n if (!projectKey) {\n throw new Error('Project key is required but not provided in event payload');\n }\n\n // Simulate a long-running task\n const reportData = await generateReport(projectKey);\n return {\n statusCode: 200,\n body: reportData,\n };\n};\n\nconst generateReport = async (projectKey) => {\n const issues = await fetchIssuesFromJira(projectKey);\n // Perform complex calculations and aggregations\n return performCalculations(issues);\n};\n\nconst createMockIssuesFromJira = (projectKey) => ([\n { id: 1, key: `${projectKey}-1`, fields: { status: { name: 'Done' }, customfield_10016: 5 } },\n { id: 2, key: `${projectKey}-2`, fields: { status: { name: 'In Progress' }, customfield_10016: 3 } },\n]);\n\nconst fetchIssuesFromJira = async (projectKey) => {\n try {\n // Simulate a delay for fetching data from Jira API\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n // Fetch real data from Jira API\n const response = await api.asApp().requestJira(\n route`/rest/api/3/search/jql`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n jql: `project = ${projectKey}`,\n fields: ['status', 'customfield_10016'],\n maxResults: 100,\n }),\n }\n );\n \n if (!response.ok) {\n console.log(`API request failed, using mock data. Status: ${response.status}`);\n // Fallback to mock data if API call fails\n return createMockIssuesFromJira(projectKey);\n }\n \n const data = await response.json();\n return data.issues || [];\n } catch (error) {\n console.error('Error fetching issues from Jira:', error);\n // Return mock data as fallback\n return createMockIssuesFromJira(projectKey);\n }\n};\n\nconst performCalculations = (issues) => {\n // Aggregate data from real Jira issues\n const statusCounts = {};\n let totalStoryPoints = 0;\n let issuesWithPoints = 0;\n\n issues.forEach(issue => {\n const status = issue.fields?.status?.name || 'Unknown';\n statusCounts[status] = (statusCounts[status] || 0) + 1;\n \n // Story points are often in customfield_10016, but this varies by instance\n const storyPoints = issue.fields?.customfield_10016 || 0;\n if (storyPoints > 0) {\n totalStoryPoints += storyPoints;\n issuesWithPoints++;\n }\n });\n\n return {\n totalIssues: issues.length,\n statusBreakdown: statusCounts,\n totalStoryPoints,\n averageStoryPoints: issuesWithPoints > 0 ? \n Math.round((totalStoryPoints / issuesWithPoints) * 100) / 100 : 0,\n reportGeneratedAt: new Date().toISOString()\n };\n};\n```\n\nExample:\n```javascript\nimport { Queue } from \"@forge/events\";\n\nexport const handler = async (event, context) => {\n try {\n const queue = new Queue({key: \"queue-consumer-name\"});\n\n // Extract project key from the Jira issue update event\n const projectKey = event.issue?.fields?.project?.key;\n \n if (!projectKey) {\n console.error('No project key found in event payload');\n return {\n statusCode: 400,\n statusText: \"Bad Request - No project key found\"\n };\n }\n\n console.log(`Pushing an event to the queue for project: ${projectKey}`);\n const { jobId } = await queue.push({ \n body: { \n projectKey: projectKey,\n issueKey: event.issue?.key,\n triggeredAt: new Date().toISOString()\n } \n });\n console.log(`Queued job ${queue.key}#${jobId}`);\n\n return {\n statusCode: 200,\n statusText: \"Success\"\n };\n } catch (error) {\n console.error('Error in pushToQueue handler:', error);\n return {\n statusCode: 500,\n statusText: \"Internal Server Error\"\n };\n }\n}\n```\n\nExample:\n```text\nmodules: \n consumer:\n - key: queue-consumer-key\n queue: queue-consumer-name\n function: generate-report\n trigger:\n - key: invoke-lrf-when-jira-issue-updated\n function: push-to-queue\n events:\n - avi:jira:updated:issue\n function:\n - key: generate-report\n handler: generateReport.handler\n timeoutSeconds: 900\n - key: push-to-queue\n handler: pushToQueue.handler\n```\n\nExample:\n```bash\nnpm install @forge/events\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\ninvocation: ... pushToQueue.handler\nINFO 14:30:01.146 ... Pushing an event to the queue for project: DEMO\nINFO 14:30:01.589 ... Queued job queue-consumer-name#...\n\ninvocation: ... generateReport.handler\nINFO 14:30:02.506 ... The handler has been invoked\nINFO 14:30:04.508 ... The handler returned with: {\"statusCode\":200,\"body\":{\"totalIssues\":25,\"statusBreakdown\":{\"Done\":10,\"In Progress\":8,\"To Do\":7},\"totalStoryPoints\":42,\"averageStoryPoints\":3.5,\"reportGeneratedAt\":\"2025-01-11T14:30:04.508Z\"}}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.347Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":233,"estimatedTokens":1572}}122{"id":"doc-add_custom_configuration_to_a_macro-9b185e01","source":"documentation","title":"Add custom configuration to a macro","url":"https://developer.atlassian.com/platform/forge/add-custom-configuration-to-a-macro","text":"Example:\n```text\nmodules:\n macro:\n - key: my-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: My Macro\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: my-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: My Macro\n config:\n resource: macro-config\n render: native # Only for UI Kit\n viewportSize: max # Optional\n title: Config # Optional\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: my-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: My Macro\n config:\n resource: macro-config\n render: native\n viewportSize: max # Optional\n title: Config # Optional\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: macro-config\n path: src/frontend/config.jsx\napp:\n runtime:\n name: nodejs24.x\n id: ari:cloud:ecosystem::app/YOUR_APP_ID\n```\n\nExample:\n```text\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: macro-config\n path: src/frontend/config.jsx\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from 'react';\nimport ForgeReconciler, { Button, Label, SectionMessage, Stack, Textfield } from '@forge/react';\nimport { view } from \"@forge/bridge\";\n\n// Submit functionality - handles configuration persistence\nconst useSubmit = () => {\n const [error, setError] = useState();\n const [message, setMessage] = useState('');\n\n const submit = async (fields) => {\n const payload = { config: fields };\n\n try {\n await view.submit(payload);\n setError(false);\n setMessage(`Submitted successfully.`);\n } catch (error) {\n setError(true);\n setMessage(`${error.code}: ${error.message}`);\n }\n };\n\n return {\n error,\n message,\n submit\n };\n};\n\n// Main configuration component\nconst Config = () => {\n const [value, setValue] = useState('');\n const [loading, setLoading] = useState(true);\n\n const {\n error,\n message,\n submit\n } = useSubmit();\n\n useEffect(() => {\n // Get the existing config from the bridge context\n view.getContext().then((context) => {\n if (context.extension?.config?.myField) {\n setValue(context.extension.config.myField);\n }\n setLoading(false);\n });\n }, []);\n\n if (loading) {\n return <Label>Loading...</Label>;\n }\n\n return (\n <Stack space=\"space.200\">\n <Label labelFor=\"myField\">Config field:</Label>\n <Textfield id=\"myField\" value={value} onChange={(e) => setValue(e.target.value)} />\n <Button appearance=\"subtle\" onClick={() => view.close()}>\n Close\n </Button>\n <Button appearance=\"primary\" onClick={() => submit({ myField: value })}>\n Submit\n </Button>\n {typeof error !== 'undefined' && (\n <SectionMessage appearance={error ? 'error' : 'success'}>{message}</SectionMessage>\n )}\n </Stack>\n );\n};\n\n// Render the configuration modal\nForgeReconciler.render(\n <React.StrictMode>\n <Config />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { useConfig, CodeBlock, Text } from '@forge/react';\n\nconst App = () => {\n const config = useConfig();\n\n return (\n <>\n <Text>Macro configuration data:</Text>\n <CodeBlock language=\"json\" text={JSON.stringify(config, null, 2)} />\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.348Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":178,"estimatedTokens":900}}123{"id":"doc-using_rich_text_bodied_macros-265b4661","source":"documentation","title":"Using rich-text bodied macros","url":"https://developer.atlassian.com/platform/forge/using-rich-text-bodied-macros","text":"Example:\n```text\nmacro:\n - key: my-macro\n ...\n layout: bodied\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, { useProductContext } from \"@forge/react\";\n\nconst App = () => {\n const context = useProductContext();\n const macroBody = context?.extension?.macro?.body;\n // ...\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n);\n```\n\nExample:\n```json\n{\n \"body\": {\n \"type\": \"doc\",\n \"version\": 1,\n \"content\": []\n }\n}\n```\n\nExample:\n```javascript\nimport { AdfRenderer } from \"@forge/react\";\n\nconst App = () => {\n // ...\n return macroBody && <AdfRenderer document={macroBody} />;\n};\n```\n\nExample:\n```javascript\nimport { requestConfluence } from \"@forge/bridge\";\n\nasync function convertMacroBody(to, macroBody, contentId) {\n const params = new URLSearchParams({\n contentIdContext: contentId,\n expand: \"webresource.tags.all,webresource.superbatch.tags.all\",\n }).toString();\n\n const response = await requestConfluence(\n `/wiki/rest/api/contentbody/convert/async/${to}?${params}`,\n {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n value: JSON.stringify(macroBody),\n representation: \"atlas_doc_format\",\n }),\n },\n );\n\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n return (await response.json()).asyncId;\n}\n\nconst macroBody = context?.extension?.macro?.body;\nconst contentId = context?.extension?.content?.id;\n\nif (macroBody) {\n const asyncId = await convertMacroBody(\"styled_view\", macroBody, contentId);\n}\n```\n\nExample:\n```javascript\nasync function fetchConvertedMacroBody(id) {\n const response = await requestConfluence(\n `/wiki/rest/api/contentbody/convert/async/${id}`,\n {\n headers: {\n Accept: \"application/json\",\n },\n },\n );\n\n const { status, error, value, webresource } = await response.json();\n if (status === \"FAILED\") {\n throw new Error(`Conversion failed: ${error}`);\n } else if (status === \"COMPLETED\") {\n const scripts = webresource.superbatch.tags.all + webresource.tags.all;\n const html = value.replace(\"</head>\", `${scripts}</head>`);\n return html;\n }\n // Keep polling until completed\n return fetchConvertedMacroBody(id);\n}\n\nconst htmlBody = await fetchConvertedMacroBody(asyncId);\n```\n\nExample:\n```text\nresources:\n ...\n - key: html-frame\n path: resources/html-frame/build\n```\n\nExample:\n```javascript\nimport { events } from \"@forge/bridge\";\n\nevents.on(\"PROPS\", ({ html }) => {\n // Create a fragment, allowing any embedded content scripts to be executed when appended\n const documentFragment = document\n .createRange()\n .createContextualFragment(html);\n document.body.innerHTML = \"\";\n document.body.appendChild(documentFragment);\n});\n```\n\nExample:\n```javascript\nimport React, { useEffect } from \"react\";\nimport { events } from \"@forge/bridge\";\nimport { Frame } from \"@forge/react\";\n\nconst App = () => {\n useEffect(() => {\n events.emit(\"PROPS\", { html: htmlBody });\n }, [htmlBody]);\n\n return <Frame resource=\"html-frame\" />;\n};\n```\n\nExample:\n```text\npermissions:\n scopes:\n - \"read:confluence-content.all\"\n```\n\nExample:\n```text\npermissions:\n content:\n styles:\n - \"unsafe-inline\" # Required for styled_view inline styles in the converted HTML\n scripts:\n - \"unsafe-inline\" # Rendering embedded content when converted to HTML with expand\n external:\n fetch:\n client:\n - \"*.atlassian.net\" # Embedded content can call back to Atlassian sites\n images:\n - address: \"*\" # Required for images in the converted HTML\n styles:\n # Rendering embedded content when converted to HTML with expand\n - \"*.atl-paas.net\"\n - \"*.cloudfront.net\"\n scripts:\n # Rendering embedded content when converted to HTML with expand\n - \"*.atl-paas.net\"\n - \"*.cloudfront.net\"\n```\n\nExample:\n```javascript\nimport { doc, p } from \"@atlaskit/adf-utils/builders\";\n\nexport function adfExport(payload) {\n const macroBody = payload.extensionPayload.macro.body;\n\n return doc(\n p(\"This is my export function. Here's the macro content:\"),\n ...macroBody.content,\n );\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.349Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":203,"estimatedTokens":1075}}124{"id":"doc-automate_jira_using_triggers-a2052ab8","source":"documentation","title":"Automate Jira using triggers","url":"https://developer.atlassian.com/platform/forge/automate-jira-using-triggers/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:jira-work\n - write:jira-work\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:jira-work\n - write:jira-work\nmodules:\n trigger:\n - key: issue-created-event\n function: main\n events:\n - avi:jira:created:issue\n function:\n - key: main\n handler: index.run\napp:\n id: '<your-app-id>'\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n\tconsole.log('Hello World!');\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n```\n\nExample:\n```javascript\nasync function addComment(issueIdOrKey, message) {\n /**\n * @issueIdOrKey - the Jira issueId number or key for the issue that this function will try to add\n * a comment to (as per the Jira Rest API)\n * @message {string} - the message that will appear in the comment\n *\n * @example addComment('10050', 'Hello world')\n */\n\n // You'll come back to this later\n const requestUrl = route`https`;\n const body = { };\n\n // Use the Forge Runtime API to fetch data from an HTTP server using your (the app developer) Authorization header\n let response = await api.asApp().requestJira(requestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n // Error checking: the Jira issue comment Rest API returns a 201 if the request is successful\n if (response.status !== 201) {\n console.log(response.status);\n throw `Unable to add comment to issueId ${issueIdOrKey} Status: ${response.status}.`;\n }\n\n return response.json();\n}\n```\n\nExample:\n```javascript\nexport async function run(event, context) {\n const response = await addComment(event.issue.id, \"Hello World! It's the Comment Issue app.\");\n\n console.log(`Response: ${JSON.stringify(response)}`);\n}\n```\n\nExample:\n```javascript\n// See https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-issue-issueIdOrKey-comment-post\n// IssueIDOrKey: The ID or key of the issue.\nconst requestUrl = route`/rest/api/3/issue/${issueIdOrKey}/comment`;\nconst body = {\n \"body\": {\n \"type\": \"doc\",\n \"version\": 1,\n \"content\": [\n {\n \"type\": \"paragraph\",\n \"content\": [\n {\n \"text\": message,\n \"type\": \"text\"\n }\n ]\n }\n ]\n }\n};\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n\nexport async function run(event, context) {\n const response = await addComment(event.issue.id, \"Hello World! It's the Comment Issue app.\");\n\n console.log(`Response: ${JSON.stringify(response)}`);\n}\n\nasync function addComment(issueIdOrKey, message) {\n /**\n * @issueIdOrKey - the Jira issueId number or key for the issue that this function will try to add\n * a comment to (as per the Jira Rest API)\n * @message {string} - the message that will appear in the comment\n *\n * @example addComment('10050', 'Hello world')\n */\n\n // See https://developer.atlassian.com/cloud/jira/platform/rest/v3/#api-rest-api-3-issue-issueIdOrKey-comment-post\n const requestUrl = route`/rest/api/3/issue/${issueIdOrKey}/comment`;\n const body = {\n \"body\": {\n \"type\": \"doc\",\n \"version\": 1,\n \"content\": [\n {\n \"type\": \"paragraph\",\n \"content\": [\n {\n \"text\": message,\n \"type\": \"text\"\n }\n ]\n }\n ]\n }\n };\n\n // Use the Forge Runtime API to fetch data from an HTTP server using your (the app developer) Authorization header\n let response = await api.asApp().requestJira(requestUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify(body)\n });\n\n // Error checking: the Jira issue comment Rest API returns a 201 if the request is successful\n if (response.status !== 201) {\n console.log(response.status);\n throw `Unable to add comment to issueId ${issueIdOrKey} Status: ${response.status}.`;\n }\n\n return response.json();\n}\n```\n\nExample:\n```bash\n✔ Deployed\n\nDeployed comment-issue-app to the development environment.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.350Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":203,"estimatedTokens":1167}}125{"id":"doc-use_selected_text_in_a_confluence_forge_app-890c872c","source":"documentation","title":"Use selected text in a Confluence Forge app","url":"https://developer.atlassian.com/platform/forge/create-confluence-contextmenu-module/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n confluence:contextMenu:\n - key: hello-world-context-menu\n resource: main\n render: native\n resolver:\n function: resolver\n title: Hello World!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n id:\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Text, Strong, useProductContext } from '@forge/react';\n\nconst App = () => {\n const context = useProductContext();\n const selectedText = context?.extension.selectedText;\n\n return (\n <>\n <Text><Strong>Selected text</Strong></Text>\n <Text>{selectedText}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.350Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":73,"estimatedTokens":250}}126{"id":"doc-use_space_settings_and_content_byline_item_to_im-2ab391ac","source":"documentation","title":"Use space settings and content byline item to implement space news","url":"https://developer.atlassian.com/platform/forge/space-news/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nconfluence:spaceSettings:\n- key: forge-space-news\n resource: settings\n render: native\n title: Space news\n```\n\nExample:\n```text\npermissions:\n scopes:\n - 'read:space:confluence'\n - 'write:space:confluence'\n```\n\nExample:\n```text\nmodules:\n confluence:spaceSettings:\n - key: forge-space-news\n resource: settings\n render: native\n title: Space news\npermissions:\n scopes:\n - 'read:space:confluence'\n - 'write:space:confluence'\nresources:\n - key: settings\n path: src/frontend/index.jsx\napp:\n id: '<your-app-id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport React from \"react\";\n// Import required components from the UI kit\nimport ForgeReconciler, {\n TextArea,\n useSpaceProperty,\n useForm,\n Form,\n Button,\n} from \"@forge/react\";\n\nconst App = () => {\n const [news, setNews] = useSpaceProperty(\"space-news\", \"No news currently.\");\n const { register, handleSubmit } = useForm({\n defaultValues: {\n news,\n },\n });\n\n const onSubmit = async ({ news }) => {\n await setNews(news);\n };\n\n return (\n <Form onSubmit={handleSubmit(onSubmit)}>\n <TextArea {...register(\"news\")} />\n <Button type=\"submit\">Submit</Button>\n </Form>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nconfluence:contentBylineItem:\n - key: forge-space-news-byline\n resource: byline\n render: native\n title: Space news\n tooltip: Displays recent space news\n```\n\nExample:\n```text\n- key: byline\n path: src/frontend/byline.jsx\n```\n\nExample:\n```text\nmodules:\n confluence:spaceSettings:\n - key: forge-space-news\n resource: settings\n render: native\n title: Space news\n confluence:contentBylineItem:\n - key: forge-space-news-byline\n resource: byline\n render: native\n title: Space news\n tooltip: Displays recent space news\npermissions:\n scopes:\n - 'read:space:confluence'\n - 'write:space:confluence'\nresources:\n - key: settings\n path: src/frontend/index.jsx\n - key: byline\n path: src/frontend/byline.jsx\napp:\n id: '<your-app-id>'\n```\n\nExample:\n```javascript\n// Import required components from the UI kit\nimport React from \"react\";\nimport ForgeReconciler, { Text, useSpaceProperty } from \"@forge/react\";\n\nconst App = () => {\n const [news] = useSpaceProperty(\"space-news\", \"No news currently.\");\n\n return <Text>{news}</Text>;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nControl + C\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.351Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":14,"totalLines":169,"estimatedTokens":674}}127{"id":"doc-use_content_actions_to_count_the_macros_in_a_con-55a78b72","source":"documentation","title":"Use content actions to count the macros in a Confluence page","url":"https://developer.atlassian.com/platform/forge/macros-in-the-page/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nconfluence:contentAction:\n - key: macro-counter\n title: Macro count\n resource: main\n render: native\n resolver:\n function: resolver\n```\n\nExample:\n```text\npermissions:\n scopes:\n - 'read:page:confluence'\n```\n\nExample:\n```text\nmodules:\n confluence:contentAction:\n - key: macro-counter\n resource: main\n render: native\n resolver:\n function: resolver\n title: Macro Count\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your-app-id>'\npermissions:\n scopes:\n - 'read:page:confluence'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport React, {useEffect, useState} from 'react';\n// Import required components from UI Kit\nimport ForgeReconciler, { Text, useProductContext } from '@forge/react';\n// Import required for calling resolver\nimport { invoke } from '@forge/bridge';\n\n// You'll implement countMacros later \nconst countMacros = (data) => {\n return 10;\n};\n\nconst App = () => {\n const [data, setData] = useState();\n const context = useProductContext();\n const pageId = context?.extension?.content?.id;\n\n useEffect(() => {\n if(pageId){\n // You'll implement getContent later\n invoke('getContent', { pageId }).then(setData);\n }\n }, [pageId]);\n\n const macroCount = countMacros(data);\n\n return (\n <Text>{`Number of macros on this page: ${macroCount}`}</Text>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nNumber of macros on this page: 10\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport api, { route } from '@forge/api';\n\nconst resolver = new Resolver();\n\nresolver.define('getContent', async ({ payload }) => {\n const response = await api.asUser().requestConfluence(route`/wiki/api/v2/pages/${payload.pageId}?body-format=atlas_doc_format`);\n\n if (!response.ok) {\n const err = `Error while getContent with pageId ${payload.pageId}: ${response.status} ${response.statusText}`;\n console.error(err);\n throw new Error(err);\n }\n\n return await response.json();\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nconst countMacros = (data) => {\n if (!data || !data.body || !data.body.atlas_doc_format || !data.body.atlas_doc_format.value) {\n return 0;\n }\n\n const { body: { atlas_doc_format: { value } } } = data;\n const { content: contentList } = JSON.parse(value);\n\n const macros = contentList.filter((content) => {\n return content.type = \"extension\";\n });\n\n return macros.length;\n};\n```ƒ\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.353Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":162,"estimatedTokens":747}}128{"id":"doc-build_a_confluence_hello_world_app-714a284a","source":"documentation","title":"Build a Confluence hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-confluence/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: hello-world-app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.354Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":87,"estimatedTokens":310}}129{"id":"doc-build_a_custom_ui_app_in_confluence-5b034789","source":"documentation","title":"Build a Custom UI app in Confluence","url":"https://developer.atlassian.com/platform/forge/build-a-custom-ui-app-in-confluence/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-custom-ui\n```\n\nExample:\n```bash\nhello-world-custom-ui\n|-- src\n| `-- index.js\n|-- static\n| `-- hello-world\n| `-- src\n| `-- index.js\n| `-- App.js\n| `-- public\n| `-- index.html\n| `-- package.json\n| `-- package-lock.json\n|-- manifest.yml\n|-- package.json\n|-- package-lock.json\n`-- README.md\n```\n\nExample:\n```text\nmodules:\n confluence:contentBylineItem:\n - key: hello-world-content-byline\n resource: main\n resolver:\n function: resolver\n title: Hello World from Emma Richards\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: static/hello-world/build\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nnpm install\n```\n\nExample:\n```bash\nnpm run build\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport { invoke } from '@forge/bridge';\n\nfunction App() {\n const [data, setData] = useState(null);\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n return (\n <div>\n {data ? data : 'Loading...'}\n </div>\n );\n}\n\nexport default App;\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', (req) => {\n console.log(req);\n\n return 'Hello, world!';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.355Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":118,"estimatedTokens":403}}130{"id":"doc-build_a_jira_comments_summarizer_app_with_openai-392e9bda","source":"documentation","title":"Build a Jira comments summarizer app with OpenAI","url":"https://developer.atlassian.com/platform/forge/build-jira-comments-summarizer-with-openai/","text":"Example:\n```text\nmodules:\n jira:issuePanel:\n - key: summarizer-hello-world-issue-panel\n resource: main\n resolver:\n function: resolver\n render: native\n title: summarizer\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\npermissions:\n scopes:\n - 'read:jira-work'\n external:\n fetch:\n backend:\n - 'api.openai.com'\napp:\n id: <your-app-id>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke, view } from '@forge/bridge';\n\nconst App = () => {\n const [summary, setSummary] = useState();\n\n // Getting all the comments of the issue.\n useEffect(() => {\n const getCommentSummary = async () => {\n const commentsData = await invoke('getComments');\n console.log(\"Comments - \" + commentsData);\n if (commentsData) {\n // ChatGPT prompt to get the summary\n const prompt = `Here is a sample data where all the comments of a jira issue is joined together: \"${commentsData}\". I want to summarize this in a way that anybody can get an idea what's going on in this issue without going through all the comments. Create a summary or TLDR for this.`\n \n // OpenAI API call to get the summary.\n const summary = await invoke('callOpenAI', { prompt });\n console.log(\"Summary - \" + summary);\n setSummary(summary);\n }\n };\n getCommentSummary();\n }, []);\n\n return (\n <>\n <Text>{summary}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport api, { route, fetch } from '@forge/api'; \n\nconst resolver = new Resolver();\n\nresolver.define('getComments', async ({context}) => {\n // API call to get all comments of Jira issue with key\n const commentsData = await api.asApp().requestJira(route`/rest/api/3/issue/${context.extension.issue.key}/comment`, {\n headers: {\n 'Accept': 'application/json'\n }\n });\n\n // API call to get all comments of Jira issue with key\n const responseData = await commentsData.json();\n const jsonData = await responseData.comments\n\n let extractedTexts = [];\n\n // Extracting all texts in the comments into extractedTexts array\n await jsonData.map(comment => {\n if (comment.body && comment.body.content) {\n comment.body.content.map(contentItem => {\n if (contentItem.type === \"paragraph\" && contentItem.content) {\n contentItem.content.map(textItem => {\n if (textItem.type === \"text\" && textItem.text) {\n extractedTexts.push(textItem.text);\n }\n });\n }\n });\n }\n });\n\n return extractedTexts.join(' ');\n});\n```\n\nExample:\n```javascript\nresolver.define('callOpenAI', async ({payload, context}) => {\n\n const choiceCount = 1;\n // OpenAI API endpoint\n const url = `https://api.openai.com/v1/chat/completions`;\n\n // Body for API call\n const body = {\n model: getOpenAPIModel(),\n n: choiceCount,\n messages: [{\n role: 'user',\n content: payload.prompt\n }]\n };\n\n // API call options\n const options = {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${getOpenAPIKey()}`,\n 'Content-Type': 'application/json',\n },\n redirect: 'follow',\n body: JSON.stringify(body)\n };\n\n // API call to OpenAI\n const response = await fetch(url, options);\n let result = ''\n\n if (response.status === 200) {\n const chatCompletion = await response.json();\n const firstChoice = chatCompletion.choices[0]\n\n if (firstChoice) {\n result = firstChoice.message.content;\n } else {\n console.warn(`Chat completion response did not include any assistance choices.`);\n result = `AI response did not include any choices.`;\n }\n } else {\n const text = await response.text();\n result = text;\n }\n\n return result;\n});\n\n// Get OpenAI API key\nconst getOpenAPIKey = () => {\n return process.env.OPEN_API_KEY;\n}\n\n// Get OpenAI model\nconst getOpenAPIModel = () => {\n return 'gpt-3.5-turbo';\n // return 'gpt-4';\n}\n```\n\nExample:\n```text\nforge variables set --encrypt OPEN_API_KEY your-key\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.356Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":184,"estimatedTokens":1098}}131{"id":"doc-create_a_giphy_app_using_the_ui_kit_on_confluenc-21fd7df9","source":"documentation","title":"Create a GIPHY app using the UI Kit on Confluence","url":"https://developer.atlassian.com/platform/forge/create-a-giphy-app-using-the-ui-kit/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd giphy-app\n```\n\nExample:\n```bash\npermissions:\n external:\n images:\n - address: <GIPHY source link >\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: giphy\n resource: main\n render: native\n resolver:\n function: resolver\n title: GIPHY\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\npermissions:\n external:\n images:\n - address: https://media3.giphy.com/media/26vUJR5VABcUJaCTm/200.gif?cid=74f3ab6481fcd606c80e02418b301c17130050edc03b7521&rid=200.gif\napp:\n runtime:\n name: nodejs24.x\n id: '<your-app-id>'\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Text, Image } from '@forge/react';\n\n// ImageCard component containing text and image\nconst ImageCard = ({title, src}) => (\n <>\n <Text>{title}</Text>\n <Image src={src} alt={title}/>\n </>\n);\n\nconst App = () => {\n const { title, url } = {\n title: \"awesome avalanche GIF\",\n url: \"https://media3.giphy.com/media/26vUJR5VABcUJaCTm/200.gif?cid=74f3ab6481fcd606c80e02418b301c17130050edc03b7521&rid=200.gif\"\n };\n\n return (\n <>\n <Text>Random GIF!</Text>\n <ImageCard src={url} title={title}/>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\npermissions:\n external:\n images:\n - address: *.giphy.com\n fetch:\n backend:\n - 'api.giphy.com'\n```\n\nExample:\n```bash\nforge variables set --encrypt GIPHY_API_KEY your-key\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport api from \"@forge/api\";\n\nconst resolver = new Resolver();\n\n// GIPHY API base URL\nconst GIPHY_API_BASE = 'https://api.giphy.com/v1/gifs/';\n\n// getRandomGif function makes the GIPHY API call to get a random GIF and filter out title and url\nresolver.define('getRandomGif', async() => {\nconst response = await api.fetch(\n `${GIPHY_API_BASE}random?api_key=${process.env.GIPHY_API_KEY}&rating=g`,\n);\n\nconst {\n data: {\n title,\n images: {\n fixed_height: { url },\n },\n },\n} = await response.json();\n\nreturn {\n title,\n url,\n};\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\nconst [title, setTitle] = useState('');\nconst [url, setURL] = useState('');\ninvoke('getRandomGif', {}).then((title, url) => {\n setTitle(title);\n setURL(url);\n});\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text, Image } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\n// ImageCard component containing text and image\nconst ImageCard = ({ title, url }) => {\n return (\n <>\n <Text>{title}</Text>\n <Image src={url} alt={title}/>\n </>\n )\n};\n\nconst App = () => {\n const [title, setTitle] = useState('');\n const [url, setURL] = useState('');\n\n useEffect(() => {\n invoke('getRandomGif', {}).then((data) => {\n setTitle(data.title);\n setURL(data.url);\n });\n }, [setTitle, setURL, invoke]);\n\n return (\n <>\n <Text>Random GIF!</Text>\n <ImageCard url={url} title={title}/>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nreturn (\n <>\n <Text>Random GIF!</Text>\n <Button\n onClick={() => {\n invoke('getRandomGif', {}).then((data) => {\n setTitle(data.title);\n setURL(data.url);\n })\n }}\n >{url ? '🔀 Shuffle!' : 'Generate!'}\n </Button>\n {url ? <ImageCard url={url} title={title}/> : <></>}\n </>\n);\n```\n\nExample:\n```javascript\nimport React, { useState } from 'react';\nimport ForgeReconciler, { Text, Image, Button } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\n// ImageCard component containing text and image\nconst ImageCard = ({ title, url }) => {\n return (\n <>\n <Text>{title}</Text>\n <Image src={url} alt={title}/>\n </>\n )\n};\n\nconst App = () => {\n const [title, setTitle] = useState('');\n const [url, setURL] = useState('');\n\n return (\n <>\n <Text>Random GIF!</Text>\n <Button\n onClick={() => {\n invoke('getRandomGif', {}).then((data) => {\n setTitle(data.title);\n setURL(data.url);\n })\n }}\n >{url ? '🔀 Shuffle!' : 'Generate!'}\n </Button>\n {url ? <ImageCard url={url} title={title}/> : <></>}\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\n``` shell\nforge deploy\n```\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.357Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":17,"totalLines":272,"estimatedTokens":1211}}132{"id":"doc-create_a_quiz_app_using_ui_kit-cde7a31e","source":"documentation","title":"Create a quiz app using UI Kit","url":"https://developer.atlassian.com/platform/forge/create-a-quiz-app-using-ui-kit/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd quiz-app\n```\n\nExample:\n```bash\npermissions:\n external:\n images:\n - address: https://media.giphy.com\n```\n\nExample:\n```text\nmodules:\n confluence:globalPage:\n - key: quizapp-global-page\n resource: main\n render: native\n title: QuizApp\n route: global-page\nresources:\n - key: main\n path: src/frontend/index.jsx\npermissions:\n external:\n images:\n - address: https://media.giphy.com\napp:\n id: ari:cloud:ecosystem::app/<YOUR_APP_ID>\n```\n\nExample:\n```json\nexport const QuestionSet = [\n {\n question: \"What year was Atlassian Founded?\",\n image: \"https://media.giphy.com/media/dwtX3iozeZCoVcyBVu/giphy.gif\",\n options:[\n { option: \"1998\", isCorrect: false },\n { option: \"2000\", isCorrect: false },\n { option: \"2002\", isCorrect: true },\n { option: \"2004\", isCorrect: false },\n ],\n correctAnswer: \"2002\",\n },\n {\n question: \"How many office locations are there at Atlassian?\",\n image: \"https://media.giphy.com/media/xUOwGj1jwTZq5Kh3Ko/giphy.gif\",\n options:[\n { option: \"6\", isCorrect: false },\n { option: \"9\", isCorrect: false },\n { option: \"13\", isCorrect: true },\n { option: \"18\", isCorrect: false },\n ],\n correctAnswer: \"13. San Francisco, Austin, Mountain View, New York, Sydney x2, Bengaluru, Yokohama, Amsterdam, Taguig!\",\n },\n {\n question: \"How many major versions of Confluence are there?\",\n options:[\n { option: \"5\", isCorrect: false },\n { option: \"8\", isCorrect: true },\n { option: \"11\", isCorrect: false },\n { option: \"14\", isCorrect: false },\n ],\n correctAnswer: \"8. You can check out https://confluence.atlassian.com/doc/confluence-release-notes-327.html for all versions\",\n },\n ];\n```\n\nExample:\n```javascript\nimport ForgeReconciler, { Heading, Image, Button, Text, Inline, Stack } from '@forge/react';\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Heading, Image, Button, Text, Inline, Stack } from '@forge/react';\n\nconst App = () => {\n return (\n <>\n <Stack space=\"space.200\" alignInline=\"center\">\n <Heading as=\"h1\">My Question</Heading>\n <Image src=\"https://media.giphy.com/media/xUOxfjsW9fWPqEWouI/giphy.gif\" alt=\"Founders\" size=\"xsmall\" />\n <Inline space=\"space.200\" alignBlock=\"center\" alignInline=\"center\">\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => {}}>\n Option 1\n </Button>\n <Button appearance=\"primary\" onClick={() => {}}>\n Option 3\n </Button>\n </Stack>\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => {}}>\n Option 2\n </Button>\n <Button appearance=\"primary\" onClick={() => {}}>\n Option 4\n </Button>\n </Stack>\n </Inline>\n <Text>Answer</Text>\n <Button appearance='default' onClick={() => {}}>Next question</Button>\n <Text>Question 1 out of 3</Text>\n </Stack>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nimport { QuestionSet } from '../data/questions';\n```\n\nExample:\n```javascript\nconst [activeQuestion, setActiveQuestion] = useState(0);\n```\n\nExample:\n```javascript\nconst { question, options, image } = QuestionSet[activeQuestion];\n```\n\nExample:\n```javascript\nimport React, { useState } from 'react';\nimport ForgeReconciler, { Heading, Image, Button, Text, Inline, Stack } from '@forge/react';\nimport { QuestionSet } from '../data/questions';\n\nconst App = () => {\n\n const [activeQuestion, setActiveQuestion] = useState(0);\n const [explanation, setExplanation] = useState('')\n const [showResult, setShowResult] = useState(false);\n\n const onClickHandler = (isCorrect) => {\n if (isCorrect) {\n setExplanation('You got it right!');\n } else {\n setExplanation('Incorrect, the correct answer is ' + `${QuestionSet[activeQuestion].correctAnswer}`);\n }\n\n setShowResult(true);\n }\n\n const onClickNext = () => {\n if (activeQuestion + 1 < QuestionSet.length) {\n setActiveQuestion(activeQuestion + 1);\n setShowResult(false);\n }\n }\n\n const { question, options, image } = QuestionSet[activeQuestion];\n\n return (\n <>\n <Stack space=\"space.200\" alignInline=\"center\">\n <Heading as=\"h1\">{question}</Heading>\n <Image src={image ? image : \"https://media.giphy.com/media/xUOxfjsW9fWPqEWouI/giphy.gif\"} alt=\"Founders\" size=\"xsmall\" />\n <Inline space=\"space.200\" alignBlock=\"center\" alignInline=\"center\">\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[0].isCorrect)} isDisabled={showResult ? true : false}>\n {options[0].option}\n </Button>\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[2].isCorrect)} isDisabled={showResult ? true : false}>\n {options[2].option}\n </Button>\n </Stack>\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[1].isCorrect)} isDisabled={showResult ? true : false}>\n {options[1].option}\n </Button>\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[3].isCorrect)} isDisabled={showResult ? true : false}>\n {options[3].option}\n </Button>\n </Stack>\n </Inline>\n <Text>{showResult ? explanation : null}</Text>\n <Button appearance='default' onClick={onClickNext} isDisabled={showResult ? false : true}>{ activeQuestion == QuestionSet.length-1 ? 'Finish' : 'Next Question'}</Button>\n <Text>Question {activeQuestion + 1} of {QuestionSet.length}</Text>\n </Stack>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport React, { useState } from 'react';\nimport ForgeReconciler, { Heading, Image, Button, Text, Inline, Stack } from '@forge/react';\nimport { QuestionSet } from '../data/questions';\n\nconst App = () => {\n\n const [activeQuestion, setActiveQuestion] = useState(0);\n const [explanation, setExplanation] = useState('')\n const [showResult, setShowResult] = useState(false);\n const [showResults, setShowResults] = useState(false);\n const [score, setScore] = useState(0);\n\n const onClickHandler = (isCorrect) => {\n if (isCorrect) {\n setExplanation('You got it right!');\n setScore(score + 1);\n } else {\n setExplanation('Incorrect, the correct answer is ' + `${QuestionSet[activeQuestion].correctAnswer}`);\n }\n\n setShowResult(true);\n }\n\n const onClickNext = () => {\n if (activeQuestion + 1 < QuestionSet.length) {\n setActiveQuestion(activeQuestion + 1);\n setShowResult(false);\n } else {\n setShowResults(true);\n }\n }\n\n const onClickReplay = () => {\n setActiveQuestion(0);\n setShowResult(false);\n setScore(0);\n setExplanation('');\n setShowResults(false);\n }\n\n const { question, options, image } = QuestionSet[activeQuestion];\n\n return (\n <>\n { showResults ? (\n <Stack space=\"space.200\" alignInline=\"center\">\n <Heading as=\"h1\">Final score: {score} out of {QuestionSet.length} </Heading>\n <Image src={\"https://media.giphy.com/media/XROOE9NApITmCgF6dZ/giphy.gif\"} alt='High-five' size = \"small\"/>\n <Button appearance=\"primary\" onClick={onClickReplay}>\n Replay\n </Button>\n </Stack>\n ) : (\n <Stack space=\"space.200\" alignInline=\"center\">\n <Heading as=\"h1\">{question}</Heading>\n <Image src={image ? image : \"https://media.giphy.com/media/xUOxfjsW9fWPqEWouI/giphy.gif\"} alt=\"Founders\" size=\"xsmall\" />\n <Inline space=\"space.200\" alignBlock=\"center\" alignInline=\"center\">\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[0].isCorrect)} isDisabled={showResult ? true : false}>\n {options[0].option}\n </Button>\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[2].isCorrect)} isDisabled={showResult ? true : false}>\n {options[2].option}\n </Button>\n </Stack>\n <Stack space=\"space.200\" grow=\"hug\">\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[1].isCorrect)} isDisabled={showResult ? true : false}>\n {options[1].option}\n </Button>\n <Button appearance=\"primary\" onClick={() => onClickHandler(options[3].isCorrect)} isDisabled={showResult ? true : false}>\n {options[3].option}\n </Button>\n </Stack>\n </Inline>\n <Text>{showResult ? explanation : null}</Text>\n <Button appearance='default' onClick={onClickNext} isDisabled={showResult ? false : true}>{ activeQuestion == QuestionSet.length-1 ? 'Finish' : 'Next Question'}</Button>\n <Text>Question {activeQuestion + 1} of {QuestionSet.length}</Text>\n </Stack> )\n }\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.359Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":318,"estimatedTokens":2384}}133{"id":"doc-build_a_confluence_keyword_extractor_with_openai-d6ee8d9e","source":"documentation","title":"Build a Confluence keyword extractor with OpenAI","url":"https://developer.atlassian.com/platform/forge/build-confluence-keyword-extractor-with-openai/","text":"Example:\n```text\nmodules:\n confluence:contentAction:\n - key: keyword-extractor\n resource: main\n resolver:\n function: resolver\n render: native\n title: keyword-extractor\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n id: <your-app-id>\npermissions:\n scopes:\n - read:confluence-content.summary\n - write:confluence-content\n external:\n fetch:\n backend:\n - https://api.openai.com\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from 'react';\nimport ForgeReconciler, { useProductContext } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n // Get the confluence page id\n const context = useProductContext();\n const contentId = context?.extension?.content?.id;\n\n // Use states to store page data asynchronously\n const [data, setData] = useState();\n const [keywords, setKeywords] = useState();\n const [response, setResponse] = useState();\n\n // Fetch and store page data\n useEffect(() => {\n if(contentId){\n invoke('getContent', { contentId }).then(setData);\n }\n }, [contentId]);\n\n console.log(data);\n\n // Define a prompt to be used for the OpenAI API\n const prompt = `Here is the data:\"${data}\"\n Give me the 5 most important keywords from the text. Return the results in the form of a JavaScript array. \n The response shouldn't contain anything apart from the array. No extra text or JavaScript formatting.`\n\n // Call OpenAI API and store the result (keywords)\n useEffect(() => {\n if(prompt){\n invoke('callOpenAI', prompt).then(setKeywords);\n }\n }, [prompt]);\n\n console.log(\"Prompt response - \" + keywords);\n\n // Use state to add the extracted keywords as labels to the current page\n useEffect(() => {\n if(keywords){\n invoke('addKeywordsToLabels', {keywords, contentId}).then(setResponse);\n }\n }, [keywords, contentId]);\n\n console.log(response)\n\n // Render nothing as the main purpose is API interactions and data processing\n return (null);\n};\n\n// Render the main component within a ContentAction\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport { route, asUser } from '@forge/api';\nimport { OpenAI } from 'openai';\n\nconst resolver = new Resolver();\n\n// Resolver function to fetch page data from Confluence\nresolver.define('getContent', async ({ payload }) => {\n\n const response = await asUser().requestConfluence(route`/wiki/api/v2/pages/${payload.contentId}?body-format=storage`, {\n headers: {\n 'Accept': 'application/json'\n }\n });\n\n if (!response.ok) {\n const err = `Error while getContent with contentId ${payload.contentId}: ${response.status} ${response.statusText}`;\n console.error(err);\n throw new Error(err);\n }\n\n const responseData = await response.json();\n const returnedData = responseData.body.storage.value;\n\n return returnedData;\n});\n\nresolver.define('callOpenAI', async (prompt) => {\n // Resolver function to interact with the OpenAI API using a given prompt\n});\n\nresolver.define('addKeywordsToLabels', async ({ payload }) => {\n // Resolver function to add keywords as labels to the current page\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n// Resolver function to interact with the OpenAI API using a given prompt\nresolver.define('callOpenAI', async (prompt) => {\n\n // Create an instance of the OpenAIApi with the provided configuration\n const openai = new OpenAI({\n apiKey: 'API-KEY',\n organisation: 'ORG-ID' \n });\n \n // Create a chat completion request using the OpenAI API\n const chatCompletion = await openai.chat.completions.create({\n messages: [{\n role: \"user\",\n content: prompt\n }],\n model: \"gpt-3.5-turbo\"\n })\n \n // Extract the response content from the API response\n const response = chatCompletion.choices[0].message.content;\n \n // Return the generated response from the OpenAI API\n return response;\n});\n```\n\nExample:\n```bash\nforge variables set --encrypt API_KEY your-api-key\nforge variables set --encrypt ORG_ID your-org-id\n```\n\nExample:\n```javascript\n// Resolver function to add keywords as labels to the current page\nresolver.define('addKeywordsToLabels', async ({ payload }) => {\n\n // Parse the keywords and prepare them for adding as labels\n const bodyData = JSON.parse(payload.keywords).map(label => ({\n prefix: \"global\",\n name: label.split(\" \").join(\"-\")\n }));\n\n // Make a request to the Confluence API to add labels to the page\n const response = await asUser().requestConfluence(route`/wiki/rest/api/content/${payload.contentId}/label`, {\n method: 'POST',\n headers: {\n 'Accept': 'application/json',\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(bodyData)\n });\n \n // Parse and return the response JSON\n const responseJson = await response.json();\n\n return responseJson;\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.360Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":199,"estimatedTokens":1272}}134{"id":"doc-create_a_logo_designer_app_using_the_frame_compo-93f3e3a3","source":"documentation","title":"Create a logo designer app using the Frame component","url":"https://developer.atlassian.com/platform/forge/ui-kit/components/frame-tutorial/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd logo-designer\n```\n\nExample:\n```bash\nnpm install @forge/kvs\n```\n\nExample:\n```bash\nmkdir resources\ncd resources\n```\n\nExample:\n```bash\nyarn create react-app project-logo-display\n```\n\nExample:\n```json\n{\n \"name\": \"project-logo-display\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"homepage\": \"./\",\n ....\n}\n```\n\nExample:\n```bash\ncd project-logo-display\n yarn build\n```\n\nExample:\n```text\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: logo-display\n path: resources/project-logo-display/build\n```\n\nExample:\n```text\nmodules:\n confluence:globalPage:\n - key: logo-designer-hello-world-global-page\n resource: main\n render: native\n resolver:\n function: resolver\n title: Logo Designer\n route: logo-designer\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: logo-display\n path: resources/project-logo-display/build\npermissions:\n scopes:\n - storage:app\n content:\n styles:\n - \"unsafe-inline\"\n external:\n images:\n - address: https://imgur.com\n - address: https://i.imgur.com\"\napp:\n runtime:\n name: nodejs24.x\n id: '<your app id>'\n```\n\nExample:\n```javascript\nexport const fruitSelectionMap = [\n { name: \"Avocado\", url: \"https://imgur.com/CIsX0ZO.png\" },\n { name: \"Coconut\", url: \"https://imgur.com/lRHQMS9.png\" },\n { name: \"Pear\", url: \"https://imgur.com/YwiIGQA.png\" },\n { name: \"Red Apple\", url: \"https://imgur.com/wduDSGD.png\" },\n { name: \"Orange\", url: \"https://imgur.com/lSyCbHI.png\" },\n { name: \"Pomegranate\", url: \"https://imgur.com/aOHoUbj.png\" },\n { name: \"Peach\", url: \"https://imgur.com/1rBCiC3.png\" },\n { name: \"Lemon\", url: \"https://imgur.com/T1s8Pts.png\" },\n];\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { Stack, Text, Range } from \"@forge/react\";\n\nexport const BorderRadiusSlider = ({ value, onChange }) => {\n return (\n <Stack space=\"space.200\">\n <Text>Radius</Text>\n <Range\n value={value}\n min={0}\n max={100}\n onChange={onChange}\n />\n </Stack>\n );\n};\n```\n\nExample:\n```javascript\nimport React, { useCallback } from \"react\";\nimport { Stack, Radio, Image } from \"@forge/react\";\n\nexport const FruitIcon = ({ name, url, isChecked, onChange }) => {\n const handleChange = useCallback((event) => {\n if (onChange) {\n onChange(event.target.value);\n }\n }, []);\n\n return (\n <Stack alignInline=\"center\">\n <Image src={url} alt={name} size=\"xlarge\" />\n <Radio\n name=\"icon\"\n value={name}\n onChange={handleChange}\n isChecked={isChecked}\n />\n </Stack>\n );\n};\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { Stack, Text, Inline } from \"@forge/react\";\nimport { FruitIcon } from \"./FruitIcon\";\nimport { fruitSelectionMap } from \"../constants\";\n\nexport const IconSelection = ({ value, onChange }) => {\n return (\n <Stack space=\"space.200\">\n <Text>Select an icon</Text>\n <Inline shouldWrap>\n {fruitSelectionMap.map(({ name, url }) => (\n <FruitIcon\n key={name}\n name={name}\n url={url}\n isChecked={name === value}\n onChange={onChange}\n />\n ))}\n </Inline>\n </Stack>\n );\n};\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, { Inline, Stack, Heading, Frame } from \"@forge/react\";\nimport { IconSelection } from \"./components/IconSelection\";\nimport { BorderRadiusSlider } from \"./components/BorderRadiusSlider\";\n\nconst App = () => {\n const [config, setConfig] = React.useState({ icon: \"Avocado\", radius: 20 });\n // TODO: setup logo update and preview logic\n const handleConfigChange = ({ key, value }) => {\n console.log(key, value);\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.400\">\n <Heading as=\"h1\">Project logo designer</Heading>\n <Inline space=\"space.400\">\n <Stack space=\"space.1000\">\n <IconSelection\n value={config.icon}\n onChange={(value) =>\n handleConfigChange({ key: \"icon\", value })\n }\n />\n <BorderRadiusSlider\n value={config.radius}\n onChange={(value) =>\n handleConfigChange({ key: \"radius\", value })\n }\n />\n </Stack>\n {/* TODO: Setup logo display component */}\n </Inline>\n </Stack>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\ncd resources/project-logo-display/\n```\n\nExample:\n```bash\nyarn add @atlaskit/css-reset \\\n @atlaskit/button \\\n @atlaskit/primitives \\\n @atlaskit/tokens \\\n @atlaskit/tooltip \\\n @atlaskit/visually-hidden \\\n @forge/bridge \\\n styled-components\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { createGlobalStyle } from \"styled-components\";\nimport { view } from \"@forge/bridge\";\nimport { token } from \"@atlaskit/tokens\";\nimport App from \"./App\";\n\nimport \"@atlaskit/css-reset\";\n\n// Enables theming\nview.theme.enable();\n\nconst GlobalStyle = createGlobalStyle`\n body {\n background: ${token(\"elevation.surface\")};\n }\n `;\n\nReactDOM.render(\n <React.StrictMode>\n <GlobalStyle />\n <App />\n </React.StrictMode>,\n document.getElementById(\"root\")\n);\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { fruitSelectionMap } from \"../constants\";\nimport styled from \"styled-components\";\nimport { token } from \"@atlaskit/tokens\";\n\nconst LogoBackground = styled.div`\n width: 280px;\n height: 280px;\n background-color: ${({ color }) =>\n color\n ? token(`color.background.accent.${color}.subtler`)\n : token(\"color.background.neutral\")};\n border-radius: ${({ borderRadius }) => borderRadius}px;\n`;\n\nconst iconUrls = fruitSelectionMap.reduce((acc, fruit) => {\n acc[fruit.name] = fruit.url;\n return acc;\n}, {});\n\nexport const LogoDisplay = ({ icon, radius, color }) => {\n return (\n <LogoBackground borderRadius={radius} color={color}>\n <img\n alt=\"logo-img\"\n src={iconUrls[icon]}\n style={{ width: \"280px\", height: \"280px\", display: \"block\" }}\n />\n </LogoBackground>\n );\n};\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { Inline, Pressable, xcss } from \"@atlaskit/primitives\";\nimport Tooltip from \"@atlaskit/tooltip\";\nimport VisuallyHidden from \"@atlaskit/visually-hidden\";\n\nconst baseStyles = xcss({\n borderWidth: \"border.width\",\n borderStyle: \"solid\",\n borderColor: \"color.border\",\n borderRadius: \"border.radius\",\n height: \"20px\",\n width: \"20px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n});\n\nconst borderSelected = xcss({\n borderColor: \"color.border.bold\",\n});\n\nconst colorMap = [\n \"red\",\n \"orange\",\n \"yellow\",\n \"lime\",\n \"green\",\n \"teal\",\n \"blue\",\n \"purple\",\n \"magenta\",\n].reduce((acc, color) => {\n acc[color] = xcss({\n backgroundColor: `color.background.accent.${color}.subtler`,\n \":hover\": {\n backgroundColor: `color.background.accent.${color}.subtler.hovered`,\n },\n \":active\": {\n backgroundColor: `color.background.accent.${color}.subtler.pressed`,\n },\n });\n return acc;\n}, {});\n\nconst ColorButton = ({ color, isSelected, onClick }) => {\n const pressableStyles = [baseStyles, colorMap[color]];\n return (\n <Tooltip content={color}>\n <Pressable\n interactionName={`color-${color}`}\n xcss={\n isSelected\n ? [...pressableStyles, borderSelected]\n : pressableStyles\n }\n aria-pressed={isSelected}\n onClick={onClick}\n >\n <VisuallyHidden>{color}</VisuallyHidden>\n </Pressable>\n </Tooltip>\n );\n};\n\nexport const ColorPalette = ({ value, onChange }) => {\n return (\n <Inline space=\"space.100\">\n {Object.keys(colorMap).map((color) => {\n return (\n <ColorButton\n key={color}\n color={color}\n isSelected={color === value}\n onClick={() => {\n onChange(color);\n }}\n />\n );\n })}\n </Inline>\n );\n};\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { ColorPalette } from \"./components/ColorPalette\";\nimport { LogoDisplay } from \"./components/LogoDisplay\";\nimport { Stack } from \"@atlaskit/primitives\";\n\nfunction App() {\n const [config, setConfig] = React.useState({\n icon: \"Avocado\",\n radius: 20,\n color: \"red\",\n });\n\n const handleConfigChange = ({ key, value }) => {\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n // TODO: setup logic to track log update requests from the UI Kit side.\n\n return (\n <Stack space=\"space.200\">\n <ColorPalette\n value={config.color}\n onChange={(value) =>\n handleConfigChange({ key: \"color\", value })\n }\n />\n <LogoDisplay\n icon={config.icon}\n radius={config.radius}\n color={config.color}\n />\n </Stack>\n );\n}\n\nexport default App;\n```\n\nExample:\n```bash\nyarn build\n```\n\nExample:\n```javascript\n// .....\n\nconst App = () => {\n const [config, setConfig] = React.useState({ icon: \"Avocado\", radius: 20 });\n\n // TODO: setup logo update and preview logic\n const handleConfigChange = ({ key, value }) => {\n console.log(key, value);\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.400\">\n <Heading as=\"h1\">Project logo designer</Heading>\n <Inline space=\"space.400\">\n <Stack space=\"space.1000\">\n <IconSelection\n value={config.icon}\n onChange={(value) =>\n handleConfigChange({ key: \"icon\", value })\n }\n />\n <BorderRadiusSlider\n value={config.radius}\n onChange={(value) =>\n handleConfigChange({ key: \"radius\", value })\n }\n />\n </Stack>\n <Frame resource=\"logo-display\" />\n </Inline>\n </Stack>\n );\n};\n\n// .....\n```\n\nExample:\n```javascript\nimport { useEffect, useState, useCallback } from \"react\";\nimport { events } from \"@forge/bridge\";\n\nconst LOGO_CONFIG_UPDATES_EVENT = \"LOGO_CONFIG_UPDATES\";\n\nexport const useLogoConfigUpdates = () => {\n const [configUpdates, setConfigUpdates] = useState([]);\n\n useEffect(() => {\n const sub = events.on(LOGO_CONFIG_UPDATES_EVENT, (message) => {\n if (\n message &&\n message.updates !== undefined &&\n message.updates.length > 0\n ) {\n setConfigUpdates(message.updates);\n }\n });\n\n return () => {\n sub.then((subscription) => subscription.unsubscribe());\n };\n }, [setConfigUpdates]);\n\n const emitConfigUpdates = useCallback((updates) => {\n events.emit(LOGO_CONFIG_UPDATES_EVENT, { updates });\n }, []);\n\n return [configUpdates, emitConfigUpdates];\n};\n```\n\nExample:\n```javascript\nimport { useLogoConfigUpdates } from \"./hooks\";\n\n// .....\n\nconst App = () => {\n const [config, setConfig] = React.useState({ icon: \"Avocado\", radius: 20 });\n const [_, emitLogoConfigUpdates] = useLogoConfigUpdates();\n\n React.useEffect(() => {\n // sending through logo config updates through events API whenever\n // config object changes is detected.\n emitLogoConfigUpdates(\n Object.entries(config).map(([key, value]) => ({ key, value }))\n );\n }, [config]);\n\n const handleConfigChange = ({ key, value }) => {\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.400\">\n <Heading as=\"h1\">Project logo designer</Heading>\n <Inline space=\"space.400\">\n <Stack space=\"space.1000\">\n <IconSelection\n value={config.icon}\n onChange={(value) =>\n handleConfigChange({ key: \"icon\", value })\n }\n />\n <BorderRadiusSlider\n value={config.radius}\n onChange={(value) =>\n handleConfigChange({ key: \"radius\", value })\n }\n />\n </Stack>\n <Frame resource=\"logo-display\" />\n </Inline>\n </Stack>\n );\n};\n\n// .....\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { ColorPalette } from \"./components/ColorPalette\";\nimport { LogoDisplay } from \"./components/LogoDisplay\";\nimport { Stack } from \"@atlaskit/primitives\";\nimport { useLogoConfigUpdates } from \"./hooks\";\n\nfunction App() {\n const [config, setConfig] = React.useState({\n icon: \"Avocado\",\n radius: 20,\n color: \"red\",\n });\n\n // track logo update requests from the UI Kit side.\n const [logoConfigUpdates] = useLogoConfigUpdates();\n\n React.useEffect(() => {\n if (logoConfigUpdates && logoConfigUpdates.length > 0) {\n // upon receiving the config updates, apply the updates to the current\n // config state to update the logo preview.\n setConfig((prevConfig) => {\n const updatedConfig = { ...prevConfig };\n logoConfigUpdates.forEach((update) => {\n updatedConfig[update.key] = update.value;\n });\n return updatedConfig;\n });\n }\n }, [logoConfigUpdates, setConfig]);\n\n const handleConfigChange = ({ key, value }) => {\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.200\">\n <ColorPalette\n value={config.color}\n onChange={(value) =>\n handleConfigChange({ key: \"color\", value })\n }\n />\n <LogoDisplay\n icon={config.icon}\n radius={config.radius}\n color={config.color}\n />\n </Stack>\n );\n}\nexport default App;\n```\n\nExample:\n```javascript\nimport Resolver from \"@forge/resolver\";\nimport { kvs } from \"@forge/kvs\";\n\nconst LOGO_CONFIG_STORAGE_KEY = \"LOGO_CONFIG\";\n\n// Move default configuration to the shared backend to remove the duplicated\n// logics in the frontend.\nconst defaultLogoConfig = {\n icon: \"Avocado\",\n radius: 20,\n color: \"red\",\n};\n\nconst resolver = new Resolver();\n\nresolver.define(\"setLogoConfig\", async ({ payload: { config } }) => {\n return await kvs.set(LOGO_CONFIG_STORAGE_KEY, config);\n});\n\nresolver.define(\"getLogoConfig\", async () => {\n let config = null;\n try {\n config = await kvs.get(LOGO_CONFIG_STORAGE_KEY);\n } catch (e) {}\n return config || defaultLogoConfig;\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n# file path: manifest.yml\n\nmodules:\n# ...\nfunction:\n - key: resolve\n handler: index.handler\n```\n\nExample:\n```javascript\nimport { useEffect, useState, useCallback } from \"react\";\nimport { events, invoke } from \"@forge/bridge\";\n\n// ....\n\nexport const useGetLogoConfigFromStorage = () => {\n const [config, setConfig] = useState(null);\n\n useEffect(() => {\n // this works in both UI Kit and inside Frame component.\n invoke(\"getLogoConfig\").then((config) => {\n setConfig(config);\n });\n }, [setConfig]);\n\n return config;\n};\n\nexport const useSetLogoConfigToStorage = () => {\n return useCallback((config) => {\n invoke(\"setLogoConfig\", { config });\n }, []);\n};\n```\n\nExample:\n```javascript\nimport React, { useEffect } from \"react\";\nimport ForgeReconciler, {\n Inline,\n Stack,\n Heading,\n Frame,\n} from \"@forge/react\";\nimport { IconSelection } from \"./components/IconSelection\";\nimport { BorderRadiusSlider } from \"./components/BorderRadiusSlider\";\nimport {\n useLogoConfigUpdates,\n useGetLogoConfigFromStorage,\n} from \"./hooks\";\n\nconst LogoDesignControlPanel = ({ logoConfig: initialConfig }) => {\n const [config, setConfig] = React.useState(initialConfig);\n const [_, emitLogoConfigUpdates] = useLogoConfigUpdates();\n\n useEffect(() => {\n emitLogoConfigUpdates(\n Object.entries(config).map(([key, value]) => ({ key, value }))\n );\n }, [config]);\n\n const handleConfigChange = ({ key, value }) => {\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.400\">\n <Heading as=\"h1\">Project logo designer</Heading>\n <Inline space=\"space.400\">\n <Stack space=\"space.1000\">\n <IconSelection\n value={config.icon}\n onChange={(value) =>\n handleConfigChange({ key: \"icon\", value })\n }\n />\n <BorderRadiusSlider\n value={config.radius}\n onChange={(value) =>\n handleConfigChange({ key: \"radius\", value })\n }\n />\n </Stack>\n <Frame resource=\"logo-display\" />\n </Inline>\n </Stack>\n );\n};\n\nconst App = () => {\n // Load the configuration from the storage API\n const logoConfig = useGetLogoConfigFromStorage();\n if (!logoConfig) {\n return null;\n }\n\n // Move the logo design control logic into a dedicated component.\n return <LogoDesignControlPanel logoConfig={logoConfig} />;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport React, { useEffect } from \"react\";\nimport { ColorPalette } from \"./components/ColorPalette\";\nimport { LogoDisplay } from \"./components/LogoDisplay\";\nimport { Stack } from \"@atlaskit/primitives\";\nimport Button from \"@atlaskit/button\";\nimport {\n useLogoConfigUpdates,\n useGetLogoConfigFromStorage,\n useSetLogoConfigToStorage,\n} from \"./hooks\";\n\nconst LogoDesignPanel = ({ logoConfig: initialConfig }) => {\n const [config, setConfig] = React.useState(initialConfig);\n const [logoConfigUpdates] = useLogoConfigUpdates();\n const storeLogoConfig = useSetLogoConfigToStorage();\n\n useEffect(() => {\n if (logoConfigUpdates && logoConfigUpdates.length > 0) {\n setConfig((prevConfig) => {\n const updatedConfig = { ...prevConfig };\n logoConfigUpdates.forEach((update) => {\n updatedConfig[update.key] = update.value;\n });\n return updatedConfig;\n });\n }\n }, [logoConfigUpdates, setConfig]);\n\n const handleConfigChange = ({ key, value }) => {\n setConfig((prevConfig) => ({ ...prevConfig, [key]: value }));\n };\n\n return (\n <Stack space=\"space.200\" alignInline=\"start\">\n <Stack alignInline=\"center\" space=\"space.200\">\n <ColorPalette\n value={config.color}\n onChange={(value) =>\n handleConfigChange({ key: \"color\", value })\n }\n />\n <Stack space=\"space.200\">\n <LogoDisplay\n icon={config.icon}\n radius={config.radius}\n color={config.color}\n />\n {/* Persist the logo configuration to storage api */}\n <Button\n appearance=\"primary\"\n onClick={() => storeLogoConfig(config)}\n >\n Save changes\n </Button>\n </Stack>\n </Stack>\n </Stack>\n );\n};\n\nfunction App() {\n // Load the configuration from the storage API\n const logoConfig = useGetLogoConfigFromStorage();\n\n if (!logoConfig) {\n return null;\n }\n\n return <LogoDesignPanel logoConfig={logoConfig} />;\n}\n\nexport default App;\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.362Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":33,"totalLines":845,"estimatedTokens":5382}}135{"id":"doc-add_configuration_to_a_macro_with_ui_kit-90703b75","source":"documentation","title":"Add configuration to a macro with UI Kit","url":"https://developer.atlassian.com/platform/forge/add-configuration-to-a-macro-with-ui-kit/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: pet-facts\n resource: main\n render: native\n resolver:\n function: resolver\n title: Pet\n config: true\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: \"<your app id>\"\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: pet-facts\n resource: main\n render: native\n resolver:\n function: resolver\n title: Pet\n config:\n openOnInsert: true\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport { Label, Textfield } from \"@forge/react\";\n\nconst Config = () => {\n return (\n <>\n <Label>Pet age</Label>\n <Textfield name=\"age\" />\n </>\n );\n};\n```\n\nExample:\n```javascript\nimport React from 'react';\nimport ForgeReconciler, { Label, Textfield } from '@forge/react';\n\nconst Config = () => {\n return (\n <>\n <Label>Pet age</Label>\n <Textfield name=\"age\" />\n </>\n );\n};\n\nconst App = () => {\n ...\n}\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, {\n Label,\n Text,\n Textfield,\n useConfig,\n} from \"@forge/react\";\n```\n\nExample:\n```javascript\nconst Config = () => {\n...\n};\n\nconst App = () => {\n const config = useConfig();\n\n return (\n <>\n <Text>Hello World</Text>\n </>\n );\n};\n```\n\nExample:\n```javascript\nconst App = () => {\n\n ...\n\n const config = useConfig();\n const age = config?.age;\n\n return (\n <>\n <Text>\n {age || 'Fetching config...'}\n </Text>\n </>\n );\n}\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, {\n Label,\n Text,\n Textfield,\n useConfig,\n} from \"@forge/react\";\n\nconst Config = () => {\n return (\n <>\n <Label>Pet age</Label>\n <Textfield name=\"age\" />\n </>\n );\n};\n\nconst App = () => {\n const config = useConfig();\n const age = config?.age;\n\n return <Text>{age || \"Fetching config...\"}</Text>;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```javascript\nimport ForgeReconciler, {\n Label,\n Text,\n Textfield,\n SectionMessage,\n} from \"@forge/react\";\n//Add SectionMessage to the import statement\n\n<SectionMessage title=\"You need to configure this macro\" appearance=\"warning\">\n <Text>\n While editing the page, select the macro, and click on the pencil icon to\n display configuration options.\n </Text>\n</SectionMessage>;\n```\n\nExample:\n```javascript\nconst defaultConfig = {\n name: \"Unnamed Pet\",\n age: \"0\",\n};\n```\n\nExample:\n```javascript\nconst actualConfig = useConfig();\nconst config = actualConfig || defaultConfig;\n```\n\nExample:\n```javascript\nconst Config = () => {\n return (\n <>\n <Label>Pet name</Label>\n <Textfield name=\"name\" defaultValue={defaultConfig.name} />\n <Label>Pet age</Label>\n <Textfield name=\"age\" defaultValue={defaultConfig.age} />\n </>\n );\n};\n```\n\nExample:\n```javascript\nreturn (\n <Text>\n {config.name} is {config.age} years old.\n </Text>\n);\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, {\n Label,\n Textfield,\n Text,\n useConfig,\n SectionMessage,\n} from \"@forge/react\";\n\nconst defaultConfig = {\n name: \"Unnamed Pet\",\n age: \"0\",\n};\n\n// Function that defines the configuration UI for the pet's name and age\nconst Config = () => {\n return (\n <>\n <Label>Pet name</Label>\n <Textfield name=\"name\" defaultValue={defaultConfig.name} />\n <Label>Pet age</Label>\n <Textfield name=\"age\" defaultValue={defaultConfig.age} />\n </>\n );\n};\n\nconst App = () => {\n const actualConfig = useConfig();\n const config = actualConfig || defaultConfig;\n return (\n <>\n <Text>\n {config.name} is {config.age} years old.\n </Text>\n {!actualConfig && (\n <SectionMessage\n title=\"You need to configure this macro\"\n appearance=\"warning\"\n >\n <Text>\n While editing the page, select the macro, and click on the pencil icon\n to display configuration options.\n </Text>\n </SectionMessage>\n )}\n </>\n );\n};\n\n// Adding the Config function to the ForgeReconciler to allow for configuration changes\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n\nForgeReconciler.addConfig(<Config />);\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.364Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":297,"estimatedTokens":1160}}136{"id":"doc-build_a_dashboard_app_with_the_full_page_module_-4637cfe1","source":"documentation","title":"Build a dashboard app with the full page module in Confluence","url":"https://developer.atlassian.com/platform/forge/build-a-dashboard-app-with-the-confluence-full-page-module","text":"Example:\n```text\nmodules:\n confluence:fullPage:\n - key: my-full-page-module\n resource: main\n render: native\n resolver:\n function: resolver\n routePrefix: ui-kit\n title: My full page module # Optional\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: \"<your app id>\"\n runtime:\n name: nodejs24.x\npermissions:\n scopes:\n - read:confluence-user\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from \"react\";\nimport ForgeReconciler, { Box, Stack, Heading, Text, BarChart, Button, Inline, Image, xcss } from \"@forge/react\";\nimport { requestConfluence } from \"@forge/bridge\";\n\nconst chartContainerStyle = xcss({\n maxWidth: \"600px\",\n});\n\nconst App = () => {\n const [selectedPeriod, setSelectedPeriod] = useState(\"Q1\");\n const [userInfo, setUserInfo] = useState(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n // Dashboard usage data for different periods\n const q1Data = [\n { month: 'Jan', usage: 45 },\n { month: 'Feb', usage: 52 },\n { month: 'Mar', usage: 48 },\n ];\n\n const q2Data = [\n { month: 'Apr', usage: 61 },\n { month: 'May', usage: 55 },\n { month: 'Jun', usage: 67 },\n ];\n\n const chartData = selectedPeriod === \"Q1\" ? q1Data : q2Data;\n\n // Fetch current user information from Confluence API\n const fetchUserInfo = async () => {\n setLoading(true);\n setError(null);\n try {\n const response = await requestConfluence('/wiki/rest/api/user/current');\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n const data = await response.json();\n setUserInfo(data);\n } catch (err) {\n setError(`Failed to fetch user info: ${err.message}`);\n console.error(err);\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n fetchUserInfo();\n }, []);\n\n return (\n <Box padding=\"space.400\">\n <Stack space=\"space.400\">\n <Stack space=\"space.200\">\n <Heading size=\"xlarge\">Dashboard</Heading>\n <Text>Monthly usage overview</Text>\n </Stack>\n\n <Inline space=\"space.200\">\n <Button \n appearance={selectedPeriod === \"Q1\" ? \"primary\" : \"default\"}\n onClick={() => setSelectedPeriod(\"Q1\")}\n >\n Q1\n </Button>\n <Button \n appearance={selectedPeriod === \"Q2\" ? \"primary\" : \"default\"}\n onClick={() => setSelectedPeriod(\"Q2\")}\n >\n Q2\n </Button>\n </Inline>\n\n <Stack space=\"space.300\">\n <Box xcss={chartContainerStyle}>\n <BarChart\n data={chartData}\n xAccessor=\"month\"\n yAccessor=\"usage\"\n />\n </Box>\n\n <Box padding=\"space.200\">\n <Stack space=\"space.100\">\n <Text>Current period: {selectedPeriod}</Text>\n <Text>Total usage: {chartData.reduce((sum, item) => sum + item.usage, 0)}</Text>\n </Stack>\n </Box>\n </Stack>\n\n <Box padding=\"space.400\">\n <Stack space=\"space.300\">\n <Heading size=\"medium\">Current User Information</Heading>\n {loading && <Text>Loading user info...</Text>}\n {error && <Text>{error}</Text>}\n {!loading && !error && userInfo && (\n <Box padding=\"space.300\" xcss={xcss({\n borderWidth: \"border.width\",\n borderStyle: \"solid\",\n borderColor: \"color.border\",\n borderRadius: \"border.radius.300\",\n })}>\n <Stack space=\"space.200\">\n <Text>\n <Text weight=\"bold\">Name:</Text> {userInfo.displayName}\n </Text>\n {userInfo.email && (\n <Text>\n <Text weight=\"bold\">Email:</Text> {userInfo.email}\n </Text>\n )}\n {userInfo.accountId && (\n <Text>\n <Text weight=\"bold\">Account ID:</Text> {userInfo.accountId}\n </Text>\n )}\n {userInfo.profilePicture && (\n <Box padding=\"space.200\">\n <Image \n src={userInfo.profilePicture.path} \n alt=\"Profile\" \n width=\"48px\"\n height=\"48px\"\n />\n </Box>\n )}\n </Stack>\n </Box>\n )}\n </Stack>\n </Box>\n </Stack>\n </Box>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:confluence-user\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\nhttps://<your-site>.atlassian.net/forge-apps/a/<app-id>/e/<forge-environment-id>/r/<route-prefix>/<app-route>\n```\n\nExample:\n```text\nhttps://example.atlassian.net/forge-apps/a/21e590df-79e6-40dd-9ee4-ba2c7b678f26/e/9f699e8b-33f1-4fa7-bd48-c5fdc44fa4c2/r/ui-kit\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.365Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":208,"estimatedTokens":1335}}137{"id":"doc-build_a_jira_ui_modifications_app-dd9b16d3","source":"documentation","title":"Build a Jira UI modifications app","url":"https://developer.atlassian.com/platform/forge/build-a-jira-uim-app/","text":"Example:\n```text\nmodules:\n jira:uiModifications:\n - key: ui-modifications-app\n title: Example UI modifications app\n resource: uiModificationsApp\nresources:\n - key: uiModificationsApp\n path: static/ui-modifications/dist\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// static/ui-modifications/index.js\nimport { uiModificationsApi } from '@forge/jira-bridge';\n\nuiModificationsApi.onInit(\n ({ api }) => {\n const { getFieldById, getScreenTabs } = api;\n\n // Hiding the priority field\n const priority = getFieldById('priority');\n priority?.setVisible(false);\n\n // Changing the summary field label\n const summary = getFieldById('summary');\n summary?.setName('Modified summary label');\n\n // Changing the assignee field description\n const assignee = getFieldById('assignee');\n assignee?.setDescription('Description added by UI modifications');\n\n // Get value of labels field\n const labels = getFieldById('labels');\n const labelsData = labels?.getValue() || [];\n labels?.setDescription(\n `${labelsData.length} label(s) are currently selected`\n );\n\n // Hide the last screen tab\n const tabs = getScreenTabs();\n if (tabs.length > 0) {\n tabs.at(-1).setVisible(false);\n }\n },\n () => ['priority', 'summary', 'assignee', 'labels']\n);\n```\n\nExample:\n```javascript\nimport { uiModificationsApi } from '@forge/jira-bridge';\n\n// Imaginary import\nimport { shouldPriorityBeHidden } from '../my-services';\n\nuiModificationsApi.onInit(\n ({ api }) => {\n const { getFieldById } = api;\n\n // Hiding the priority field\n const priority = getFieldById('priority');\n // Store the update Promise\n const priorityUpdate = shouldPriorityBeHidden().then((result) => {\n if (result === true) {\n priority?.setVisible(false);\n }\n });\n\n // Changing the assignee field description\n const assignee = getFieldById('assignee');\n assignee?.setDescription('Description added by UI modifications');\n\n // Return the promise. In this case even the assignee field description\n // is updated only after the priorityUpdate promise resolves.\n return priorityUpdate;\n },\n () => ['priority', 'assignee']\n);\n```\n\nExample:\n```javascript\nimport { uiModificationsApi } from '@forge/jira-bridge';\n\n// Imaginary import\nimport { shouldPriorityBeHidden } from '../my-services';\n\nuiModificationsApi.onInit(\n async ({ api }) => {\n const { getFieldById } = api;\n\n // Hiding the priority field\n const priority = getFieldById('priority');\n const result = await shouldPriorityBeHidden();\n if (result === true) {\n priority?.setVisible(false);\n }\n\n // Changing the assignee field description\n const assignee = getFieldById('assignee');\n assignee?.setDescription('Description added by UI modifications');\n },\n () => ['priority', 'assignee']\n);\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\nconst createUiModification = async (projectId, issueTypeId) => {\n const result = await api.asApp().requestJira(route`/rest/api/3/uiModifications`, {\n method: \"POST\",\n body: JSON.stringify({\n name: 'demo-ui-modification',\n data: '[\"custom data\", \"for your app\"]',\n contexts: [\n { projectId, issueTypeId, viewType: 'GIC' }, \n { projectId, issueTypeId, viewType: 'IssueView' }\n ],\n }),\n headers: {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n },\n });\n console.log(`Created UI modification with status ${result.status}`);\n return result.status;\n}\n```\n\nExample:\n```typescript\nuiModifications: Array<{\n id: string,\n data?: string,\n}>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.365Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":148,"estimatedTokens":915}}138{"id":"doc-build_a_jira_hello_world_app-07e66a50","source":"documentation","title":"Build a Jira hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-jira/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-app-hello-world-issue-panel\n resource: main\n resolver:\n function: resolver\n render: native\n title: hello-world-app\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.367Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":88,"estimatedTokens":337}}139{"id":"doc-build_a_feedback_integration_app-501d34ab","source":"documentation","title":"Build a feedback integration app","url":"https://developer.atlassian.com/platform/forge/build-a-feedback-integration-app/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n- key: feedbackintegration-webtrigger-sync\n function: sync\n response:\n type: dynamic\n```\n\nExample:\n```text\n- key: sync\n handler: index.runSync4\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: feedbackintegration-webtrigger-async\n function: async\n urlFormat: v2\n response:\n type: dynamic\n function:\n - key: async\n handler: index.runAsync\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/your-app-id5\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: feedbackintegration-webtrigger-async\n function: async\n urlFormat: v2\n response:\n type: dynamic\n function:\n - key: async\n handler: index.onCreateFeedback\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/your-app-id5\n```\n\nExample:\n```javascript\n/**\n * @returns {import('@forge/api').WebTriggerResponse}\n*/\nconst buildOutput = (rnd) => ({\nbody: '{\"hello\": \"world\"}',\nheaders: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: 200,\n statusText: 'OK'\n});\n\n/**\n* @param {import('@forge/api').WebTriggerRequest} event\n* @param {import('@forge/api').WebTriggerContext} context\n* @returns {Promise<import('@forge/api').WebTriggerResponse>}\n*/\nexports.onCreateFeedback = (event, context) => {\nreturn new Promise((resolve) => {\n setTimeout(() => {\n const result = buildOutput(Math.random());\n resolve(result);\n }, 1000);\n });\n};\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge webtrigger create\n```\n\nExample:\n```bash\nCopy your web trigger URL below to start using it:\nhttps://123456-0441-46ab-a647-440e9b6db295.hello.atlassian-dev.net/x1/123456-7ztMKudBQPrIng9GwA\n```\n\nExample:\n```javascript\n/**\n* @param {import('@forge/api').WebTriggerRequest} request\n* @param {import('@forge/api').WebTriggerContext} context\n* @returns {Promise<import('@forge/api').WebTriggerResponse>}\n*/\nexport const onCreateFeedback = async (request, context) => {\nconsole.log('Received request:', JSON.stringify(request, null, 2));\nconsole.log('Context:', JSON.stringify(context, null, 2));\nconst authorizationHeader = request.headers['authorization'];\nif (authorizationHeader && authorizationHeader[0] === 'Basic SECRET_KEY') {\n if (request.method !== 'POST') {\n return buildMethodNotAllowedOutput(Math.random());\n }\n const result = buildOutput(Math.random());\n return result;\n} else {\n console.warn('Unauthorized access attempt with header:', authorizationHeader);\n return buildUnauthorzesOutput(Math.random());\n}\n};\n\n/**\n* @returns {import('@forge/api').WebTriggerResponse}\n*/\nconst buildOutput = (rnd) => ({\nbody: '{\"hello\": \"world\"}',\nheaders: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: 200,\n statusText: 'OK'\n});\n\nconst buildUnauthorzesOutput = (rnd) => ({\nbody: '{\"error\": \"Unauthorized\"}',\nheaders: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: 401,\n statusText: 'Unauthorized'\n});\n\nconst buildMethodNotAllowedOutput = (rnd) => ({\nbody: '{\"error\": \"Method Not Allowed\"}',\nheaders: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n},\n statusCode: 405,\n statusText: 'Method Not Allowed'\n});\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\ncurl -X POST --header \"Authorization: Basic SECRET_KEY\" https://your-webtrigger-url\n```\n\nExample:\n```bash\ncurl -X POST --header \"Authorization: Basic INVALID_KEY\" https://your-webtrigger-url\n```\n\nExample:\n```javascript\nconst feedbackSpaceKey = 'FEED'; // Make sure you use your space key here\nconst feedbackSpaceId = '10049'; // Make sure you use your identifier value here\nconst feedbackWorkItemTypeId = '10114'; // Make sure you use your identifier value here\n```\n\nExample:\n```text\npermissions:\nscopes:\n - write:jira-work\n```\n\nExample:\n```javascript\nconst buildAdf = (paragraphText) => {\nconst adf = {\n content: [\n {\n content: [\n {\n type: \"text\",\n text: paragraphText,\n }\n ],\n type: \"paragraph\"\n }\n ],\n type: \"doc\",\n version: 1\n};\nreturn adf;\n}\n```\n\nExample:\n```javascript\nconst createFeedbackWorkItem = async (request) => {\nconst summary = requestBody.summary;\nconst details = requestBody.details;\nconst ratingString = requestBody.rating;\nconst rating = ratingString ? parseInt(ratingString) : -1;\nconsole.log(`summary: ${summary}`);\nconsole.log(`details: ${details}`);\nconsole.log(`rating: ${rating}`);\n}\n```\n\nExample:\n```javascript\nif (request.method !== 'POST') {\n return buildMethodNotAllowedOutput(Math.random());\n}\nawait createFeedbackWorkItem(request);\n```\n\nExample:\n```javascript\nconst buildCreateIssueBodyData = (summary, details, rating) => {\n// Note: the rating parameter is currently being ignored\nconst bodyData = {\nfields: {\n project: {\n key: feedbackSpaceKey,\n },\n summary: summary,\n description: buildAdf(details ? details : \"(no details provided)\"),\n issuetype: {\n id: feedbackWorkItemTypeId,\n },\n},\n}\nconsole.log(`feedback.buildCreateIssueBodyData: Issue creation bodyData: ${JSON.stringify(bodyData, null, 2)}`);\nreturn bodyData;\n}\n```\n\nExample:\n```javascript\nconst createFeedbackWorkItem = async (request) => {\n let feedbackCreationResult = {status: 500, message: ''};\n try {\n const requestBodyText = request.body;\n const requestBody = requestBodyText ? JSON.parse(requestBodyText) : {};\n const summary = requestBody.summary;\n const details = requestBody.details;\n const ratingString = requestBody.rating;\n const rating = ratingString ? parseInt(ratingString) : -1;\n console.log(`summary: \"${summary}\"`);\n console.log(`details: \"${details}\"`);\n console.log(`rating: ${rating}`);\n if (summary) {\n const bodyData = buildCreateIssueBodyData(summary, details, rating);\n // We invoke the API using the `asApp`. This means the request is made on behalf of the\n // app rather than a particular user.\n const response = await api.asApp().requestJira(route`/rest/api/3/issue`, {\n method: 'POST',\n body: JSON.stringify(bodyData),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (response.ok) {\n feedbackCreationResult = {status: response.status, message: 'Feedback created successfully'};\n } else {\n const errorText = await response.text();\n feedbackCreationResult = {status: response.status, message: `Failed to create feedback: ${errorText}`};\n }\n } else {\n feedbackCreationResult = {status: 400, message: 'Summary is required for feedback submission.'};\n }\n } catch (error) {\n feedbackCreationResult = {status: 400, message: `Error handling feedback page request: ${error.message}`};\n }\n return feedbackCreationResult;\n }\n```\n\nExample:\n```bash\nnpm install @forge/api --save\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n```\n\nExample:\n```javascript\nconst buildOutput = (feedbackCreationResult, rnd) => ({\nbody: `{\"message\": \"${feedbackCreationResult.message}\"}`,\nheaders: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n},\nstatusCode: feedbackCreationResult.status,\nstatusText: feedbackCreationResult.message\n});\n```\n\nExample:\n```javascript\nconst feedbackCreationResult = await createFeedbackWorkItem(request);\nconst result = buildOutput(feedbackCreationResult, Math.random());\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\nconst feedbackSpaceKey = 'FEED'; // Make sure you use your space key here\nconst feedbackSpaceId = '10049'; // Make sure you use your identifier value here\nconst feedbackWorkItemTypeId = '10114'; // Make sure you use your identifier value here\n/**\n * @param {import('@forge/api').WebTriggerRequest} request\n * @param {import('@forge/api').WebTriggerContext} context\n * @returns {Promise<import('@forge/api').WebTriggerResponse>}\n */\nexport const onCreateFeedback = async (request, context) => {\n console.log('Received request:', JSON.stringify(request, null, 2));\n console.log('Context:', JSON.stringify(context, null, 2));\n const authorizationHeader = request.headers['authorization'];\n if (authorizationHeader && authorizationHeader[0] === 'Basic SECRET_KEY') {\n if (request.method !== 'POST') {\n return buildMethodNotAllowedOutput(Math.random());\n }\n const feedbackCreationResult = await createFeedbackWorkItem(request);\n const result = buildOutput(feedbackCreationResult, Math.random());\n return result;\n } else {\n console.warn('Unauthorized access attempt with header:', authorizationHeader);\n return buildUnauthorzesOutput(Math.random());\n }\n};\nconst createFeedbackWorkItem = async (request) => {\n let feedbackCreationResult = {status: 500, message: ''};\n try {\n const requestBodyText = request.body;\n const requestBody = requestBodyText ? JSON.parse(requestBodyText) : {};\n const summary = requestBody.summary;\n const details = requestBody.details;\n const ratingString = requestBody.rating;\n const rating = ratingString ? parseInt(ratingString) : -1;\n console.log(`summary: \"${summary}\"`);\n console.log(`details: \"${details}\"`);\n console.log(`rating: ${rating}`);\n if (summary) {\n const bodyData = buildCreateIssueBodyData(summary, details, rating);\n // We invoke the API using the `asApp`. This means the request is made on behalf of the\n // app rather than a particular user.\n const response = await api.asApp().requestJira(route`/rest/api/3/issue`, {\n method: 'POST',\n body: JSON.stringify(bodyData),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (response.ok) {\n feedbackCreationResult = {status: response.status, message: 'Feedback created successfully'};\n } else {\n const errorText = await response.text();\n feedbackCreationResult = {status: response.status, message: `Failed to create feedback: ${errorText}`};\n }\n } else {\n feedbackCreationResult = {status: 400, message: 'Summary is required for feedback submission.'};\n }\n } catch (error) {\n feedbackCreationResult = {status: 400, message: `Error handling feedback page request: ${error.message}`};\n }\n return feedbackCreationResult;\n}\nconst buildCreateIssueBodyData = (summary, details, rating) => {\n // Note: the rating parameter is currently being ignored\n const bodyData = {\n fields: {\n project: {\n key: feedbackSpaceKey,\n },\n summary: summary,\n description: buildAdf(details ? details : \"(no details provided)\"),\n // [ratingCustomFieldId]: rating ? rating : -1,\n issuetype: {\n id: feedbackWorkItemTypeId,\n },\n },\n }\n console.log(`feedback.buildCreateIssueBodyData: Issue creation bodyData: ${JSON.stringify(bodyData, null, 2)}`);\n return bodyData;\n}\nconst buildAdf = (paragraphText) => {\n const adf = {\n content: [\n {\n content: [\n {\n type: \"text\",\n text: paragraphText,\n }\n ],\n type: \"paragraph\"\n }\n ],\n type: \"doc\",\n version: 1\n };\n return adf;\n}\n/**\n * @returns {import('@forge/api').WebTriggerResponse}\n */\nconst buildOutput = (feedbackCreationResult, rnd) => ({\n body: `{\"message\": \"${feedbackCreationResult.message}\"}`,\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: feedbackCreationResult.status,\n statusText: feedbackCreationResult.message\n});\nconst buildUnauthorzesOutput = (rnd) => ({\n body: '{\"error\": \"Unauthorized\"}',\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: 401,\n statusText: 'Unauthorized'\n});\nconst buildMethodNotAllowedOutput = (rnd) => ({\n body: '{\"error\": \"Method Not Allowed\"}',\n headers: {\n 'Content-Type': ['application/json'],\n 'X-Request-Id': [`rnd-${rnd}`]\n },\n statusCode: 405,\n statusText: 'Method Not Allowed'\n});\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```bash\n{\"message\": \"Summary is required for feedback submission.\"}\n```\n\nExample:\n```bash\ncurl -X POST --header \"Authorization: Basic SECRET_KEY\" -d '{\"summary\":\"My feedback\"}' https://your-webtrigger-url\n```\n\nExample:\n```bash\n{\"message\": \"Feedback created successfully\"}\n```\n\nExample:\n```javascript\nexport const onIssueMutationEvent = async (event, context) => {\n console.log('Received event:', JSON.stringify(event, null, 2));\n console.log('Context:', JSON.stringify(context, null, 2));\n}\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: issue-mutation-trigger\n function: issue-mutation-fn\n events:\n - avi:jira:created:issue\n - avi:jira:updated:issue\n - avi:jira:deleted:issue\n```\n\nExample:\n```text\nfunction:\n - key: issue-mutation-fn\n handler: eventHandler.onIssueMutationEvent5\n```\n\nExample:\n```text\npermissions:\nscopes:\n - read:jira-work\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: feedbackintegration-webtrigger-async\n function: async\n urlFormat: v2\n response:\n type: dynamic\n trigger:\n - key: issue-mutation-trigger\n function: issue-mutation-fn\n events:\n - avi:jira:created:issue\n - avi:jira:updated:issue\n - avi:jira:deleted:issue\n function:\n - key: async\n handler: index.onCreateFeedback\n - key: issue-mutation-fn\n handler: eventHandler.onIssueMutationEvent\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/your-app-id\npermissions:\n scopes:\n - read:jira-work\n - write:jira-work\n```\n\nExample:\n```javascript\nconst webApplicationEventHandlerUrl = ''; // Replace with an endpoint URL in your web application, e.g. example.com/onWorkItemMutation\n```\n\nExample:\n```bash\nforge variables set --encrypt WEB_APPLICATION_SECRET_TOKEN {your-value-here}\n```\n\nExample:\n```javascript\nconst webApplicationEventHandlerUrl = ''; // Replace with an endpoint URL in your web application, e.g. example.com/onWorkItemMutation\nexport const onIssueMutationEvent = async (event, context) => {\nconsole.log('Received issue event:', JSON.stringify(event, null, 2));\nconsole.log('Context:', JSON.stringify(context, null, 2));\nconst issueData = event.issue;\nconst eventType = event.eventType;\nif (webApplicationEventHandlerUrl) {\n // Set an environment variable named \"WEB_APPLICATION_SECRET_TOKEN\" with the secret token value that your web application expects. This\n // is just an example as your web application could use a different kind of authentication scheme. See the Forge environment variable\n // documentation for details about setting environment variables, https://developer.atlassian.com/platform/forge/environments-and-versions/.\n const myWebApplicationSecretToken = process.env.WEB_APPLICATION_SECRET_TOKEN;\n const changeType = eventType === 'avi:jira:created:issue' ? 'created' :\n eventType === 'avi:jira:updated:issue' ? 'updated' :\n eventType === 'avi:jira:deleted:issue' ? 'deleted' : 'unknown';\n const issueKey = issueData ? issueData.key : 'unknown';\n console.log(`Invoking web application event handler for issue ${issueKey} that was ${changeType}.`);\n const bodyData = {\n changeType: changeType,\n workItem: issueData\n }\n const webApplicationResponse = await fetch(webApplicationEventHandlerUrl, {\n method: 'POST',\n body: JSON.stringify(bodyData),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${myWebApplicationSecretToken}` // Replace with your web application secret token\n },\n });\n if (Response.ok) {\n // TODO\n } else {\n const errorText = await webApplicationResponse.text();\n console.error(`Failed to invoke web application event handler: ${errorText}`);\n }\n} else {\n console.warn('No web application endpoint configured to handle issue events.');\n}\n}\n```\n\nExample:\n```bash\nforge logs\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.369Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":40,"totalLines":613,"estimatedTokens":4091}}140{"id":"doc-how_to_use_custom_ui_with_the_forge_module_comma-dd6724f6","source":"documentation","title":"How to use Custom UI with the Forge module command","url":"https://developer.atlassian.com/platform/forge/how-to-use-custom-ui-with-the-forge-module-command/","text":"Example:\n```text\nmodules:\n jira:issuePanel:\n - key: jira-issue-panel\n resource: main-custom-ui/entry\n resolver:\n function: resolver\n viewportSize: medium\n title: Forge app for Mia\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: my-function\n handler: index.run\n - key: resolver\n handler: jira-issue-panel.handler\nresources:\n - key: main-custom-ui\n path: static/main-custom-ui/build\n entry:\n entry: jira-issue-panel.html\npermissions:\n scopes:\n - read:jira-work\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd static/main-custom-ui\nnpm run build\ncd ../..\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.370Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":46,"estimatedTokens":201}}141{"id":"doc-check_whether_jira_issues_are_assigned_using_a_w-315a982a","source":"documentation","title":"Check whether Jira issues are assigned using a workflow validator","url":"https://developer.atlassian.com/platform/forge/check-jira-issues-assigned-using-workflow-validator/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\njira:workflowValidator:\n - key: issue-is-assigned-validator\n name: Issue is assigned validator\n description: Validates that the issue has an assignee before transitioning.\n function: validator\n```\n\nExample:\n```text\nmodules:\n jira:workflowValidator:\n - key: issue-is-assigned-validator\n name: Issue is assigned validator\n description: Validates that the issue has an assignee before transitioning.\n function: validator\n function:\n - key: validator\n handler: index.run\n```\n\nExample:\n```javascript\nexport const run = async ({ issue }) => {\n // Function code\n};\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n```\n\nExample:\n```javascript\nconst { key: issueKey } = issue;\nconst response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueKey}`);\nconst issueJson = await response.json();\n```\n\nExample:\n```javascript\nreturn {\n result: !!issueJson.fields.assignee,\n errorMessage: \"The issue must have an assignee before transitioning.\"\n};\n```\n\nExample:\n```javascript\nimport api, { route } from \"@forge/api\";\n\nexport const run = async ({ issue }) => {\n const { key: issueKey } = issue;\n const response = await api.asApp().requestJira(route`/rest/api/3/issue/${issueKey}`);\n const issueJson = await response.json();\n\n return {\n result: !!issueJson.fields.assignee,\n errorMessage: \"The issue must have an assignee before transitioning.\"\n };\n};\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.371Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":92,"estimatedTokens":407}}142{"id":"doc-build_a_rovo_agent_hello_world_app-79fee9ee","source":"documentation","title":"Build a Rovo Agent hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-rovo-agent/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-rovo-agent\n```\n\nExample:\n```text\n├── manifest.yml\n├── package.json\n└── src\n └── index.js\n```\n\nExample:\n```text\nmodules:\n rovo:agent:\n - key: hello-world-agent\n name: Hello world agent\n description: An Agent for testing Forge agent functionality\n prompt: >\n You are a simple Rovo Agent that helps Forge developers build their first\n Agent.\n\n You can create Forge logs by using the hello-world-logger action.\n\n If the user hasn't provided a message when they ask you to create a log ask them to provide one.\n conversationStarters:\n - Log a message to Forge logs\n actions:\n - hello-world-logger\n action:\n - key: hello-world-logger\n function: messageLogger\n actionVerb: TRIGGER\n description: >\n When a user asks to log a message, this action logs the message to the\n Forge logs.\n inputs:\n message:\n title: Message\n type: string\n required: true\n description: |\n \"The message that the user has requested be logged to Forge logs\"\n function:\n - key: messageLogger\n handler: index.messageLogger\napp:\n runtime:\n name: nodejs24.x\n id: <your app id>\n```\n\nExample:\n```javascript\nexport function messageLogger(payload) {\n console.log(`Logging message: ${payload.message}`);\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge logs\n```\n\nExample:\n```javascript\nexport function messageLogger (payload) {\n console.log(`Message: ${payload.message}`);\n return `Successfully logged payload: ${payload.message}`\n}\n```\n\nExample:\n```javascript\nconsole.log(`Payload: ${JSON.stringify(payload)}`);\n```\n\nExample:\n```json\n{\n \"context\": {\n \"confluence\": {\n \"url\": \"https://mysite.atlassian.com/wiki/spaces/~61df1116125b12007152148f/pages/10092545/Mypage\",\n \"resourceType\": \"page\",\n \"contentId\": \"10092545\",\n \"spaceKey\": \"~61df1116125b12007152148f\",\n \"spaceId\": \"33248\"\n },\n \"cloudId\": \"13c6457e-69c5-4ad4-880a-dbdd77ef39f2\",\n \"moduleKey\": \"hello-world-logger\"\n }\n}\n```\n\nExample:\n```javascript\nexport function messageLogger(payload) {\n console.log(`Message: ${payload.message}`);\n\n const message = `The user is on a Confluence ${payload.context?.confluence?.resourceType}`;\n console.log(message);\n return message;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.371Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":129,"estimatedTokens":619}}143{"id":"doc-build_a_jira_app_with_the_forge_module_command-d3fc7d51","source":"documentation","title":"Build a Jira app with the Forge module command","url":"https://developer.atlassian.com/platform/forge/build-a-jira-app-with-the-module-command/","text":"Example:\n```text\nmodules:\n function:\n - key: my-function\n handler: index.run\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge module list\n```\n\nExample:\n```bash\nforge module list --product jira\n```\n\nExample:\n```bash\nforge module show jira:issuePanel\n```\n\nExample:\n```bash\nforge module add\n```\n\nExample:\n```bash\nforge module add --product jira --module-type jira:issuePanel --ui-type ui-kit\n```\n\nExample:\n```bash\nforge module add --product jira --module-type jira:issuePanel --ui-type ui-kit --dry-run\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: jira-issue-panel\n resource: main-ui-kit/entry\n resolver:\n function: resolver\n render: native\n title: Forge app for Mia\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: my-function\n handler: index.run\n - key: resolver\n handler: jira-issue-panel.handler\nresources:\n - key: main-ui-kit\n path: src/frontend/main-ui-kit\n entry:\n entry: jira-issue-panel.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.374Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":90,"estimatedTokens":334}}144{"id":"doc-build_a_rovo_mcp_hello_world_app-2d32559c","source":"documentation","title":"Build a Rovo MCP hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-rovo-mcp/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-rovo-mcp\n```\n\nExample:\n```bash\nforge module add\n```\n\nExample:\n```text\n├── manifest.yml\n├── package.json\n└── src\n ├── hello-world-mcp.js\n └── index.js\n```\n\nExample:\n```text\nmodules:\n rovo:mcp:\n - key: hello-world-mcp\n name: Hello world MCP\n tools:\n - hello-world-mcp-logger\n action:\n - key: hello-world-mcp-logger\n name: Log a message\n function: resolver\n actionVerb: GET\n description: >\n When a user asks to log a message, this action logs the message to the\n Forge logs.\n inputs:\n message:\n title: Message\n type: string\n required: true\n description: The message that the user has requested be logged to Forge logs\n function:\n - key: resolver\n handler: hello-world-mcp.messageLogger\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: <your app id>\n```\n\nExample:\n```javascript\nexport function messageLogger(payload) {\n console.log(`Logging message: ${payload.message}`);\n return `Logged message: ${payload.message}`;\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nexport function messageLogger(payload) {\n console.log(`Logging message: ${payload.message}`);\n console.log(`Payload: ${JSON.stringify(payload)}`);\n return `Logged message: ${payload.message}`;\n}\n```\n\nExample:\n```json\n{\n \"context\": {\n \"confluence\": {\n \"url\": \"https://mysite.atlassian.com/wiki/spaces/~61df1116125b12007152148f/pages/10092545/Mypage\",\n \"resourceType\": \"page\",\n \"contentId\": \"10092545\",\n \"spaceKey\": \"~61df1116125b12007152148f\",\n \"spaceId\": \"33248\"\n },\n \"cloudId\": \"13c6457e-69c5-4ad4-880a-dbdd77ef39f2\",\n \"moduleKey\": \"hello-world-mcp-logger\"\n }\n}\n```\n\nExample:\n```javascript\nexport function messageLogger(payload) {\n console.log(`Logging message: ${payload.message}`);\n console.log(`Payload: ${JSON.stringify(payload)}`);\n\n const message = `The user is on a Confluence ${payload.context?.confluence?.resourceType}`;\n console.log(message);\n return message;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.375Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":120,"estimatedTokens":552}}145{"id":"doc-build_a_jira_service_management_hello_world_app-742ee79a","source":"documentation","title":"Build a Jira Service Management hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-jira-service-management/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:queuePage:\n - key: hello-world-app-hello-world-queue-page\n resource: main\n resolver:\n function: resolver\n render: native\n title: hello-world-app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.376Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":83,"estimatedTokens":299}}146{"id":"doc-build_a_q_a_agent_for_confluence-34573e29","source":"documentation","title":"Build a Q&A Agent for Confluence","url":"https://developer.atlassian.com/platform/forge/build-a-q-and-a-rovo-agent-for-confluence","text":"Example:\n```text\n├── src\n └── backend\n └── action.js\n └── confluenceUtil.js\n └── qandaUtil.js\n └── resolver.js\n └── frontend\n └── macro.jsx\n └── index.js\n├── manifest.yml\n├── package.json\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: qanda-macro\n title: Q&A Quiz\n ...\n rovo:agent:\n - key: q-and-a\n ...\n actions:\n - fetch-content\n - register-q-and-a\n - insert-q-and-a\n action:\n - key: fetch-content\n ...\n - key: register-q-and-a\n ...\n - key: insert-q-and-a\n ...\n function:\n - key: fetchContent\n handler: index.fetchContent\n - key: registerQandA\n handler: index.registerQandA\n - key: insertQandAMacro\n handler: index.insertQandAMacro\n - key: macro-resolver\n handler: index.macroHandler\n ...\napp:\n runtime:\n name: nodejs24.x\n id: ari:cloud:ecosystem::app/6452e1d8-c457-446a-82a7-d186ef150f15\npermissions:\n scopes:\n - read:content.metadata:confluence\n - read:page:confluence\n - write:page:confluence\nresources:\n - key: q-and-a-macro\n path: src/frontend/macro.jsx\n - key: static-resources\n path: static\n```\n\nExample:\n```text\nPreamble\nWorkflow\nJobs\nTemplates\n```\n\nExample:\n```text\n4. DEBUG: Inform the user of the number of characters in the content.\n5. If the length of the fecthed content is less than 200 characters, return a failure message to the user and abort further processing.\n6. DEBUG: Provide the user with an explanation of how you fetched the text of the Confluence content.\n7. DEBUG: Return up to the first 500 characters of the text of the Confluence content to the user.\n8. DEBUG: Return the URL of the Confluence content to the user.\n```\n\nExample:\n```text\nSome instructions begin with \"DEBUG: \", indicating its purpose is to help \ndebug the state. Debugging is enabled so perform these instructions.\n```\n\nExample:\n```text\nDebugging is disabled so ignore these instructions.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.377Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":97,"estimatedTokens":503}}147{"id":"doc-build_a_custom_ui_app_in_jira-e272449a","source":"documentation","title":"Build a Custom UI app in Jira","url":"https://developer.atlassian.com/platform/forge/build-a-custom-ui-app-in-jira/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-custom-ui\n```\n\nExample:\n```bash\nhello-world-custom-ui\n|-- src\n| `-- index.js\n|-- static\n| `-- hello-world\n| `-- src\n| `-- index.js\n| `-- App.js\n| `-- public\n| `-- index.html\n| `-- package.json\n| `-- package-lock.json\n|-- manifest.yml\n|-- package.json\n|-- package-lock.json\n`-- README.md\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n resource: main\n resolver:\n function: resolver\n viewportSize: medium\n title: Hello World from Emma Richards\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: static/hello-world/build\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nnpm install\n```\n\nExample:\n```bash\nnpm run build\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport { invoke } from '@forge/bridge';\n\nfunction App() {\n const [data, setData] = useState(null);\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n return (\n <div>\n {data ? data : 'Loading...'}\n </div>\n );\n}\n\nexport default App;\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', (req) => {\n console.log(req);\n\n return 'Hello, world!';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.378Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":120,"estimatedTokens":426}}148{"id":"doc-import_third_party_data_into_assets-75575df9","source":"documentation","title":"Import third party data into Assets","url":"https://developer.atlassian.com/platform/forge/assets-import-app/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\njiraServiceManagement:assetsImportType:\n - key: '<your-module-key>'\n description: '<your-app-description>'\n title: '<your-import-type-name>'\n icon: '<your-icon-url>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```text\nHello World!, ImportId = {your-import-id}, WorkspaceId = {your-workspace-id}\n```\n\nExample:\n```javascript\nconst onDeleteImport = async (context) => {\n console.log(\"import with id \", context.importId + \" got deleted\");\n\n return {\n result: \"on delete import\",\n };\n};\n```\n\nExample:\n```javascript\nconst startImport = async (context) => {\n console.log(\"import with id \", context.importId + \" got started\");\n\n return {\n result: \"start import\",\n };\n};\n```\n\nExample:\n```javascript\nconst stopImport = async (context) => {\n console.log(\"import with id \", context.importId + \" got stopped\");\n\n return {\n result: \"stop import\",\n };\n};\n```\n\nExample:\n```javascript\nconst importStatus = async (context) => {\n console.log(\"import with id \", context.importId + \" sending import status\");\n\n return {\n status: \"NOT_CONFIGURED\"\n };\n};\n```\n\nExample:\n```json\n{\n \"links\": {\n \"getStatus\": \"https://api.atlassian.com/jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/configstatus\",\n \"start\": \"https://api.atlassian.com/jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/executions\",\n \"mapping\": \"https://api.atlassian.com/jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/mapping\"\n }\n}\n```\n\nExample:\n```javascript\nconst asUserRequest = await api\n .asUser()\n .requestJira(\n route`/jsm/assets/workspace/${context.workspaceId}/v1/importsource/${context.importId}/executions`,\n {\n method: \"POST\",\n }\n );\n```\n\nExample:\n```javascript\nconst asAppRequest = await api\n .asApp()\n .requestJira(\n route`/jsm/assets/workspace/${context.workspaceId}/v1/importsource/${context.importId}/executions`,\n {\n method: \"POST\",\n }\n );\n```\n\nExample:\n```javascript\nconst importStatus = async (context) => {\n console.log(\"import with id \", context.importId + \" sending import status\");\n\n return {\n status: \"READY\"\n };\n};\n```\n\nExample:\n```javascript\n// Call Assets API here to mark import as started\n// Push event onto controller queue to start data ingestion process\nconst id = await controllerQueue.push({ body: {eventContext: {importConfigurationId: context.importId}} });\nconsole.log(`Pushed queueControllerEvent with id ${id}`);\n```\n\nExample:\n```javascript\nconst handleControllerEvent = async (eventContext) => {\n // Push initial work item to worker queue here \n // e.g. await workerQueue.push({ body: { eventContext: workItem } });\n\n // Once the initial work item is pushed to the worker queue,\n // keep pushing events to the controller queue with a delay until the work items are all complete\n // e.g. await controllerQueue.push({ body: { eventContext: workItem } });\n\n // Once work items are all complete call the Assets API to signal the completion of data submission\n};\n```\n\nExample:\n```javascript\nconst handleWork = async (eventContext) => {\n // Fetch data from external system here\n // Update work items according to how much data is left to be fetched\n // And push to worker queue again if there is more data to be fetched\n // eg. await workerQueue.push({ body: { eventContext: updatedWorkItem } });\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.379Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":163,"estimatedTokens":898}}149{"id":"doc-build_a_custom_ui_app_in_jira_service_management-4deacb34","source":"documentation","title":"Build a Custom UI app in Jira Service Management","url":"https://developer.atlassian.com/platform/forge/build-a-custom-ui-app-in-jira-service-management/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-custom-ui\n```\n\nExample:\n```bash\nhello-world-custom-ui\n|-- src\n| `-- index.js\n|-- static\n| `-- hello-world\n| `-- src\n| `-- index.js\n| `-- App.js\n| `-- public\n| `-- index.html\n| `-- package.json\n| `-- package-lock.json\n|-- manifest.yml\n|-- package.json\n|-- package-lock.json\n`-- README.md\n```\n\nExample:\n```text\nmodules:\n jiraServiceMangement:queuePage:\n - key: hello-world-panel\n resource: main\n resolver:\n function: resolver\n title: Hello World from Emma Richards\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: static/hello-world/build\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nnpm install\n```\n\nExample:\n```bash\nnpm run build\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport { invoke } from '@forge/bridge';\nfunction App() {\n const [data, setData] = useState(null);\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n return (\n <div>\n {data ? data : 'Loading...'}\n </div>\n );\n} \nexport default App;\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', (req) => {\n console.log(req);\n\n return 'Hello, world!';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.380Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":116,"estimatedTokens":398}}150{"id":"doc-build_a_dashboard_app_with_the_full_page_module_-083fdc6a","source":"documentation","title":"Build a dashboard app with the full page module in Jira","url":"https://developer.atlassian.com/platform/forge/build-a-dashboard-app-with-the-jira-full-page-module","text":"Example:\n```text\nmodules:\n jira:fullPage:\n - key: my-full-page-module\n resource: main\n render: native\n resolver:\n function: resolver\n routePrefix: ui-kit\n title: My full page module # Optional\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: \"<your app id>\"\n runtime:\n name: nodejs24.x\npermissions:\n scopes:\n - read:jira-user\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from \"react\";\nimport ForgeReconciler, { Box, Stack, Heading, Text, BarChart, Button, Inline, Image, xcss } from \"@forge/react\";\nimport { requestJira } from \"@forge/bridge\";\n\nconst chartContainerStyle = xcss({\n maxWidth: \"600px\",\n});\n\nconst App = () => {\n const [selectedPeriod, setSelectedPeriod] = useState(\"Q1\");\n const [userInfo, setUserInfo] = useState(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n // Dashboard usage data for different periods\n const q1Data = [\n { month: 'Jan', usage: 45 },\n { month: 'Feb', usage: 52 },\n { month: 'Mar', usage: 48 },\n ];\n\n const q2Data = [\n { month: 'Apr', usage: 61 },\n { month: 'May', usage: 55 },\n { month: 'Jun', usage: 67 },\n ];\n\n const chartData = selectedPeriod === \"Q1\" ? q1Data : q2Data;\n\n // Fetch current user information from Jira API\n const fetchUserInfo = async () => {\n setLoading(true);\n setError(null);\n try {\n const response = await requestJira(`/rest/api/3/users/search`, {\n headers: {\n 'Accept': 'application/json'\n }\n });\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`API error: ${response.status} - ${errorText}`);\n }\n const data = await response.json();\n setUserInfo(data[0]);\n } catch (err) {\n setError(`Failed to fetch user info: ${err.message}`);\n console.error(err);\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n fetchUserInfo();\n }, []);\n\n return (\n <Box padding=\"space.400\">\n <Stack space=\"space.400\">\n <Stack space=\"space.200\">\n <Heading size=\"xlarge\">Dashboard</Heading>\n <Text>Monthly usage overview</Text>\n </Stack>\n\n <Inline space=\"space.200\">\n <Button \n appearance={selectedPeriod === \"Q1\" ? \"primary\" : \"default\"}\n onClick={() => setSelectedPeriod(\"Q1\")}\n >\n Q1\n </Button>\n <Button \n appearance={selectedPeriod === \"Q2\" ? \"primary\" : \"default\"}\n onClick={() => setSelectedPeriod(\"Q2\")}\n >\n Q2\n </Button>\n </Inline>\n\n <Stack space=\"space.300\">\n <Box xcss={chartContainerStyle}>\n <BarChart\n data={chartData}\n xAccessor=\"month\"\n yAccessor=\"usage\"\n />\n </Box>\n\n <Box padding=\"space.200\">\n <Stack space=\"space.100\">\n <Text>Current period: {selectedPeriod}</Text>\n <Text>Total usage: {chartData.reduce((sum, item) => sum + item.usage, 0)}</Text>\n </Stack>\n </Box>\n </Stack>\n\n <Box padding=\"space.400\">\n <Stack space=\"space.300\">\n <Heading size=\"medium\">Current User Information</Heading>\n {loading && <Text>Loading user info...</Text>}\n {error && <Text>{error}</Text>}\n {!loading && !error && userInfo && (\n <Box padding=\"space.300\" xcss={xcss({\n borderWidth: \"border.width\",\n borderStyle: \"solid\",\n borderColor: \"color.border\",\n borderRadius: \"border.radius.300\",\n })}>\n <Stack space=\"space.200\">\n <Text>\n <Text weight=\"bold\">Display name for first user:</Text> \n {userInfo.displayName}\n </Text>\n </Stack>\n </Box>\n )}\n </Stack>\n </Box>\n </Stack>\n </Box>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\npermissions:\n scopes:\n - read:jira-user\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\nhttps://<your-site>.atlassian.net/forge-apps/a/<app-id>/e/<forge-environment-id>/r/<route-prefix>/<app-route>\n```\n\nExample:\n```text\nhttps://example.atlassian.net/forge-apps/a/21e590df-79e6-40dd-9ee4-ba2c7b678f26/e/9f699e8b-33f1-4fa7-bd48-c5fdc44fa4c2/r/ui-kit\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.381Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":193,"estimatedTokens":1167}}151{"id":"doc-use_async_events_api_to_queue_jobs_to_import_obj-13ccb057","source":"documentation","title":"Use Async Events API to queue jobs to import objects into Assets","url":"https://developer.atlassian.com/platform/forge/queue-events-with-async-events-api-to-import-assets/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnpm install @forge/react@latest @forge/events@latest @forge/resolver@latest @forge/bridge@latest @forge/kvs@latest --save\n```\n\nExample:\n```text\npermissions:\n scopes:\n - import:import-configuration:cmdb\n - storage:app # scope required to use Forge Storage\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:assetsImportType:\n ...\n\n consumer:\n - key: submit-data-chunk-queue-consumer\n queue: submit-data-chunk-queue\n function: processImportQueue\n - key: import-completed-queue-consumer\n queue: import-completed-queue\n function: importCompletedHandler\n\n function:\n - key: processImportQueue\n handler: index.processQueue\n timeoutSeconds: 900\n - key: importCompletedHandler\n handler: index.processQueue\n timeoutSeconds: 900\n ...\n```\n\nExample:\n```text\npermissions:\n scopes:\n ...\n\n external:\n fetch:\n backend:\n - \"example.com\" # replace this URL with that of your third-party service\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:assetsImportType:\n ...\n\n webtrigger:\n - key: scheduled-import-trigger\n function: scheduledImportHandler\n urlFormat: v2\n\n function:\n - key: scheduledImportHandler\n handler: index.handleScheduledImport\n timeoutSeconds: 900\n - key: processImportQueue\n handler: index.processQueue\n timeoutSeconds: 900\n - key: importCompletedHandler\n handler: index.processQueue\n timeoutSeconds: 900\n ...\n```\n\nExample:\n```javascript\nimport { requestJira } from '@forge/bridge';\nimport { useForm, Form, Button, FormSection, FormFooter } from '@forge/react';\nimport { FullContext } from '@forge/bridge/out/types';\n ...\nconst App = () => {\n ...\n const [context, setContext] = useState<FullContext | undefined>(undefined);\n useEffect(() => {\n if (!context) {\n view.getContext().then(setContext);\n }\n }, [context]);\n\n const { handleSubmit } = useForm()\n const onSubmit = async () => {\n const {\n extension: { workspaceId, importId },\n } = context;\n await requestJira(`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/mapping`,\n {\n method: \"PUT\",\n body: JSON.stringify(/* TODO provide your mapping here */),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n // should capture error, throw Error and handle error state here if required.\n };\n\n return (\n <Form onSubmit={handleSubmit(onSubmit)}>\n <FormSection>\n ...\n </FormSection>\n <FormFooter>\n <Button appearance=\"primary\" type=\"submit\">\n Save configuration\n </Button>\n </FormFooter>\n </Form>\n );\n};\n```\n\nExample:\n```javascript\nexport const importQueue = new Queue({ key: \"submit-data-chunk-queue\" });\nexport const importCompletedQueue = new Queue({\n key: \"import-completed-queue\",\n});\n```\n\nExample:\n```javascript\n/**\n * Extracts the execution ID from the submitResults URL\n * The URL format is: /jsm/assets/workspace/{workspaceId}/v1/importsource/{importId}/executions/{executionId}/data\n */\nconst extractExecutionId = (submitResultsUrl) => {\n const urlParts = submitResultsUrl.split('/');\n // The execution ID is located before '/data' in the URL path\n return urlParts[urlParts.length - 2];\n};\n```\n\nExample:\n```javascript\nconst getImportQueueJobsStorageKey = (importId) =>\n \"import-\" + importId + \"_import-queue_jobs\";\n\nconst startImport = async (context) => {\n // create a new execution\n const newlyCreatedExecution = await api\n .asUser()\n .requestJira(\n route`/jsm/assets/workspace/${context.workspaceId}/v1/importsource/${context.importId}/executions`,\n {\n method: \"POST\",\n }\n );\n\n const newlyCreatedExecutionJson = await newlyCreatedExecution.json();\n\n // the response is a set of links which contain a UUID that represents the execution\n // hence, we need to extract it\n const executionId = extractExecutionId(\n newlyCreatedExecutionJson.links.submitResults\n );\n\n // Set up our queue jobs tracker\n await kvs.set(getImportQueueJobsStorageKey(importId, 0), []);\n\n // Push a single event with JSON payload\n const jobId = await importQueue.push({ body: {\n workspaceId,\n importId,\n executionId,\n start: 0,\n end: apiPageLimit,\n } });\n\n return {\n result: \"start import\",\n };\n};\n```\n\nExample:\n```javascript\nconst getImportQueueJobsStorageKey = (importId) =>\n \"import-\" + importId + \"_import-queue_jobs\";\n\nresolver.define(\n \"submit-data-chunk-queue-listener\",\n async ({ payload, context }) => {\n const { workspaceId, importId, executionId, start, end } = payload;\n\n // keep track of all the jobs we have queued so far so we can check if they have finished successfully later\n // when we need to mark the execution as completed\n const jobs =\n (await kvs.get(getImportQueueJobsStorageKey(importId))) || [];\n const newJobs = jobs.concat(context.jobId);\n await kvs.set(getImportQueueJobsStorageKey(importId), newJobs);\n\n // fetch paged response for objects to import from 3rd party source\n const options = {\n method: \"GET\",\n headers: { \"Content-Type\": \"application/json\" },\n };\n const get3rdPartyDataResponse = await api.fetch(\n \"https://example.com/GET\", // TODO replace this with your 3rd party API\n options\n );\n\n // calculate the payload for the next job to be queued\n const nextJobPayload = {\n workspaceId,\n importId,\n executionId,\n start: end + 1,\n // TODO insert number of objects per paginated request\n end: end + 100,\n };\n\n if (get3rdPartyDataResponse.hasNext) {\n // trigger this event listener again\n importQueue.push({ body: nextJobPayload });\n } else {\n // all jobs have finished and mark import as done\n // the details of this queue will be answered in Step 5\n importCompletedQueue.push({ body: { workspaceId, importId, executionId } });\n }\n\n const transformedData = transform3rdPartyData(get3rdPartyDataResponse.data); /* TODO transform3rdPartyData will need to be implemented by you */\n\n // send data chunk to Assets\n await api\n .asApp()\n .requestJira(\n route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/executions/${executionId}/data`,\n {\n method: \"POST\",\n body: JSON.stringify({\n data: { /* TODO insert your transformed third-party data here */ },\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n }\n);\n```\n\nExample:\n```javascript\nresolver.define(\n \"import-completed-queue-listener\",\n async ({ payload, context }) => {\n const { workspaceId, importId, executionId } = payload;\n\n // Check if all jobs have finished successfully\n const jobs = await kvs.get(getImportQueueJobsStorageKey(importId));\n let allJobsSucceed = true;\n\n for (i = 0; i < jobs.length; i++) {\n const jobId = jobs[i];\n const jobProgress = importQueue.getJob(jobId);\n const { success, inProgress, failed } = await jobProgress.getStats();\n\n // If a job to submit data to Assets is still in progress, we continue to wait\n // by pushing a new event to the same queue\n if (inProgress) {\n allJobsSucceed = false;\n // we can add how long to wait until this event is picked up by the resolver\n importCompletedQueue.push({ body: { workspaceId, importId, executionId }, delayInSeconds: /* TODO insert the amount of time to wait*/ });\n\n break;\n }\n\n if (failed) {\n // TODO we can handle the case that a data submission for some reason failed here...\n }\n }\n\n if (allJobsSucceed) {\n console.log(\"All jobs have succeeded, marking import as COMPLETED...\");\n // If all jobs have finished, mark data chunk submission as completed\n const submitResponse = await api\n .asApp()\n .requestJira(\n route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/executions/${executionId}/data`,\n {\n method: \"POST\",\n body: JSON.stringify({\n completed: true\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n }\n }\n);\n```\n\nExample:\n```javascript\nexport const handleScheduledImport = async (event, context) => {\n console.log(\"Scheduled import triggered via webtrigger\");\n \n // Extract the payload from the webtrigger request\n const { workspaceId, importsourceId } = event.body;\n \n let executionId = null;\n \n try {\n // Create a new execution for the scheduled import\n const newlyCreatedExecution = await api\n .asUser()\n .requestJira(\n route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importsourceId}/executions`,\n {\n method: \"POST\",\n body: JSON.stringify({\n scheduled: true // IMPORTANT: This flag is required for scheduled imports\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n\n const newlyCreatedExecutionJson = await newlyCreatedExecution.json();\n\n // Extract the execution ID from the response\n executionId = extractExecutionId(\n newlyCreatedExecutionJson.links.submitResults\n );\n\n // Start the import queue process\n await importQueue.push({ \n body: {\n workspaceId,\n importId: importsourceId,\n executionId,\n start: 0,\n end: apiPageLimit,\n } \n });\n \n console.log(\"Scheduled import started successfully\", { executionId });\n return {\n statusCode: 200,\n body: JSON.stringify({ \n success: true, \n message: \"Import started\",\n executionId \n })\n };\n } catch (error) {\n console.error(\"Scheduled import failed\", error);\n \n // If we have an executionId, report the failure to import history.\n // This check is necessary because the error might occur before execution creation\n // (e.g., during the API call to create the execution). In that case, there's no\n // executionId yet, so we can't report to history and should just return the error.\n if (executionId) {\n try {\n await api\n .asUser()\n .requestJira(\n route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importsourceId}/executions/${executionId}/history/failed`,\n {\n method: \"POST\",\n body: JSON.stringify({\n failureReason: error.message || \"Scheduled import failed to start\"\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n } catch (historyError) {\n console.error(\"Failed to report error to import history\", historyError);\n }\n }\n \n return {\n statusCode: 500,\n body: JSON.stringify({ success: false, error: error.message })\n };\n }\n};\n```\n\nExample:\n```javascript\nconst reportImportFailure = async (workspaceId, importsourceId, executionId, failureReason) => {\n try {\n await api\n .asUser()\n .requestJira(\n route`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importsourceId}/executions/${executionId}/history/failed`,\n {\n method: \"POST\",\n body: JSON.stringify({\n failureReason: failureReason // Max 1024 characters, required\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n console.log(\"Failure reported to import history\");\n } catch (error) {\n console.error(\"Failed to report error to import history\", error);\n }\n};\n```\n\nExample:\n```javascript\nimport { requestJira, invoke } from '@forge/bridge';\nimport { useForm, Form, Button, FormSection, FormFooter, Select } from '@forge/react';\n\nconst App = () => {\n const [context, setContext] = useState<FullContext | undefined>(undefined);\n const [scheduleFrequency, setScheduleFrequency] = useState('DAILY');\n // Set initial schedule time to tomorrow at 9 AM to avoid immediate past date errors\n const [scheduleTime, setScheduleTime] = useState(() => {\n const tomorrow = new Date();\n tomorrow.setDate(tomorrow.getDate() + 1);\n tomorrow.setHours(9, 0, 0, 0);\n return tomorrow.toISOString();\n });\n \n useEffect(() => {\n if (!context) {\n view.getContext().then(setContext);\n }\n }, [context]);\n\n const { handleSubmit } = useForm();\n \n const onSubmit = async () => {\n const {\n extension: { workspaceId, importId },\n } = context;\n \n // Create the mapping first\n await requestJira(`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/mapping`,\n {\n method: \"PUT\",\n body: JSON.stringify(/* your mapping here */),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n };\n \n const createSchedule = async () => {\n const {\n extension: { workspaceId, importId },\n } = context;\n \n // Get the webtrigger URL from your backend resolver\n const webtriggerUrlResponse = await invoke('getWebtriggerUrl');\n const callbackUrl = webtriggerUrlResponse.url;\n \n // Create the schedule by calling the Assets API\n const scheduleResponse = await requestJira(`/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/importschedule`,\n {\n method: \"POST\",\n body: JSON.stringify({\n runFrequency: scheduleFrequency,\n startTime: scheduleTime,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n callbackUrl: callbackUrl\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n \n // Store the importScheduleId for future operations (update/delete)\n const scheduleData = await scheduleResponse.json();\n const importScheduleId = scheduleData.id;\n await invoke('storeScheduleId', { importId, importScheduleId });\n \n // Note: You can also retrieve the importScheduleId dynamically later\n // by calling the GET importsource API, which includes the schedule details\n };\n\n return (\n <Form onSubmit={handleSubmit(onSubmit)}>\n <FormSection>\n {/* Your existing configuration fields */}\n \n {/* Schedule configuration */}\n <Select\n label=\"Schedule Frequency\"\n value={scheduleFrequency}\n onChange={setScheduleFrequency}\n options={[\n { label: \"Once\", value: \"ONCE\" },\n { label: \"Daily\", value: \"DAILY\" },\n { label: \"Weekly\", value: \"WEEKLY\" },\n { label: \"Monthly\", value: \"MONTHLY\" }\n ]}\n />\n </FormSection>\n <FormFooter>\n <Button appearance=\"primary\" type=\"submit\">\n Save configuration\n </Button>\n <Button appearance=\"default\" onClick={createSchedule}>\n Create schedule\n </Button>\n </FormFooter>\n </Form>\n );\n};\n```\n\nExample:\n```javascript\nimport { webTrigger } from '@forge/api';\n\nresolver.define(\"getWebtriggerUrl\", async () => {\n // Generate the webtrigger URL\n const url = webTrigger.getUrl('scheduled-import-trigger');\n \n return { url };\n});\n```\n\nExample:\n```javascript\nconst getSchedule = async (workspaceId, importId, importScheduleId) => {\n const response = await requestJira(\n `/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/importschedule/${importScheduleId}`,\n {\n method: \"GET\",\n headers: {\n Accept: \"application/json\",\n },\n }\n );\n \n return await response.json();\n};\n```\n\nExample:\n```javascript\nconst updateSchedule = async (workspaceId, importId, importScheduleId, scheduleData) => {\n await requestJira(\n `/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/importschedule/${importScheduleId}`,\n {\n method: \"PUT\",\n body: JSON.stringify(scheduleData),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n }\n );\n};\n```\n\nExample:\n```javascript\nconst deleteSchedule = async (workspaceId, importId, importScheduleId) => {\n await requestJira(\n `/jsm/assets/workspace/${workspaceId}/v1/importsource/${importId}/importschedule/${importScheduleId}`,\n {\n method: \"DELETE\",\n headers: {\n Accept: \"application/json\",\n },\n }\n );\n};\n```\n\nExample:\n```text\nforge tunnel\n```\n\nExample:\n```text\nforge deploy\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.383Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":22,"totalLines":616,"estimatedTokens":4216}}152{"id":"doc-read_jira_issues_with_a_rovo_mcp_tool-fe4a589e","source":"documentation","title":"Read Jira issues with a Rovo MCP tool","url":"https://developer.atlassian.com/platform/forge/read-jira-issues-with-a-rovo-mcp-tool/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd rovo-mcp-issue-reader\n```\n\nExample:\n```bash\nforge module add\n```\n\nExample:\n```bash\nnpm install @forge/api\n```\n\nExample:\n```text\nmodules:\n rovo:mcp:\n - key: issue-reader-mcp\n name: Issue reader MCP\n tools:\n - get-issue\n action:\n - key: get-issue\n name: Get a Jira issue\n function: resolver\n actionVerb: GET\n description: >\n Fetches a Jira issue's summary and status by its issue key.\n inputs:\n issueKey:\n title: Issue key\n type: string\n required: true\n description: The Jira issue key, for example PROJ-123.\n function:\n - key: resolver\n handler: issue-reader-mcp.getIssue\npermissions:\n scopes:\n - read:jira-work\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: <your app id>\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\nexport async function getIssue(payload) {\n const { issueKey } = payload;\n\n // asUser() runs the request as the person invoking the tool, so it can only\n // read issues that user is already allowed to see.\n const response = await api\n .asUser()\n .requestJira(route`/rest/api/3/issue/${issueKey}?fields=summary,status`);\n\n if (!response.ok) {\n return `Could not fetch issue ${issueKey} (status ${response.status}).`;\n }\n\n const issue = await response.json();\n const summary = issue.fields.summary;\n const status = issue.fields.status.name;\n\n // The return value passes back to the agent, which relays it to the user.\n return `${issueKey}: \"${summary}\" — status: ${status}`;\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\nThe Jira issue KAN-3: \"Rovo MCP test issue - issue reader tool\" is a Task in the\n\"My Kanban Space\" (KAN) project. Here is a summary of its details:\n\n- Status: To Do\n- Reporter: Chris Ganta\n- Created: 30 July 2026\n- Description: A test case for verifying the \"Get a Jira issue\" tool.\n```\n\nExample:\n```javascript\nconst response = await api\n .asUser()\n .requestJira(route`/rest/api/3/issue/${issueKey}?fields=summary,status,assignee`);\n```\n\nExample:\n```javascript\nconst assignee = issue.fields.assignee?.displayName ?? 'Unassigned';\nreturn `${issueKey}: \"${summary}\" — status: ${status}, assignee: ${assignee}`;\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.384Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":122,"estimatedTokens":598}}153{"id":"doc-forge_manifest-78d13967","source":"documentation","title":"Forge manifest","url":"https://developer.atlassian.com/platform/forge/manifest/","text":"Example:\n```text\napp:\n id: ari:cloud:ecosystem::app/baf12188-3db6-4ec9-aa26-f85cecb62d05\nmodules:\n confluence:contextMenu:\n - key: dictionary\n function: main\n title: Define word\n function:\n - key: main\n handler: index.run\npermissions:\n scopes:\n - read:content-details:confluence\n - read:content.property:confluence\n - write:content.property:confluence\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.385Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":26,"estimatedTokens":108}}154{"id":"doc-app_compatibility-ecc94715","source":"documentation","title":"App compatibility","url":"https://developer.atlassian.com/platform/forge/app-compatibility/","text":"Example:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<app id>'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\napp:\n id: '<app id>'\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n jira:issuePanel:\n - key: hello-world-hello-world-issue-panel\n resource: main\n resolver:\n function: resolver\n render: native\n title: Forge app for Mia\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: <your app id>\n # required and optional Atlassian apps added here:\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.385Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":74,"estimatedTokens":335}}155{"id":"doc-forge_modules-049219d1","source":"documentation","title":"Forge modules","url":"https://developer.atlassian.com/platform/forge/modules/","text":"Example:\n```text\nmodules:\n macro:\n - key: hello-world-macro\n function: hello-world-macro-func\n title: Hello world macro!\n description: Inserts hello world!\n webtrigger:\n - key: webtrigger-sync\n function: my-forge-app-sync-func\n urlFormat: v2\n response:\n type: dynamic\n - key: my-webtrigger-async\n function: my-async-func\n urlFormat: v2\n response:\n type: dynamic\n trigger:\n - key: issue-creation-trigger\n events:\n - avi:jira:created:issue\n - avi:jira:updated:issue\n function: issue-trigger-func\n jira:workflowValidator:\n - key: my-forge-workflow-validator\n name: My example Forge workflow validator\n description: The description of my example Forge workflow validator\n function: my-forge-validator-function\n function:\n - key: my-forge-app-sync-func\n handler: index.runSync\n - key: my-async-func\n handler: index.runAsync\n - key: hello-world-macro-func\n handler: macro.run\n - key: issue-trigger-func\n handler: jira.issueCreationTrigger\n - key: my-forge-validator-function\n handler: index.runValidate\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.386Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":50,"estimatedTokens":300}}156{"id":"doc-getting_started_with_global_ui_eap-e5b916fe","source":"documentation","title":"Getting started with global:ui (EAP)","url":"https://developer.atlassian.com/platform/forge/global-ui/getting-started/","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd my-global-app\n```\n\nExample:\n```text\nmy-global-app/\n├── manifest.yml # App configuration\n├── package.json\n└── src/\n └── frontend/\n └── index.jsx # UI Kit frontend code\n```\n\nExample:\n```text\nmodules:\n global:ui:\n - key: my-app\n resource: main\n render: native\n title:\n default: My App\n resolver:\n function: resolver\napp:\n id: ari:cloud:ecosystem::app/<your-app-id>\n runtime:\n name: nodejs22.x\n compatibility:\n confluence:\n required: true\nresources:\n - key: main\n path: src/frontend\n tunnel:\n port: 3000\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, {\n Global,\n Sidebar,\n LinkMenuItem,\n Main,\n} from \"@forge/react/global\";\nimport { Text } from \"@forge/react\";\n\nconst App = () => {\n return (\n <Global>\n <Sidebar>\n <LinkMenuItem label=\"Dashboard\" href=\"/dashboard\" icon=\"chart-bar\" />\n <LinkMenuItem label=\"Settings\" href=\"/settings\" icon=\"settings\" />\n </Sidebar>\n <Main>\n <Text>Welcome to your global app!</Text>\n </Main>\n </Global>\n );\n};\n\nForgeReconciler.render(<App />);\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\n{site-hostname}/apps/{installation-id}\n```\n\nExample:\n```bash\nforge install list\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.386Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":105,"estimatedTokens":367}}157{"id":"doc-storage-de757390","source":"documentation","title":"Storage","url":"https://developer.atlassian.com/platform/forge/storage/","text":"Example:\n```javascript\nimport { kvs } from '@forge/kvs';\n\n// Store user preferences\nexport async function saveUserPreferences(userId, preferences) {\n await kvs.set(`user:${userId}:preferences`, preferences);\n}\n\n// Retrieve user preferences\nexport async function getUserPreferences(userId) {\n return await kvs.get(`user:${userId}:preferences`);\n}\n\n// Delete user preferences\nexport async function deleteUserPreferences(userId) {\n await kvs.delete(`user:${userId}:preferences`);\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.387Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":27,"estimatedTokens":132}}158{"id":"doc-call_the_teamwork_graph_api-51c25a0d","source":"documentation","title":"Call the Teamwork Graph API","url":"https://developer.atlassian.com/platform/forge/call-the-teamwork-graph-api/","text":"Example:\n```text\npermissions:\n scopes:\n - 'read:graph:jira'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - 'read:graph:confluence'\n```\n\nExample:\n```javascript\nimport api from '@forge/api';\n\nasync function testConnection() {\n const response = await api.asUser().requestTeamworkGraph(`\n query {\n echo(myString: \"Hello World!\")\n }\n `);\n \n const data = await response.json();\n console.log(data); // Should log: { data: { echo: \"Hello World!\" } }\n}\n```\n\nExample:\n```text\nhttps://<your-site>.atlassian.net/gateway/api/graphql/twg\n```\n\nExample:\n```text\nhttps://abc.atlassian.net/gateway/api/graphql/twg\n```\n\nExample:\n```text\nMATCH (user:IdentityUser {ari: $id})-[:user_is_in_team]->(team:IdentityTeam) \nRETURN collect(distinct team) as teams\n```\n\nExample:\n```text\nari:cloud:identity::user/{userId}\n```\n\nExample:\n```text\nari:cloud:identity::user/712020:5fb4febcfacfd60076a1c699\n```\n\nExample:\n```text\nquery companyName_userTeams($cypherQuery: String!, $params: CypherRequestParams) {\n cypherQuery(query: $cypherQuery, params: $params) {\n edges {\n node {\n columns {\n key\n value {\n __typename\n ... on CypherQueryResultListNode {\n nodes {\n id\n data {\n __typename\n ... on AtlassianTeam {\n id\n displayName\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"cypherQuery\": \"MATCH (user:IdentityUser {ari: $id})-[:user_is_in_team]->(team:IdentityTeam) RETURN collect(distinct team) as teams\",\n \"params\": {\n \"id\": \"ari:cloud:identity::user/712020:5fb4febcfacfd60076a1c699\"\n }\n}\n```\n\nExample:\n```javascript\n// Makes an API call to TWG\nexport async function executeCypherGraphQL(graphqlQuery, variables, headers) {\n console.log('executing cypher graphql', graphqlQuery, variables, headers);\n return api.asUser()\n .requestTeamworkGraph(query, variables, null, null, headers)\n .then(async r => {\n console.log(`response status: ${r.status}`);\n const json = await r.json();\n console.log(`response json: ${JSON.stringify(json)}`);\n return json;\n });\n}\n```\n\nExample:\n```javascript\n// The GraphQL Query\nexport const USER_TEAMS_GRAPHQL = `\nquery companyName_userTeams($cypherQuery: String!, $params: CypherRequestParams) {\n cypherQuery(query: $cypherQuery, params: $params) {\n edges {\n node {\n columns {\n key\n value {\n __typename\n ... on CypherQueryResultListNode {\n __typename\n nodes {\n \tid\n data {\n __typename\n ... on Team { \n id\n displayName\n }\n } \n }\n }\n }\n }\n }\n }\n }\n}\n`; \n\n// Preparing the Variables\nexport function getTeamsByUserRequestParams(userAri) {\n return {\n cypherQuery: `\n MATCH (user:IdentityUser {ari: $id})-[:user_is_in_team]->(team:IdentityTeam) \n RETURN collect(distinct team) as teams`,\n params: {id: userAri},\n };\n}\n```\n\nExample:\n```javascript\n// Actual call given a user & context to fetch all the teams\nexport async function fetchTeamsForAUser(userAri, context) {\n if (!userAri || typeof userAri !== 'string') {\n throw new Error('fetchTeamsForAUser: userAri is required');\n }\n const { cypherQuery, params } = getTeamsByUserRequestParams(userAri);\n const json = await executeCypherGraphQL(USER_TEAMS_GRAPHQL_QUERY, { cypherQuery, params}, null);\n return extractCollections(json,['teams']);\n}\n\n// Helper method to Extract Teams from the Json Output\nexport function extractCollections(json, keys) {\n const result = keys.reduce((acc, k) => {\n acc[k] = [];\n return acc;\n }, {});\n try {\n const edges = json?.data?.cypherQuery?.edges || [];\n for (const edge of edges) {\n const columns = edge?.node?.columns || [];\n for (const col of columns) {\n const key = col?.key;\n const value = col?.value;\n if (!key || !value || !(key in result)) continue;\n\n const pushItem = (data) => {\n if (!data) return;\n const item = {};\n if (data.id) item.id = data.id;\n if (data.displayName) item.displayName = data.displayName;\n if (Object.keys(item).length) result[key].push(item);\n };\n\n if (Array.isArray(value.nodes)) {\n for (const n of value.nodes) pushItem(n?.data);\n } else if (value.data) {\n pushItem(value.data);\n }\n }\n }\n } catch (e) {\n }\n return result;\n}\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nforge deploy\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.389Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":221,"estimatedTokens":1281}}159{"id":"doc-migrate_remote_agents_in_jira_from_a2a_0_3_to_1_-a64a1afd","source":"documentation","title":"Migrate remote agents in Jira from A2A 0.3 to 1.0","url":"https://developer.atlassian.com/platform/forge/migrate-remote-agents-in-jira-from-a2a-0.3-to-1.0/","text":"Example:\n```text\nmodules:\n rovo:agentConnector:\n - key: my-agent-connector\n name: My Agent\n productContexts:\n - jira\n protocols:\n agent2Agent:\n version: \"1.0\"\n jsonRpcTransport:\n endpoint: my-endpoint\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"<requestId>\",\n \"method\": \"tasks/get\",\n \"params\": {\n \"taskId\": \"<taskId>\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"<requestId>\",\n \"method\": \"GetTask\",\n \"params\": {\n \"taskId\": \"<taskId>\"\n }\n}\n```\n\nExample:\n```json\n{\n \"kind\": \"text\",\n \"text\": \"Please summarize the latest blocker.\"\n}\n```\n\nExample:\n```json\n{\n \"text\": \"Please summarize the latest blocker.\"\n}\n```\n\nExample:\n```json\n{\n \"kind\": \"data\",\n \"data\": {\n \"cloudId\": \"a436116f-02ce-4520-8fbb-7301462a1674\",\n \"issueId\": \"10001\",\n \"issueKey\": \"MCP-1305\",\n \"commentId\": \"10000\"\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"cloudId\": \"a436116f-02ce-4520-8fbb-7301462a1674\",\n \"issueId\": \"10001\",\n \"issueKey\": \"MCP-1305\",\n \"commentId\": \"10000\"\n }\n}\n```\n\nExample:\n```json\n{\n \"status\": {\n \"state\": \"working\"\n },\n \"message\": {\n \"role\": \"agent\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"Investigating the issue.\"\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\"\n },\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [\n {\n \"text\": \"Investigating the issue.\"\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"error\": {\n \"code\": -32001,\n \"message\": \"Task not found\",\n \"data\": {\n \"taskId\": \"123\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"error\": {\n \"code\": -32001,\n \"message\": \"Task not found\",\n \"data\": [\n {\n \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n \"reason\": \"TASK_NOT_FOUND\",\n \"domain\": \"a2a-protocol.org\",\n \"metadata\": {\n \"taskId\": \"123\"\n }\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": {\n \"state\": \"working\"\n },\n \"message\": {\n \"role\": \"agent\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"Analyzing the issue...\"\n }\n ]\n },\n \"kind\": \"status-update\",\n \"final\": false\n }\n}\n```\n\nExample:\n```json\n{\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\"\n },\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [\n {\n \"text\": \"Analyzing the issue...\"\n }\n ]\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"artifactUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"artifact\": {\n \"artifactId\": \"artifact-1\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"Partial response\"\n }\n ]\n },\n \"append\": true,\n \"lastChunk\": false,\n \"kind\": \"artifact-update\"\n }\n}\n```\n\nExample:\n```json\n{\n \"artifactUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"artifact\": {\n \"artifactId\": \"artifact-1\",\n \"parts\": [\n {\n \"text\": \"Partial response\"\n }\n ]\n },\n \"append\": true,\n \"lastChunk\": false\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.389Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":245,"estimatedTokens":840}}160{"id":"doc-build_an_app_with_teamwork_graph_smart_links-a5146f72","source":"documentation","title":"Build an app with Teamwork Graph Smart Links","url":"https://developer.atlassian.com/platform/forge/build-a-smart-link-app/","text":"Example:\n```text\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd my-first-smartlink-app\n```\n\nExample:\n```text\n├── manifest.yml\n├── package.json\n└── src\n └── index.js\n```\n\nExample:\n```text\nmodules:\n function:\n - key: my-function\n handler: index.run\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: <your app id>\n```\n\nExample:\n```bash\nnpm install typescript --save-dev\n```\n\nExample:\n```json\n{\n \"compilerOptions\": {\n \"target\": \"es2016\", // Specifies the target JavaScript version for compilation\n \"module\": \"commonjs\", // Specifies the module system to use (e.g., commonjs, esnext)\n \"outDir\": \"./dist\", // Specifies the output directory for compiled JavaScript files\n \"strict\": true, // Enables a wide range of type-checking validation rules\n \"esModuleInterop\": true // Enables interoperability between CommonJS and ES Modules\n },\n \"include\": [\n \"src/**/*.ts\" // Specifies files to include in the compilation\n ],\n \"exclude\": [\n \"node_modules\" // Specifies files/directories to exclude from the compilation\n ]\n}\n```\n\nExample:\n```text\nmodules:\n graph:smartLink:\n - key: my-first-smartlink\n icon: https://static.my-first-smartlink.com/favicon.ico\n name: My first Smart Links\n function: getEntityByUrlFn\n domains:\n - my-first-smartlink.com\n - www.my-first-smartlink.com\n subdomains: true\n patterns:\n - >-\n https:\\/\\/([\\w\\.-]+\\.)?my-first-smartlink\\.com\\/([0-9a-zA-Z]{4,128})(?:\\/.*)?$\n\n function:\n - key: getEntityByUrlFn\n handler: index.resolveSmartLinks\n```\n\nExample:\n```typescript\n// SOME HELPER OBJECTS\nexport interface ResolveSmartLinkRequest {\n type: 'resolve';\n payload: {\n urls: string[];\n }\n}\n\nexport interface ResolveSmartLinkResponse {\n entities: ResolveUrlEntityResult[];\n}\n\nexport type Identifier = { url : string}\n\nexport interface ResolveUrlEntityResult {\n identifier: Identifier\n meta: {\n access: string,\n visibility: string\n };\n entity?: any;\n}\n\nexport const buildResolveSmartLinksResponse = (entities: ResolveUrlEntityResult[]): ResolveSmartLinkResponse => {\n return {\n entities: entities,\n };\n}\n\n/**\n * This is the entrypoint for the Smart Link function.\n * It receives a request containing a list of URLs to resolve and returns a list of entities.\n * The function uses async processing to resolve all URLs concurrently.\n */\nexport async function resolveSmartLinks(request: ResolveSmartLinkRequest):Promise<ResolveSmartLinkResponse> {\n console.log(request);\n const urls = request.payload.urls;\n\n const processedEntities = await Promise.all(\n urls.map(url => {\n return processUrl(url);\n }),\n );\n\n return buildResolveSmartLinksResponse(processedEntities);\n\n}\n\n/**\n * This function processes a single URL.\n * It extracts the fileId from the URL and then pulls the file information from Google using the user authenticated OAuth2 token.\n */\nasync function processUrl(url: string) : Promise<ResolveUrlEntityResult> {\n\n return {\n identifier: {\n url: url\n },\n meta: {\n access: 'granted',\n visibility: 'public'\n },\n entity: {\n schemaVersion: '2.0',\n id: '1231321',\n updateSequenceNumber: 1111,\n displayName: \"Hello World!\",\n description: \"Well done you created your first Smart Link!\",\n url,\n 'atlassian:remote-link': {\n type: 'document',\n },\n createdAt: new Date().toISOString(),\n lastUpdatedAt: new Date().toISOString(),\n thumbnail: {\n externalUrl: `https://picsum.photos/200`,\n },\n createdBy: {\n accountId: '6076c075c642ff0070ef3787',\n }\n }\n };\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.390Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":186,"estimatedTokens":1011}}161{"id":"doc-build_a_jira_issue_analyst_rovo_agent-f6c88bfe","source":"documentation","title":"Build a Jira issue analyst Rovo Agent","url":"https://developer.atlassian.com/platform/forge/build-a-jira-issue-analyst-rovo-agent","text":"Example:\n```text\nnpm install --g @forge/cli@latest or\nnpm install --g @forge/cli@^10.3.0`\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd Jira Analyst\n```\n\nExample:\n```text\nmodules:\n rovo:agent:\n - key: analyst-agent\n name: Jira Analyst\n description: An agent that can analyze Jira issues.\n prompt: >\n You are an experienced data analyst specializing in analyzing Jira issues. \n\n You can perform the following jobs based on the user's request:\n\n a. Analyze a list of Jira issues\n\n I'll separate the instructions for each job with a '---' on a new line, followed by the job title.\n\n ---\n\n a. Analyze a list of Jira issues\n\n To do this, follow these steps:\n\n 1. Check if the project key is available in the context. If not, prompt the user to provide the project key.\n Also check if the user has provided labels to filter the issues by.\n\n 2. Fetch the issues using the get-issues action.\n If there are no issues, skip steps 3 and 4 and return a message to the user specifying the project key and label used in this run.\n\n 3. Analyze the Jira issues data according to the user's request. Structure your response as follows:\n i. A brief summary of the main findings\n ii. Show the data in tabular format wherever possible. Table should have columns 'Category', 'Count', 'Percentage of Total'.\n iii. Detailed explanations of identified trends or patterns\n iv. Any additional insights that may be relevant to the user's request\n v. If applicable, recommendations based on your analysis \n \n Follow these rules:\n - Mention the common theme between issues of certain type.\n - Do not mention specific issue details unless asked by the user.\n\n 4. Return the analysis to the user.\n\n conversationStarters:\n - Analyse Jira Issues\n actions:\n - get-issues\n```\n\nExample:\n```text\nmodules:\n action:\n - key: get-issues\n name: Get issues\n function: getIssues\n description: Fetches issues from a project.\n inputs:\n label:\n title: Label\n type: string\n description: The label to filter the issues by.\n required: false\n actionVerb: GET\n function:\n - key: getIssues\n handler: index.getIssues\napp:\n runtime:\n name: nodejs24.x\n id: <your-app-id>\npermissions:\n scopes:\n - read:jira-work\n```\n\nExample:\n```text\nmodules:\n rovo:agent:\n - key: analyst-agent\n name: Jira Analyst\n description: An agent that can analyze Jira issues.\n prompt: >\n You are an experienced data analyst specializing in analyzing Jira issues. \n\n You can perform the following jobs based on the user's request:\n\n a. Analyze a list of Jira issues\n\n I'll separate the instructions for each job with a '---' on a new line, followed by the job title.\n\n ---\n\n a. Analyze a list of Jira issues\n\n To do this, follow these steps:\n\n 1. Check if the project key is available in the context. If not, prompt the user to provide the project key.\n Also check if the user has provided labels to filter the issues by.\n\n 2. Fetch the issues using the get-issues action.\n If there are no issues, skip steps 3 and 4 and return a message to the user specifying the project key and label used in this run.\n\n 3. Analyze the Jira issues data according to the user's request. Structure your response as follows:\n i. A brief summary of the main findings\n ii. Show the data in tabular format wherever possible. Table should have columns 'Category', 'Count', 'Percentage of Total'.\n iii. Detailed explanations of identified trends or patterns\n iv. Any additional insights that may be relevant to the user's request\n v. If applicable, recommendations based on your analysis \n \n Follow these rules:\n - Mention the common theme between issues of certain type.\n - Do not mention specific issue details unless asked by the user.\n\n 4. Return the analysis to the user.\n\n conversationStarters:\n - Analyse Jira Issues\n actions:\n - get-issues\n \n action:\n - key: get-issues\n name: Get issues\n function: getIssues\n description: Fetches issues from a project.\n inputs:\n label:\n title: Label\n type: string\n description: The label to filter the issues by.\n required: false\n actionVerb: GET\n \n function:\n - key: getIssues\n handler: index.getIssues\napp:\n runtime:\n name: nodejs24.x\n id: ari:cloud:ecosystem::app/fbe803d8-7661-4267-bef4-0916ce416720\npermissions:\n scopes:\n - read:jira-work\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\n// Fetch issues from jira in a specific project\nexport const getIssues = async (payload, context) => {\n console.log(`Payload: ${JSON.stringify(payload)}`);\n console.log(`Request Context: ${JSON.stringify(context)}`);\n\n const projectKey = payload.context.jira.projectKey;\n const label = payload.label ? payload.label : null;\n\n console.log(`Fetching issues for project: ${projectKey} and label: ${label}`);\n const jql = label ? `project=${projectKey} AND labels=${label}` : `project=${projectKey}`;\n const response = await api.asApp().requestJira(route`/rest/api/3/search/jql?jql=${jql}`);\n const data = await response.json();\n const cleanData = await extractIssueDetails(data);\n return cleanData;\n}\n\n// Extract issue details from the response\nexport const extractIssueDetails = async (data) => {\n // console.log(`Extracting issue details from response: ${JSON.stringify(data)}`);\n return data.issues.map(issue => ({\n key: issue.key,\n summary: issue.fields.summary\n }));\n}\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.391Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":208,"estimatedTokens":1502}}162{"id":"doc-build_a_teamwork_graph_connector-58106a9e","source":"documentation","title":"Build a Teamwork Graph connector","url":"https://developer.atlassian.com/platform/forge/build-a-teamwork-graph-connector/","text":"Example:\n```text\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - write:object:jira\n - read:object:jira\n - delete:object:jira\n external:\n fetch:\n backend:\n - \"https://www.googleapis.com\"\n```\n\nExample:\n```text\nmodules:\n graph:connector:\n - key: google-drive-connector\n name: Google Drive\n capabilities:\n replicatesPermissions: false\n syncFidelity: append\n supportsIncrementalSync: false\n icons:\n light: https://static.example-hello-world.com/favicon-light.ico\n dark: https://static.example-hello-world.com/favicon-dark.ico\n objectTypes:\n - atlassian:document\n datasource:\n formConfiguration:\n form:\n - key: connectionDetails\n type: header\n title: Connection Details\n description: Please provide your Google Drive API Key and Folder ID\n properties:\n - key: apiKey\n label: Api Key\n type: string\n isRequired: true\n - key: folderId\n label: Google Drive Folder ID\n type: string\n isRequired: true\n validateConnection:\n function: validateConnectionFn\n instructions:\n - 1. Enable Google Drive API on Google Cloud\n - 2. Add an API Key and Google Drive Folder ID\n onConnectionChange:\n function: onConnectionChangeFn\n```\n\nExample:\n```text\nimport { fetch } from '@forge/api';\n// Connection management types for graph connector\nexport interface ConnectionRequest {\n name: string;\n configProperties: Record<string, any>;\n}\nexport interface ConnectionResponse {\n success: boolean;\n message?: string;\n}\nexport interface ValidateConnectionRequest extends ConnectionRequest {}\nexport interface ValidateConnectionResponse extends ConnectionResponse {}\nexport const validateConnection = async (request: ValidateConnectionRequest): Promise<ValidateConnectionResponse> => {\n try {\n console.log('Validating connection:', request.name);\n \n const apiKey = request.configProperties.apiKey;\n const folderId = request.configProperties.folderId;\n \n if (!apiKey || !folderId) {\n return {\n success: false,\n message: 'Either API key or folderId is missing'\n };\n }\n // Test the API key by making a simple request to Google Drive API\n const testUrl = `https://www.googleapis.com/drive/v3/files?q='${folderId}'%20in%20parents&key=${apiKey}`;\n \n const response = await fetch(testUrl, {\n method: 'GET',\n headers: {\n 'Accept': 'application/json'\n }\n });\n if (!response.ok) {\n const errorText = await response.text();\n console.error('API validation failed:', response.status, errorText);\n return {\n success: false,\n message: `API key validation failed: ${response.status} ${response.statusText}`\n };\n }\n const data = await response.json() as GoogleDriveApiResponse;\n console.log('API validation successful for folderId: ', folderId, ' with data size: ', data.files.length);\n \n return {\n success: true,\n message: 'Connection validated successfully'\n };\n \n } catch (error) {\n console.error('Error validating connection:', error);\n return {\n success: false,\n message: `Connection validation error: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n};\n```\n\nExample:\n```text\nimport { kvs } from '@forge/kvs';\nexport interface ConnectorConfig {\n connectorName?: string;\n connectionId?: string;\n apiKey?: string;\n folderId?: string;\n}\nexport interface OnConnectionChangeRequest extends ConnectionRequest {\n action: 'CREATED' | 'UPDATED' | 'DELETED';\n connectionId: string;\n}\nexport interface OnConnectionChangeResponse extends ConnectionResponse {}\nexport const onConnectionChange = async (request: OnConnectionChangeRequest): Promise<OnConnectionChangeResponse> => {\n try {\n console.log('Connection change event:', request);\n const connectorConfig: ConnectorConfig = {\n connectorName: request.configProperties.connectorName,\n connectionId: request.connectionId,\n apiKey: request.configProperties.apiKey,\n folderId: request.configProperties.folderId\n };\n console.log('Connector config:', JSON.stringify(connectorConfig));\n switch (request.action) {\n case 'CREATED':\n console.log('New connection created:', request.name);\n await kvs.setSecret(request.name, connectorConfig);\n // Here you could initialize any resources needed for the new connection\n // For example, create initial data sync jobs, set up webhooks, etc.\n break;\n \n case 'UPDATED':\n console.log('Connection updated:', request.name);\n await kvs.setSecret(request.name, connectorConfig);\n // Here you could handle configuration changes\n // For example, update sync settings, refresh tokens, etc.\n break;\n \n case 'DELETED':\n console.log('Connection deleted:', request.name);\n await kvs.deleteSecret(request.name);\n // Here you could clean up resources associated with the connection\n // For example, cancel sync jobs, remove webhooks, clean up cached data, etc.\n break;\n \n default:\n console.warn('Unknown change type:', request.action);\n }\n return {\n success: true,\n message: `Connection ${request.action} handled successfully`\n };\n \n } catch (error) {\n console.error('Error handling connection change:', error);\n return {\n success: false,\n message: `Connection change error: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n};\n```\n\nExample:\n```text\nforge deploy\n```\n\nExample:\n```text\nforge install\n```\n\nExample:\n```text\n// Type definitions for Google Drive API response\ninterface GoogleDriveFile {\n id: string;\n name?: string;\n mimeType?: string;\n createdAt?: string;\n lastUpdatedAt?: string;\n}\n\ninterface GoogleDriveApiResponse {\n files: GoogleDriveFile[];\n}\n\n// Connection config object\ninterface ConnectorConfig {\n connectorName?: string;\n connectionId?: string;\n apiKey?: string;\n folderId?: string;\n}\n\n// Request interfaces - Compatible with Forge WebtriggerRequest\ninterface ApiRequest {\n queryParameters?: {\n [key: string]: any;\n externalId?: string;\n objectType?: string;\n objectIds?: string;\n connectorName?: string;\n };\n body?: string;\n headers?: Record<string, string>;\n method?: string;\n path?: string;\n}\n\ninterface ApiResponse<T = any> {\n body: T;\n statusCode: number;\n}\n```\n\nExample:\n```text\nimport { graph } from '@forge/teamwork-graph';\nimport { FetchDataResponse } from '@forge/teamwork-graph/out/types';\n\nexport async function fetchData(request: ApiRequest, connectorConfig: ConnectorConfig): Promise<ObjectApiResponse<GoogleDriveApiResponse | string>> {\n console.log('fetchData called with request:', request);\n\n try {\n // Get the connector name from the request\n if (!connectorConfig.connectorName) {\n return {\n body: 'Missing required parameter: connectorName. Please provide a connectorName to fetch the data.',\n statusCode: 400\n };\n }\n const apiKey = connectorConfig.apiKey;\n const folderId = connectorConfig.folderId;\n console.log('Using Folder ID:', folderId);\n\n const requestConfig = {\n url: `https://www.googleapis.com/drive/v3/files?q='${folderId}'%20in%20parents&key=${apiKey}`,\n method: 'GET' as const,\n headers: {\n 'Accept': 'application/json'\n }\n };\n\n const response = await fetch(requestConfig.url, {\n headers: requestConfig.headers,\n method: requestConfig.method\n });\n console.log(\"fetch response: \", response);\n\n if (!response.ok) {\n throw new Error('Failed to fetch data from Google Drive API: ' + response.statusText);\n }\n\n const data = await response.json();\n console.log(\"data: \", data);\n\n const googleDriveApiResponse: GoogleDriveApiResponse = data;\n console.log('Parsed GoogleDriveApiResponse:', googleDriveApiResponse);\n\n // Validate the response structure\n if (!googleDriveApiResponse.files) {\n throw new Error('Invalid response structure: missing files array');\n }\n\n return {\n body: googleDriveApiResponse,\n statusCode: 200\n };\n } catch (error) {\n console.error('Error in fetchData:', error);\n return {\n body: 'Error fetching data: ' + (error as Error).message,\n statusCode: 500\n };\n }\n}\n```\n\nExample:\n```text\ninterface WebtriggerRequest {\n body?: string;\n headers: Record<string, string>;\n method: string;\n path: string;\n queryParameters: Record<string, string>;\n}\n\ninterface WebtriggerResponse<T = any> {\n statusCode: number;\n body: T;\n headers?: Record<string, string>;\n}\n```\n\nExample:\n```text\nimport { kvs } from '@forge/kvs';\nimport { fetchData } from './objects';\n\nasync function extractConnectionConfig(request: WebtriggerRequest): Promise<ConnectorConfig> {\n const connectorName = request.queryParameters?.connectorName?.toString().trim();\n if (connectorName) {\n return await kvs.getSecret(connectorName as string) as ConnectorConfig;\n }\n return {} as ConnectorConfig;\n}\n\nexport const googleIngestion = async (\n request: WebtriggerRequest): Promise<WebtriggerResponse> => {\n\n // Debug: Log the entire queryParameters object\n console.log('TS: Full queryParameters:', request.queryParameters);\n\n try {\n // Try multiple possible parameter names and clean the value\n let action = request.queryParameters?.func ||\n request.queryParameters?.action ||\n request.queryParameters?.function;\n\n // Clean the action value (trim whitespace and normalize case)\n action = action?.toString().trim();\n\n console.log('Extracted action:', action);\n console.log('Action type:', typeof action);\n console.log('Action length:', action?.length);\n\n let connectorConfig: ConnectorConfig = await extractConnectionConfig(request);\n console.log('Connector name:', connectorConfig.connectorName);\n console.log('Connection ID:', connectorConfig.connectionId);\n\n switch (action) {\n case 'fetchData':\n const data = await fetchData(request, connectorConfig);\n console.log('FetchData response:', data);\n return {\n statusCode: 200,\n body: 'Data fetched successfully. Files count: ' + (data.body as any).files.length\n };\n default:\n console.log('Going to default case, action was:', JSON.stringify(action));\n return {\n statusCode: 400,\n body: `Invalid action parameter. Received: \\\"${action}\\\". Expected: \\\"fetchData\\\"`\n };\n }\n } catch (error) {\n console.error('Error in exampleWebtrigger:', error);\n return {\n statusCode: 500,\n body: `Internal server error: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n};\n```\n\nExample:\n```text\nwebtrigger:\n - key: google-ingestion-webtrigger\n function: google-ingestion\nfunction:\n - key: google-ingestion\n handler: index.googleIngestion\n```\n\nExample:\n```text\ncurl 'your-webtrigger-url?func=..'\n```\n\nExample:\n```text\n/**\n * Processes the setObjects API request and handles the response\n */\nasync function processsetObjectsRequest(documents: DocumentObject[], connectionId: string): Promise<ApiResponse<string>> {\n const setObjectsResponse: BulkObjectResponse = await graph.setObjects({\n objects: documents as any,\n connectionId: connectionId\n });\n console.log(\"setObjectsResponse: \", setObjectsResponse);\n if (setObjectsResponse.success) {\n return {\n body: 'objects set successfully',\n statusCode: 200\n };\n } else {\n return {\n body: 'Error setting objects: ' + setObjectsResponse.error,\n statusCode: 500\n };\n }\n}\n/**\n * Transforms Google Drive file data into Atlassian document objects\n */\nfunction transformData(data: GoogleDriveApiResponse, folderId: string): DocumentObject[] {\n const files = data.files;\n console.log(\"files: \", files);\n return files.map((item: GoogleDriveFile, idx: number): DocumentObject => ({\n schemaVersion: '1.0',\n id: item.id || \\`doc-\\${idx}\\`,\n updateSequenceNumber: 1,\n displayName: item.name || 'Untitled Document',\n url: 'https://drive.google.com/file/d/' + item.id + '/view?usp=drive_link',\n createdAt: item.createdAt || new Date().toISOString(),\n permissions: [{\n accessControls: [\n {\n principals: [{ type: 'EVERYONE' }]\n }\n ]\n }],\n parentKey: {\n type: 'atlassian:document',\n value: {\n entityId: folderId\n }\n },\n associations: {\n set: [\n {\n associationType: 'issueIdOrKeys',\n values: ['Test'],\n }\n ]\n },\n lastUpdatedAt: item.lastUpdatedAt || new Date().toISOString(),\n 'atlassian:document': {\n type: {\n category: 'image', // TODO: get category from mimeType\n mimeType: item.mimeType || 'Unknown',\n },\n content: {\n mimeType: item.mimeType || 'Unknown',\n text: 'Sample document content',\n }\n }\n }));\n}\nexport async function setObjects(request: ApiRequest, connectorConfig: ConnectorConfig): Promise<ApiResponse<string>> {\n console.log('setObjects called with request:', request);\n try {\n const response = await fetchData(request, connectorConfig);\n if (response.statusCode !== 200 || typeof response.body === 'string') {\n return {\n body: 'Error fetching data: ' + response.body,\n statusCode: response.statusCode\n };\n }\n const data = response.body as GoogleDriveApiResponse;\n console.log(\"data: \", data);\n const folderId = connectorConfig.folderId;\n let documents: DocumentObject[] = transformData(data, folderId);\n console.log(\"transformedData: \", documents);\n const setObjectsResponse = await processsetObjectsRequest(documents, connectorConfig?.connectionId as string);\n if (setObjectsResponse.statusCode === 500) {\n return setObjectsResponse;\n }\n documents = [];\n documents.push(createFolderDocument(folderId));\n return await processsetObjectsRequest(documents, connectorConfig?.connectionId as string);\n } catch (error) {\n console.error('Error setting objects:', error);\n return {\n body: 'Error setting objects: ' + (error as Error).message,\n statusCode: 500\n };\n }\n}\nfunction createFolderDocument(folderId: any): DocumentObject {\n // Create a folder document object from folderId and return\n return {\n schemaVersion: '1.0',\n id: folderId,\n updateSequenceNumber: 1,\n displayName: 'Google Drive Folder',\n url: \\`https://drive.google.com/drive/folders/\\${folderId}\\`,\n createdAt: new Date().toISOString(),\n permissions: [{\n accessControls: [\n {\n principals: [{ type: 'EVERYONE' }]\n }\n ]\n }],\n lastUpdatedAt: new Date().toISOString(),\n 'atlassian:document': {\n type: {\n category: 'folder',\n },\n content: {\n mimeType: 'application/vnd.google-apps.folder',\n text: 'Google Drive Folder',\n }\n }\n };\n}\n```\n\nExample:\n```text\ncase 'setObjects':\n return await setObjects(request, connectorConfig);\n```\n\nExample:\n```text\ncurl '$URL?func=setObjects&connectorName=<connector-name>'\n```\n\nExample:\n```text\ninterface ObjectResponse {\n message: string;\n externalId: string;\n objectType: string;\n data: any;\n}\n```\n\nExample:\n```text\nexport async function getObject(request: ApiRequest, connectorConfig: ConnectorConfig): Promise<ApiResponse<ObjectResponse | string>> {\n console.log('getObject called with request:', request);\n try {\n // Extract externalId and objectType from request parameters\n let externalId: string | null = null;\n let objectType: string = 'atlassian:document'; // Default object type\n // Try to get parameters from query parameters first\n if (request.queryParameters?.externalId) {\n const rawExternalId = request.queryParameters.externalId;\n externalId = String(rawExternalId).trim();\n }\n if (request.queryParameters?.objectType) {\n const rawobjectType = request.queryParameters.objectType;\n objectType = String(rawobjectType).trim();\n }\n console.log('External ID:', externalId);\n console.log('Object type:', objectType);\n // Validate required parameters\n if (!externalId) {\n return {\n body: 'Missing required parameter: externalId. Please provide an externalId to retrieve the object.',\n statusCode: 400\n };\n }\n const data = await graph.getObjectByExternalId({\n externalId: externalId,\n objectType: objectType,\n connectionId: connectorConfig?.connectionId\n });\n console.log(\"getObject response: \", data);\n if (data.success) {\n return {\n body: JSON.stringify(data.object),\n statusCode: 200\n };\n } else {\n return {\n body: 'Error getting object: ' + data.error,\n statusCode: 500\n };\n }\n } catch (error) {\n console.error('Error in getObject:', error);\n return {\n body: 'Error getting object: ' + (error as Error).message,\n statusCode: 500\n };\n }\n}\n```\n\nExample:\n```text\nimport { kvs } from '@forge/kvs';\nimport { fetchData, getObject, setObjects } from './objects';\nexport const googleIngestion = async (\n request: WebtriggerRequest): Promise<WebtriggerResponse> => {\n // Debug: Log the entire queryParameters object\n console.log('TS: Full queryParameters:', request.queryParameters);\n try {\n // Try multiple possible parameter names and clean the value\n let action = request.queryParameters?.func ||\n request.queryParameters?.action ||\n request.queryParameters?.function;\n // Clean the action value (trim whitespace and normalize case)\n action = action?.toString().trim();\n console.log('Extracted action:', action);\n console.log('Action type:', typeof action);\n console.log('Action length:', action?.length);\n let connectorConfig: ConnectorConfig = await extractConnectionConfig(request);\n console.log('Connector name:', connectorConfig.connectorName);\n console.log('Connection ID:', connectorConfig.connectionId);\n switch (action) {\n case 'fetchData':\n const data = await fetchData(request, connectorConfig);\n console.log('FetchData response:', data);\n return {\n statusCode: 200,\n body: 'Data fetched successfully. Files count: ' + (data.body as any).files.length\n };\n case 'setObjects':\n return await setObjects(request, connectorConfig);\n case 'getObject':\n return await getObject(request, connectorConfig);\n default:\n console.log('Going to default case, action was:', JSON.stringify(action));\n return {\n statusCode: 400,\n body: `Invalid action parameter. Received: \\\"${action}\\\". Expected: \\\"fetchData\\\", \\\"setObjects\\\", \\\"getObject\\\"`\n };\n }\n } catch (error) {\n console.error('Error in exampleWebtrigger:', error);\n return {\n statusCode: 500,\n body: `Internal server error: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n};\nasync function extractConnectionConfig(request: WebtriggerRequest): Promise<ConnectorConfig> {\n const connectorName = request.queryParameters?.connectorName?.toString().trim();\n if (connectorName) {\n return await kvs.getSecret(connectorName as string) as ConnectorConfig;\n }\n return {} as ConnectorConfig;\n}\n```\n\nExample:\n```text\ncurl '$URL?func=getObject&externalId=<someID>&connectorName=<connector-name>'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.396Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":21,"totalLines":667,"estimatedTokens":5437}}163{"id":"doc-integrate_remote_agents_with_jira_a2a_0_3-f81e448a","source":"documentation","title":"Integrate remote agents with Jira (A2A 0.3)","url":"https://developer.atlassian.com/platform/forge/remote-agents-in-jira-a2a-0.3/","text":"Example:\n```text\nprotocols:\n agent2Agent:\n jsonRpcTransport:\n endpoint: a2a-json-rpc-endpoint\n streaming: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"task\": {\n \"id\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"working\" },\n \"kind\": \"task\"\n }\n }\n}\n\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"working\" },\n \"message\": {\n \"role\": \"agent\",\n \"parts\": [{ \"kind\": \"text\", \"text\": \"Analyzing the issue...\" }]\n },\n \"kind\": \"status-update\",\n \"final\": false\n }\n }\n}\n\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"completed\" },\n \"message\": {\n \"role\": \"agent\",\n \"parts\": [{ \"kind\": \"text\", \"text\": \"Done! I've drafted a fix.\" }]\n },\n \"kind\": \"status-update\",\n \"final\": true\n }\n }\n}\n```\n\nExample:\n```text\napp:\n id: 324888fb-c374-44d3-aff5-2daa24721084\n name: your-awesome-app\n\nmodules:\n rovo:agentConnector:\n - key: your-awesome-agent\n name: Your Awesome Agent\n description: An awesome agent that you built\n icon: resource:agent-resources;icons/your-agent.svg\n productContexts:\n - jira\n protocols:\n agent2Agent:\n jsonRpcTransport:\n endpoint: a2a-json-rpc-endpoint\n\n trigger:\n - key: installed-trigger\n endpoint: installed-endpoint\n events:\n - avi:forge:installed:app\n\n jira:adminPage:\n - key: configuration-page\n resource: config-page\n title: Awesome Agent Configuration\n render: native\n resolver:\n endpoint: config-endpoint\n useAsConfig: true\n\n endpoint:\n - key: a2a-json-rpc-endpoint\n remote: agent-remote\n route: /a2a/json-rpc\n - key: installed-endpoint\n remote: agent-remote\n route: /atlassian/installed\n - key: config-endpoint\n remote: agent-remote\n route: /atlassian/config\n\nremotes:\n - key: agent-remote\n baseUrl: https://youragent.com\n operations:\n - compute\n - storage\n auth:\n appSystemToken:\n enabled: true\n appUserToken:\n enabled: true\n\nresources:\n - key: agent-resources\n path: static/agent\n\npermissions:\n scopes:\n - read:app-system-token\n - read:app-user-token\n - read:jira-work\n```\n\nExample:\n```javascript\n{\n \"app\": {\n \"id\": \"b9c0e1ec-3d22-4560-a650-34ff4155692c\",\n \"name\": \"agent-forge-app\",\n \"ownerAccountId\": \"557058:6ea56496-8c9b-4e0a-bc03-7412dedb3304\",\n \"version\": \"5.2.0\"\n },\n \"context\": \"ari:cloud:jira::site/02cfe711-47c9-46f3-ae22-9af9259c75be\", // \"cloudId\" of Jira site being installed into\n \"environment\": {\n \"id\": \"f11e5c8c-4f17-4547-b236-dc0ffbfff13b\" // ID of Forge app environment being installed\n },\n \"eventType\": \"avi:forge:installed:app\", // event name\n \"id\": \"4e874af6-799e-4236-b172-23e57f9e549e\", // \"installationId\" of the installation record for your app into this site\n \"installerAccountId\": \"557058:6ea56496-8c9b-4e0a-bc03-7412dedb3304\" // account ID of the user installing the app\n}\n```\n\nExample:\n```text\nhttps://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/serverInfo\n```\n\nExample:\n```text\nhttps://${subdomain}.atlassian.net/_edge/tenant_info\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"user\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": $message\n }, {\n \"kind\": \"data\",\n \"data\": $data\n }\n ],\n \"messageId\": $messageId\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"user\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"A user has assigned you to a work item.\"\n }, {\n \"kind\": \"data\",\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\",\n \"fields\": {\n \"summary\": \"QA checkout flow updates\",\n \"description\": \"Perform a comprehensive QA review...\"\n }\n }\n }\n }\n ],\n \"messageId\": \"a198b5e2-342b-4159-b9e0-c063ba627a4c\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"working\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"Cursor is reviewing AW26-11.\"\n }],\n \"messageId\": \"0fca37e8-80c3-43d3-bcf5-cb4be26f4df8\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"user\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"A user has mentioned you in a comment.\"\n }, {\n \"kind\": \"data\",\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\",\n \"fields\": {\n \"summary\": \"QA checkout flow updates\",\n \"description\": \"Perform a comprehensive QA review...\"\n }\n },\n \"comment\": {\n \"id\": \"91283\",\n \"body\": \"QA is showing that when a customer enters a valid discount code at checkout, it's not being reflected in the order summary or total. [@Cursor](~11111) can you analyze and suggest a fix?\"\n }\n }\n }\n ],\n \"messageId\": \"a198b5e2-342b-4159-b9e0-c063ba627a4c\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"working\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"Cursor is reviewing your comment.\"\n }],\n \"messageId\": \"0fca37e8-80c3-43d3-bcf5-cb4be26f4df8\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"2561267b-a71a-42f6-bbb1-fbcb30359b41\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"user\",\n \"parts\": [\n {\n \"kind\": \"text\",\n \"text\": \"A user has sent a message in the chat.\"\n }, {\n \"kind\": \"data\",\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\"\n },\n \"chat\": {\n \"message\": \"The test credentials are username: testuser@example.com, password: Test1234!\"\n }\n }\n }\n ],\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"messageId\": \"d2c9e3f1-5a6b-7c8d-9e0f-1a2b3c4d5e6f\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"2561267b-a71a-42f6-bbb1-fbcb30359b41\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"working\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"Thanks! Resuming QA review with the provided credentials.\"\n }],\n \"messageId\": \"e3d4c5b6-a7b8-9c0d-1e2f-3a4b5c6d7e8f\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:05:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"tasks/get\",\n \"params\": {\n \"taskId\": $taskId\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"method\": \"tasks/get\",\n \"params\": {\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"working\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"Cursor is running through the checkout flow test cases.\"\n }],\n \"messageId\": \"f4e5d6c7-b8a9-0b1c-2d3e-4f5a6b7c8d9e\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:10:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"completed\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"## QA Review Complete\\n\\nAll 12 checkout flow test cases passed. The discount code issue (AW26SAVE) has been reproduced and a root cause identified in `checkout/discountService.ts:142`. A fix has been drafted in PR #847.\"\n }],\n \"messageId\": \"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:30:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\nExample:\n```text\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"tasks/cancel\",\n \"params\": {\n \"taskId\": $taskId\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"f1a032e3-9d46-4464-a50e-70cf12ff86bd\",\n \"method\": \"tasks/cancel\",\n \"params\": {\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"f1a032e3-9d46-4464-a50e-70cf12ff86bd\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"canceled\",\n \"message\": {\n \"kind\": \"message\",\n \"role\": \"agent\",\n \"parts\": [{\n \"kind\": \"text\",\n \"text\": \"Cursor canceled the task at the user's request.\"\n }],\n \"messageId\": \"a199fa50-a320-4c6b-b610-ef2664c43f4e\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n },\n \"kind\": \"task\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.401Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":21,"totalLines":515,"estimatedTokens":2897}}164{"id":"doc-integrate_remote_agents_with_jira-bec476f2","source":"documentation","title":"Integrate remote agents with Jira","url":"https://developer.atlassian.com/platform/forge/remote-agents-in-jira/","text":"Example:\n```text\nprotocols:\n agent2Agent:\n version: \"1.0\"\n jsonRpcTransport:\n endpoint: a2a-json-rpc-endpoint\n streaming: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"task\": {\n \"id\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"TASK_STATE_WORKING\" }\n }\n }\n}\n\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"TASK_STATE_WORKING\" },\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{ \"text\": \"Analyzing the issue...\" }]\n }\n }\n }\n}\n\ndata: {\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"result\": {\n \"statusUpdate\": {\n \"taskId\": \"task-123\",\n \"contextId\": \"ctx-456\",\n \"status\": { \"state\": \"TASK_STATE_COMPLETED\" },\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{ \"text\": \"Done! I've drafted a fix.\" }]\n }\n }\n }\n}\n```\n\nExample:\n```text\napp:\n id: 324888fb-c374-44d3-aff5-2daa24721084\n name: your-awesome-app\n\nmodules:\n rovo:agentConnector:\n - key: your-awesome-agent\n name: Your Awesome Agent\n description: An awesome agent that you built\n icon: resource:agent-resources;icons/your-agent.svg\n productContexts:\n - jira\n protocols:\n agent2Agent:\n version: \"1.0\"\n jsonRpcTransport:\n endpoint: a2a-json-rpc-endpoint\n\n trigger:\n - key: installed-trigger\n endpoint: installed-endpoint\n events:\n - avi:forge:installed:app\n\n jira:adminPage:\n - key: configuration-page\n resource: config-page\n title: Awesome Agent Configuration\n render: native\n resolver:\n endpoint: config-endpoint\n useAsConfig: true\n\n endpoint:\n - key: a2a-json-rpc-endpoint\n remote: agent-remote\n route: /a2a/json-rpc\n - key: installed-endpoint\n remote: agent-remote\n route: /atlassian/installed\n - key: config-endpoint\n remote: agent-remote\n route: /atlassian/config\n\nremotes:\n - key: agent-remote\n baseUrl: https://youragent.com\n operations:\n - compute\n - storage\n auth:\n appSystemToken:\n enabled: true\n appUserToken:\n enabled: true\n\nresources:\n - key: agent-resources\n path: static/agent\n\npermissions:\n scopes:\n - read:app-system-token\n - read:app-user-token\n - read:jira-work\n```\n\nExample:\n```javascript\n{\n \"app\": {\n \"id\": \"b9c0e1ec-3d22-4560-a650-34ff4155692c\",\n \"name\": \"agent-forge-app\",\n \"ownerAccountId\": \"557058:6ea56496-8c9b-4e0a-bc03-7412dedb3304\",\n \"version\": \"5.2.0\"\n },\n \"context\": \"ari:cloud:jira::site/02cfe711-47c9-46f3-ae22-9af9259c75be\", // \"cloudId\" of Jira site being installed into\n \"environment\": {\n \"id\": \"f11e5c8c-4f17-4547-b236-dc0ffbfff13b\" // ID of Forge app environment being installed\n },\n \"eventType\": \"avi:forge:installed:app\", // event name\n \"id\": \"4e874af6-799e-4236-b172-23e57f9e549e\", // \"installationId\" of the installation record for your app into this site\n \"installerAccountId\": \"557058:6ea56496-8c9b-4e0a-bc03-7412dedb3304\" // account ID of the user installing the app\n}\n```\n\nExample:\n```text\nhttps://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/serverInfo\n```\n\nExample:\n```text\nhttps://${subdomain}.atlassian.net/_edge/tenant_info\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"role\": \"ROLE_USER\",\n \"parts\": [\n {\n \"text\": $message\n }, {\n \"data\": $data\n }\n ],\n \"messageId\": $messageId\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"role\": \"ROLE_USER\",\n \"parts\": [\n {\n \"text\": \"A user has assigned you to a work item.\"\n }, {\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\",\n \"fields\": {\n \"summary\": \"QA checkout flow updates\",\n \"description\": \"Perform a comprehensive QA review...\"\n }\n }\n }\n }\n ],\n \"messageId\": \"a198b5e2-342b-4159-b9e0-c063ba627a4c\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"Cursor is reviewing AW26-11.\"\n }],\n \"messageId\": \"0fca37e8-80c3-43d3-bcf5-cb4be26f4df8\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"role\": \"ROLE_USER\",\n \"parts\": [\n {\n \"text\": \"A user has mentioned you in a comment.\"\n }, {\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\",\n \"fields\": {\n \"summary\": \"QA checkout flow updates\",\n \"description\": \"Perform a comprehensive QA review...\"\n }\n },\n \"comment\": {\n \"id\": \"91283\",\n \"body\": \"QA is showing that when a customer enters a valid discount code at checkout, it's not being reflected in the order summary or total. [@Cursor](~11111) can you analyze and suggest a fix?\"\n }\n }\n }\n ],\n \"messageId\": \"a198b5e2-342b-4159-b9e0-c063ba627a4c\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"03fbd406-dc47-472d-9c5c-03b6f2716fce\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"Cursor is reviewing your comment.\"\n }],\n \"messageId\": \"0fca37e8-80c3-43d3-bcf5-cb4be26f4df8\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"2561267b-a71a-42f6-bbb1-fbcb30359b41\",\n \"method\": \"message/send\",\n \"params\": {\n \"message\": {\n \"role\": \"ROLE_USER\",\n \"parts\": [\n {\n \"text\": \"A user has sent a message in the chat.\"\n }, {\n \"data\": {\n \"userAccountId\": \"22222\",\n \"agentAccountId\": \"11111\",\n \"issue\": {\n \"id\": \"21930\"\n },\n \"chat\": {\n \"message\": \"The test credentials are username: testuser@example.com, password: Test1234!\"\n }\n }\n }\n ],\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"messageId\": \"d2c9e3f1-5a6b-7c8d-9e0f-1a2b3c4d5e6f\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"2561267b-a71a-42f6-bbb1-fbcb30359b41\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"Thanks! Resuming QA review with the provided credentials.\"\n }],\n \"messageId\": \"e3d4c5b6-a7b8-9c0d-1e2f-3a4b5c6d7e8f\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:05:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"tasks/get\",\n \"params\": {\n \"taskId\": $taskId\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"method\": \"tasks/get\",\n \"params\": {\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_WORKING\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"Cursor is running through the checkout flow test cases.\"\n }],\n \"messageId\": \"f4e5d6c7-b8a9-0b1c-2d3e-4f5a6b7c8d9e\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:10:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_COMPLETED\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"## QA Review Complete\\n\\nAll 12 checkout flow test cases passed. The discount code issue (AW26SAVE) has been reproduced and a root cause identified in `checkout/discountService.ts:142`. A fix has been drafted in PR #847.\"\n }],\n \"messageId\": \"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:30:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": $requestId,\n \"method\": \"tasks/cancel\",\n \"params\": {\n \"taskId\": $taskId\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"f1a032e3-9d46-4464-a50e-70cf12ff86bd\",\n \"method\": \"tasks/cancel\",\n \"params\": {\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\"\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"f1a032e3-9d46-4464-a50e-70cf12ff86bd\",\n \"result\": {\n \"id\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\",\n \"status\": {\n \"state\": \"TASK_STATE_CANCELED\",\n \"message\": {\n \"role\": \"ROLE_AGENT\",\n \"parts\": [{\n \"text\": \"Cursor canceled the task at the user's request.\"\n }],\n \"messageId\": \"a199fa50-a320-4c6b-b610-ef2664c43f4e\",\n \"taskId\": \"909aef32-059d-46d7-ade3-38fa4d2c5162\",\n \"contextId\": \"4bdcf71e-0441-4564-95a3-f1c50594b60c\"\n },\n \"timestamp\": \"2025-01-01T12:00:00Z\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"error\": {\n \"code\": -32001,\n \"message\": \"Task not found\",\n \"data\": [\n {\n \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n \"reason\": \"TASK_NOT_FOUND\",\n \"domain\": \"a2a-protocol.org\",\n \"metadata\": {\n \"taskId\": \"task-123\"\n }\n }\n ]\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.405Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":22,"totalLines":504,"estimatedTokens":2813}}165{"id":"doc-global_ui_module_eap-9abede3b","source":"documentation","title":"global:ui module (EAP)","url":"https://developer.atlassian.com/platform/forge/global-ui/global-ui-module/","text":"Example:\n```text\nmodules []\n└─ global:ui {}\n ├─ key (string) [Required]\n ├─ resource (string) [Required]\n ├─ render (string) [Required - must be native]\n ├─ resolver {} [Optional]\n ├─ title (string | i18n object) [Required]\n └─ icon (string) [Optional]\n\napp {}\n├─ id (string) [Required]\n├─ runtime {} [Required]\n└─ compatibility {} [Required]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n global:ui:\n - key: my-app\n resource: main\n render: native\n resolver:\n function: resolver\n title: My App\n icon: resource:icons;icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n - key: icons\n path: icons\npermissions:\n content:\n styles:\n - \"unsafe-inline\"\napp:\n runtime:\n name: nodejs22.x\n memoryMB: 256\n architecture: arm64\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n id: ari:cloud:ecosystem::app/your-app-id\n```\n\nExample:\n```text\ntitle:\n i18n:\n default: My App\n es: Mi aplicación\n```\n\nExample:\n```text\nmodules:\n global:ui:\n - key: my-app\n icon: resource:icons;icon.svg\nresources:\n - key: icons\n path: icons # directory containing icon.svg\n```\n\nExample:\n```text\napp:\n compatibility:\n confluence:\n required: true\n jira:\n required: false\n```\n\nExample:\n```text\n{site-hostname}/apps/{installation-id}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.407Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":94,"estimatedTokens":361}}166{"id":"doc-add_custom_content_to_a_global_ui_app_using_fram-e08a537c","source":"documentation","title":"Add custom content to a global:ui app using Frame (EAP)","url":"https://developer.atlassian.com/platform/forge/global-ui/frame-routing/","text":"Example:\n```text\nmy-global-app/\n├── manifest.yml\n├── src/\n│ └── frontend/\n│ └── index.jsx # UI Kit shell: sidebar + Frame\n└── static/\n └── main-content/ # Custom UI resource loaded by Frame\n ├── package.json\n ├── vite.config.js\n ├── index.html\n └── src/\n ├── main.jsx\n └── App.jsx # Routing with view.createHistory()\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nmkdir -p static/main-content/src\n```\n\nExample:\n```json\n{\n \"name\": \"main-content\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"vite --port 3001\",\n \"build\": \"vite build\"\n },\n \"dependencies\": {\n \"@forge/bridge\": \"6.1.0-next.8\",\n \"react\": \"^18.2.0\",\n \"react-dom\": \"^18.2.0\",\n \"react-router\": \"^7.0.0\"\n },\n \"devDependencies\": {\n \"@vitejs/plugin-react\": \"^4.0.0\",\n \"vite\": \"^5.0.0\"\n }\n}\n```\n\nExample:\n```javascript\nimport { defineConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nexport default defineConfig({\n plugins: [react()],\n base: \"./\",\n build: { outDir: \"build\" },\n});\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Frame App</title>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/src/main.jsx\"></script>\n </body>\n</html>\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport App from \"./App\";\n\nReactDOM.createRoot(document.getElementById(\"root\")).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport { useEffect, useState } from \"react\";\nimport { view } from \"@forge/bridge\";\nimport { Router, Routes, Route } from \"react-router\";\n\nfunction ForYouPage() {\n return <h1>For you</h1>;\n}\n\nfunction DashboardPage() {\n return <h1>Dashboard</h1>;\n}\n\nfunction SettingsPage() {\n return <h1>Settings</h1>;\n}\n\nexport default function App() {\n const [historyState, setHistoryState] = useState(null);\n const [navigator, setNavigator] = useState(null);\n\n useEffect(() => {\n (async () => {\n const history = await view.createHistory();\n setNavigator(history);\n setHistoryState({ action: history.action, location: history.location });\n history.listen((location, action) => setHistoryState({ action, location }));\n })();\n }, []);\n\n if (!navigator || !historyState) {\n return <div>Loading...</div>;\n }\n\n return (\n <Router\n navigator={navigator}\n navigationType={historyState.action}\n location={historyState.location}\n >\n <Routes>\n <Route path=\"/\" element={<ForYouPage />} />\n <Route path=\"/for-you\" element={<ForYouPage />} />\n <Route path=\"/dashboard\" element={<DashboardPage />} />\n <Route path=\"/settings\" element={<SettingsPage />} />\n </Routes>\n </Router>\n );\n}\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler from \"@forge/react\";\nimport { Frame } from \"@forge/react\";\nimport { Global, Sidebar, LinkMenuItem, Main } from \"@forge/react/global\";\n\nconst App = () => (\n <Global>\n <Sidebar>\n <LinkMenuItem label=\"Dashboard\" href=\"/dashboard\" icon=\"chart-bar\" />\n <LinkMenuItem label=\"Settings\" href=\"/settings\" icon=\"settings\" />\n </Sidebar>\n <Main>\n <Frame resource=\"main-content\" />\n </Main>\n </Global>\n);\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\nmodules:\n global:ui:\n - key: my-global-app-global-ui\n resource: main\n render: native\n resolver:\n function: resolver\n title: My Global App\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\n tunnel:\n port: 3000\n - key: main-content\n path: static/main-content/build\n tunnel:\n port: 3001\napp:\n runtime:\n name: nodejs24.x\n compatibility:\n confluence:\n required: true\n id: ari:cloud:ecosystem::app/<your-app-id>\n```\n\nExample:\n```bash\ncd static/main-content\nnpm install\n```\n\nExample:\n```bash\nnpm run build\n```\n\nExample:\n```bash\ncd ../..\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.408Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":227,"estimatedTokens":1068}}167{"id":"doc-user_privacy_guide_for_forge_app_developers-5d48a5e7","source":"documentation","title":"User privacy guide for Forge app developers","url":"https://developer.atlassian.com/platform/forge/user-privacy-guidelines/","text":"Example:\n```json\n{\n\"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"updatedAt\": \"2018-10-25T23:08:51.382Z\"\n }, {\n \"accountId\": \"account-id-b\",\n \"updatedAt\": \"2018-10-25T23:14:44.231Z\"\n }, {\n \"accountId\": \"account-id-c\",\n \"updatedAt\": \"2018-12-01T02:44:21.020Z\"\n }]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"status\": \"closed\"\n }, {\n \"accountId\": \"account-id-c\",\n \"status\": \"updated\"\n }]\n}\n```\n\nExample:\n```json\n{\n \"errorType\": \"string\",\n \"errorMessage\": \"string\"\n}\n```\n\nExample:\n```typescript\ninterface Account {\n references: string[];\n accountId: string;\n displayName: string;\n emailAddress: string;\n updatedAt?: string;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.409Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":55,"estimatedTokens":188}}168{"id":"doc-design_tokens_and_theming-84f23360","source":"documentation","title":"Design tokens and theming","url":"https://developer.atlassian.com/platform/forge/design-tokens-and-theming/","text":"Example:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nawait view.theme.enable();\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<html data-theme=\"dark:dark light:light\" data-color-mode=\"dark\">\n <head>\n <style data-theme=\"dark\">\n /* Dark theme CSS */\n </style>\n </head>\n <body>\n <!-- Your app here -->\n </body>\n</html>\n```\n\nExample:\n```text\n.example {\n background: var(--ds-surface-raised);\n padding: var(--ds-space-100);\n font: var(--ds-font-heading-large);\n}\n```\n\nExample:\n```javascript\nimport { token } from \"@atlaskit/tokens\";\n\nconst example = {\n color: token(\"color.background.selected.bold\"),\n margin: token(\"space.150\"),\n font: token(\"font.body\"),\n};\n```\n\nExample:\n```text\nhtml[data-color-mode=\"light\"] {\n --my-custom-background-token: white;\n --my-custom-text-token: black;\n}\n\nhtml[data-color-mode=\"dark\"] {\n --my-custom-background-token: black;\n --my-custom-text-token: white;\n}\n```\n\nExample:\n```javascript\nimport { token } from \"@atlaskit/tokens\";\n\nconst AppStyles = {\n backgroundColor: token(\"utility.elevation.surface.current\"),\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.410Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":70,"estimatedTokens":275}}169{"id":"doc-ui_kit-4e8cf387","source":"documentation","title":"UI Kit","url":"https://developer.atlassian.com/platform/forge/ui-kit/","text":"Example:\n```text\nimport { view } from '@forge/bridge';\nconst context = await view.getContext();\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.412Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":13,"estimatedTokens":35}}170{"id":"doc-global_ui_ui_kit_components_eap-f3591364","source":"documentation","title":"global:ui UI Kit components (EAP)","url":"https://developer.atlassian.com/platform/forge/global-ui/ui-kit-components/","text":"Example:\n```javascript\nimport {\n Global,\n Sidebar,\n LinkMenuItem,\n FlyOutMenuItem,\n ExpandableMenuItem,\n ReorderableMenuItems,\n HelpLink,\n PersonalSettings,\n PersonalSettingsItem,\n CreateButton,\n CreateMenuItem,\n Main,\n} from \"@forge/react/global\";\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<Global>\n├─ <HelpLink />\n│\n├─ <CreateButton />\n│ └─ <CreateMenuItem />\n│\n├─ <PersonalSettings />\n│ └─ <PersonalSettingsItem />\n│\n├─ <Sidebar />\n│ ├─ <LinkMenuItem />\n│ │\n│ ├─ <ReorderableMenuItems />\n│ │\n│ ├─ <FlyOutMenuItem />\n│ │ └─ <LinkMenuItem />\n│ │\n│ └─ <ExpandableMenuItem />\n│ └─ <LinkMenuItem />\n│\n└─ <Main />\n └─ {Your app content}\n```\n\nExample:\n```javascript\n<LinkMenuItem label=\"Reports\" href=\"/reports\" icon=\"chart-bar\" />\n```\n\nExample:\n```javascript\nimport React, { useState } from \"react\";\nimport ForgeReconciler, { Button, Text } from \"@forge/react\";\nimport {\n Global,\n Sidebar,\n LinkMenuItem,\n ExpandableMenuItem,\n HelpLink,\n PersonalSettings,\n PersonalSettingsItem,\n ReorderableMenuItems,\n CreateButton,\n CreateMenuItem,\n FlyOutMenuItem,\n Main,\n} from \"@forge/react/global\";\n\nconst initialNotes = [\n { id: \"1\", label: \"Document 1\", href: \"/notes/1\" },\n { id: \"2\", label: \"Document 2\", href: \"/notes/2\" },\n { id: \"3\", label: \"Document 3\", href: \"/notes/3\" },\n];\n\nconst App = () => {\n const [notes, setNotes] = useState(initialNotes);\n const [message, setMessage] = useState(\n \"Select an action from the header or sidebar.\",\n );\n\n const handleCreateDocument = () => {\n setMessage(\"Create document selected.\");\n };\n\n const handleCreateFolder = () => {\n setMessage(\"Create folder selected.\");\n };\n\n return (\n <Global>\n <HelpLink href=\"https://support.atlassian.com/\" />\n\n <CreateButton>\n <CreateMenuItem\n label=\"Create document\"\n onClick={handleCreateDocument}\n />\n <CreateMenuItem label=\"Create folder\" onClick={handleCreateFolder} />\n </CreateButton>\n\n <PersonalSettings>\n <PersonalSettingsItem\n label=\"Notification preferences\"\n onClick={() => {}}\n />\n <PersonalSettingsItem label=\"Preferences\" onClick={() => {}} />\n </PersonalSettings>\n\n <Sidebar>\n <LinkMenuItem label=\"Reports\" href=\"/reports\" icon=\"chart-bar\" />\n <LinkMenuItem label=\"Recent\" href=\"/recent\" />\n\n <ExpandableMenuItem label=\"Projects\">\n <LinkMenuItem label=\"Project Alpha\" href=\"/projects/alpha\" />\n <LinkMenuItem label=\"Project Beta\" href=\"/projects/beta\" />\n </ExpandableMenuItem>\n\n <FlyOutMenuItem label=\"Resources\">\n <LinkMenuItem label=\"Documentation\" href=\"/resources/docs\" />\n <LinkMenuItem label=\"Templates\" href=\"/resources/templates\" />\n </FlyOutMenuItem>\n\n <ReorderableMenuItems\n items={notes}\n onReorder={(items) => {\n setNotes(items);\n setMessage(\"Sidebar order changed.\");\n }}\n onError={(error, currentItems, nextItems) => {\n console.error(\"Failed to persist sidebar order:\", error);\n }}\n />\n </Sidebar>\n\n <Main>\n <Text>{message}</Text>\n <Button onClick={() => setMessage(\"Main content action selected.\")}>\n Run action\n </Button>\n </Main>\n </Global>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n);\n```\n\nExample:\n```typescript\nimport React, { useReducer } from \"react\";\nimport ForgeReconciler, { Frame } from \"@forge/react\";\nimport {\n Global,\n Sidebar,\n LinkMenuItem,\n LinkMenuItemProps,\n Main,\n} from \"@forge/react/global\";\n\ninterface GlobalState {\n sidebar: LinkMenuItemProps[];\n}\n\ntype AddSidebarItemAction = { type: 'ADD_SIDEBAR_ITEM', item: LinkMenuItemProps };\nconst initialState: GlobalState = {\n sidebar: [\n { id: \"dashboard\", label: \"Dashboard\", href: \"/dashboard\" },\n { id: \"bugs\", label: \"Bugs\", href: \"/bugs\" },\n {\n id: \"reports\",\n label: \"Reports\",\n type: \"expandable\",\n children: [\n { id: \"weekly\", label: \"Weekly\", href: \"/reports/weekly\" },\n { id: \"monthly\", label: \"Monthly\", href: \"/reports/monthly\" },\n ],\n },\n ]\n};\n\nconst reducer = (state: GlobalState, action: Action) => {\n switch (action.type) {\n case \"ADD_SIDEBAR_ITEM\":\n return {\n ...state,\n sidebar: [...state.sidebar, action.item]\n };\n default:\n return state;\n }\n};\n\nconst App = () => {\n const [state, dispatch] = useReducer(reducer, initialState);\n\n return (\n <Global>\n <Sidebar>\n {state.sidebar.map((item) => (\n <LinkMenuItem key={item.id} {...item} />\n ))}\n </Sidebar>\n <Main>\n <Frame resource=\"my-resource\" dispatch={dispatch} />\n </Main>\n </Global>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from \"react\";\nimport { view } from \"@forge/bridge\";\n\nconst App = () => {\n const [dispatch, setDispatch] = useState(undefined);\n\n useEffect(() => {\n view\n .getFrameDispatch()\n // If using React to set the dispatch function in state to use, make sure to\n // wrap it in a callback. Otherwise React treats dispatch as a functional updater\n // and tries to execute it.\n .then((dispatch) => setDispatch(() => dispatch));\n }, []);\n\n if (!dispatch) {\n return <div>Loading...</div>;\n }\n\n return (\n <div>\n <button\n onClick={() =>\n dispatch({\n type: \"ADD_SIDEBAR_ITEM\",\n item: {\n id: \"settings\",\n label: \"Settings\",\n href: \"/settings\",\n },\n })\n }\n >\n Add Settings menu item\n </button>\n </div>\n );\n};\n\nexport default App;\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.414Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":271,"estimatedTokens":1468}}171{"id":"doc-jira_full_page_modules-78072965","source":"documentation","title":"Jira full-page modules","url":"https://developer.atlassian.com/platform/forge/jira-full-page-modules/","text":"Example:\n```text\nhttps://<your-site>.atlassian.net/forge-apps/a/<app-id>/e/<forge-environment-id>/r/<route-prefix>/<app-route>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nhttps://example.atlassian.net/forge-apps/a/21e590df-79e6-40dd-9ee4-ba2c7b678f26/e/9f699e8b-33f1-4fa7-bd48-c5fdc44fa4c2/r/ui-kit\n```\n\nExample:\n```javascript\nrouter.navigate({ target: \"module\", moduleKey: \"my-full-page-module\" });\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.416Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":22,"estimatedTokens":106}}172{"id":"doc-internationalization-4335fcee","source":"documentation","title":"Internationalization","url":"https://developer.atlassian.com/platform/forge/internationalization/","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge --version\n```\n\nExample:\n```bash\nnpm install @forge/bridge@latest\n```\n\nExample:\n```bash\nnpm install @forge/react@latest\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.418Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":27,"estimatedTokens":60}}173{"id":"doc-extend_ui_with_custom_options-638e3583","source":"documentation","title":"Extend UI with custom options","url":"https://developer.atlassian.com/platform/forge/extend-ui-with-custom-options/","text":"Example:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n resource: example-resource\n title: Hello world!\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html>\n <body>\n <div>Hello, world!</div>\n </body>\n</html>\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html>\n <body>\n <div>Hello, world!</div>\n <img src=\"./images/image.png\"></img>\n </body>\n</html>\n```\n\nExample:\n```javascript\n// This is the UI Kit part of the app, e.g., `src/frontend/index.jsx` for a Forge app\n\nimport React, { useEffect } from 'react';\nimport ForgeReconciler, { Frame } from '@forge/react';\nimport { events } from '@forge/bridge';\n\nconst App = () => {\n // add your resource to the Frame component\n return <Frame resource=\"example-resource\" />;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\n// This is the UI Kit part of the app, e.g., `src/frontend/index.jsx` for a Forge app\n\nimport React, { useEffect } from 'react';\nimport ForgeReconciler, { Frame } from '@forge/react';\nimport { events } from '@forge/bridge';\n\nconst App = () => {\n useEffect(() => {\n // Send a message to the Frame component\n setTimeout(() => {\n events.emit('MY_FRAME_RESOURCE_DATA', { msg: 'hello' });\n }, 2000);\n }, []);\n return <Frame resource=\"example-frame-resource\" />;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\n// This is the Frame resource part of the app, e.g., `resources/frame-app/src/App.jsx` for a Forge app\n\nimport React, {useEffect, useState } from 'react';\nimport { events } from \"@forge/bridge\";\n\nfunction App() {\n const [msg, setMsg] = useState('');\n\n useEffect(() => {\n let subscription = null;\n const registerMessage = async () => {\n subscription = await events.on('MY_FRAME_RESOURCE_DATA', ({ msg }) => {\n // This will be called whenever the message is sent from UI Kit\n setMsg(msg);\n });\n };\n registerMessage();\n return () => {\n if (subscription) {\n subscription.then(({ unsubscribe }) => unsubscribe());\n }\n };\n }, [setMsg]);\n\n return <b>{msg}</b>;\n}\n```\n\nExample:\n```text\nicon: resource:<resource key>;<relative path to resource>\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n title: Hello world!\n icon: resource:example-resource;icons/issue-panel.svg\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```text\nicon: https://example.com/icon.png\n```\n\nExample:\n```javascript\nimport { invoke } from \"@forge/bridge\";\n\ninvoke(\"exampleFunctionKey\", { example: \"my-invoke-variable\" }).then(setData);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.419Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":144,"estimatedTokens":701}}174{"id":"doc-understanding_ui_modifications-9de49acd","source":"documentation","title":"Understanding UI modifications","url":"https://developer.atlassian.com/platform/forge/understanding-ui-modifications/","text":"Example:\n```javascript\n// Pseudocode\nif (project === 1) {\n summary.hidden()\n}\n\nif (project === 2) {\n priority.hidden()\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// Pseudocode\nswitch (jiraTenantId) {\n case \"alpha.atlassian.net\":\n alphaModifications();\n break;\n case \"beta.atlassian.net\":\n betaModifications();\n break;\n // ...\n}\n```\n\nExample:\n```javascript\n// Pseudocode\nonInit(({api, uiModifications}) => {\n // Variant 1 (NOT RECOMMENDED): Running UIM entity data as code\n eval(uiModifications[0].data);\n // Variant 2 (NOT RECOMMENDED): Using UIM entity data as content of an extra iframe\n iframe.srcdoc = uiModifications[0].data;\n})\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.420Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":44,"estimatedTokens":180}}175{"id":"doc-graphql_authentication-b3814b01","source":"documentation","title":"GraphQL authentication","url":"https://developer.atlassian.com/platform/forge/apis-reference/fetch-api-product.requestgraph/","text":"Example:\n```javascript\nimport api, { route } from '@forge/api';\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nconst result = await api\n .asApp()\n .requestGraph(\n query, variables, headers\n );\nconst status = result.status;\n```\n\nExample:\n```javascript\n// Only authenticated calls are permitted\napi.[asApp | asUser]().requestGraph(query, variables, headers) => Promise<Response>\n```\n\nExample:\n```text\nThis field is in the 'EXPERIMENTAL' lifecycle stage\n```\n\nExample:\n```javascript\nconst query = \"query { me { user { name } } }\";\nconst variables = { \"key1\": \"value1\", \"key2\": \"value2\" };\nconst headers = { \"key1\": \"value1\", \"key2\": \"value2\" };\n\napi.[asApp | asUser]().requestGraph(query, variables, headers);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.421Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":42,"estimatedTokens":186}}176{"id":"doc-promote_an_app_to_staging_or_production-84b9fd1b","source":"documentation","title":"Promote an app to staging or production","url":"https://developer.atlassian.com/platform/forge/staging-and-production-apps/","text":"Example:\n```bash\nforge deploy -e production\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge install -e production\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.423Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":35}}177{"id":"doc-environments-deda4382","source":"documentation","title":"Environments","url":"https://developer.atlassian.com/platform/forge/environments-and-versions/","text":"Example:\n```bash\nforge variables list\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge variables set MY_KEY my-value\n```\n\nExample:\n```bash\nforge variables set --encrypt MY_KEY my-value\n```\n\nExample:\n```bash\nforge variables unset MY_KEY\n```\n\nExample:\n```javascript\nprocess.env.MY_KEY\n```\n\nExample:\n```bash\nforge variables set --environment production --encrypt MY_KEY my-value\n```\n\nExample:\n```bash\nexport FORGE_USER_VAR_MY_KEY=test\n```\n\nExample:\n```javascript\nconst myVar = process.env.MY_KEY // MY_KEY will be \"test\"\n```\n\nExample:\n```text\nforge environments create\n```\n\nExample:\n```text\nCreate a new development environment.\n\n? Enter a name for your environment: my-dev-environment\n\nCreating environment...\n\nCreated DEV my-dev-environment\n\nYou can now tunnel or deploy to this new development environment.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.424Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":65,"estimatedTokens":208}}178{"id":"doc-billing_models-ef0897c1","source":"documentation","title":"Billing models","url":"https://developer.atlassian.com/platform/forge/licensing-overview/","text":"Example:\n```text\napp:\n id: ari:cloud:ecosystem::app/your-app-id\n licensing:\n enabled: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\napp:\n id: ari:cloud:ecosystem::app/your-app-id\n licensing:\n enabled: true\n access:\n userAccess: true\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.425Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":25,"estimatedTokens":68}}179{"id":"doc-debugging-65bed899","source":"documentation","title":"Debugging","url":"https://developer.atlassian.com/platform/forge/debugging/","text":"Example:\n```bash\nINFO 2020-01-22T06:36:33.843Z f93rfad-1234-d920-a98r-9aendas93 Number of comments on this page: 2\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nINFO 2020-01-22T06:36:33.843Z 194b0adb-7362-4f0d-8fc9-6ee950bea769 Number of comments on this page: 2\n App version: 1001000\n Function name: main\n```\n\nExample:\n```bash\ninvocation: 194b0adb-7362-4f0d-8fc9-6ee950bea769\nINFO 06:36:33.843Z Number of comments on this page: 2\n\ninvocation: b3d763d0-2ebd-40fe-88a4-79e8170f8c48\nINFO 01:09:26.988Z Number of comments on this page: 0\n```\n\nExample:\n```bash\n┌─────────────────────────────────────────────────────┐\n│ App version 1001000 │\n│ Invocation ID 194b0adb-7362-4f0d-8fc9-6ee950bea769 │\n│ Function name main │\n└─────────────────────────────────────────────────────┘\n\nINFO 2020-01-22T06:36:33.843Z 194b0adb-7362-4f0d-8fc9-6ee950bea769 Number of comments on this page: 2\n App version: 1001000\n Function name: main\n\n┌─────────────────────────────────────────────────────┐\n│ App version 6 │\n│ Invocation ID b3d763d0-2ebd-40fe-88a4-79e8170f8c48 │\n│ Function name main │\n└─────────────────────────────────────────────────────┘\n\nINFO 2020-02-21T01:09:26.988Z b3d763d0-2ebd-40fe-88a4-79e8170f8c48 Number of comments on this page: 0\n App version: 6\n Function name: main\n```\n\nExample:\n```bash\n✕ Deploying hello-world-app to development...\nℹ Packaging app files\nError: Bundling failed: ./src/index.jsx\nModule build failed (from /usr/local/lib/node_modules/@forge/cli/node_modules/babel-loader/lib/index.js):\nSyntaxError: /Users/alui/src/forge/hello-world-app/src/index.jsx: Unexpected token (19:6)\n 17 | <Fragment>\n 18 | <Text>Number of comments on this page: {comments.length}</Text>\n> 19 | <Image\n | ^\n 20 | src=\"https://media.giphy.com/media/jUwpNzg9IcyrK/source.gif\"\n 21 | alt=\"homer\"\n 22 | />\n at Object.raise (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:7017:17)\n at Object.unexpected (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:8395:16)\n at Object.jsxParseIdentifier (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:3894:12)\n at Object.jsxParseNamespacedName (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:3904:23)\n at Object.jsxParseAttribute (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:3988:22)\n at Object.jsxParseOpeningElementAfterName (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4009:28)\n at Object.jsxParseOpeningElementAfterName (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:6459:18)\n at Object.jsxParseOpeningElementAt (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4002:17)\n at Object.jsxParseElementAt (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4034:33)\n at Object.jsxParseElementAt (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4050:32)\n at Object.jsxParseElement (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4108:17)\n at Object.parseExprAtom (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:4115:19)\n at Object.parseExprSubscripts (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:9259:23)\n at Object.parseMaybeUnary (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:9239:21)\n at Object.parseMaybeUnary (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:6269:20)\n at Object.parseExprOps (/usr/local/lib/node_modules/@forge/cli/node_modules/@babel/parser/lib/index.js:9109:23)\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.426Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":83,"estimatedTokens":993}}180{"id":"doc-contributors-cd66808d","source":"documentation","title":"Contributors","url":"https://developer.atlassian.com/platform/forge/contributors/","text":"Example:\n```text\nforge tunnel -e my-dev-environment\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nforge deploy -e my-dev-environment\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.426Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":38}}181{"id":"doc-app_versions-24234806","source":"documentation","title":"App versions","url":"https://developer.atlassian.com/platform/forge/versions/","text":"Example:\n```text\nforge version list\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n✔ Getting app version list...\n\nℹ Details of a total of [5 major versions] in [development] can be seen below:\n\n┌────────────────┬──────────────────────────┬──────────────────┬─────────────────┬──────────┬────────┬──────────────┬───────────┬─────────┬──────────┬─────────┐\n│ Major Versions │ Deployment Date │ Egresses │ Analytics │ Policies │ Scopes │ Connect keys │ Functions │ Remotes │ Modules │ License │\n├────────────────┼──────────────────────────┼──────────────────┼─────────────────┼──────────┼────────┼──────────────┼───────────┼─────────┼──────────┼─────────┤\n│ 5 │ 2024-12-17T04:38:54.678Z │ fetch.backend: 1 │ fetch.client: 1 │ │ 2 │ 0 │ 1 │ 1 │ macro: 1 │ false │\n│ │ │ │ │ │ │ │ │ │ sql: 1 │ │\n├────────────────┼──────────────────────────┼──────────────────┼─────────────────┼──────────┼────────┼──────────────┼───────────┼─────────┼──────────┼─────────┤\n│ 4 │ 2024-11-05T06:22:00.488Z │ │ fetch.client: 1 │ │ 2 │ 0 │ 1 │ 1 │ macro: 1 │ false │\n├────────────────┼──────────────────────────┼──────────────────┼─────────────────┼──────────┼────────┼──────────────┼───────────┼─────────┼──────────┼─────────┤\n│ 3 │ 2024-11-05T06:18:03.129Z │ │ │ │ 2 │ 0 │ 1 │ 0 │ macro: 1 │ false │\n├────────────────┼──────────────────────────┼──────────────────┼─────────────────┼──────────┼────────┼──────────────┼───────────┼─────────┼──────────┼─────────┤\n│ 2 │ 2023-08-11T04:36:36.473Z │ │ │ │ 0 │ 0 │ 1 │ 0 │ macro: 1 │ false │\n├────────────────┼──────────────────────────┼──────────────────┼─────────────────┼──────────┼────────┼──────────────┼───────────┼─────────┼──────────┼─────────┤\n│ 1 │ 2023-08-11T04:32:53.379Z │ │ │ │ 0 │ 0 │ 0 │ 0 │ │ false │\n└────────────────┴──────────────────────────┴──────────────────┴─────────────────┴──────────┴────────┴──────────────┴───────────┴─────────┴──────────┴─────────┘\n```\n\nExample:\n```text\nforge version details --major-version [version]\n```\n\nExample:\n```text\n? Select option: Show all properties\n✔ Getting app version details...\n\nℹ App [3] in [development] includes the following:\n\n┌─────────────────┬───────────────────────────────────────────────────────────────────────────────┐\n│ Property │ Details │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ analytics │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ connect keys │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ deployment date │ 2024-11-05T06:18:03.129Z │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ egress │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ functions │ - [main]: is configured with the following properties: │\n│ │ runtime: nodejs24.x │\n│ │ handler: index.run │\n│ │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ license │ No │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ modules │ macro │\n│ │ - [test-runtime-v2-hello-world]: is configured with the following properties: │\n│ │ title: test-runtime-v2 │\n│ │ function: main │\n│ │ description: Inserts Hello world! │\n│ │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ policies │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ remotes │ │\n├─────────────────┼───────────────────────────────────────────────────────────────────────────────┤\n│ scopes │ - import:import-configuration:cmdb │\n│ │ - read:servicedesk-request │\n└─────────────────┴───────────────────────────────────────────────────────────────────────────────┘\n```\n\nExample:\n```text\nforge version compare --version1 <version> --version2 <version>\n```\n\nExample:\n```text\n✔ Comparing app versions...\n\nℹ Comparison between app versions [3 and 5] in [development] is shown below:\n\n┌─────────────────┬──────────────────────────────────────────────────┬──────────────────────────────────────────────────┐\n│ Property │ Version 1 [3] │ Version 2 [5] │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ analytics │ [] │ [ │\n│ │ │ { │\n│ │ │ \"addresses\": [ │\n│ │ │ \"https://sentry.com\" │\n│ │ │ ], │\n│ │ │ \"type\": \"fetch.client\", │\n│ │ │ \"inScopeEUD\": false │\n│ │ │ } │\n│ │ │ ] │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ deployment date │ \"2024-11-05T06:18:03.129Z\" │ \"2024-12-17T04:38:54.678Z\" │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ egress │ [] │ [ │\n│ │ │ { │\n│ │ │ \"addresses\": [ │\n│ │ │ \"https://google.com\" │\n│ │ │ ], │\n│ │ │ \"type\": \"fetch.backend\" │\n│ │ │ } │\n│ │ │ ] │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ functions │ [ │ [ │\n│ │ { │ { │\n│ │ \"handler\": \"index.run\", │ \"handler\": \"index.run\", │\n│ │ \"key\": \"main\", │ \"key\": \"main\", │\n│ │ │ │\n│ │ \"runtimeName\": \"nodejs24.x\" │ \"runtimeName\": \"nodejs20.x\" │\n│ │ │ │\n│ │ } │ } │\n│ │ ] │ ] │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ modules │ [ │ [ │\n│ │ { │ { │\n│ │ \"items\": [ │ \"items\": [ │\n│ │ { │ { │\n│ │ \"key\": \"test-runtime-v2-hello-world\", │ \"key\": \"test-runtime-v2-hello-world\", │\n│ │ \"properties\": { │ \"properties\": { │\n│ │ \"description\": \"Inserts Hello world!\", │ \"description\": \"Inserts Hello world!\", │\n│ │ \"function\": \"main\", │ \"function\": \"main\", │\n│ │ \"title\": \"test-runtime-v2\" │ \"title\": \"test-runtime-v2\" │\n│ │ }, │ }, │\n│ │ \"type\": \"xen:macro\" │ \"type\": \"xen:macro\" │\n│ │ } │ } │\n│ │ ], │ ], │\n│ │ \"type\": \"macro\" │ \"type\": \"macro\" │\n│ │ }, │ }, │\n│ │ │ │\n│ │ ] │ { │\n│ │ │ \"items\": [ │\n│ │ │ { │\n│ │ │ \"key\": \"test-sql-module\", │\n│ │ │ \"properties\": { │\n│ │ │ \"dareCompliant\": true, │\n│ │ │ \"engine\": \"mysql\" │\n│ │ │ }, │\n│ │ │ \"type\": \"core:sql\" │\n│ │ │ } │\n│ │ │ ], │\n│ │ │ \"type\": \"sql\" │\n│ │ │ } │\n│ │ │ │\n│ │ │ ] │\n├─────────────────┼──────────────────────────────────────────────────┼──────────────────────────────────────────────────┤\n│ remotes │ [] │ [ │\n│ │ │ { │\n│ │ │ \"baseUrl\": \"https://backend.example.com\", │\n│ │ │ \"key\": \"remote-backend\", │\n│ │ │ \"operations\": [ │\n│ │ │ \"fetch\" │\n│ │ │ ] │\n│ │ │ } │\n│ │ │ ] │\n└─────────────────┴──────────────────────────────────────────────────┴──────────────────────────────────────────────────┘\n```\n\nExample:\n```text\nforge install --site https://example.atlassian.net --product jira --major-version 2\n```\n\nExample:\n```text\nforge install --site https://example.atlassian.net --product jira --major-version 2 --environment development --non-interactive\n```\n\nExample:\n```bash\nforge deploy --major-version [version] --verbose\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.428Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":184,"estimatedTokens":3924}}182{"id":"doc-key_value_store-95aca596","source":"documentation","title":"Key-value store","url":"https://developer.atlassian.com/platform/forge/storage-reference/kvs/","text":"Example:\n```javascript\nimport { kvs, WhereConditions } from '@forge/kvs';\n\nawait kvs.set('foo', 'bar');\nawait kvs.get('foo');\nawait kvs.query()\n .where('key', WhereConditions.beginsWith('fo'))\n .getMany();\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n```\n\nExample:\n```javascript\n// Inserts some dummy data\nawait kvs.set('account.1', 'account 1');\nawait kvs.set('account.2', 'account 2');\nawait kvs.set('account.3', 'account 3');\nawait kvs.set('account.4', 'account 4');\nawait kvs.set('account.5', 'account 5');\nawait kvs.set('user.1', 'user 1');\nawait kvs.set('user.2', 'user 2');\nawait kvs.set('user.3', 'user 3');\nawait kvs.set('user.4', 'user 4');\n\n// Fetch up to 2 results, but will return \"account.1\" and \"account.2\" and a cursor to next page\nconst { nextCursor: firstCursor } = await kvs\n .query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .getMany();\n\n// Fetch up to 2 results after the cursor, but will return \"account.3\" and \"account.4\" and a cursor to next page\nconst { nextCursor: secondCursor } = await kvs.query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .cursor(firstCursor)\n .getMany();\n\n// Fetch up to 2 results, but will only return \"account.5\" and no cursor\nconst { nextCursor: undefinedCursor } = await kvs.query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .cursor(secondCursor)\n .getMany();\n```\n\nExample:\n```javascript\nimport { kvs, WhereConditions } from '@forge/kvs';\n\n// Nested entities\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\n\nconst results = await kvs\n .query()\n .where('key', WhereConditions.beginsWith('survey-response#1#'))\n .limit(10)\n .getMany();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.429Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":77,"estimatedTokens":478}}183{"id":"doc-manage_the_1_000_value_limit_in_custom_jql_funct-02a8b64c","source":"documentation","title":"Manage the 1,000 value limit in custom JQL functions","url":"https://developer.atlassian.com/platform/forge/working-around-jql-1000-limit/","text":"Example:\n```javascript\n// Function: relatedIssues(parentKey)\n// Returns: ALL related issues for a parent (might be 5000+)\nfunction relatedIssues(parentKey) {\n const issues = getAllRelatedIssues(parentKey);\n return {\n jql: `key IN (${issues.join(', ')})` // ❌ Fails if > 1000\n };\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// Function: relatedIssues(parentKey, status, dateRange)\n// Returns: Filtered related issues (< 1000)\nfunction relatedIssues(parentKey, status, dateRange) {\n const issues = getFilteredRelatedIssues(parentKey, status, dateRange);\n return {\n jql: `key IN (${issues.join(', ')})` // ✅ Under 1000\n };\n}\n```\n\nExample:\n```text\nissue in relatedIssues(\"PARENT-123\")\n```\n\nExample:\n```text\nissue in relatedIssues(\"PARENT-123\", \"Open\", \"-30d\") AND priority = High\n```\n\nExample:\n```javascript\n// Function: relatedIssues(parentKey, page, pageSize)\nfunction relatedIssues(parentKey, page = 1, pageSize = 500) {\n const allIssues = getAllRelatedIssues(parentKey);\n const totalResults = allIssues.length;\n const totalPages = Math.ceil(totalResults / pageSize);\n \n // Calculate pagination\n const startIndex = (page - 1) * pageSize;\n const endIndex = startIndex + pageSize;\n const pageResults = allIssues.slice(startIndex, endIndex);\n \n if (pageResults.length === 0) {\n return {\n error: `No results on page ${page}. Total pages: ${totalPages}`,\n storeErrorAsPrecomputation: true\n };\n }\n \n return {\n jql: `key IN (${pageResults.join(', ')})` // ✅ Max 500 per page\n };\n}\n```\n\nExample:\n```text\n-- Page 1 (results 1-500)\nissue in relatedIssues(\"PARENT-123\", 1, 500)\n\n-- Page 2 (results 501-1000)\nissue in relatedIssues(\"PARENT-123\", 2, 500)\n\n-- Page 3 (results 1001-1500)\nissue in relatedIssues(\"PARENT-123\", 3, 500)\n```\n\nExample:\n```text\nissue in relatedIssues(\"PARENT-123\", 1, 500) \n OR issue in relatedIssues(\"PARENT-123\", 2, 500)\n```\n\nExample:\n```json\n{\n \"jql\": \"key IN (ISSUE-1, ISSUE-2, ISSUE-3, ..., ISSUE-1500)\"\n}\n```\n\nExample:\n```json\n{\n \"jql\": \"project = XTP AND labels = test-execution AND 'Test Plan' = PLAN-123\"\n}\n```\n\nExample:\n```javascript\n// Instead of returning a list of keys\nfunction relatedIssues(parentKey) {\n // ❌ OLD WAY - Returns explicit list\n // const issues = getAllRelatedIssues(parentKey);\n // return { jql: `key IN (${issues.join(', ')})` };\n \n // ✅ NEW WAY - Query by custom field\n return {\n jql: `\"Parent Reference\" = \"${parentKey}\" AND type = \"Sub-task\"`\n };\n}\n```\n\nExample:\n```javascript\nfunction relatedIssues(parentKey) {\n // When issues are created, tag them with labels\n return {\n jql: `labels = \"parent-${parentKey}\" AND type = \"Related Issue\"`\n };\n}\n```\n\nExample:\n```javascript\nfunction relatedIssues(parentKey) {\n // Query issues linked to the parent\n return {\n jql: `issue in linkedIssues(\"${parentKey}\", \"relates to\")`\n };\n}\n```\n\nExample:\n```javascript\nfunction complexQuery(projectKey, category, status) {\n return {\n jql: `\n project = ${projectKey}\n AND \"Category\" = \"${category}\"\n AND status = ${status}\n AND created >= -90d\n `.trim()\n };\n}\n```\n\nExample:\n```text\nissue in (\n \"Parent Reference\" = \"PARENT-123\" AND type = \"Sub-task\"\n)\n```\n\nExample:\n```javascript\nfunction relatedIssues(parentKey, page = null, status = null) {\n // Build base query using JQL conditions\n let jqlParts = [\n `\"Parent Reference\" = \"${parentKey}\"`,\n `type = \"Sub-task\"`\n ];\n \n // Add filters if provided to narrow results\n if (status) {\n jqlParts.push(`status = \"${status}\"`);\n }\n \n const baseJql = jqlParts.join(' AND ');\n \n // If no pagination needed, return query directly\n if (page === null) {\n return { jql: baseJql };\n }\n \n // If pagination requested, fall back to explicit enumeration\n const results = executeQuery(baseJql);\n const pageSize = 500;\n const startIndex = (page - 1) * pageSize;\n const pageResults = results.slice(startIndex, startIndex + pageSize);\n \n return {\n jql: `key IN (${pageResults.map(r => r.key).join(', ')})`\n };\n}\n```\n\nExample:\n```text\nissue in relatedIssues(\"PARENT-123\", null, \"Open\")\n```\n\nExample:\n```text\nissue in relatedIssues(\"PARENT-123\", 1)\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.431Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":198,"estimatedTokens":1043}}184{"id":"doc-user_based_billing_for_your_app-bc4358cd","source":"documentation","title":"User-based billing for your app","url":"https://developer.atlassian.com/platform/forge/adopt-user-based-billing/","text":"Example:\n```text\nmodules:\n ...\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n id: ari:cloud:ecosystem::app/22a5950e-2b5a-4cb0-8fc0-cf9a6cc64b33\n licensing:\n enabled: true\n access:\n userAccess: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nuserAccess {\n enabled: boolean //indicates whether this installation adopts user-based billing or not\n hasAccess: boolean //indicates whether this user has access to the app\n }\n```\n\nExample:\n```javascript\nimport { useProductContext } from '@forge/react';\nconst context = useProductContext();\nconst hasAccess = context?.userAccess?.hasAccess;\nconst enabledUserAccessDecoupling = context?.userAccess?.enabled\n```\n\nExample:\n```javascript\nimport { view } from '@forge/bridge';\nconst context = await view.getContext();\nconst hasAccess = context?.userAccess?.hasAccess;\nconst enabledUserAccessDecoupling = context?.userAccess?.enabled\n```\n\nExample:\n```javascript\nresolver.define('GET projects', async ({ payload, context }) => {\n if (!context.userAccess?.hasAccess) {\n return {\n 'error': 'user does not have access',\n };\n }\n // Additional logic...\n});\n```\n\nExample:\n```text\nFIT: {\n app: {\n...\n license: {\n active: true,\n }\n },\n context: {\n ...\n userAccess: { \n hasAccess: true,\n enabled: true\n }\n },\n...\n}\n```\n\nExample:\n```bash\nforge install --license-modes user-access --users-with-access <list_of_aaid>\n```\n\nExample:\n```text\n➜ forge-test-arm forge whoami\nLogged in as Khanh Nguyen (XXX@atlassian.com)\nAccount ID: aaid\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.432Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":92,"estimatedTokens":402}}185{"id":"doc-tunneling-3355e74c","source":"documentation","title":"Tunneling","url":"https://developer.atlassian.com/platform/forge/tunneling/","text":"Example:\n```bash\nforge tunnel\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where\nyour app is installed in the specific development environment. You will not see requests from other\nusers.\n\nPress Ctrl+C to cancel.\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n\nListening for requests...\n```\n\nExample:\n```text\nINFO 17:34:04.955 Count of objects in test array: 0\n```\n\nExample:\n```text\ntunnel:\n port: <YOUR_PORT_HERE>\n```\n\nExample:\n```text\nresources:\n - key: main\n path: static/hello-world/build\n tunnel:\n port: 3000\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install list\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```bash\nnvm use 22\n```\n\nExample:\n```bash\nforge lint\n```\n\nExample:\n```bash\nrm -rf node_modules && npm install\nforge tunnel\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.434Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":80,"estimatedTokens":232}}186{"id":"doc-custom_entity_store-139de5ba","source":"documentation","title":"Custom Entity Store","url":"https://developer.atlassian.com/platform/forge/storage-reference/entities/","text":"Example:\n```javascript\nimport kvs, { WhereConditions, Sort } from \"@forge/kvs\";\n\nawait kvs\n .entity('employee')\n .query()\n .index('by-age')\n .where(WhereConditions.greaterThan(30))\n .sort(Sort.DESC)\n .getMany()\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n```\n\nExample:\n```javascript\n// Inserts some dummy data\nawait kvs.set('account.1', 'account 1');\nawait kvs.set('account.2', 'account 2');\nawait kvs.set('account.3', 'account 3');\nawait kvs.set('account.4', 'account 4');\nawait kvs.set('account.5', 'account 5');\nawait kvs.set('user.1', 'user 1');\nawait kvs.set('user.2', 'user 2');\nawait kvs.set('user.3', 'user 3');\nawait kvs.set('user.4', 'user 4');\n\n// Fetch up to 2 results, but will return \"account.1\" and \"account.2\" and a cursor to next page\nconst { nextCursor: firstCursor } = await kvs\n .query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .getMany();\n\n// Fetch up to 2 results after the cursor, but will return \"account.3\" and \"account.4\" and a cursor to next page\nconst { nextCursor: secondCursor } = await kvs.query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .cursor(firstCursor)\n .getMany();\n\n// Fetch up to 2 results, but will only return \"account.5\" and no cursor\nconst { nextCursor: undefinedCursor } = await kvs.query()\n .limit(2)\n .where('key', WhereConditions.beginsWith('account.'))\n .cursor(secondCursor)\n .getMany();\n```\n\nExample:\n```javascript\nimport { kvs, WhereConditions } from '@forge/kvs';\n\n// Nested entities\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\nawait kvs.set('survey-responses#1#{UUID}', { });\n\nconst results = await kvs\n .query()\n .where('key', WhereConditions.beginsWith('survey-response#1#'))\n .limit(10)\n .getMany();\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.434Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":79,"estimatedTokens":481}}187{"id":"doc-external_authentication-e24270d5","source":"documentation","title":"External authentication","url":"https://developer.atlassian.com/platform/forge/runtime-reference/external-fetch-api/","text":"Example:\n```javascript\nimport api from \"@forge/api\";\n\nconst response = await api\n .asUser()\n .withProvider(\"google\", \"google-apis\")\n .fetch(\"/userinfo/v2/me\");\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nexport interface ExternalAuthAccount {\n id: string;\n displayName: string;\n avatarUrl?: string;\n scopes: string[];\n}\nexport interface ExternalAuthAccountMethods {\n hasCredentials: (scopes?: string[]) => Promise<boolean>;\n requestCredentials: (scopes?: string[]) => Promise<boolean>;\n fetch: FetchMethodAllowingRoute;\n getAccount: () => Promise<ExternalAuthAccount | undefined>;\n}\nexport interface ExternalAuthFetchMethods extends ExternalAuthAccountMethods {\n listAccounts: () => Promise<ExternalAuthAccount[]>;\n asAccount: (externalAccountId: string) => ExternalAuthAccountMethods;\n}\nexport interface ExternalAuthFetchMethodsProvider {\n withProvider: (\n provider: string,\n remoteName?: string\n ) => ExternalAuthFetchMethods;\n}\n```\n\nExample:\n```javascript\napi.asUser().withProvider(provider).hasCredentials(scopes?: string[]) => Promise<boolean>\n```\n\nExample:\n```javascript\nconst [data] = useState(async () => {\n const google = api.asUser().withProvider('google', 'google-apis');\n if (!await google.hasCredentials()) {\n await google.requestCredentials();\n }\n const response = await google.fetch('/userinfo/v2/me');\n ...\n})\n```\n\nExample:\n```javascript\napi.asUser().withProvider(provider).requestCredentials(scopes?: string[])\n```\n\nExample:\n```javascript\nconst google = api.asUser().withProvider(\"google\", \"google-apis\");\nif (!(await google.hasCredentials())) {\n await google.requestCredentials();\n}\n\n//another example with scopes provided\nconst profileScopes = [\n \"https://www.googleapis.com/auth/userinfo.profile\",\n \"https://www.googleapis.com/auth/userinfo.email\",\n];\nif (!(await google.hasCredentials(profileScopes))) {\n //ask for profileScopes and any scope that user already granted\n await google.requestCredentials([\n ...new Set([...profileScopes, ...google.getAccount().scopes]),\n ]);\n}\n``;\n```\n\nExample:\n```javascript\nfetch(url[, options])\n```\n\nExample:\n```javascript\napi.asUser().withProvider(provider).getAccount();\n```\n\nExample:\n```javascript\napi.asUser().withProvider(provider).listAccounts();\n```\n\nExample:\n```javascript\napi.asUser().withProvider(provider).asAccount(externalAccountId: string)\n```\n\nExample:\n```javascript\n// example on how to switch to account whose displayName is `example@gmail.com` to make API call\nconst selectedDisplayName = \"example@gmail.com\";\nconst selectedAccount = (await google.listAccounts()).find(\n (account) => account.displayName == selectedDisplayName\n);\nconst response = await google\n .asAccount(selectedAccount.id)\n .fetch(\"/userinfo/v2/me\");\n```\n\nExample:\n```javascript\nAuthProfile({ id, displayName, avatarUrl });\n```\n\nExample:\n```typescript\ninterface ProfileRetrieverParameters {\n status: number;\n body: {\n [key: string]: any;\n };\n}\n\nexport const retriever = (response: ProfileRetrieverParameters) => {\n const { status, body: externalProfile } = response;\n\n if (status === 200) {\n return new AuthProfile({\n id: externalProfile.user.id,\n displayName: externalProfile.user.name,\n });\n } else {\n // handle error\n }\n};\n```\n\nExample:\n```javascript\nwithProvider(provider[, remoteName])\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.435Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":151,"estimatedTokens":833}}188{"id":"doc-configuring_scheduled_triggers_to_invoke_a_remot-1cdaecb6","source":"documentation","title":"Configuring scheduled triggers to invoke a remote backend","url":"https://developer.atlassian.com/platform/forge/remote/scheduled-triggers/","text":"Example:\n```text\nmodules: \n scheduledTrigger:\n - key: remote-scheduled-trigger-node\n endpoint: remote-trigger-node\n interval: hour\n endpoint:\n - key: remote-trigger-node\n remote: remote-app-node\n route:\n path: /frc-trigger\n auth:\n appSystemToken:\n enabled: true\npermissions:\n scopes:\n - read:app-system-token \n - read:confluence-content:summary # relevant app scopes must be included when oauth tokens are enabled\nremotes:\n - key: remote-app-node\n baseUrl: https://forge-remote-refapp-nodejs.services.atlassian.com\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.436Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":31,"estimatedTokens":161}}189{"id":"doc-sending_atlassian_app_and_lifecycle_events_to_a_-7520ac2b","source":"documentation","title":"Sending Atlassian app and lifecycle events to a remote","url":"https://developer.atlassian.com/platform/forge/remote/sending-product-events/","text":"Example:\n```text\nmodules: \n trigger:\n - key: remote-comment-trigger-node\n endpoint: remote-events-node\n events:\n - avi:confluence:created:comment\n endpoint:\n - key: remote-events-node\n remote: remote-app-node\n route:\n path: /frc-event\n auth:\n appSystemToken:\n enabled: true\npermissions:\n scopes:\n - read:app-system-token\n - read:confluence-content.summary \nremotes:\n - key: remote-app-node\n baseUrl: https://forge-remote-refapp-nodejs.services.atlassian.com\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"payload\": {\n ...\n \"retryContext\": {\n \"retryData\": null,\n \"retryCount\": 2,\n \"retryReason\": \"INVALID_REMOTE_INVOCATION_ERROR\"\n }\n ...\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.437Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":47,"estimatedTokens":198}}190{"id":"doc-expose_forge_app_rest_apis_preview-43812ab8","source":"documentation","title":"Expose Forge app REST APIs (Preview)","url":"https://developer.atlassian.com/platform/forge/expose-forge-app-rest-apis/","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ntitle: Custom Scopes Data\n$schema: http://json-schema.org/draft-07/schema#\n$comment: >-\n Note: schema.json is generated from schema.yaml\ntype: object\nadditionalProperties: false\nproperties:\n version:\n # This is a file version maintained by Atlassian. It cannot be changed.\n type: number\n exclusiveMinimum: 0\n enum:\n - 1\n scopes:\n type: object\n minProperties: 1\n maxProperties: 20\n additionalProperties: false\n patternProperties:\n \"^[-.:_a-zA-Z0-9]{1,93}:custom$\":\n type: object\n additionalProperties: false\n properties:\n description:\n type: string\n minLength: 1\n maxLength: 2048\n pattern: \"^\\\\S[\\\\s\\\\S]*\\\\S$|^\\\\S$\" # Allow newlines in the middle.\n displayName:\n type: string\n minLength: 1\n maxLength: 100\n pattern: \"^\\\\S.*\\\\S$|^\\\\S$\"\n $comment: >-\n Scope name suitable for displaying to end users.\n required:\n - description\nrequired:\n - version\n - scopes\n```\n\nExample:\n```text\nversion: 1\nscopes:\n read:employee:custom:\n displayName: Read Employee Info\n description: >-\n Read employee information such as name, date of joining, etc.\n write:employee:custom:\n displayName: Edit Employee Info\n description: >-\n Edit information related to an employee such as name, dob, payroll info, etc.\n```\n\nExample:\n```text\n$ forge custom-scopes create -e <env> -f <location of custom-scopes.yaml file>\n```\n\nExample:\n```text\n$ forge custom-scopes list -e <env>\n```\n\nExample:\n```text\n✔ Completed fetching custom scopes\n┌───────────────────────┬────────────────────┐\n│ Name │ Display name │\n├───────────────────────┼────────────────────┤\n│ read:employee:custom │ Read Employee Info │\n├───────────────────────┼────────────────────┤\n│ write:employee:custom │ Edit Employee Info │\n└───────────────────────┴────────────────────┘\n```\n\nExample:\n```text\nmodules:\n apiRoute:\n - key: employee-api-1\n path: /employeeName\n operation: GET\n function: handler1\n accept:\n - application/json \n scopes:\n - read:employee:custom\n - key: employee-api-2\n path: /employeeName\n operation: POST\n function: handler2\n accept:\n - application/json \n scopes:\n - read:employee:custom\n - write:employee:custom\n```\n\nExample:\n```text\nfunction:\n function:\n - key: handler1\n handler: index.handler1\n - key: handler2\n handler: index.handler2\n```\n\nExample:\n```text\nexport const handler1 = (req) => {\n return {\n \"statusCode\": 200,\n \"customHeader\": \"done\",\n \"body\": \"Received GET request /employeeName: ${JSON.stringify(req)}\"\n }\n}\n\nexport const handler2 = (req) => {\n return {\n \"statusCode\": 201,\n \"customHeader\": \"done\",\n \"body\": \"Received POST request /employeeName: ${JSON.stringify(req)}\"\n }\n}\n```\n\nExample:\n```text\nexport { handler1, handler2 } from './resolvers';\n```\n\nExample:\n```text\nforge deploy -e <env>\n```\n\nExample:\n```text\nforge install -e <env>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.438Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":159,"estimatedTokens":805}}191{"id":"doc-accessing_forge_storage_from_a_remote_via_rest-dbce08ff","source":"documentation","title":"Accessing Forge storage from a remote via REST","url":"https://developer.atlassian.com/platform/forge/remote/accessing-storage-graphql/","text":"Example:\n```json\n{\n \"query\": \"...\",\n \"operationName\": \"...\",\n \"variables\": { \"myVariable\": \"someValue\", ... }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"data\": { ... },\n \"errors\": [ ... ]\n}\n```\n\nExample:\n```text\nquery badGet {\n appStoredEntity(\n key: \"\",\n encrypted: false\n ) {\n value\n }\n}\n```\n\nExample:\n```json\n{\n \"errors\": [\n {\n \"path\": [\n \"appStoredEntity\"\n ],\n \"locations\": [\n {\n \"line\": 2,\n \"column\": 3\n }\n ],\n \"message\": \"Key was 0 characters, but should be at least 1 characters\",\n \"extensions\": {\n \"statusCode\": 400,\n \"errorType\": \"KEY_TOO_SHORT\"\n }\n }\n ],\n \"data\": {\n \"appStoredEntity\": null\n }\n}\n```\n\nExample:\n```text\nquery nameOfQuery($variable: VariableType) {\n fieldRequiringArgs(variable: $variable) {\n fieldA\n fieldB\n }\n}\n```\n\nExample:\n```text\n{\n \"variable\" : {\n \"nestedVariable\": \"value\"\n }\n}\n```\n\nExample:\n```text\nquery nameOfQuery {\n fieldRequiringArgs(variable: {\n nestedVariable: \"value\"\n }) {\n fieldA\n fieldB\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"fieldRequiringArgs\": {\n \"fieldA\": \"valueA\",\n \"fieldB\": \"valueB\"\n }\n }\n}\n```\n\nExample:\n```text\nmutation forge_app_setExample($input: SetAppStoredEntityMutationInput!) {\n appStorage {\n setAppStoredEntity(input: $input) {\n success\n errors {\n message\n extensions {\n errorType\n statusCode\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"input\": {\n \"contextAri\": \"contextAri associated with the request\",\n \"key\": \"example key\",\n \"value\": \"example value\",\n \"encrypted\": true | false\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStorage\": {\n \"setAppStoredEntity\": {\n \"success\": true,\n \"errors\": null\n }\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStorage\": {\n \"setAppStoredEntity\": {\n \"success\": false,\n \"errors\": [\n {\n \"message\": \"Key \\\"test-key@\\\" should match the pattern \\\"/^(?!\\\\s+$)[a-zA-Z0-9:._\\\\s-#]+$/\\\"\",\n \"extensions\": {\n \"errorType\": \"INVALID_KEY\"\n }\n }\n ]\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"input\": {\n \"key\": \"example-key\",\n \"value\": \"hello world\",\n \"encrypted\": false\n }\n}\n```\n\nExample:\n```text\nquery forge_app_getExample($key: ID!, $encrypted: Boolean!) {\n appStoredEntity(\n key: $key,\n encrypted: $encrypted\n ) {\n value\n }\n}\n```\n\nExample:\n```text\n{\n \"key\": \"example-key\",\n \"encrypted\": true | false\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStoredEntity\": {\n \"value\": \"example-value\"\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStoredEntity\": {\n \"value\": null\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"key\": \"example-key\",\n \"encrypted\": false\n}\n```\n\nExample:\n```text\nquery forge_app_queryExample($where: [AppStoredEntityFilter!]!, $first: Int!, $after: String) {\n appStoredEntities(where: $where, first: $first, after: $after) {\n edges {\n node {\n value\n key\n }\n cursor\n }\n pageInfo {\n hasNextPage\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"where\": {\n \"field\": \"key\",\n \"condition\": \"STARTS_WITH\",\n \"value\": \"example key\"\n },\n \"first\": 10,\n \"encrypted\": true | false,\n \"after\": \"Optional, used for paginated requests. Use the cursor field from the previous query to fetch the next page.\"\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStoredEntities\": {\n \"edges\": [\n {\n \"node\": {\n \"value\": \"test-value-1\",\n \"key\": \"test-key-1\"\n },\n \"cursor\": \"FAJ0ZXN0LWtleS0xAA==\"\n },\n {\n \"node\": {\n \"value\": \"test-value-2\",\n \"key\": \"test-key-2\"\n },\n \"cursor\": \"FAJ0ZXN0LWtleS0yAA==\"\n }\n ],\n \"pageInfo\": {\n \"hasNextPage\": true\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"where\": {\n \"field\": \"key\",\n \"condition\": \"STARTS_WITH\",\n \"value\": \"example\"\n },\n \"first\": 10,\n \"encrypted\": false,\n \"after\": \"FAJ0ZXN0LWtleS0yAA==\"\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStoredEntities\": {\n \"edges\": [\n {\n \"node\": {\n \"value\": \"test-value-2\",\n \"key\": \"example-2\"\n },\n \"cursor\": \"FAJ0ZXN0LWtleS0yAA==\"\n },\n {\n \"node\": {\n \"value\": \"test-value-3\",\n \"key\": \"example-3\"\n },\n \"cursor\": \"FAJ0ZXN0LWtleS0zAA==\"\n }\n ]\n }\n }\n}\n```\n\nExample:\n```text\nmutation forge_app_deleteExample($input: DeleteAppStoredEntityMutationInput!) {\n appStorage {\n deleteAppStoredEntity(input: $input) {\n success\n errors {\n message\n extensions {\n errorType\n statusCode\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"input\": {\n \"key\": \"example key\",\n \"encrypted\" true | false\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStorage\": {\n \"deleteAppStoredEntity\": {\n \"success\": true,\n \"errors\": null\n }\n }\n }\n}\n```\n\nExample:\n```json\n{\n \"data\": {\n \"appStorage\": {\n \"deleteAppStoredEntity\": {\n \"success\": false,\n \"errors\": [\n {\n \"message\": \"Key \\\"test-key-@\\\" should match the pattern \\\"/^(?!\\\\s+$)[a-zA-Z0-9:._\\\\s-#]+$/\\\"\",\n \"extensions\": {\n \"errorType\": \"INVALID_KEY\",\n \"statusCode\": 400\n }\n }\n ]\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"input\": {\n \"key\": \"example key\",\n \"encrypted\" false\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.439Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":29,"totalLines":407,"estimatedTokens":1491}}192{"id":"doc-accessing_forge_storage_from_a_remote-b882fc20","source":"documentation","title":"Accessing Forge storage from a Remote","url":"https://developer.atlassian.com/platform/forge/remote/accessing-storage/","text":"Example:\n```typescript\n'use strict'\nimport fetch from 'node-fetch';\nconst apiBaseUrl = 'https://api.atlassian.com/forge/storage/kvs';\nexport async function fetchFromStorage(token, apiBaseUrl) {\n const headers = {\n 'Accept': 'application/json',\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json'\n }\n return await fetch(`${apiBaseUrl}/v1/get`, { method: \"POST\", headers });\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n code: string; // \"INVALID_KEY_FORMAT\"\n message: string; // \"Invalid key format\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.440Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":30,"estimatedTokens":141}}193{"id":"doc-providers-cb02a63a","source":"documentation","title":"Providers","url":"https://developer.atlassian.com/platform/forge/manifest-reference/providers/","text":"Example:\n```text\nproviders:\n auth:\n - key: google\n name: Google\n scopes:\n - 'profile'\n - 'https://www.googleapis.com/auth/userinfo.email'\n type: oauth2\n clientId: EXAMPLE\n remotes:\n - google-apis\n bearerMethod: authorization-header\n actions:\n authorization:\n remote: google-account\n path: /o/oauth2/v2/auth\n exchange:\n remote: google-oauth\n path: /token\n refreshToken:\n remote: google-oauth\n path: /token\n revokeToken:\n remote: google-oauth\n path: /revoke\n retrieveProfile:\n remote: google-apis\n path: /userinfo/v2/me\n resolvers:\n id: id\n displayName: email\n avatarUrl: picture\nremotes:\n - key: google-apis\n baseUrl: https://www.googleapis.com\n - key: google-account\n baseUrl: https://accounts.google.com\n - key: google-oauth\n baseUrl: https://oauth2.googleapis.com\npermissions:\n external:\n fetch:\n backend:\n - https://www.googleapis.com\n - https://accounts.google.com\n - https://oauth2.googleapis.com\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nbearerMethod:\n type: authorization-header\n prefix: 'token '\n parameter: 'X-Custom-Header'\n```\n\nExample:\n```text\noverrides:\n headers:\n foo: bar # static value\n 'x-client-id': '{{client_id}}' # template value\n 'x-authorization': 'Basic {{http_basic_auth_credentials}}' # template with string interpolation\n body:\n # static values:\n client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer'\n # templates:\n client_id: '{{client_id}}'\n client_assertion: '{{client_secret}}'\n assertion: '{{authorization_code}}'\n redirect_uri: '{{redirect_uri}}'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.441Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":83,"estimatedTokens":474}}194{"id":"doc-permissions-f6feee1e","source":"documentation","title":"Permissions","url":"https://developer.atlassian.com/platform/forge/manifest-reference/permissions","text":"Example:\n```text\npermissions:\n scopes:\n - \"read:confluence-content.summary\"\n - \"write:jira-work\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes: []\n```\n\nExample:\n```text\npermissions:\n scopes:\n read:confluence-content.summary:\n allowImpersonation: true\n write:confluence-content:\n allowImpersonation: false\n write:jira-work: {}\n```\n\nExample:\n```text\npermissions:\n content:\n scripts:\n - \"unsafe-hashes\"\n```\n\nExample:\n```text\npermissions:\n content:\n styles:\n - \"unsafe-inline\"\n```\n\nExample:\n```text\n^(\\*\\.)?[.a-zA-Z0-9_\\-\\/:~#%]+$\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - \"*.example-dev.com\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - \"*.example-dev.com\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: \"*.example-dev.com\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - address: \"*.example-dev.com\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - \"https://api.example.com\"\nremotes:\n - key: api-backend\n baseUrl: \"https://api.example.com\"\n operations:\n - fetch\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - remote: remote-backend\nremotes:\n - key: remote-backend\n baseUrl: \"https://example-dev.io\"\n operations:\n - fetch\n```\n\nExample:\n```text\npermissions:\n external:\n fonts:\n - \"https://www.example-dev.com/fonts.css\"\n```\n\nExample:\n```text\npermissions:\n external:\n styles:\n - \"https://www.example-dev.com/stylesheet.css\"\n```\n\nExample:\n```text\npermissions:\n external:\n frames:\n - \"https://www.example-dev.com/embed/page\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - address: \"*\"\n```\n\nExample:\n```text\npermissions:\n external:\n images:\n - address: https://www.example-dev.com/image.png\n```\n\nExample:\n```text\npermissions:\n external:\n media:\n - \"https://www.example-dev.com/video.mp4\"\n```\n\nExample:\n```text\npermissions:\n external:\n scripts:\n - \"https://www.example-dev.com/script.js\"\n```\n\nExample:\n```text\npermissions:\n external:\n scripts:\n - address: \"https://www.example-dev.com/script.js\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n sandbox:\n - \"allow-storage-access-by-user-activation\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.442Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":22,"totalLines":195,"estimatedTokens":647}}195{"id":"doc-jira_authentication-dc47bab7","source":"documentation","title":"Jira authentication","url":"https://developer.atlassian.com/platform/forge/apis-reference/fetch-api-product.requestjira/","text":"Example:\n```javascript\nimport api, { route } from '@forge/api';\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nconst result = await api\n .asApp()\n .requestJira(\n path[, options]\n );\nconst status = result.status;\n```\n\nExample:\n```javascript\n// Authenticated\napi.[asApp | asUser]().requestJira(path[, options]) => Promise<Response>\n\n// Authenticated, requires user impersonation scopes, see table below\napi.asUser(accountId).requestJira(path[, options]) => Promise<Response>\n\n// Unauthenticated (used for operations that do not support OAuth 2.0)\napi.requestJira(path[, options]) => Promise<Response>\n```\n\nExample:\n```javascript\nimport api, { route } from '@forge/api';\n\n// With no parameters\nroute`/rest/api/3/myself`;\n\n// With parameter: e.g. '/rest/api/3/issue/ISSUE-1'\nroute`/rest/api/3/issue/${issueKey}`;\n\n// With query parameters: e.g. '/rest/api/3/issue/ISSUE-1?fields=summary'\nroute`/rest/api/3/issue/${issueKey}?fields=${fieldsToDisplay}`;\n\n// With more complex query parameters: e.g. '/rest/api/3/issue/ISSUE-1?fields=summary' or '/rest/api/3/issue/ISSUE-1'\nconst queryParams = new URLSearchParams({\n ...(filterFields ? { fields: fieldsToDisplay } : {}),\n});\nroute`/rest/api/3/issue/${issueKey}?${queryParams}`;\n\n// With multiple query parameters: e.g. multiple accountIds in the '/rest/api/3/user/bulk' '/rest/api/3/user/email/bulk' endpoint\n// which results in a URL like '/rest/api/3/user/email/bulk?accountId=accountId1&accountId=accountId2'\nconst accountIds = [\"12557057:2bce73f1-69ca6-4520-8d27-54f7b6d9f73c3\", \"558057:490d13e2-bca8-4e15-875e-a37445f59ee1\"];\nconst queryParams = new URLSearchParams();\naccountIds.forEach((val) => queryParams.append('accountId', val));\nconst emailUrl = route`/rest/api/3/user/email/bulk?${queryParams}`;\nconst response = await api.asApp().requestJira(emailUrl, {\n headers: {\n 'Accept': 'application/json'\n }\n});\n```\n\nExample:\n```javascript\napi.[asApp() | asUser(accountId?)].requestJira(route`/rest/api/3/myself`);\n```\n\nExample:\n```javascript\napi.[asApp() | asUser(accountId?)]\n .requestJira(route`/rest/api/3/issue/${issueKey}/watchers`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Accept\": \"application/json\"\n },\n body: JSON.stringify(`${accountId}`)\n });\n```\n\nExample:\n```javascript\napi.requestJira(route`/rest/api/2/auditing/record`, {\n headers: {\n 'Authorization': 'Basic <base64 token>'\n }\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.443Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":93,"estimatedTokens":620}}196{"id":"doc-how_to_secure_your_macro-bff1ffb3","source":"documentation","title":"How to secure your macro","url":"https://developer.atlassian.com/server/confluence/secure-your-macro/","text":"Example:\n```text\n<dependency>\n <groupId>com.atlassian.plugins</groupId>\n <artifactId>atlassian-whitelist-api-plugin</artifactId>\n <version>6.0.3</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n//...other fields\nprivate final OutboundWhitelist outboundWhitelist;\n\npublic YourMacro(@ComponentImport OutboundWhitelist outboundWhitelist) {\n this.outboundWhitelist = outboundWhitelist;\n}\n\npublic String execute(Map<String, String> map, String s, ConversionContext conversionContext) throws MacroExecutionException {\n String url = map.get(\"url\");\n if (outboundWhitelist.isAllowed(new URI(url)) {\n //make your HTTP outbound calls here\n return \"URI:\" + HtmlUtil.htmlEncode(url) + \" is allowed\";\n } else {\n return \"URI:\" + HtmlUtil.htmlEncode(url) + \" is not allowed\";\n }\n}\n\n//...other macro methods omitted\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.443Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":39,"estimatedTokens":228}}197{"id":"doc-connect_app_authorization-313e5b53","source":"documentation","title":"Connect app authorization","url":"https://developer.atlassian.com/developer-guide/connect-app-authorization","text":"Example:\n```javascript\nfunction userIsJiraAdmin(httpClient, userAccountId) {\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: \"/rest/api/3/permissions/check\",\n headers: {\n \"X-Atlassian-Token\": \"nocheck\",\n },\n anonymous: !userAccountId,\n json: {\n globalPermissions: [\"ADMINISTER\"],\n accountId: userAccountId,\n },\n },\n function (err, _, body) {\n if (err) {\n reject(err);\n return;\n }\n\n if (body.hasOwnProperty(\"errors\")) {\n reject(body.errors);\n return;\n }\n\n const isAdmin = body.globalPermissions.indexOf(\"ADMINISTER\") !== -1;\n resolve(isAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint\", [addon.authenticate()], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userId = req.context.userAccountId;\n if (!(await userIsJiraAdmin(addon.httpClient(req), userId))) {\n res.status(401).send(\"Unauthorized: you must be a Jira admin\");\n return;\n }\n\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeJira({ global: [\"ADMINISTER\"] })],\n (req, res) => {\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nfunction userHasProjectPermission(httpClient, userAccountId, projectPermission, projectId) {\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: \"/rest/api/3/permissions/check\",\n headers: {\n \"X-Atlassian-Token\": \"nocheck\",\n },\n // in the case of no user ID, make the request anonymously (e.g. with no Authorization header)\n anonymous: !userAccountId,\n json: {\n projectPermissions: [{\n permissions: [projectPermission],\n projects: [projectId],\n }],\n accountId: userAccountId,\n },\n },\n function (err, _, body) {\n if (err) {\n reject(err);\n return;\n }\n\n if (body.hasOwnProperty(\"errors\")) {\n reject(body.errors);\n return;\n }\n\n const isProjectAdmin = body.projectPermissions.permission === projectPermission && body.projectPermissions[0].projects.indexOf(projectId) !== -1;\n resolve(isProjectAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint/:projectId\", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userAccountId = req.context.userAccountId;\n\n // The content the user is interacting with\n const { projectId } = req.params;\n const canAdmin = await userHasProjectPermission(addon.httpClient(req), userAccountId, \"ADMINISTER_PROJECTS\", projectId);\n if (!canAdmin) {\n res.status(401).send(\"Unauthorized: you don't have access to administer this project\");\n return;\n }\n\n // do something with the project, e.g. with the Atlassian app APIs or your own database\n\n res.status(200).send(\"ok\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeJira({ project: [\"ADMINISTER_PROJECTS\"] })],\n (req, res) => {\n // do something with the project\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```javascript\nfunction userIsConfluenceAdmin(httpClient, userAccountId) {\n return new Promise((resolve, reject) =>\n httpClient.get(\n {\n url: `/rest/api/user?accountId=${encodeURIComponent(userAccountId)}&expand=operations`,\n json: true\n },\n function (err, httpResponse, body) {\n if (err) {\n reject(err);\n return;\n }\n\n const isAdmin = body.operations.find(\n (op) =>\n op.operation === \"administer\" && op.targetType === \"application\"\n );\n resolve(isAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint\", [addon.authenticate()], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userId = req.context.userAccountId;\n if (!(await userIsConfluenceAdmin(addon.httpClient(req), userId))) {\n res.status(401).send(\"Unauthorized: you must be a Confluence admin\");\n return;\n }\n\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeConfluence({ application: [\"administer\"] })],\n (req, res) => {\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```javascript\nfunction userCanReadContent(httpClient, userAccountId, contentId) {\n // make sure the content ID is valid to prevent traversal\n if (!/^[A-Z0-9-]+$/i.test(contentId)) {\n throw new Error(\"Invalid content ID\");\n }\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: `/rest/api/content/${encodeURIComponent(contentId)}/permission/check`,\n headers: {\n \"X-Atlassian-Token\": \"no-check\",\n },\n json: {\n subject: {\n type: \"user\",\n identifier: userAccountId,\n },\n operation: \"read\",\n },\n },\n function (err, httpResponse, body) {\n if (err) {\n reject(err);\n return;\n }\n\n resolve(body.hasPermission);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint/:contentId\", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userAccountId = req.context.userAccountId;\n\n // The content the user is interacting with\n const { contentId } = req.params;\n const canRead = await userCanReadContent(addon.httpClient(req), userAccountId, contentId);\n if (!canRead) {\n res.status(401).send(\"Unauthorized: you don't have access to this content\");\n return;\n }\n\n // do something with the content, e.g. with the Atlassian app APIs or your own database\n\n res.status(200).send(\"ok\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeConfluence({ content: \"read\" })],\n (req, res) => {\n // do something with the conten\n\n res.status(200).send(\"OK\");\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.444Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":242,"estimatedTokens":1665}}198{"id":"doc-configuring_the_velocity_method_allowlist-b6bcc51d","source":"documentation","title":"Configuring the Velocity method allowlist","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/configuring-the-velocity-allowlist/","text":"Example:\n```text\n<velocity-allowlist key=\"velocity-allowlist-dynamictasklist2\">\n <method>com.atlassian.confluence.extra.dynamictasklist2.NameRenderer#render(java.lang.String boolean)</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.Task#getAssignee()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.Task$Priority#toString()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.TaskList#getCompleteTasks()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.TaskList#getName()</method>\n</velocity-allowlist>\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.444Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":18,"estimatedTokens":163}}199{"id":"doc-connect_app_descriptor-20f3335d","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/jira/platform/connect-app-descriptor/","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://issues.example.com\",\n \"displayUrlServicedeskHelpCenter\": \"https://support.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.445Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":99,"estimatedTokens":466}}200{"id":"doc-connect_app_authorization-f042c981","source":"documentation","title":"Connect app authorization","url":"https://developer.atlassian.com/developer-guide/connect-app-authorization/","text":"Example:\n```javascript\nfunction userIsJiraAdmin(httpClient, userAccountId) {\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: \"/rest/api/3/permissions/check\",\n headers: {\n \"X-Atlassian-Token\": \"nocheck\",\n },\n anonymous: !userAccountId,\n json: {\n globalPermissions: [\"ADMINISTER\"],\n accountId: userAccountId,\n },\n },\n function (err, _, body) {\n if (err) {\n reject(err);\n return;\n }\n\n if (body.hasOwnProperty(\"errors\")) {\n reject(body.errors);\n return;\n }\n\n const isAdmin = body.globalPermissions.indexOf(\"ADMINISTER\") !== -1;\n resolve(isAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint\", [addon.authenticate()], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userId = req.context.userAccountId;\n if (!(await userIsJiraAdmin(addon.httpClient(req), userId))) {\n res.status(401).send(\"Unauthorized: you must be a Jira admin\");\n return;\n }\n\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeJira({ global: [\"ADMINISTER\"] })],\n (req, res) => {\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nfunction userHasProjectPermission(httpClient, userAccountId, projectPermission, projectId) {\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: \"/rest/api/3/permissions/check\",\n headers: {\n \"X-Atlassian-Token\": \"nocheck\",\n },\n // in the case of no user ID, make the request anonymously (e.g. with no Authorization header)\n anonymous: !userAccountId,\n json: {\n projectPermissions: [{\n permissions: [projectPermission],\n projects: [projectId],\n }],\n accountId: userAccountId,\n },\n },\n function (err, _, body) {\n if (err) {\n reject(err);\n return;\n }\n\n if (body.hasOwnProperty(\"errors\")) {\n reject(body.errors);\n return;\n }\n\n const isProjectAdmin = body.projectPermissions.permission === projectPermission && body.projectPermissions[0].projects.indexOf(projectId) !== -1;\n resolve(isProjectAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint/:projectId\", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userAccountId = req.context.userAccountId;\n\n // The content the user is interacting with\n const { projectId } = req.params;\n const canAdmin = await userHasProjectPermission(addon.httpClient(req), userAccountId, \"ADMINISTER_PROJECTS\", projectId);\n if (!canAdmin) {\n res.status(401).send(\"Unauthorized: you don't have access to administer this project\");\n return;\n }\n\n // do something with the project, e.g. with the Atlassian app APIs or your own database\n\n res.status(200).send(\"ok\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeJira({ project: [\"ADMINISTER_PROJECTS\"] })],\n (req, res) => {\n // do something with the project\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```javascript\nfunction userIsConfluenceAdmin(httpClient, userAccountId) {\n return new Promise((resolve, reject) =>\n httpClient.get(\n {\n url: `/rest/api/user?accountId=${encodeURIComponent(userAccountId)}&expand=operations`,\n json: true\n },\n function (err, httpResponse, body) {\n if (err) {\n reject(err);\n return;\n }\n\n const isAdmin = body.operations.find(\n (op) =>\n op.operation === \"administer\" && op.targetType === \"application\"\n );\n resolve(isAdmin);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint\", [addon.authenticate()], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userId = req.context.userAccountId;\n if (!(await userIsConfluenceAdmin(addon.httpClient(req), userId))) {\n res.status(401).send(\"Unauthorized: you must be a Confluence admin\");\n return;\n }\n\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeConfluence({ application: [\"administer\"] })],\n (req, res) => {\n // perform admin-only actions\n\n res.status(200).send(\"OK\");\n});\n```\n\nExample:\n```javascript\nfunction userCanReadContent(httpClient, userAccountId, contentId) {\n // make sure the content ID is valid to prevent traversal\n if (!/^[A-Z0-9-]+$/i.test(contentId)) {\n throw new Error(\"Invalid content ID\");\n }\n return new Promise((resolve, reject) =>\n httpClient.post(\n {\n url: `/rest/api/content/${encodeURIComponent(contentId)}/permission/check`,\n headers: {\n \"X-Atlassian-Token\": \"no-check\",\n },\n json: {\n subject: {\n type: \"user\",\n identifier: userAccountId,\n },\n operation: \"read\",\n },\n },\n function (err, httpResponse, body) {\n if (err) {\n reject(err);\n return;\n }\n\n resolve(body.hasPermission);\n }\n )\n );\n}\n\napp.get(\"/my-app-endpoint/:contentId\", [addon.authenticate(true /* allow context JWTs */)], async (req, res) => {\n // The user ID based on the context JWT\n // This could be any user ID if you needed to check if a different user was an admin\n const userAccountId = req.context.userAccountId;\n\n // The content the user is interacting with\n const { contentId } = req.params;\n const canRead = await userCanReadContent(addon.httpClient(req), userAccountId, contentId);\n if (!canRead) {\n res.status(401).send(\"Unauthorized: you don't have access to this content\");\n return;\n }\n\n // do something with the content, e.g. with the Atlassian app APIs or your own database\n\n res.status(200).send(\"ok\");\n});\n\n// ACE 7.1.5+ example with context JWTs\napp.get(\"/my-app-context-endpoint\",\n [addon.authenticate(true), addon.authorizeConfluence({ content: \"read\" })],\n (req, res) => {\n // do something with the conten\n\n res.status(200).send(\"OK\");\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.445Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":242,"estimatedTokens":1665}}201{"id":"doc-getting_started_with_connect-cac1b193","source":"documentation","title":"Getting started with Connect","url":"https://developer.atlassian.com/cloud/jira/platform/getting-started-with-connect/","text":"Example:\n```bash\nnpm install -g ngrok\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nngrok authtoken <token>\n```\n\nExample:\n```bash\nngrok http 8000\n```\n\nExample:\n```text\nForwarding https://84c5df439d74.ngrok-free.dev -> http://localhost:8000\n```\n\nExample:\n```json\n{\n \"name\": \"Hello World\",\n \"description\": \"Atlassian Connect app\",\n \"key\": \"com.example.myapp\",\n \"baseUrl\": \"https://<placeholder-url>\",\n \"vendor\": {\n \"name\": \"Example, Inc.\",\n \"url\": \"http://example.com\"\n },\n \"authentication\": {\n \"type\": \"none\"\n },\n \"apiVersion\": 1,\n \"modules\": {\n \"generalPages\": [\n {\n \"url\": \"/helloworld.html\",\n \"key\": \"hello-world\",\n \"location\": \"system.top.navigation.bar\",\n \"name\": {\n \"value\": \"Greeting\"\n }\n }\n ]\n }\n}\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <link rel=\"stylesheet\" href=\"https://unpkg.com/@atlaskit/css-reset@2.0.0/dist/bundle.css\" media=\"all\">\n <script src=\"https://connect-cdn.atl-paas.net/all.js\" async></script>\n </head>\n <body>\n <section id=\"content\" class=\"ac-content\">\n <h1>Hello World</h1>\n </section>\n </body>\n</html>\n```\n\nExample:\n```bash\nnpm install http-server -g\n```\n\nExample:\n```bash\nhttp-server -p 8000\n```\n\nExample:\n```bash\nStarting up http-server, serving ./ on: http://0.0.0.0:8000/\n```\n\nExample:\n```json\n\"baseUrl\": \"https://bb5a163ef415.ngrok.io\"\n```\n\nExample:\n```bash\nhttps://bb5a163ef415.ngrok.io/atlassian-connect.json\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.447Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":98,"estimatedTokens":417}}202{"id":"doc-atlassian_rest_api_design_guidelines_version_1-a9c6c8ce","source":"documentation","title":"Atlassian REST API design guidelines version 1","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/atlassian-rest-api-design-guidelines-version-1/","text":"Example:\n```text\n<plugin key=\"a-plugin-key\" enabled=\"true\" expand=\"modules,info\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key\" />\n <info name=\"A plugin\"/>\n <modules size=\"2\" expand=\"module\"/>\n</plugin>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<plugin key=\"a-plugin-key\" enabled=\"true\" expand=\"modules,info\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key\" />\n <info name=\"A plugin\">\n <description>This is an awesome plugin</description>\n <version>1.1</version>\n </info>\n <modules size=\"2\" expand=\"module\"/>\n</plugin>\n```\n\nExample:\n```text\n<plugin key=\"a-plugin-key\" enabled=\"true\" expand=\"modules,info\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key\" />\n <info name=\"A plugin\"/>\n <modules size=\"2\" expand=\"module\">\n <module key=\"module-key-1\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-1\" />\n </module>\n <module key=\"module-key-2\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-2\" />\n </module>\n </modules>\n</plugin>\n```\n\nExample:\n```text\n<plugin key=\"a-plugin-key\" enabled=\"true\" expand=\"modules,info\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key\" />\n <info name=\"A plugin\"/>\n <modules size=\"2\" expand=\"module\">\n <module key=\"module-key-1\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-1\" />\n <name>Module 1</name>\n <description>This is my first module</description>\n </module>\n <module key=\"module-key-2\">\n <link rel=\"self\" href=\"http://host:port/context/rest/upm/1/plugin/a-plugin-key/module/module-key-2\" />\n <name>Module 2</name>\n <description>This is my second module</description>\n </module>\n </modules>\n</plugin>\n```\n\nExample:\n```text\nClass example:\n@Path(\"/a\")\n@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})\npublic class RestHelloWorldService { ... }\n\n \nMethod example:\npublic class RestAnotherService { \n...\n @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})\n @POST\n @Path(\"/b\")\n public Response getUncompletedUsers() { ... }\n}\n```\n\nExample:\n```text\n@Path(\"/exampleXsrfTokenProtectedMethod\")\n@Consumes({MediaType.TEXT_PLAIN})\npublic class RestHelloWorldService {\n \n @POST\n @com.atlassian.plugins.rest.common.security.RequiresXsrfCheck \n public Response exampleXsrfTokenProtectedMethod( args... ) { ... }\n \n}\n```\n\nExample:\n```text\n<form name=\"do-something\" action=\"/rest/examplePlugin/latest/exampleXsrfTokenProtectedMethod\" method=\"post\" enctype=\"text/plain\">\n <input type=\"hidden\" name=\"atl_token\" value=\"xsrfTokenValueHere\">\n <input type=\"text\" name=\"something\">\n</form>\n```\n\nExample:\n```text\npackage com.random.plugin.code.rest;\n\nimport com.atlassian.annotations.security.XsrfProtectionExcluded;\n\nimport javax.ws.rs.Path;\nimport javax.ws.rs.POST;\n\n/**\n */\n@Path (\"/thing\")\npublic class XsrfCheck\n{\n @Path(\"post\")\n @POST\n @XsrfProtectionExcluded // does not mutate state.\n public String nonMutativePost()\n {\n return \"Request succeeded\";\n }\n\n}\n```\n\nExample:\n```javascript\nAJS.$.ajaxPrefilter(\n function(options, originalOptions, jqXHR) {\n function isMutativeHttpMethod(method) {\n return !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));\n }\n if (isMutativeHttpMethod(options.type)) {\n options.crossDomain = false;\n jqXHR.setRequestHeader(\"X-Atlassian-Token\", \"no-check\");\n }\n });\n```\n\nExample:\n```javascript\nfunction isMutativeHttpMethod(method) {\n return !(/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));\n }\n \n$.ajaxSetup({\n crossDomain: false,\n beforeSend: function(xhr, settings) {\n if (isMutativeHttpMethod(settings.type)) {\n xhr.setRequestHeader(\"X-Atlassian-Token\", \"no-check\");\n }\n }\n});\n```\n\nExample:\n```text\n<link title=\"This is my resource\" rel=\"edit\" href=\"http://host:port/context/rest/api/1/myresource\" />\n```\n\nExample:\n```text\n<status>\n <plugin key=\"a-plugin-key\" version=\"1.0\" />\n <status-code>201</status-code>\n <sub-code>604</sub-code>\n <message>Some human readable message</message>\n <etag>233223</etag>\n <resources-created>\n <link rel=\"self\" href=\"http:...\" />\n </resources-created>\n <resources-updated />\n</status>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.448Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":178,"estimatedTokens":1124}}203{"id":"doc-jira_entity_properties-884b2071","source":"documentation","title":"Jira entity properties","url":"https://developer.atlassian.com/cloud/jira/platform/jira-entity-properties/","text":"Example:\n```bash\ncurl -X PUT -H \"Content-type: application/json\" https://jira-instance1.net/rest/api/2/issue/`ENPR-4`/properties/tasks -d '{\"content\":\"Test if works on Jira Cloud\", \"completed\" : 1}'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl -X GET https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/\n```\n\nExample:\n```javascript\n{\"keys\":[{\"self\":\"https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/tasks\",\"key\":\"tasks\"}]}\n```\n\nExample:\n```bash\ncurl -X DELETE https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/tasks\n```\n\nExample:\n```javascript\n\"jiraEntityProperties\": [{\n \"key\": \"jira-issue-tasklist-indexing\",\n \"name\": {\n \"value\" :\"Tasks index\",\n \"i18n\": \"tasks.index\"\n },\n \"entityType\": \"issue\",\n \"keyConfigurations\": [{\n \"propertyKey\" : \"tasks\",\n \"extractions\" : [{\n \"objectName\" : \"content\",\n \"type\" : \"text\"\n }]\n }]\n}]\n```\n\nExample:\n```text\nissue.property[tasks].completed = 1 AND \nissue.property[tasks].content ~ \"works\"\n```\n\nExample:\n```text\nmodules:\n jira:entityProperty:\n - key: \"jira-issue-tasklist-indexing\"\n entityType: \"issue\"\n propertyKey: tasks\n values:\n - path: content\n type: text\n - path: completed\n type: number\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"issue\",\n \"propertyKey\": \"myExtraProperties\",\n \"objectName\": \"isSpecial\",\n \"value\": \"true\"\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"issue\",\n \"propertyKey\": \"myExtraProperties\",\n \"objectName\": \"isSpecial\",\n \"value\": \"\\\"special\\\"\"\n }\n}\n```\n\nExample:\n```bash\ncurl --request PUT \\ \n--user 'email@example.com:<api_token>' \\\n--header \"Accept: application/json\" \\\n--header \"Content-Type: application/json\" \\\n--data '{\"string\":\"string-value\",\"number\":5}' \\\n 'https://your-domain.atlassian.net/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'\n```\n\nExample:\n```bash\ncurl --request GET \\ \n--user 'email@example.com:<api_token>' \\\n--header \"Accept: application/json\" \\\n 'https://your-domain.atlassian.net/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'\n```\n\nExample:\n```javascript\nAP.request({\n url: '/rest/atlassian-connect/1/addons/my-app-key/properties/my-property-key?jsonValue=true',\n success: function(response) {\n // Convert the string response to JSON\n response = JSON.parse(response);\n alert(response);\n },\n error: function(response) {\n console.log(\"Error loading API (\" + uri + \")\");\n console.log(arguments);\n },\n contentType: \"application/json\"\n});\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"addon\",\n \"propertyKey\": \"activated\",\n \"objectName\": \"for-users\" \n \"value\": \"true\"\n }\n}\n```\n\nExample:\n```json\n{\n \"for-anonymous\": false,\n \"for-users\": true,\n \"for-admins\": true,\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"addon_property_contains_any_user_group\",\n \"params\": {\n \"propertyKey\": \"myListOfGroups\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.450Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":159,"estimatedTokens":797}}204{"id":"doc-reliability_connect_metrics-a905b2af","source":"documentation","title":"Reliability: Connect metrics","url":"https://developer.atlassian.com/platform/marketplace/cloud-fortified-apps-program-connect-metrics","text":"Example:\n```text\n<script type=\"application/json\" id=\"incident-data\">\n{\n \"appKey\": \"com.example.appkey\",\n \"appDetails\": {\n \"name\": \"AppName\",\n \"partnerName\": \"Atlassian\"\n },\n \"incidentId\": \"FOotlQrY5ZQ\",\n \"incidentStatus\": \"ANOMALOUS\",\n \"time\": \"2022-03-24T15:40:00Z\",\n \"metricName\": \"Jira Installation Callback Success Rate\",\n \"metricUnit\": \"PERCENT\",\n \"measurementDurationSeconds\": 86400,\n \"sliValue\": 60.0,\n \"sloValue\": 99.0\n}\n</script>\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.450Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":28,"estimatedTokens":125}}205{"id":"doc-understanding_jwt_for_connect_apps-8ae1148a","source":"documentation","title":"Understanding JWT for Connect apps","url":"https://developer.atlassian.com/cloud/jira/service-desk/understanding-jwt/","text":"Example:\n```javascript\nPOST https://<my-dev-environment>.atlassian.net/jira/rest/api/2/issue/AC-1/attachments\n\"Authorization\" header value: \"JWT <jwt-token>\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport com.atlassian.jwt.core.writer.JsonSmartJwtJsonBuilder;\nimport com.atlassian.jwt.core.writer.JwtClaimsBuilder;\nimport com.atlassian.jwt.core.writer.NimbusJwtWriterFactory;\nimport com.atlassian.jwt.httpclient.CanonicalHttpUriRequest;\nimport com.atlassian.jwt.writer.JwtJsonBuilder;\nimport com.atlassian.jwt.writer.JwtWriterFactory;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.http.HttpRequest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\n\npublic class JWTSample {\n //the key from the app descriptor\n private String key = \"atlassian-connect-addon\";\n\n //the sharedsecret key received during the app installation handshake\n private String sharedSecret = \"this is not a secret that you should use in your code\";\n\n public HttpRequest createRequestWithJwt() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException {\n String method = \"GET\";\n String baseUrl = \"https://<my-dev-environment>.atlassian.net/\";\n String contextPath = \"/\";\n String apiPath = \"/rest/api/latest/serverInfo\";\n\n String jwt = createJwt(method, apiPath, contextPath);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(baseUrl + apiPath))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwt)\n .build();\n\n return request;\n\n }\n\n private String createJwt(String method, String apiPath, String contextPath) throws UnsupportedEncodingException, NoSuchAlgorithmException {\n\n long issuedAt = System.currentTimeMillis() / 1000L;\n long expiresAt = issuedAt + 180L;\n\n JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()\n .issuedAt(issuedAt)\n .expirationTime(expiresAt)\n .issuer(key);\n\n CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method,\n apiPath, contextPath, new HashMap());\n JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical);\n\n JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();\n String jwtbuilt = jwtBuilder.build();\n\n String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,\n sharedSecret).jsonToJwt(jwtbuilt);\n\n return jwtToken;\n }\n}\n```\n\nExample:\n```javascript\nimport com.atlassian.jwt.*;\nimport com.atlassian.jwt.core.http.JavaxJwtRequestExtractor;\nimport com.atlassian.jwt.core.reader.*;\nimport com.atlassian.jwt.exception.*;\nimport com.atlassian.jwt.reader.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.UnsupportedEncodingException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Map;\n\npublic class JWTVerificationSample {\n\n public Jwt verifyRequest(HttpServletRequest request,\n JwtIssuerValidator issuerValidator,\n JwtIssuerSharedSecretService issuerSharedSecretService)\n throws UnsupportedEncodingException, NoSuchAlgorithmException,\n JwtVerificationException, JwtIssuerLacksSharedSecretException,\n JwtUnknownIssuerException, JwtParseException {\n JwtReaderFactory jwtReaderFactory = new NimbusJwtReaderFactory(\n issuerValidator, issuerSharedSecretService);\n JavaxJwtRequestExtractor jwtRequestExtractor = new JavaxJwtRequestExtractor();\n CanonicalHttpRequest canonicalHttpRequest\n = jwtRequestExtractor.getCanonicalHttpRequest(request);\n Map<String, ? extends JwtClaimVerifier> requiredClaims = JwtClaimVerifiersBuilder.build(canonicalHttpRequest);\n String jwt = jwtRequestExtractor.extractJwt(request);\n return jwtReaderFactory.getReader(jwt).readAndVerify(jwt, requiredClaims);\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n```\n\nExample:\n```javascript\nString jwtToken = ...;//e.g. extracted from the request\nString[] base64UrlEncodedSegments = jwtToken.split('.');\nString base64UrlEncodedHeader = base64UrlEncodedSegments[0];\nString base64UrlEncodedClaims = base64UrlEncodedSegments[1];\nString signature = base64UrlEncodedSegments[2];\nString header = base64Urldecode(base64UrlEncodedHeader);\nString claims = base64Urldecode(base64UrlEncodedClaims);\n```\n\nExample:\n```javascript\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:15489595\",\n \"iat\": 1386898951,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"exp\": 1386899131\n}\n```\n\nExample:\n```text\nuKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSObject;\nimport com.nimbusds.jose.JWSVerifier;\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport net.minidev.json.JSONObject;\n\npublic JWTClaimsSet read(String jwt, JWSVerifier verifier) throws ParseException, JOSEException\n{\n JWSObject jwsObject = JWSObject.parse(jwt);\n\n if (!jwsObject.verify(verifier))\n {\n throw new IllegalArgumentException(\"Fraudulent JWT token: \" + jwt);\n }\n\n JSONObject jsonPayload = jwsObject.getPayload().toJSONObject();\n return JWTClaimsSet.parse(jsonPayload);\n}\n```\n\nExample:\n```json\n{\n \"kid\": \"0e50fccb-239d-4991-a5db-dc850ba3f236\",\n \"typ\": \"JWT\",\n \"alg\": \"RS256\"\n}\n```\n\nExample:\n```text\nhttps://connect-install-keys.atlassian.com/0e50fccb-239d-4991-a5db-dc850ba3f236\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jose.crypto.*;\nimport com.nimbusds.jwt.*;\n\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.security.interfaces.RSAPublicKey;\n\npublic boolean verify(String jwt, String privateKey) {\n try (Reader reader = new StringReader(privateKey)) {\n PEMParser pemParser = new PEMParser(reader);\n Object object = pemParser.readObject();\n\n SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(object);\n JcaPEMKeyConverter converter = new JcaPEMKeyConverter();\n\n RSAPublicKey rsaPublicKey = (RSAPublicKey) converter.getPublicKey(pub);\n SignedJWT signedJWT = SignedJWT.parse(jwt);\n\n JWSVerifier verifier = new RSASSAVerifier(rsaPublicKey);\n\n return signedJWT.verify(verifier);\n } catch (Exception e) {\n // log and handle exception\n return false;\n }\n}\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jwt.*;\nimport java.net.URI;\n\nprivate static final String APP_BASE_URL = \"https://example.com/path/\";\n\npublic boolean verifyAudienceClaim(String jwt) {\n // `aud` claim should match your app baseUrl defined in the descriptor file.\n final JWSObject jwsObject = JWSObject.parse(jwt);\n JWTClaimsSet claims = JWTClaimsSet.parse(jwsObject.getPayload().toString());\n String audienceClaim = claims.getAudience().stream().findFirst().orElse(\"\");\n URI appBaseUrl = URI.create(APP_BASE_URL);\n return audienceClaim.equals(appBaseUrl.toString());\n}\n```\n\nExample:\n```javascript\nqsh = `sign(canonical-request)`\ncanonical-request = `canonical-method + '&' + canonical-URI + '&' + canonical-query-string`\n```\n\nExample:\n```javascript\n\"https://<my-dev-environment>.atlassian.net/path/to/service?zee_last=param&repeated=parameter 1&first=param&repeated=parameter 2\"\n```\n\nExample:\n```javascript\n\"GET&/path/to/service&first=param&repeated=parameter%201,parameter%202&zee_last=param\"\n```\n\nExample:\n```javascript\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\n<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>\n```\n\nExample:\n```json\n{\n \"typ\":\"JWT\",\n \"alg\":\"HS256\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:1234567\",\n \"iat\": 1300819370,\n \"exp\": 1300819380,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"sub\": \"123456:1234abcd-1234-abcd-1234-1234abcd1234\"\n}\n```\n\nExample:\n```text\npublic class JwtClaims {\n protected String iss;\n protected long iat;\n protected long exp;\n protected String qsh;\n protected String sub;\n // + getters/setters/constructors\n}\n\n[...]\n\npublic class JwtHeader {\n protected String alg;\n protected String typ;\n // + getters/setters/constructors\n}\n\n[...]\n\nimport static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;\nimport static org.apache.commons.codec.binary.Hex.encodeHexString;\nimport java.io.UnsupportedEncodingException;\nimport java.security.*;\nimport javax.crypto.*;\nimport javax.crypto.spec.SecretKeySpec;\nimport com.google.gson.Gson;\n\npublic class JwtBuilder {\n\n public static String generateJWTToken(String requestUrl, String canonicalUrl,\n String key, String sharedSecret)\n throws NoSuchAlgorithmException, UnsupportedEncodingException,\n InvalidKeyException {\n\n JwtClaims claims = new JwtClaims();\n claims.setIss(key);\n claims.setIat(System.currentTimeMillis() / 1000L);\n claims.setExp(claims.getIat() + 180L);\n\n claims.setQsh(getQueryStringHash(canonicalUrl));\n String jwtToken = sign(claims, sharedSecret);\n return jwtToken;\n }\n\n private static String sign(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n String signingInput = getSigningInput(claims, sharedSecret);\n String signed256 = signHmac256(signingInput, sharedSecret);\n return signingInput + \".\" + signed256;\n }\n\n private static String getSigningInput(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n JwtHeader header = new JwtHeader();\n header.alg = \"HS256\";\n header.typ = \"JWT\";\n Gson gson = new Gson();\n String headerJsonString = gson.toJson(header);\n String claimsJsonString = gson.toJson(claims);\n String signingInput = encodeBase64URLSafeString(headerJsonString\n .getBytes())\n + \".\"\n + encodeBase64URLSafeString(claimsJsonString.getBytes());\n return signingInput;\n }\n\n private static String signHmac256(String signingInput, String sharedSecret)\n throws NoSuchAlgorithmException, InvalidKeyException {\n SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), \"HmacSHA256\");\n Mac mac = Mac.getInstance(\"HmacSHA256\");\n mac.init(key);\n return encodeBase64URLSafeString(mac.doFinal(signingInput.getBytes()));\n }\n\n private static String getQueryStringHash(String canonicalUrl)\n throws NoSuchAlgorithmException,UnsupportedEncodingException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n md.update(canonicalUrl.getBytes(\"UTF-8\"));\n byte[] digest = md.digest();\n return encodeHexString(digest);\n }\n }\n\n[...]\n\npublic class Sample {\n public HttpRequest getUrlSample() throws Exception {\n String requestUrl =\n \"https://<my-dev-environment>.atlassian.net/rest/atlassian-connect/latest/license\";\n String canonicalUrl = \"GET&/rest/atlassian-connect/latest/license&\";\n String key = \"...\"; //from the app descriptor\n //and received during installation handshake\n String sharedSecret = \"...\"; //received during installation Handshake\n\n String jwtToken = JwtBuilder.generateJWTToken(\n requestUrl, canonicalUrl, key, sharedSecret);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(requestUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwtToken)\n .build();\n\n return request;\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.452Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":23,"totalLines":533,"estimatedTokens":3191}}206{"id":"doc-cacheable_app_iframes_for_connect_apps-13360143","source":"documentation","title":"Cacheable app iframes for Connect apps","url":"https://developer.atlassian.com/cloud/confluence/cacheable-app-iframes-for-connect-apps/","text":"Example:\n```text\n<iframe src=\"https://some-app.com/confirm-dialog?project=FOO&etc\">\n\t<!-- app content here -->\n</iframe>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"dialogs\": [\n {\n \"key\": \"confirm-dialog-module-key\",\n \"cacheable\": true,\n \"url\": \"/confirm-dialog\",\n }\n ]\n }\n}\n```\n\nExample:\n```json\n\"dialogs\": [{\n \"key\": \"my-cacheable-dialog\",\n \"url\": \"/my-cacheable-dialog?v=1\"\n}]\n```\n\nExample:\n```text\napp.get('/my-cacheable-dialog', function (req, res) {\n res.setHeader('Cache-Control', 'private, max-age=31557600, s-maxage=31557600');\n res.set('Expires', 'Mon, 27 Dec 2068 03:03:03 GMT');\n res.render('my-cacheable-dialog');\n});\n```\n\nExample:\n```text\napp.disable('etag');\napp.set('etag', false);\n```\n\nExample:\n```text\n<script src=\"https://connect-cdn.atl-paas.net/all.js\" type=\"text/javascript\"></script>\n<script src=“/my-app.js”></script>\n```\n\nExample:\n```json\n{\n \"context\": {\n \"license\": {\n \"active\": true,\n }, \n \"jira\": {\n \"project\": {\n \"id\": \"123\",\n \"key\": \"FOO\"\n },\n \"issue\": {\n \"id\": \"12345\",\n \"key\": \"FOO-1024\",\n \"issuetype\": {\n \"id\": \"789\"\n }\n },\n }\n }\n}\n```\n\nExample:\n```javascript\nAP.context.getToken(function(token) {\n // Ajax call to app server to act upon the context knowing it will not have been tampered with.\n $.ajax(‘/my-app-operation’, {\n data: token,\n method: ‘POST'\n });\n});\n```\n\nExample:\n```javascript\nAP.context.getContext(function(context) {\n alert('The current project key is ' + context.jira.project.key);\n});\n```\n\nExample:\n```text\n<html>\n <head>\n <title>My General Page</title>\n <link rel=\"prefetch\" href=\"/my-prefetched-dialog\">\n <script src=\"https://connect-cdn.atl-paas.net/all.js\" async></script>\n </head>\n <body>\n etc\n </body>\n</html>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.453Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":114,"estimatedTokens":468}}207{"id":"doc-managing_secrets-e411650f","source":"documentation","title":"Managing Secrets","url":"https://developer.atlassian.com/cloud/trello/power-ups/client-library/managing-secrets/","text":"Example:\n```javascript\nvar t = window.TrelloPowerUp.iframe();\n\nreturn t.storeSecret('token', 'shhh...its a secret');\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nvar t = window.TrelloPowerUp.iframe();\n\nreturn t.loadSecret('token')\n.then(function (secret) {\n console.log(secret);\n});\n```\n\nExample:\n```javascript\nTrelloPowerUp.initialize({\n 'remove-data': function(t) {\n // the user has pressed the remove personal settings button\n // let's imagine earlier we stored a secret like so:\n // t.storeSecret('token', 'shh its a secret');\n // now we just want to clean it up\n return t.clearSecret('token');\n }\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.453Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":37,"estimatedTokens":163}}208{"id":"doc-secret_store-d4600117","source":"documentation","title":"Secret store","url":"https://developer.atlassian.com/platform/forge/runtime-reference/storage-api-secret/","text":"Example:\n```javascript\nimport { kvs } from '@forge/kvs';\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes:\n - storage:app\n```\n\nExample:\n```javascript\n{\n \"ttl\": {\n \"unit\": \"SECONDS\" | \"MINUTES\" | \"HOURS\" | \"DAYS\";\n \"value\": number;\n }\n}\n```\n\nExample:\n```typescript\ntype SetOptions = {\n ttl?: {\n value: number;\n unit: 'SECONDS' | 'MINUTES' | 'HOURS' | 'DAYS';\n };\n keyPolicy?: 'OVERRIDE' | 'FAIL_IF_EXISTS';\n // If returnValue is provided, keyPolicy must be 'OVERRIDE'\n returnValue?: 'PREVIOUS' | 'LATEST';\n // If returnMetadataFields is provided, keyPolicy must be 'OVERRIDE' and returnValue must be provided\n returnMetadataFields?: MetadataField[];\n};\n\nkvs.setSecret(\n key: string,\n value: array | boolean | number | object | string,\n options?: SetOptions\n): Promise<void>;\n```\n\nExample:\n```typescript\n// array\nkvs.setSecret('example-key', [ 'Hello', 'World' ]);\n\n// boolean\nkvs.setSecret('example-key', true);\n\n// number\nkvs.setSecret('example-key', 123);\n\n// object\nkvs.setSecret('example-key', { hello: 'world' });\n\n// string\nkvs.setSecret('example-key', 'Hello world');\n```\n\nExample:\n```typescript\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n ttl: {\n unit: 'DAYS',\n value: 7,\n },\n});\n```\n\nExample:\n```typescript\n// override the value if key already exists\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n keyPolicy: 'OVERRIDE',\n});\n\n// fail the set request if the key already exists\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n keyPolicy: 'FAIL_IF_EXISTS',\n});\n```\n\nExample:\n```typescript\n// returns the latest value (i.e. the one just set)\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n returnValue: 'LATEST',\n});\n\n// returns the previous value (i.e. the value overriden)\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n returnValue: 'PREVIOUS',\n});\n```\n\nExample:\n```typescript\nimport { kvs, MetadataField } from '@forge/kvs';\n\nawait kvs.setSecret('example-key', { hello: 'world' }, {\n keyPolicy: 'OVERRIDE',\n returnValue: 'LATEST',\n returnMetadataFields: [MetadataField.CREATED_AT, MetadataField.UPDATED_AT, MetadataField.EXPIRE_TIME]\n});\n```\n\nExample:\n```json\n{ \n \"key\": \"example-key\", \n \"value\": \"example-value\", \n \"createdAt\": 1753750184233, \n \"updatedAt\": 1753750200296,\n \"expireTime\": \"2026-01-15T16:12:19.000Z\"\n}\n```\n\nExample:\n```typescript\nexport enum MetadataField {\n CREATED_AT = 'CREATED_AT',\n UPDATED_AT = 'UPDATED_AT',\n EXPIRE_TIME = 'EXPIRE_TIME'\n}\n\nexport interface GetOptions {\n metadataFields?: MetadataField[];\n}\n\nkvs.getSecret(key: string, options?: GetOptions): Promise<array | boolean | number | object | string>;\n```\n\nExample:\n```typescript\n// Read the value for key `example-key`\nawait kvs.getSecret('example-key');\n\n// Request expiry metadata\nawait kvs.getSecret('example-key', {\n metadataFields: [MetadataField.EXPIRE_TIME]\n});\n```\n\nExample:\n```typescript\nkvs.deleteSecret(key: string): Promise<void>;\n```\n\nExample:\n```typescript\n// Delete the value with the key `example-key`\nawait kvs.deleteSecret('example-key');\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.453Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":163,"estimatedTokens":773}}209{"id":"doc-utils_and_helpers-cbd84a16","source":"documentation","title":"Utils and Helpers","url":"https://developer.atlassian.com/cloud/trello/power-ups/utils-and-helpers/","text":"Example:\n```javascript\nvar t = window.TrelloPowerUp.iframe();\n\nconsole.log(t.safe('<script>alert(\"danger\")</script>'));\n\n// prints out:\n// <script>alert(\"danger\")</script>\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n```\n\nExample:\n```javascript\nvar Promise = window.TrelloPowerUp.Promise;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nvar colors = window.TrelloPowerUp.util.colors;\n\n// returns the hex string for a named Trello color\n// first arg is the name of the color\n// second is optional weight, defaults to 500\ncolors.getHexString('blue', 500);\n// returns \"#0079BF\"\n\ncolors.getHexString('gray');\n// returns \"#C4C9CC\"\n\n// Given a specially formatted color name string\n// returns the hex string\ncolors.namedColorStringToHex('business-blue#600');\n// returns \"#3E4D80\"\n\ncolors.namedColorStringToHex('yellow');\n// returns \"#F2D600\"\n```\n\nExample:\n```javascript\nvar absolute = window.TrelloPowerUp.util.relativeUrl('./folder.html');\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.453Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":60,"estimatedTokens":238}}210{"id":"doc-t_jwt_opts-56c5bf11","source":"documentation","title":"t.jwt(opts)","url":"https://developer.atlassian.com/cloud/trello/power-ups/client-library/t-jwt/","text":"Example:\n```javascript\nwindow.TrelloPowerUp.initialize({\n \"board-buttons\": async function (t, opts) {\n if (t.isMemberSignedIn()) {\n const jwt = await t.jwt({\n state: JSON.stringify({ hello: \"world\" }),\n });\n console.log(jwt);\n }\n return [];\n },\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\neyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZFBsdWdpbiI6IjVlNjY3ODRjNjA2Y2IxMTYwYThmNWRjMCIsImlkQm9hcmQiOiI1ZTY2NzgxMTYwNmNiMTE2MGE4ZjViZjciLCJpZE1lbWJlciI6IjVlNjQ1ZGMyYzQyMDVlMTQxNjIxODQwNCIsInN0YXRlIjoidGVzdCIsImlhdCI6MTU4Mzg1ODY2OCwiZXhwIjoxNTgzODU5NTY4LCJpc3MiOiJ0cmVsbG8ifQ.GubnXxnGTN0-rk3hQL_BDMDYjh-cbKldpQNWBi5aIhxqiuPyVSE9grbBjk7DSU3nn8YT3GmqgLu0SUv4K-ir53GptEfRAN1_jRrMrLj5HNosteeqeQV18qK1v_Tg0EzLpJg29JF62dfrDwG-aYsdjMKAIDbY7gyWCyv8O7xsSdzj1mwiMXS2xbhNTIIatxKq6jiRV82lIHV6iCmir0_Gj4Xg1gbMLwG8FfijDmEQTpdXm6DdMsTmm_Ep-T-rFreSSIOF6Qq9Ee03EkkjHxzCqvWGZzU-bF6EVLU5oVdPs9V0_KOBcKsaoKgMaIsSgT5C1tVC_Am6YmFTUBtSDWg0AzGLn2-4dsL_mWfylSgNBqyANeVpFISZJaGNS5Zw8-lBzN3kO_0IvVWfF3ESaCDGra3rjEfphcnNWaVafGSsh2KftlZ_LWoc6lYTcTEow19IvfLU3R3f3Ywa3Gv7-WwQt0F2Npq6E4qGyQucmjy7-u4ns8KwAzP5sej23s6G1ni2utMiqOv1EPzyusR32bVhe7Ps0aANXgFIWlJtGo8rvJeSwrnXZodJx2L3GgY5WBMP271wgvQB-kOcAkf0uIP8WzAycEd8-voYBeB28KMIQatWpRngO4c-DexG4Wr1LAt4YmbyDZzxC4mQZah4kkxsx-0IxA06UhqqETAllVjtUIQ\n```\n\nExample:\n```javascript\n{\n // the id of your Power-Up\n idPlugin: \"5e66784c606cb1160a8f5dc0\",\n // the id of the board the user is currently using your Power-Up on\n idBoard: \"5e667811606cb1160a8f5bf7\",\n // the id of the member currently using your Power-Up\n idMember: \"5e645dc2c4205e1416218404\",\n // optional Power-Up defined state, max of 2048 characters\n state: \"test\",\n // issued at time, in seconds\n iat: 1583858668,\n // expiration time, in seconds\n exp: 1583859568,\n // issuer\n iss: \"trello\"\n}\n```\n\nExample:\n```javascript\nconst bent = require(\"bent\");\nconst jwt = require(\"jsonwebtoken\");\n\nconst getJSON = bent(\"json\", { \"user-agent\": \"bent/7.1.1\" });\n\nlet publicKeys;\nlet publicKeysExpiry;\nconst getPublicKeys = async () => {\n // get a fresh set if needed\n if (!publicKeys || publicKeysExpiry < Date.now()) {\n const resp = await getJSON(\n \"https://api.trello.com/1/resource/jwt-public-keys\"\n );\n publicKeys = resp.keys;\n // cache for 4 hours\n publicKeysExpiry = Date.now() + 14400000;\n }\n\n return publicKeys;\n};\n\nconst decodeAndValidate = async (token) => {\n const publicKeys = await getPublicKeys();\n\n // the vast majority of the time, Trello will provide a single public key\n // but should Trello need to rotate our keys we may for brief periods serve\n // two public keys\n const errors = [];\n let decoded;\n for (let key of publicKeys) {\n try {\n decoded = jwt.verify(token, key);\n } catch (err) {\n errors.push(err);\n }\n }\n\n if (decoded) {\n // the JWT is valid, generally we should now confirm that it belongs to us\n if (decoded.idPlugin !== \"YOUR-POWER-UP-ID\") {\n // this JWT is from a different Power-Up, not ours\n throw new Error(\"Unexpected plugin\");\n }\n return decoded;\n }\n\n // there are a variety of reasons it might not be valid\n // the JWT could be expired, or the signature could be invalid for example\n throw errors[0];\n};\n```\n\nExample:\n```json\n{\n \"keys\": [\n \"-----BEGIN PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0RnzInoZ0WHK4eRwwlcy\\nouP6kULEFjpX+zZfhKUCGGeHj+gBkgehlbAMsKoefOUGcUXmLBH6+J7tsw2s1Qqt\\naNjgEwuVdeFNCuvJvbTHosMaB0XmOLHlxagPeclSeapJmIw81vyPovSXpJK/Co5l\\nm0ofcXps0EzpPUajJTLCrqzRHlQiOOCnGHBUJeN6VoNBqVgcfKOWZPYCoEyfh+yp\\nRzWQ/njYTPb0lLHsASKLAA8pTGegb+2WsJ/C+k6qLcyb79wl42VOx0O/1dW+WR3s\\nRAxwhRadg4aR902r5ztHdrfMslHST5b1xWs5Rj23DN1bqBIpMbsWXnhyG1m8vh+p\\npsb5rxZpHTW9g5qB7NlUa15zTN5OMswLyNiJR4lZsdAyWRX8HeF6V/00l1Xuz0rh\\nmsYesQzlHTDVoI6SGDBShuCEoAak8wCg9gANatXBZRQ7NxBsiqx/+MIih7eihqhm\\ns37G87TLNlEsYZQs1N1hb6Q2ZQDbvu/6mg9PQuGB80ZNU+6IHoX8DdHs6kayBhva\\nzfWQTyskaWxTBBvRLwphlp4J9lMwSWd+SVqFyWb+1DbKcDYOkqmEF2yeh5Y1lbJR\\n5zfylS7XMwvngOHxFwv7mYUTmWiDWsyFwGKB0ydqXQdumRdJ/KFXxFv5eM1HRV+J\\nQs9U01Dm9Y23J4JH6ZOOeRsCAwEAAQ==\\n-----END PUBLIC KEY-----\"\n ]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.454Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":110,"estimatedTokens":1026}}211{"id":"doc-scopes_for_connect_apps-402d2c3b","source":"documentation","title":"Scopes for Connect apps","url":"https://developer.atlassian.com/cloud/jira/platform/scopes-for-connect-apps/","text":"Example:\n```json\n{\n \"baseUrl\": \"http://my-app.com\",\n \"key\": \"atlassian-connect-app\",\n \"scopes\": [\n \"read\", \"write\"\n ],\n \"modules\": {}\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.455Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":19,"estimatedTokens":51}}212{"id":"doc-confluence_entity_properties-1a6d0fe0","source":"documentation","title":"Confluence entity properties","url":"https://developer.atlassian.com/cloud/confluence/confluence-entity-properties/","text":"Example:\n```bash\n# Retrieves content properties associated with a piece of content with ID 12345\ncurl -u admin@example.com:api_token -X GET \"https://your-domain.atlassian.net/wiki/rest/api/content/12345/property\" | python -mjson.tool\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n# Stores a JSON document under the key \"myprop\" against content with ID 12345\ncurl --request PUT \\\n --user email@example.com:<api_token> \\\n --header 'Accept: application/json' \\\n --header 'Content-Type: application/json' \\\n --data '{ \n \"key\": \"my-property\",\n \"version\": { \"number\": 1 },\n \"value\": {\"party\": { \"attendees\": [\"andrew\",\"becky\",\"charlie\",\"dave\"], \"attendeeCount\": 4 }}\n }' \\\n --url 'https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/my-property'\n```\n\nExample:\n```bash\ncurl --request PUT \\\n --user email@example.com:<api_token> \\\n --header 'Accept: application/json' \\\n --header 'Content-Type: application/json' \\\n --data '{ \n \"key\": \"my-property\",\n \"version\": { \"number\": 2 },\n \"value\": {\"party\": { \"attendees\": [\"andrew\",\"becky\",\"charlie\",\"dave\", \"evie\"], \"attendeeCount\": 5 }}\n }' \\\n --url 'https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/my-property'\n```\n\nExample:\n```bash\n# Removes JSON document associated with key \"myprop from content with ID 12345\ncurl -i -u admin@example.com:api_token -X DELETE \"https://your-domain.atlassian.net/wiki/rest/api/content/12345/property/myprop\"\n```\n\nExample:\n```bash\n# fetch properties at the same time as fetching content, note the expand=metadata.properties.myprop\ncurl -u admin@example.com:api_token -X GET \n \"https://your-domain.atlassian.net/wiki/rest/api/content/12345?expand=metadata.properties.myprop\" | python -mjson.tool\n{ \n id: \"12345\",\n type: \"page\",\n status: \"current\",\n title: \"New in the platform team? Read me first\",\n metadata: {\n _expandable: {\n currentuser: \"\",\n labels: \"\"\n properties: {\n my-property: {\n \"id\": \"507f1f77bcf86cd799439011\",\n \"editDate\": \"2000-01-01T11:00:00.000+11:00\",\n \"version\": { \"number\": 2 },\n \"value\": {\"party\": { \"attendees\": [\"andrew\",\"becky\",\"charlie\",\"dave\", \"evie\"], \"attendeeCount\": 5 }}\n }\n }\n }\n }\n}\n```\n\nExample:\n```javascript\n{\n \"id\": \"507f1f77bcf86cd799439011\",\n \"editDate\": \"2000-01-01T11:00:00.000+11:00\",\n \"description\": \"If you have any questions please address them to admin@example.com\",\n \"content\": {\n \"likes\": 5,\n \"tags\": [\"cql\", \"confluence\"]\n }\n}\n```\n\nExample:\n```bash\n\"confluenceContentProperties\": [{\n \"name\": {\n \"value\" :\"Attachments index\",\n \"i18n\": \"attachments.index\"\n },\n \"keyConfigurations\": [{\n \"propertyKey\" : \"attachments\",\n \"extractions\" : [{\n \"objectName\" : \"id\",\n \"type\" : \"string\"\n }]\n }]\n}]\n```\n\nExample:\n```bash\ncontent.property[attachments].editDate >= 2001-01-01\ncontent.property[attachments].description ~ \"questions\"\ncontent.property[metadata].content.tags IN (\"cql\", \"help\")\ncontent.property[metadata].content.likes <= 5\n```\n\nExample:\n```bash\ncurl --request PUT \\ \n--user 'email@example.com:<api_token>' \\\n--header \"Accept: application/json\" \\\n--header \"Content-Type: application/json\" \\\n--data '{\"string\":\"string-value\",\"number\":5}' \\\n 'https://your-domain.atlassian.net/wiki/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'\n```\n\nExample:\n```bash\ncurl --request GET \\ \n --user 'email@example.com:<api_token>' \\\n --header \"Accept: application/json\" \\\n --url 'https://your-domain.atlassian.net/wiki/rest/atlassian-connect/1/addons/{addonKey}/properties/{propertyKey}'\n```\n\nExample:\n```javascript\nAP.require(['request'], function(request) {\n request({\n url: '/rest/atlassian-connect/1/addons/my-app-key/properties/my-property-key?jsonValue=true',\n success: function(response) {\n // Convert the string response to JSON\n response = JSON.parse(response);\n alert(response);\n },\n error: function(response) {\n console.log(\"Error loading API (\" + uri + \")\");\n console.log(arguments);\n },\n contentType: \"application/json\"\n });\n});\n```\n\nExample:\n```javascript\n{\n \"condition\": \"addon_property_equal_to\",\n \"params\": {\n \"propertyKey\": \"activated\",\n \"objectName\": \"for-users\" \n \"value\": \"true\"\n }\n}\n```\n\nExample:\n```javascript\n{\n \"for-anonymous\": false,\n \"for-users\": true,\n \"for-admins\": true,\n}\n```\n\nExample:\n```javascript\n{\n \"condition\": \"addon_property_contains_any_user_group\",\n \"params\": {\n \"propertyKey\": \"myListOfGroups\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.456Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":181,"estimatedTokens":1215}}213{"id":"doc-connect_conditions-57d400cd","source":"documentation","title":"Connect conditions","url":"https://developer.atlassian.com/cloud/jira/platform/connect-conditions/","text":"Example:\n```javascript\n{\n \"modules\": {\n \"generalPages\": [\n {\n \"conditions\": [\n {\n \"condition\": \"user_is_logged_in\"\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n{\n \"modules\": {\n \"jiraProjectPages\": [\n {\n \"conditions\": [\n {\n \"condition\": \"has_issue_permission\",\n \"params\": {\n \"permission\": \"RESOLVE_ISSUE\"\n }\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```javascript\n{\n \"modules\": {\n \"webItems\": [\n {\n \"key\": \"conditional-web-item\",\n \"name\": {\n \"value\": \"Conditional Web Item\"\n },\n \"url\": \"/conditional-web-item\",\n \"location\": \"page.metadata.banner\",\n \"conditions\": [\n {\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"content\",\n \"propertyKey\": \"workflow\",\n \"value\": \"In-Progress\",\n \"objectName\": \"state\"\n }\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"one\": {\n \"ignored\": \"value\",\n \"two\": true\n },\n \"also\", \"ignored\"\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_exists\",\n \"params\": {\n \"entity\": \"issue\",\n \"propertyKey\": \"specialUrls\"\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_exists\",\n \"params\": {\n \"entity\": \"issue\",\n \"propertyKey\": \"specialUrls\",\n \"objectName\": \"path.to.my.field\"\n }\n}\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"webPanels\": [\n {\n \"location\": \"atl.jira.view.issue.right.context\",\n \"conditions\": [\n {\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"project\",\n \"propertyKey\": \"mySettings\",\n \"objectName\": \"isEnabled\",\n \"value\": \"true\"\n }\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"teamCount\": 4,\n \"teams\": [\"Legal\", \"Human Resources\", \"Accounting\", \"Taxation\"]\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_contains_any\",\n \"params\": {\n \"entity\": \"project\",\n \"propertyKey\": \"consumingTeams\",\n \"objectName\": \"teams\",\n \"value\": \"[\\\"Accounting\\\", \\\"Development\\\"]\"\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_equal_to_context\",\n \"params\": {\n \"entity\": \"project\",\n \"propertyKey\": \"teamRoles\",\n \"objectName\": \"scrumMaster\",\n \"contextParameter\": \"user.key\"\n }\n}\n```\n\nExample:\n```json\n{\n\t\"condition\": \"entity_property_equal_to\",\n\t\"params\": {\n\t\t\"entity\": \"content\"\n\t\t\"propertyKey\": \"flagged\",\n\t\t\"value\": \"true\"\n\t}\n}\n```\n\nExample:\n```json\n{\n\t\"condition\": \"content_property_equal_to\",\n\t\"params\": {\n\t\t\"propertyKey\": \"flagged\",\n\t\t\"value\": \"true\"\n\t}\n}\n```\n\nExample:\n```json\n\"jiraIssueTabPanels\": [{\n \"conditions\": [\n {\n \"condition\": \"addon_is_licensed\"\n },\n {\n \"condition\": \"user_is_logged_in\"\n }\n ],\n \"key\": \"your-module-key\",\n \"name\": {\n \"value\": \"Your module name\"\n },\n \"url\": \"/panel/issue?issue_id={issue.id}&issue_key={issue.key}\",\n \"weight\": 100\n}]\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"generalPages\": [\n {\n \"conditions\": [\n {\n \"condition\": \"can_use_application\",\n \"params\": {\n \"applicationKey\": \"jira-software\"\n }\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"jiraProjectPages\": [\n {\n \"conditions\": [\n {\n \"condition\": \"project_type\",\n \"params\": {\n \"projectTypeKey\": \"business\"\n }\n }\n ]\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"conditions\": [\n {\n \"condition\": \"jira_expression\",\n \"params\": {\n \"expression\": \"app.properties['supportedIssueTypes'].includes(issue.issueType.id)\"\n }\n }\n]\n```\n\nExample:\n```json\n{\n \"key\": \"app-key\",\n \"modules\": {\n \"jiraTimeTrackingProviders\" : [\n {\n \"key\": \"module-key\",\n \"name\": \"My time tracking module\"\n }\n ]\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"addon_time_tracking_provider_enabled\",\n \"params\": {\n \"addonKey\": \"app-key\",\n \"moduleKey\": \"module-key\"\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"has_attachment\",\n \"params\": {\n \"extension\": \"pdf\"\n }\n}\n```\n\nExample:\n```json\n{\n \"condition\": \"entity_property_contains_any_user_role\",\n \"params\": {\n \"entity\": \"project\",\n \"propertyKey\": \"keyOfPropertyContainingMyListOfRoles\",\n \"objectName\": \"someObject\"\n }\n}\n```\n\nExample:\n```json\n{\n \"someObject\": [ \"developers\" ]\n}\n```\n\nExample:\n```json\n[ \"developers\" ]\n```\n\nExample:\n```json\n[ 1234 ]\n```\n\nExample:\n```json\n[ \"developers\", 1234 ]\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"webItems\": [\n {\n \"conditions\": [\n {\n \"condition\": \"user_is_logged_in\"\n },\n {\n \"or\": [\n {\n \"condition\": \"entity_property_equal_to\",\n \"invert\": true,\n \"params\": {\n \"entity\": \"addon\",\n \"propertyKey\": \"ep-tool.disabled-for-all\",\n \"value\": \"true\"\n }\n },\n {\n \"condition\": \"entity_property_equal_to\",\n \"params\": {\n \"entity\": \"user\",\n \"propertyKey\": \"ep-tool.enabled-for-me\",\n \"value\": \"true\"\n }\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.457Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":26,"totalLines":369,"estimatedTokens":1712}}214{"id":"doc-action-062304ed","source":"documentation","title":"Action","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/rovo-action/","text":"Example:\n```text\nmodules {}\n└─ action []\n ├─ key (string) [Mandatory]\n ├─ name (string) [Mandatory]\n ├─ function (string) [Mandatory]\n ├─ actionVerb (string) [Mandatory]\n ├─ description (string) [Mandatory]\n ├─ inputs {} [Mandatory]\n │ └─ inputName {}\n │ ├─ title (string) [Mandatory]\n │ ├─ type (string) [Mandatory]\n │ ├─ required (boolean) [Mandatory]\n │ └─ description (string) [Optional]\nfunction []\n└─ key (string) [Mandatory]\n└─ handler (string) [Mandatory]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n action:\n - key: fetch-timesheet-by-date\n name: Fetch timesheet by date\n function: getTimesheetByDate\n actionVerb: GET\n description: |\n Retrieve a user's timesheet based on a date\n inputs:\n timesheetDate:\n title: Timesheet Date\n type: string\n required: true\n description: \"The date that the user wants a timesheet for\"\n function:\n - key: getTimesheetByDate\n handler: index.getTimesheetByDate\n```\n\nExample:\n```text\nmodules:\n action:\n - key: log-time\n endpoint: logTime\n name: Log time\n actionVerb: CREATE\n description: |\n Log some time for the user against a Jira issue\n inputs:\n issueKey:\n title: Jira Issue Key\n type: string\n required: true\n description: \"The jira issue to log time against\"\n time:\n title: Time to log in minutes\n type: integer\n required: true\n description: \"The number of minutes to log\"\n endpoint:\n - key: logTime\n remote: timesheetapp\n route:\n path: log-time\n auth:\n appUserToken:\n enabled: true\n appSystemToken:\n enabled: false\nremote: \n - key: timesheetapp\n baseUrl: \"https://backend.timesheetapp.com\"\n```\n\nExample:\n```javascript\n\"payload\": {\n \"input1ToYourAction\": 1241,\n \"input2ToYourAction\": 12412,\n \"context\": {\n \"cloudId\":\"7607d59e-650b-4c16-adcf-c19d17c915ac\",\n \"moduleKey\":\"sum-2-numbers-new-action\",\n \"jira\": {\n \"url\": \"https://mysite.atlassian.com/browse/FAA-1\",\n \"resourceType\": \"issue\",\n \"issueKey\": 1,\n \"issueId\": 123,\n \"issueType\": \"story\",\n \"issueTypeId\": 1234,\n \"projectKey\": \"FAA\",\n \"projectId\": 5678\n }\n }\n}\n```\n\nExample:\n```javascript\n\"payload\": {\n \"input1ToYourAction\": 1241,\n \"input2ToYourAction\": 12412,\n \"context\": {\n \"cloudId\":\"7607d59e-650b-4c16-adcf-c19d17c915ac\",\n \"moduleKey\":\"sum-2-numbers-new-acion\",\n \"confluence\" : {\n \"url\": \"https://mysite.atlassian.com/wiki/spaces/~65536301eb7512314748ebb489aba9d526b0f8/blog/2024/06/27/44662787/Holiday+in+Japan\",\n \"resourceType\": \"blog\",\n \"contentId\": \"44662787\",\n \"spaceKey\": \"~65536301eb7512314748ebb489aba9d526b0f8\",\n \"spaceId\": \"2064386\"\n }\n }\n}\n```\n\nExample:\n```javascript\nexport function logTime (payload, context) {\n console.log(`payload: ${JSON.stringify(payload)}`);\n console.log(`payload: ${JSON.stringify(context)}`);\n \n // Extract necessary information\n const { issueKey, minutes } = payload;\n \n // Code to actually log time to the timesheet\n \n // Construct the success message\n const result = `${minutes} minutes added to issue ${issueKey}`;\n return result;\n}\n```\n\nExample:\n```text\nmodules:\n rovo:mcp:\n - key: timesheet-mcp\n name: Timesheet MCP\n tools:\n - log-time\n action:\n - key: log-time\n # ...action configuration\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.457Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":155,"estimatedTokens":910}}215{"id":"doc-log_in_with_atlassian_account-13962366","source":"documentation","title":"Log in with Atlassian account","url":"https://developer.atlassian.com/platform/identity/learning/sso/federated-login-oidc/","text":"Atlassian JavaScript load error We tried to load scripts but something went wrong. Please make sure that your network settings allow you to download scripts from the following ://id-frontend.prod-east.frontend.public.atl-paas.net\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.458Z","totalSectionsIncluded":1,"totalCodeBlocksIncluded":0,"totalLines":3,"estimatedTokens":61}}216{"id":"doc-getting_and_setting_plugin_data-f91d5557","source":"documentation","title":"Getting and Setting Plugin Data","url":"https://developer.atlassian.com/cloud/trello/power-ups/client-library/getting-and-setting-data/","text":"Example:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst data = await t.get(\"board\", \"shared\", \"myKey\");\nconsole.log(JSON.stringify(data, null, 2));\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst data = await t.get(\"board\", \"shared\", \"myKey\", \"Uh oh, not yet set\");\nconsole.log(JSON.stringify(data, null, 2));\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst data = await t.get(\"board\", \"shared\");\nconsole.log(JSON.stringify(data, null, 2));\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst data = await t.get(\"542b0bd40d309dc6eba7ec91\", \"shared\", \"myKey\");\nconsole.log(JSON.stringify(data, null, 2));\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst data = await t.getAll();\nconsole.log(JSON.stringify(data, null, 2));\n```\n\nExample:\n```json\n{\n \"board\": {\n \"shared\": {\n \"enabled\": true\n },\n \"private\": {\n \"shh\": \"its a secret\"\n }\n },\n \"organization\": {\n \"shared\": {\n \"interval\": 1500\n }\n }\n}\n```\n\nExample:\n```javascript\nlet myKeyValueObject = {\n myKey: \"myValue\",\n otherKey: 25,\n enabled: true,\n};\nawait t.set(\"card\", \"shared\", myKeyValueObject);\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nawait t.set(\"card\", \"shared\", \"myKey\", \"myValue\");\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nawait t.set(\"card\", \"shared\", { myKey: \"myValue\", more: 25, enabled: true });\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nawait t.set(\"542b0bd40d309dc6eba7ec91\", \"shared\", \"myKey\", \"myValue\");\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nconst permissions = t.getContext().permissions;\n\nif (permissions.board === \"write\") {\n await t.set(\"board\", \"shared\", { myKey: \"myValue\", more: 25, enabled: true });\n}\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nawait t.remove(\"member\", \"private\", \"myKey\");\n```\n\nExample:\n```javascript\nconst t = window.TrelloPowerUp.iframe();\n\nawait t.remove(\"member\", \"private\", [\"myKey\", \"anotherKey\", \"enabled\"]);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.458Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":14,"totalLines":122,"estimatedTokens":541}}217{"id":"doc-log_in_with_atlassian_account-37814d74","source":"documentation","title":"Log in with Atlassian account","url":"https://developer.atlassian.com/platform/identity/learning/oauth-onboarding/guide","text":"Atlassian JavaScript load error We tried to load scripts but something went wrong. Please make sure that your network settings allow you to download scripts from the following ://id-frontend.prod-east.frontend.public.atl-paas.net\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.458Z","totalSectionsIncluded":1,"totalCodeBlocksIncluded":0,"totalLines":3,"estimatedTokens":61}}218{"id":"doc-understanding_jwt-bbaa25bf","source":"documentation","title":"Understanding JWT","url":"https://developer.atlassian.com/cloud/confluence/understanding-jwt/","text":"Example:\n```javascript\nPOST https://<my-dev-environment>.atlassian.net/jira/rest/api/2/issue/AC-1/attachments\n\"Authorization\" header value: \"JWT <jwt-token>\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport com.atlassian.jwt.core.writer.JsonSmartJwtJsonBuilder;\nimport com.atlassian.jwt.core.writer.JwtClaimsBuilder;\nimport com.atlassian.jwt.core.writer.NimbusJwtWriterFactory;\nimport com.atlassian.jwt.httpclient.CanonicalHttpUriRequest;\nimport com.atlassian.jwt.writer.JwtJsonBuilder;\nimport com.atlassian.jwt.writer.JwtWriterFactory;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.http.HttpRequest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\n\npublic class JWTSample {\n //the key from the app descriptor\n private String key = \"atlassian-connect-addon\";\n\n //the sharedsecret key received during the app installation handshake\n private String sharedSecret = \"this is not a secret that you should use in your code\";\n\n public HttpRequest createRequestWithJwt() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException {\n String method = \"GET\";\n String baseUrl = \"https://<my-dev-environment>.atlassian.net/\";\n String contextPath = \"/\";\n String apiPath = \"/rest/api/latest/serverInfo\";\n\n String jwt = createJwt(method, apiPath, contextPath);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(baseUrl + apiPath))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwt)\n .build();\n\n return request;\n\n }\n\n private String createJwt(String method, String apiPath, String contextPath) throws UnsupportedEncodingException, NoSuchAlgorithmException {\n\n long issuedAt = System.currentTimeMillis() / 1000L;\n long expiresAt = issuedAt + 180L;\n\n JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()\n .issuedAt(issuedAt)\n .expirationTime(expiresAt)\n .issuer(key);\n\n CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method,\n apiPath, contextPath, new HashMap());\n JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical);\n\n JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();\n String jwtbuilt = jwtBuilder.build();\n\n String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,\n sharedSecret).jsonToJwt(jwtbuilt);\n\n return jwtToken;\n }\n}\n```\n\nExample:\n```javascript\nimport com.atlassian.jwt.*;\nimport com.atlassian.jwt.core.http.JavaxJwtRequestExtractor;\nimport com.atlassian.jwt.core.reader.*;\nimport com.atlassian.jwt.exception.*;\nimport com.atlassian.jwt.reader.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.UnsupportedEncodingException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Map;\n\npublic class JWTVerificationSample {\n\n public Jwt verifyRequest(HttpServletRequest request,\n JwtIssuerValidator issuerValidator,\n JwtIssuerSharedSecretService issuerSharedSecretService)\n throws UnsupportedEncodingException, NoSuchAlgorithmException,\n JwtVerificationException, JwtIssuerLacksSharedSecretException,\n JwtUnknownIssuerException, JwtParseException {\n JwtReaderFactory jwtReaderFactory = new NimbusJwtReaderFactory(\n issuerValidator, issuerSharedSecretService);\n JavaxJwtRequestExtractor jwtRequestExtractor = new JavaxJwtRequestExtractor();\n CanonicalHttpRequest canonicalHttpRequest\n = jwtRequestExtractor.getCanonicalHttpRequest(request);\n Map<String, ? extends JwtClaimVerifier> requiredClaims = JwtClaimVerifiersBuilder.build(canonicalHttpRequest);\n String jwt = jwtRequestExtractor.extractJwt(request);\n return jwtReaderFactory.getReader(jwt).readAndVerify(jwt, requiredClaims);\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n```\n\nExample:\n```javascript\nString jwtToken = ...;//e.g. extracted from the request\nString[] base64UrlEncodedSegments = jwtToken.split('.');\nString base64UrlEncodedHeader = base64UrlEncodedSegments[0];\nString base64UrlEncodedClaims = base64UrlEncodedSegments[1];\nString signature = base64UrlEncodedSegments[2];\nString header = base64Urldecode(base64UrlEncodedHeader);\nString claims = base64Urldecode(base64UrlEncodedClaims);\n```\n\nExample:\n```javascript\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:15489595\",\n \"iat\": 1386898951,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"exp\": 1386899131\n}\n```\n\nExample:\n```text\nuKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSObject;\nimport com.nimbusds.jose.JWSVerifier;\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport net.minidev.json.JSONObject;\n\npublic JWTClaimsSet read(String jwt, JWSVerifier verifier) throws ParseException, JOSEException\n{\n JWSObject jwsObject = JWSObject.parse(jwt);\n\n if (!jwsObject.verify(verifier))\n {\n throw new IllegalArgumentException(\"Fraudulent JWT token: \" + jwt);\n }\n\n JSONObject jsonPayload = jwsObject.getPayload().toJSONObject();\n return JWTClaimsSet.parse(jsonPayload);\n}\n```\n\nExample:\n```json\n{\n \"kid\": \"0e50fccb-239d-4991-a5db-dc850ba3f236\",\n \"typ\": \"JWT\",\n \"alg\": \"RS256\"\n}\n```\n\nExample:\n```text\nhttps://connect-install-keys.atlassian.com/0e50fccb-239d-4991-a5db-dc850ba3f236\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jose.crypto.*;\nimport com.nimbusds.jwt.*;\n\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.security.interfaces.RSAPublicKey;\n\npublic boolean verify(String jwt, String privateKey) {\n try (Reader reader = new StringReader(privateKey)) {\n PEMParser pemParser = new PEMParser(reader);\n Object object = pemParser.readObject();\n\n SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(object);\n JcaPEMKeyConverter converter = new JcaPEMKeyConverter();\n\n RSAPublicKey rsaPublicKey = (RSAPublicKey) converter.getPublicKey(pub);\n SignedJWT signedJWT = SignedJWT.parse(jwt);\n\n JWSVerifier verifier = new RSASSAVerifier(rsaPublicKey);\n\n return signedJWT.verify(verifier);\n } catch (Exception e) {\n // log and handle exception\n return false;\n }\n}\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jwt.*;\nimport java.net.URI;\n\nprivate static final String APP_BASE_URL = \"https://example.com/path/\";\n\npublic boolean verifyAudienceClaim(String jwt) {\n // `aud` claim should match your app baseUrl defined in the descriptor file.\n final JWSObject jwsObject = JWSObject.parse(jwt);\n JWTClaimsSet claims = JWTClaimsSet.parse(jwsObject.getPayload().toString());\n String audienceClaim = claims.getAudience().stream().findFirst().orElse(\"\");\n URI appBaseUrl = URI.create(APP_BASE_URL);\n return audienceClaim.equals(appBaseUrl.toString());\n}\n```\n\nExample:\n```javascript\nqsh = `sign(canonical-request)`\ncanonical-request = `canonical-method + '&' + canonical-URI + '&' + canonical-query-string`\n```\n\nExample:\n```javascript\n\"https://<my-dev-environment>.atlassian.net/path/to/service?zee_last=param&repeated=parameter 1&first=param&repeated=parameter 2\"\n```\n\nExample:\n```javascript\n\"GET&/path/to/service&first=param&repeated=parameter%201,parameter%202&zee_last=param\"\n```\n\nExample:\n```javascript\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\n<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>\n```\n\nExample:\n```json\n{\n \"typ\":\"JWT\",\n \"alg\":\"HS256\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:1234567\",\n \"iat\": 1300819370,\n \"exp\": 1300819380,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"sub\": \"123456:1234abcd-1234-abcd-1234-1234abcd1234\"\n}\n```\n\nExample:\n```text\npublic class JwtClaims {\n protected String iss;\n protected long iat;\n protected long exp;\n protected String qsh;\n protected String sub;\n // + getters/setters/constructors\n}\n\n[...]\n\npublic class JwtHeader {\n protected String alg;\n protected String typ;\n // + getters/setters/constructors\n}\n\n[...]\n\nimport static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;\nimport static org.apache.commons.codec.binary.Hex.encodeHexString;\nimport java.io.UnsupportedEncodingException;\nimport java.security.*;\nimport javax.crypto.*;\nimport javax.crypto.spec.SecretKeySpec;\nimport com.google.gson.Gson;\n\npublic class JwtBuilder {\n\n public static String generateJWTToken(String requestUrl, String canonicalUrl,\n String key, String sharedSecret)\n throws NoSuchAlgorithmException, UnsupportedEncodingException,\n InvalidKeyException {\n\n JwtClaims claims = new JwtClaims();\n claims.setIss(key);\n claims.setIat(System.currentTimeMillis() / 1000L);\n claims.setExp(claims.getIat() + 180L);\n\n claims.setQsh(getQueryStringHash(canonicalUrl));\n String jwtToken = sign(claims, sharedSecret);\n return jwtToken;\n }\n\n private static String sign(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n String signingInput = getSigningInput(claims, sharedSecret);\n String signed256 = signHmac256(signingInput, sharedSecret);\n return signingInput + \".\" + signed256;\n }\n\n private static String getSigningInput(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n JwtHeader header = new JwtHeader();\n header.alg = \"HS256\";\n header.typ = \"JWT\";\n Gson gson = new Gson();\n String headerJsonString = gson.toJson(header);\n String claimsJsonString = gson.toJson(claims);\n String signingInput = encodeBase64URLSafeString(headerJsonString\n .getBytes())\n + \".\"\n + encodeBase64URLSafeString(claimsJsonString.getBytes());\n return signingInput;\n }\n\n private static String signHmac256(String signingInput, String sharedSecret)\n throws NoSuchAlgorithmException, InvalidKeyException {\n SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), \"HmacSHA256\");\n Mac mac = Mac.getInstance(\"HmacSHA256\");\n mac.init(key);\n return encodeBase64URLSafeString(mac.doFinal(signingInput.getBytes()));\n }\n\n private static String getQueryStringHash(String canonicalUrl)\n throws NoSuchAlgorithmException,UnsupportedEncodingException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n md.update(canonicalUrl.getBytes(\"UTF-8\"));\n byte[] digest = md.digest();\n return encodeHexString(digest);\n }\n }\n\n[...]\n\npublic class Sample {\n public HttpRequest getUrlSample() throws Exception {\n String requestUrl =\n \"https://<my-dev-environment>.atlassian.net/rest/atlassian-connect/latest/license\";\n String canonicalUrl = \"GET&/rest/atlassian-connect/latest/license&\";\n String key = \"...\"; //from the app descriptor\n //and received during installation handshake\n String sharedSecret = \"...\"; //received during installation Handshake\n\n String jwtToken = JwtBuilder.generateJWTToken(\n requestUrl, canonicalUrl, key, sharedSecret);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(requestUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwtToken)\n .build();\n\n return request;\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.460Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":23,"totalLines":533,"estimatedTokens":3191}}219{"id":"doc-providers-dec5c934","source":"documentation","title":"Providers","url":"https://developer.atlassian.com/platform/forge/manifest-reference/providers","text":"Example:\n```text\nproviders:\n auth:\n - key: google\n name: Google\n scopes:\n - 'profile'\n - 'https://www.googleapis.com/auth/userinfo.email'\n type: oauth2\n clientId: EXAMPLE\n remotes:\n - google-apis\n bearerMethod: authorization-header\n actions:\n authorization:\n remote: google-account\n path: /o/oauth2/v2/auth\n exchange:\n remote: google-oauth\n path: /token\n refreshToken:\n remote: google-oauth\n path: /token\n revokeToken:\n remote: google-oauth\n path: /revoke\n retrieveProfile:\n remote: google-apis\n path: /userinfo/v2/me\n resolvers:\n id: id\n displayName: email\n avatarUrl: picture\nremotes:\n - key: google-apis\n baseUrl: https://www.googleapis.com\n - key: google-account\n baseUrl: https://accounts.google.com\n - key: google-oauth\n baseUrl: https://oauth2.googleapis.com\npermissions:\n external:\n fetch:\n backend:\n - https://www.googleapis.com\n - https://accounts.google.com\n - https://oauth2.googleapis.com\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nbearerMethod:\n type: authorization-header\n prefix: 'token '\n parameter: 'X-Custom-Header'\n```\n\nExample:\n```text\noverrides:\n headers:\n foo: bar # static value\n 'x-client-id': '{{client_id}}' # template value\n 'x-authorization': 'Basic {{http_basic_auth_credentials}}' # template with string interpolation\n body:\n # static values:\n client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'\n grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer'\n # templates:\n client_id: '{{client_id}}'\n client_assertion: '{{client_secret}}'\n assertion: '{{authorization_code}}'\n redirect_uri: '{{redirect_uri}}'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.462Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":83,"estimatedTokens":474}}220{"id":"doc-oauth_2_0_3lo_apps-406a859e","source":"documentation","title":"OAuth 2.0 (3LO) apps","url":"https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/","text":"Example:\n```text\nhttps://auth.atlassian.com/authorize?\n audience=api.atlassian.com&\n client_id=YOUR_CLIENT_ID&\n scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&\n redirect_uri=https://YOUR_APP_CALLBACK_URL&\n state=YOUR_USER_BOUND_VALUE&\n response_type=code&\n prompt=consent\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{\"grant_type\": \"authorization_code\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"code\": \"YOUR_AUTHORIZATION_CODE\",\"redirect_uri\": \"https://YOUR_APP_CALLBACK_URL\"}'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/oauth/token/accessible-resources \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"read:servicedesk-request\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:confluence-content\",\n \"read:confluence-content.all\",\n \"manage:confluence-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url <request URL> \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/ex/jira/11223344-a1b2-3b33-c444-def123456789/rest/api/2/project \\\n --header 'Authorization: Bearer aBCxYz654123' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --header 'Authorization: Bearer <access_token>' \\\n --url 'https://api.atlassian.com/oauth/token/accessible-resources'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{ \"grant_type\": \"refresh_token\", \"client_id\": \"YOUR_CLIENT_ID\", \"client_secret\": \"YOUR_CLIENT_SECRET\", \"refresh_token\": \"YOUR_REFRESH_TOKEN\" }'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"refresh_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/me \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n{\n \"account_type\": \"atlassian\",\n \"account_id\": \"112233aa-bb11-cc22-33dd-445566abcabc\",\n \"email\": \"mia@example.com\",\n \"name\": \"Mia Krystof\",\n \"picture\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/112233aa-bb11-cc22-33dd-445566abcabc/1234abcd-9876-54aa-33aa-1234dfsade9487ds\",\n \"account_status\": \"active\",\n \"nickname\": \"mkrystof\",\n \"zoneinfo\": \"Australia/Sydney\",\n \"locale\": \"en-US\",\n \"extended_profile\": {\n \"job_title\": \"Designer\",\n \"organization\": \"mia@example.com\",\n \"department\": \"Design team\",\n \"location\": \"Sydney\"\n }\n}\n```\n\nExample:\n```typescript\nconst response = await fetch(`https://auth.atlassian.com/oauth/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json'\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: this.refreshToken\n })\n });\n \n const json = response.json();\n \n this.accessToken = json.access_token;\n \n if(json.refresh_token) { \n this.refreshToken = json.refresh_token;\n }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.464Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":246,"estimatedTokens":1465}}221{"id":"doc-cacheable_app_iframes_for_connect_apps-f1966ae2","source":"documentation","title":"Cacheable app iframes for Connect apps","url":"https://developer.atlassian.com/cloud/confluence/cacheable-app-iframes/","text":"Example:\n```text\n<iframe src=\"https://some-app.com/confirm-dialog?project=FOO&etc\">\n\t<!-- app content here -->\n</iframe>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"modules\": {\n \"dialogs\": [\n {\n \"key\": \"confirm-dialog-module-key\",\n \"cacheable\": true,\n \"url\": \"/confirm-dialog\",\n }\n ]\n }\n}\n```\n\nExample:\n```json\n\"dialogs\": [{\n \"key\": \"my-cacheable-dialog\",\n \"url\": \"/my-cacheable-dialog?v=1\"\n}]\n```\n\nExample:\n```text\napp.get('/my-cacheable-dialog', function (req, res) {\n res.setHeader('Cache-Control', 'private, max-age=31557600, s-maxage=31557600');\n res.set('Expires', 'Mon, 27 Dec 2068 03:03:03 GMT');\n res.render('my-cacheable-dialog');\n});\n```\n\nExample:\n```text\napp.disable('etag');\napp.set('etag', false);\n```\n\nExample:\n```text\n<script src=\"https://connect-cdn.atl-paas.net/all.js\" type=\"text/javascript\"></script>\n<script src=“/my-app.js”></script>\n```\n\nExample:\n```json\n{\n \"context\": {\n \"license\": {\n \"active\": true,\n }, \n \"jira\": {\n \"project\": {\n \"id\": \"123\",\n \"key\": \"FOO\"\n },\n \"issue\": {\n \"id\": \"12345\",\n \"key\": \"FOO-1024\",\n \"issuetype\": {\n \"id\": \"789\"\n }\n },\n }\n }\n}\n```\n\nExample:\n```javascript\nAP.context.getToken(function(token) {\n // Ajax call to app server to act upon the context knowing it will not have been tampered with.\n $.ajax(‘/my-app-operation’, {\n data: token,\n method: ‘POST'\n });\n});\n```\n\nExample:\n```javascript\nAP.context.getContext(function(context) {\n alert('The current project key is ' + context.jira.project.key);\n});\n```\n\nExample:\n```text\n<html>\n <head>\n <title>My General Page</title>\n <link rel=\"prefetch\" href=\"/my-prefetched-dialog\">\n <script src=\"https://connect-cdn.atl-paas.net/all.js\" async></script>\n </head>\n <body>\n etc\n </body>\n</html>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.464Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":114,"estimatedTokens":468}}222{"id":"doc-understanding_jwt_for_connect_apps-1056bc0a","source":"documentation","title":"Understanding JWT for Connect apps","url":"https://developer.atlassian.com/cloud/jira/platform/understanding-jwt/","text":"Example:\n```javascript\nPOST https://<my-dev-environment>.atlassian.net/jira/rest/api/2/issue/AC-1/attachments\n\"Authorization\" header value: \"JWT <jwt-token>\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport com.atlassian.jwt.core.writer.JsonSmartJwtJsonBuilder;\nimport com.atlassian.jwt.core.writer.JwtClaimsBuilder;\nimport com.atlassian.jwt.core.writer.NimbusJwtWriterFactory;\nimport com.atlassian.jwt.httpclient.CanonicalHttpUriRequest;\nimport com.atlassian.jwt.writer.JwtJsonBuilder;\nimport com.atlassian.jwt.writer.JwtWriterFactory;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.http.HttpRequest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\n\npublic class JWTSample {\n //the key from the app descriptor\n private String key = \"atlassian-connect-addon\";\n\n //the sharedsecret key received during the app installation handshake\n private String sharedSecret = \"this is not a secret that you should use in your code\";\n\n public HttpRequest createRequestWithJwt() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException {\n String method = \"GET\";\n String baseUrl = \"https://<my-dev-environment>.atlassian.net/\";\n String contextPath = \"/\";\n String apiPath = \"/rest/api/latest/serverInfo\";\n\n String jwt = createJwt(method, apiPath, contextPath);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(baseUrl + apiPath))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwt)\n .build();\n\n return request;\n\n }\n\n private String createJwt(String method, String apiPath, String contextPath) throws UnsupportedEncodingException, NoSuchAlgorithmException {\n\n long issuedAt = System.currentTimeMillis() / 1000L;\n long expiresAt = issuedAt + 180L;\n\n JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()\n .issuedAt(issuedAt)\n .expirationTime(expiresAt)\n .issuer(key);\n\n CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method,\n apiPath, contextPath, new HashMap());\n JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical);\n\n JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();\n String jwtbuilt = jwtBuilder.build();\n\n String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,\n sharedSecret).jsonToJwt(jwtbuilt);\n\n return jwtToken;\n }\n}\n```\n\nExample:\n```javascript\nimport com.atlassian.jwt.*;\nimport com.atlassian.jwt.core.http.JavaxJwtRequestExtractor;\nimport com.atlassian.jwt.core.reader.*;\nimport com.atlassian.jwt.exception.*;\nimport com.atlassian.jwt.reader.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.UnsupportedEncodingException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Map;\n\npublic class JWTVerificationSample {\n\n public Jwt verifyRequest(HttpServletRequest request,\n JwtIssuerValidator issuerValidator,\n JwtIssuerSharedSecretService issuerSharedSecretService)\n throws UnsupportedEncodingException, NoSuchAlgorithmException,\n JwtVerificationException, JwtIssuerLacksSharedSecretException,\n JwtUnknownIssuerException, JwtParseException {\n JwtReaderFactory jwtReaderFactory = new NimbusJwtReaderFactory(\n issuerValidator, issuerSharedSecretService);\n JavaxJwtRequestExtractor jwtRequestExtractor = new JavaxJwtRequestExtractor();\n CanonicalHttpRequest canonicalHttpRequest\n = jwtRequestExtractor.getCanonicalHttpRequest(request);\n Map<String, ? extends JwtClaimVerifier> requiredClaims = JwtClaimVerifiersBuilder.build(canonicalHttpRequest);\n String jwt = jwtRequestExtractor.extractJwt(request);\n return jwtReaderFactory.getReader(jwt).readAndVerify(jwt, requiredClaims);\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n```\n\nExample:\n```javascript\nString jwtToken = ...;//e.g. extracted from the request\nString[] base64UrlEncodedSegments = jwtToken.split('.');\nString base64UrlEncodedHeader = base64UrlEncodedSegments[0];\nString base64UrlEncodedClaims = base64UrlEncodedSegments[1];\nString signature = base64UrlEncodedSegments[2];\nString header = base64Urldecode(base64UrlEncodedHeader);\nString claims = base64Urldecode(base64UrlEncodedClaims);\n```\n\nExample:\n```javascript\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:15489595\",\n \"iat\": 1386898951,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"exp\": 1386899131\n}\n```\n\nExample:\n```text\nuKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSObject;\nimport com.nimbusds.jose.JWSVerifier;\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport net.minidev.json.JSONObject;\n\npublic JWTClaimsSet read(String jwt, JWSVerifier verifier) throws ParseException, JOSEException\n{\n JWSObject jwsObject = JWSObject.parse(jwt);\n\n if (!jwsObject.verify(verifier))\n {\n throw new IllegalArgumentException(\"Fraudulent JWT token: \" + jwt);\n }\n\n JSONObject jsonPayload = jwsObject.getPayload().toJSONObject();\n return JWTClaimsSet.parse(jsonPayload);\n}\n```\n\nExample:\n```json\n{\n \"kid\": \"0e50fccb-239d-4991-a5db-dc850ba3f236\",\n \"typ\": \"JWT\",\n \"alg\": \"RS256\"\n}\n```\n\nExample:\n```text\nhttps://connect-install-keys.atlassian.com/0e50fccb-239d-4991-a5db-dc850ba3f236\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jose.crypto.*;\nimport com.nimbusds.jwt.*;\n\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.security.interfaces.RSAPublicKey;\n\npublic boolean verify(String jwt, String privateKey) {\n try (Reader reader = new StringReader(privateKey)) {\n PEMParser pemParser = new PEMParser(reader);\n Object object = pemParser.readObject();\n\n SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(object);\n JcaPEMKeyConverter converter = new JcaPEMKeyConverter();\n\n RSAPublicKey rsaPublicKey = (RSAPublicKey) converter.getPublicKey(pub);\n SignedJWT signedJWT = SignedJWT.parse(jwt);\n\n JWSVerifier verifier = new RSASSAVerifier(rsaPublicKey);\n\n return signedJWT.verify(verifier);\n } catch (Exception e) {\n // log and handle exception\n return false;\n }\n}\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jwt.*;\nimport java.net.URI;\n\nprivate static final String APP_BASE_URL = \"https://example.com/path/\";\n\npublic boolean verifyAudienceClaim(String jwt) {\n // `aud` claim should match your app baseUrl defined in the descriptor file.\n final JWSObject jwsObject = JWSObject.parse(jwt);\n JWTClaimsSet claims = JWTClaimsSet.parse(jwsObject.getPayload().toString());\n String audienceClaim = claims.getAudience().stream().findFirst().orElse(\"\");\n URI appBaseUrl = URI.create(APP_BASE_URL);\n return audienceClaim.equals(appBaseUrl.toString());\n}\n```\n\nExample:\n```javascript\nqsh = `sign(canonical-request)`\ncanonical-request = `canonical-method + '&' + canonical-URI + '&' + canonical-query-string`\n```\n\nExample:\n```javascript\n\"https://<my-dev-environment>.atlassian.net/path/to/service?zee_last=param&repeated=parameter 1&first=param&repeated=parameter 2\"\n```\n\nExample:\n```javascript\n\"GET&/path/to/service&first=param&repeated=parameter%201,parameter%202&zee_last=param\"\n```\n\nExample:\n```javascript\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\n<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>\n```\n\nExample:\n```json\n{\n \"typ\":\"JWT\",\n \"alg\":\"HS256\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:1234567\",\n \"iat\": 1300819370,\n \"exp\": 1300819380,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"sub\": \"123456:1234abcd-1234-abcd-1234-1234abcd1234\"\n}\n```\n\nExample:\n```text\npublic class JwtClaims {\n protected String iss;\n protected long iat;\n protected long exp;\n protected String qsh;\n protected String sub;\n // + getters/setters/constructors\n}\n\n[...]\n\npublic class JwtHeader {\n protected String alg;\n protected String typ;\n // + getters/setters/constructors\n}\n\n[...]\n\nimport static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;\nimport static org.apache.commons.codec.binary.Hex.encodeHexString;\nimport java.io.UnsupportedEncodingException;\nimport java.security.*;\nimport javax.crypto.*;\nimport javax.crypto.spec.SecretKeySpec;\nimport com.google.gson.Gson;\n\npublic class JwtBuilder {\n\n public static String generateJWTToken(String requestUrl, String canonicalUrl,\n String key, String sharedSecret)\n throws NoSuchAlgorithmException, UnsupportedEncodingException,\n InvalidKeyException {\n\n JwtClaims claims = new JwtClaims();\n claims.setIss(key);\n claims.setIat(System.currentTimeMillis() / 1000L);\n claims.setExp(claims.getIat() + 180L);\n\n claims.setQsh(getQueryStringHash(canonicalUrl));\n String jwtToken = sign(claims, sharedSecret);\n return jwtToken;\n }\n\n private static String sign(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n String signingInput = getSigningInput(claims, sharedSecret);\n String signed256 = signHmac256(signingInput, sharedSecret);\n return signingInput + \".\" + signed256;\n }\n\n private static String getSigningInput(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n JwtHeader header = new JwtHeader();\n header.alg = \"HS256\";\n header.typ = \"JWT\";\n Gson gson = new Gson();\n String headerJsonString = gson.toJson(header);\n String claimsJsonString = gson.toJson(claims);\n String signingInput = encodeBase64URLSafeString(headerJsonString\n .getBytes())\n + \".\"\n + encodeBase64URLSafeString(claimsJsonString.getBytes());\n return signingInput;\n }\n\n private static String signHmac256(String signingInput, String sharedSecret)\n throws NoSuchAlgorithmException, InvalidKeyException {\n SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), \"HmacSHA256\");\n Mac mac = Mac.getInstance(\"HmacSHA256\");\n mac.init(key);\n return encodeBase64URLSafeString(mac.doFinal(signingInput.getBytes()));\n }\n\n private static String getQueryStringHash(String canonicalUrl)\n throws NoSuchAlgorithmException,UnsupportedEncodingException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n md.update(canonicalUrl.getBytes(\"UTF-8\"));\n byte[] digest = md.digest();\n return encodeHexString(digest);\n }\n }\n\n[...]\n\npublic class Sample {\n public HttpRequest getUrlSample() throws Exception {\n String requestUrl =\n \"https://<my-dev-environment>.atlassian.net/rest/atlassian-connect/latest/license\";\n String canonicalUrl = \"GET&/rest/atlassian-connect/latest/license&\";\n String key = \"...\"; //from the app descriptor\n //and received during installation handshake\n String sharedSecret = \"...\"; //received during installation Handshake\n\n String jwtToken = JwtBuilder.generateJWTToken(\n requestUrl, canonicalUrl, key, sharedSecret);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(requestUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwtToken)\n .build();\n\n return request;\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.467Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":23,"totalLines":533,"estimatedTokens":3191}}223{"id":"doc-connect_frameworks_and_tools-1e033140","source":"documentation","title":"Connect frameworks and tools","url":"https://developer.atlassian.com/cloud/jira/platform/frameworks-and-tools/","text":"Example:\n```text\nAuthorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIuLi4iLCJpYXQiOjE2MzY5NDgwNDYsImV4cCI6MTYzNjk0ODIyNiwicXNoIjoiZTY0OGU0ZWI2ZjZiODBmYTI3YzRkNmMzZmEwMmJjNmVkNTY4NWIzNWJlMTUyNTc4YzM4ZTAwMzM5ODk3YWUxMyJ9.0vJbfzqWkacUOZPGUVgknhvggFTfyZry1tir30I_UCM\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.467Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":12,"estimatedTokens":81}}224{"id":"doc-add_licensing_support_to_server_apps-9c95a9d8","source":"documentation","title":"Add licensing support to server apps","url":"https://developer.atlassian.com/market/add-on-licensing-for-developers/tutorial-adding-licensing-support-to-your-add-on","text":"Example:\n```bash\ncd atlastutorial/\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\natlas-create-jira-plugin\n```\n\nExample:\n```bash\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 21.863 s\n[INFO] Finished at: 2016-11-17T15:18:35+10:00\n[INFO] Final Memory: 22M/314M\n[INFO] ------------------------------------------------------------------------\n```\n\nExample:\n```bash\ncd jiraLicensedPlugin/\n```\n\nExample:\n```bash\natlas-run\n```\n\nExample:\n```text\n``` bash\n[INFO] jira started successfully in 82s at http://localhost:2990/jira\n[INFO] Type Ctrl-D to shutdown gracefully\n[INFO] Type Ctrl-C to exit\n```\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>licensing-api</artifactId>\n <version>2.21.4</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>upm-api</artifactId>\n <version>2.21</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>javax.servlet</groupId>\n <artifactId>javax.servlet-api</artifactId>\n <version>3.0.1</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n<param name=\"atlassian-licensing-enabled\">true</param>\n```\n\nExample:\n```text\n<servlet name=\"License Servlet\" class=\"com.atlassian.tutorial.LicenseServlet\" key=\"license-servlet\">\n <description>Servlet which serves the raw license string</description>\n <url-pattern>/my/license</url-pattern>\n</servlet>\n```\n\nExample:\n```text\npackage com.atlassian.tutorial;\n\nimport com.atlassian.plugin.spring.scanner.annotation.component.Scanned;\nimport com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;\nimport com.atlassian.upm.api.license.PluginLicenseManager;\n\nimport javax.inject.Inject;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n@Scanned\npublic class LicenseServlet extends HttpServlet {\n @ComponentImport\n private final PluginLicenseManager pluginLicenseManager;\n\n @Inject\n public LicenseServlet(PluginLicenseManager pluginLicenseManager) {\n this.pluginLicenseManager = pluginLicenseManager;\n }\n\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n PrintWriter w = resp.getWriter();\n if (pluginLicenseManager.getLicense().isDefined()) {\n w.println(pluginLicenseManager.getLicense().get().getRawLicense());\n } else {\n w.println(\"License missing!\");\n }\n w.close();\n }\n}\n```\n\nExample:\n```javascript\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.470Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":131,"estimatedTokens":826}}225{"id":"doc-how_do_i_ensure_my_app_works_properly_in_a_clust-3797647c","source":"documentation","title":"How do I ensure my app works properly in a cluster?","url":"https://developer.atlassian.com/confdev/development-resources/confluence-developer-faq/how-do-i-ensure-my-add-on-works-properly-in-a-cluster","text":"Example:\n```text\nClusterLock lock = clusterLockService.getLockForName(getClass().getName() + \".taskExecutionLock\");\nif (lock.tryLock()) {\n try {\n log.info(\"Acquired lock to execute task\");\n executeTask();\n }\n finally {\n lock.unlock();\n }\n}\nelse {\n log.info(\"Task is running on another instance\");\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npublic class MyClusterEventListener {\n @EventListener\n public void handleLocalEvent(MyClusterEvent event) {\n // Handle event originating from local node\n }\n\n @EventListener\n public void handleRemoteEvent(ClusterEventWrapper wrapper) {\n Event event = wrapper.getEvent();\n if (event instanceof MyClusterEvent) {\n // Handle event originating from remote node\n }\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.471Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":42,"estimatedTokens":206}}226{"id":"doc-bundling_extra_dependencies_in_an_obr-95c988d1","source":"documentation","title":"Bundling extra dependencies in an OBR","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/bundling-extra-dependencies-in-an-obr/","text":"Example:\n```text\n<properties>\n <my.library.version>1.0</my.library.version>\n ....\n</properties>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<dependencies>\n <dependency>\n <groupId>my.company.whatever</groupId>\n <artifactId>my-library</artifactId>\n <version>${my.library.version}</version>\n <scope>provided</scope>\n </dependency>\n ....\n</dependencies>\n```\n\nExample:\n```text\n<build>\n <plugins>\n <plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>confluence-maven-plugin</artifactId>\n <!-- use the latest version of the SDK -->\n <version>3.2.4</version>\n <extensions>true</extensions>\n <configuration>\n <productVersion>${atlassian.product.version}</productVersion>\n <testResourcesVersion>${atlassian.product.data.version}</testResourcesVersion>\n <!-- Specify what to bundle in the OBR -->\n <pluginDependencies>\n <pluginDependency>\n <groupId>my.company.library</groupId>\n <artifactId>my-library</artifactId>\n </pluginDependency>\n </pluginDependencies>\n <instructions>\n <!-- Specify what package to include. Ensure that any packages from OBRs are also listed. -->\n <Import-Package>\n my.company.library;version=\"${my.library.version}\",\n ....\n </Import-Package>\n <CONF_COMM/>\n ....\n </instructions>\n </configuration>\n </plugin>\n ....\n </plugins>\n ....\n</build>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.471Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":65,"estimatedTokens":474}}227{"id":"doc-configuring_the_velocity_file_and_file_type_allo-7028c025","source":"documentation","title":"Configuring the Velocity file and file type allowlist","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/velocity-file-allowlist/","text":"Example:\n```text\nVelocity.setProperty(Velocity.RESOURCE_FILE_ALLOWLIST, \"templates/example.vm, templates/example2.vtl\");\nVelocity.setProperty(Velocity.FILE_RESOURCE_FILETYPE_ALLOWLIST, \".vm , .vtl\");\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.474Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":13,"estimatedTokens":61}}228{"id":"doc-confluence_data_center_plugin_validator-fb33f8fe","source":"documentation","title":"Confluence Data Center Plugin Validator","url":"https://developer.atlassian.com/confdev/development-resources/confluence-developer-faq/how-do-i-ensure-my-add-on-works-properly-in-a-cluster/confluence-data-center-plugin-validator","text":"Example:\n```bash\nprompt% java -jar cdc-plugin-validator-1.0.0.jar \\\n -installation /usr/local/atlassian-confluence-5.5.2 \\\n -dbuser confuser -dbpassword confuser \\\n -dburl jdbc:postgresql://localhost:5432/confluence \\\n -dbdriver org.postgresql.Driver \\\n -dbfile /usr/local/atlassian-confluence-5.5.2/confluence/WEB-INF/lib/postgresql-9.2-1002.jdbc4.jar\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n[unzip] Expanding: /Users/oburn/work/cdc-tool-test/findbugs-2.0.3.zip into /Users/oburn/work/cdc-tool-test\n [delete] Deleting: /Users/oburn/work/cdc-tool-test/findbugs-2.0.3.zip\nExporting com.atlassian.analytics.analytics-client\n to ./plugins/com.atlassian.analytics.analytics-client--analytics-client-3.24.jar\nfall back\nExporting com.atlassian.analytics.analytics-whitelist\n to ./plugins/com.atlassian.analytics.analytics-whitelist--analytics-whitelist-3.24.jar\nfall back\nExporting com.atlassian.confluence.plugins.confluence-questions\n to ./plugins/com.atlassian.confluence.plugins.confluence-questions--confluence-questions-1.0.645.jar\nfall back\nValidating: ./plugins/com.atlassian.analytics.analytics-client--analytics-client-3.24.jar\n [findbugs] Executing findbugs FindBugsTask from ant task\n [findbugs] Running FindBugs...\n [findbugs] Missing classes: 24\n [findbugs] Calculating exit code...\n [findbugs] Setting 'missing class' flag (2)\n [findbugs] Exit code set to: 2\n [findbugs] Java Result: 2\n [findbugs] Classes needed for analysis were missing\n [findbugs] Output saved to /Users/oburn/work/cdc-tool-test/./plugins/com.atlassian.analytics.analytics-client--analytics-client-3.24.jar.report\nValidating: ./plugins/com.atlassian.analytics.analytics-whitelist--analytics-whitelist-3.24.jar\n [findbugs] Executing findbugs FindBugsTask from ant task\n [findbugs] Running FindBugs...\n [findbugs] java.io.IOException: No files to analyze could be opened\n [findbugs] at edu.umd.cs.findbugs.FindBugs2.execute(FindBugs2.java:275)\n [findbugs] at edu.umd.cs.findbugs.FindBugs.runMain(FindBugs.java:393)\n [findbugs] at edu.umd.cs.findbugs.FindBugs2.main(FindBugs2.java:1317)\n [findbugs] Java Result: 4\nValidating: ./plugins/com.atlassian.confluence.plugins.confluence-jira-metadata--confluence-jira-metadata-1.7.3.jar\n [findbugs] Executing findbugs FindBugsTask from ant task\n [findbugs] Running FindBugs...\n [findbugs] Warnings generated: 2\n [findbugs] Missing classes: 6\n [findbugs] Calculating exit code...\n [findbugs] Setting 'missing class' flag (2)\n [findbugs] Setting 'bugs found' flag (1)\n [findbugs] Exit code set to: 3\n [findbugs] Java Result: 3\n [findbugs] Classes needed for analysis were missing\n [findbugs] Output saved to /Users/oburn/work/cdc-tool-test/./plugins/com.atlassian.confluence.plugins.confluence-jira-metadata--confluence-jira-metadata-1.7.3.jar.report\nValidating: ./plugins/com.atlassian.confluence.plugins.confluence-questions--confluence-questions-1.0.645.jar\n [findbugs] Executing findbugs FindBugsTask from ant task\n [findbugs] Running FindBugs...\n [findbugs] Missing classes: 19\n [findbugs] Calculating exit code...\n [findbugs] Setting 'missing class' flag (2)\n [findbugs] Exit code set to: 2\n [findbugs] Java Result: 2\n [findbugs] Classes needed for analysis were missing\n [findbugs] Output saved to /Users/oburn/work/cdc-tool-test/./plugins/com.atlassian.confluence.plugins.confluence-questions--confluence-questions-1.0.645.jar.report\n\nSummary:\n========\nPASS: ./plugins/com.atlassian.analytics.analytics-client--analytics-client-3.24.jar.report\nPASS: ./plugins/com.atlassian.analytics.analytics-whitelist--analytics-whitelist-3.24.jar.report\nFAIL: ./plugins/com.atlassian.confluence.plugins.confluence-jira-metadata--confluence-jira-metadata-1.7.3.jar.report\nPASS: ./plugins/com.atlassian.confluence.plugins.confluence-questions--confluence-questions-1.0.645.jar.report\n```\n\nExample:\n```bash\nSummary:\n========\nPASS: ./plugins/com.atlassian.analytics.analytics-client--analytics-client-3.24.jar.report\nPASS: ./plugins/com.atlassian.analytics.analytics-whitelist--analytics-whitelist-3.24.jar.report\nFAIL: ./plugins/com.atlassian.confluence.plugins.confluence-jira-metadata--confluence-jira-metadata-1.7.3.jar.report\nPASS: ./plugins/com.atlassian.confluence.plugins.confluence-questions--confluence-questions-1.0.645.jar.report\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.475Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":89,"estimatedTokens":1080}}229{"id":"doc-add_licensing_support_to_server_apps-684ac7dd","source":"documentation","title":"Add licensing support to server apps","url":"https://developer.atlassian.com/platform/marketplace/adding-licensing-support-to-server-apps","text":"Example:\n```bash\ncd atlastutorial/\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\natlas-create-jira-plugin\n```\n\nExample:\n```bash\n[INFO] ------------------------------------------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] ------------------------------------------------------------------------\n[INFO] Total time: 21.863 s\n[INFO] Finished at: 2016-11-17T15:18:35+10:00\n[INFO] Final Memory: 22M/314M\n[INFO] ------------------------------------------------------------------------\n```\n\nExample:\n```bash\ncd jiraLicensedPlugin/\n```\n\nExample:\n```bash\natlas-run\n```\n\nExample:\n```text\n``` bash\n[INFO] jira started successfully in 82s at http://localhost:2990/jira\n[INFO] Type Ctrl-D to shutdown gracefully\n[INFO] Type Ctrl-C to exit\n```\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>licensing-api</artifactId>\n <version>2.21.4</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>com.atlassian.upm</groupId>\n <artifactId>upm-api</artifactId>\n <version>2.21</version>\n <scope>provided</scope>\n</dependency>\n<dependency>\n <groupId>javax.servlet</groupId>\n <artifactId>javax.servlet-api</artifactId>\n <version>3.0.1</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n<param name=\"atlassian-licensing-enabled\">true</param>\n```\n\nExample:\n```text\n<servlet name=\"License Servlet\" class=\"com.atlassian.tutorial.LicenseServlet\" key=\"license-servlet\">\n <description>Servlet which serves the raw license string</description>\n <url-pattern>/my/license</url-pattern>\n</servlet>\n```\n\nExample:\n```text\npackage com.atlassian.tutorial;\n\nimport com.atlassian.plugin.spring.scanner.annotation.component.Scanned;\nimport com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;\nimport com.atlassian.upm.api.license.PluginLicenseManager;\n\nimport javax.inject.Inject;\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.io.PrintWriter;\n\n@Scanned\npublic class LicenseServlet extends HttpServlet {\n @ComponentImport\n private final PluginLicenseManager pluginLicenseManager;\n\n @Inject\n public LicenseServlet(PluginLicenseManager pluginLicenseManager) {\n this.pluginLicenseManager = pluginLicenseManager;\n }\n\n @Override\n protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {\n PrintWriter w = resp.getWriter();\n if (pluginLicenseManager.getLicense().isDefined()) {\n w.println(pluginLicenseManager.getLicense().get().getRawLicense());\n } else {\n w.println(\"License missing!\");\n }\n w.close();\n }\n}\n```\n\nExample:\n```javascript\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.475Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":131,"estimatedTokens":826}}230{"id":"doc-developing_for_high_availability_and_clustering-3bb7203a","source":"documentation","title":"Developing for high availability and clustering","url":"https://developer.atlassian.com/jiradev/jira-platform/jira-data-center/plugin-guide-to-jira-high-availability-and-clustering","text":"Example:\n```text\n<dependency>\n <groupId>com.atlassian.scheduler</groupId>\n <artifactId>atlassian-scheduler-api</artifactId>\n <version>1.0</version>\n</dependency>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nschedulerService.registerJobRunner(JobRunnerKey.of(\"com.example.schedulingplugin.JOBXX\"), new SendFilterJob());\n```\n\nExample:\n```text\nSchedule schedule = Schedule.forCronExpression(cronExpression);\nJobConfig jobConfig = JobConfig.forJobRunnerKey(JobRunnerKey.of(\"com.example.schedulingplugin.JOBXX\"))\n .withSchedule(schedule)\n .withParameters(ImmutableMap.<String, Serializable>of(\"SUBSCRIPTION_ID\", subscriptionId));\nJobId jobId = JobId.of(\"com.atlassian.example.schedulingplugin:\" + subId);\nschedulerService.scheduleJob(jobId, jobConfig);\n```\n\nExample:\n```text\n<dependency>\n <groupId>com.atlassian.cache</groupId>\n <artifactId>atlassian-cache-api</artifactId>\n <version>2.0.2</version>\n</dependency>\n```\n\nExample:\n```text\n// Self loading cache using Atlassian Cache\n private final Cache<String, String> myLowerCaseCache;\n\n// To get or create the cache. This would normally be in your component's constructor\n cache = cacheFactory.getCache(CachingAvatarStore.class.getName() + \".cache\",\n new AvatarCacheLoader(),\n new CacheSettingsBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build()); \n\n// The loader class\n private class AvatarCacheLoader implements CacheLoader<Long, CacheObject<Avatar>>\n {\n @Override\n public CacheObject<Avatar> load(@NotNull final Long avatarId)\n {\n return new CacheObject<Avatar>(CachingAvatarStore.this.delegate.getById(avatarId));\n }\n }\n\n// To retrieve an object from the cache\n cache.get(avatarId).getValue()\n```\n\nExample:\n```text\nCachedReference<AllProjectRoles> projectRoles;\n \n// To get or create the reference. This would normally be in your components constructor\n projectRoles = cacheFactory.getCachedReference(getClass(), \"projectRoles\",\n new AllProjectRolesLoader());\n\n// The Supplier\n class AllProjectRolesLoader implements Supplier<AllProjectRoles>\n {\n public AllProjectRoles get()\n {\n return new AllProjectRoles(delegate.getAllProjectRoles());\n }\n }\n\n// To retrieve the object\n return projectRoles.get();\n// When you add/update/remove a project role reset the reference. This will be propagated across the cluster.\n projectRoles.reset();\n```\n\nExample:\n```text\npackage com.example.locking;\n\nimport com.atlassian.beehive.ClusterLockService;\nimport java.util.concurrent.locks.Lock;\nimport javax.annotation.Resource;\n\n\npublic class MyService {\n \n // Your lock should have a globally unique name, e.g. using fully qualified class name, as here\n private static final String LOCK_NAME = MyService.class.getName() + \".myLockedTask\";\n \n @Resource // or inject via constructor, etc.\n private ClusterLockService clusterLockService;\n \n public void doSomethingThatRequiresAClusterLock() {\n final Lock lock = clusterLockService.getLockForName(LOCK_NAME);\n lock.lock();\n try {\n // Do the thing that needs a lock\n }\n finally {\n lock.unlock();\n }\n }\n}\n```\n\nExample:\n```text\npackage com.example.messaging;\n\nimport com.atlassian.jira.cluster.ClusterMessageConsumer;\nimport com.atlassian.jira.cluster.ClusterMessagingService;\n\npublic class MyService {\n\n private static final String EXAMPLE_CHANNEL = \"EXAMPLE\";\n private final ClusterMessagingService clusterMessagingService;\n private final MessageConsumer messageConsumer;\n\n public MyService(final ClusterMessagingService clusterMessagingService) {\n this.clusterMessagingService = clusterMessagingService;\n messageConsumer = new MessageConsumer();\n clusterMessagingService.registerListener(EXAMPLE_CHANNEL, messageConsumer);\n }\n\n public void actionRequiringOtherNodesToBeNotified() {\n // Perform action\n clusterMessagingService.sendRemote(EXAMPLE_CHANNEL, \"Action completed\");\n }\n\n private static class MessageConsumer implements ClusterMessageConsumer {\n\n @Override\n public void receive(final String channel, final String message, final String senderId) {\n // Handle message\n }\n }\n}\n```\n\nExample:\n```text\n<param name=\"atlassian-data-center-status\">compatible</param>\n<param name=\"atlassian-data-center-compatible\">true</param>\n```\n\nExample:\n```text\n<plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"atlassian-data-center-status\">compatible</param>\n <param name=\"atlassian-data-center-compatible\">true</param>\n</plugin-info>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.476Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":168,"estimatedTokens":1215}}231{"id":"doc-oauth_2_0_3lo_apps-457a1096","source":"documentation","title":"OAuth 2.0 (3LO) apps","url":"https://developer.atlassian.com/cloud/jira/platform/oauth-2-3lo-apps/","text":"Example:\n```text\nhttps://auth.atlassian.com/authorize?\n audience=api.atlassian.com&\n client_id=YOUR_CLIENT_ID&\n scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&\n redirect_uri=https://YOUR_APP_CALLBACK_URL&\n state=YOUR_USER_BOUND_VALUE&\n response_type=code&\n prompt=consent\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{\"grant_type\": \"authorization_code\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"code\": \"YOUR_AUTHORIZATION_CODE\",\"redirect_uri\": \"https://YOUR_APP_CALLBACK_URL\"}'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/oauth/token/accessible-resources \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"read:servicedesk-request\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:confluence-content\",\n \"read:confluence-content.all\",\n \"manage:confluence-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url <request URL> \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/ex/jira/11223344-a1b2-3b33-c444-def123456789/rest/api/2/project \\\n --header 'Authorization: Bearer aBCxYz654123' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --header 'Authorization: Bearer <access_token>' \\\n --url 'https://api.atlassian.com/oauth/token/accessible-resources'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{ \"grant_type\": \"refresh_token\", \"client_id\": \"YOUR_CLIENT_ID\", \"client_secret\": \"YOUR_CLIENT_SECRET\", \"refresh_token\": \"YOUR_REFRESH_TOKEN\" }'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"refresh_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/me \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n{\n \"account_type\": \"atlassian\",\n \"account_id\": \"112233aa-bb11-cc22-33dd-445566abcabc\",\n \"email\": \"mia@example.com\",\n \"name\": \"Mia Krystof\",\n \"picture\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/112233aa-bb11-cc22-33dd-445566abcabc/1234abcd-9876-54aa-33aa-1234dfsade9487ds\",\n \"account_status\": \"active\",\n \"nickname\": \"mkrystof\",\n \"zoneinfo\": \"Australia/Sydney\",\n \"locale\": \"en-US\",\n \"extended_profile\": {\n \"job_title\": \"Designer\",\n \"organization\": \"mia@example.com\",\n \"department\": \"Design team\",\n \"location\": \"Sydney\"\n }\n}\n```\n\nExample:\n```typescript\nconst response = await fetch(`https://auth.atlassian.com/oauth/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json'\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: this.refreshToken\n })\n });\n \n const json = response.json();\n \n this.accessToken = json.access_token;\n \n if(json.refresh_token) { \n this.refreshToken = json.refresh_token;\n }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.478Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":246,"estimatedTokens":1465}}232{"id":"doc-automating_part_of_your_platform_7_upgrades_with-c14c048f","source":"documentation","title":"Automating part of your Platform 7 upgrades with Openrewrite","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-platform-7-openrewrite","text":"Example:\n```text\n<profile>\n <id>openrewrite</id>\n <build>\n <plugins>\n <plugin>\n <groupId>org.openrewrite.maven</groupId>\n <artifactId>rewrite-maven-plugin</artifactId>\n <version>5.15.4</version> <!-- This version can be newer if you want -->\n <configuration>\n <!-- This tag ensures that we first do a dry run on all modules, before doing a real run. -->\n <failOnDryRunResults>true</failOnDryRunResults>\n </configuration>\n <dependencies>\n <dependency>\n <groupId>org.openrewrite.recipe</groupId>\n <artifactId>rewrite-migrate-java</artifactId>\n <!-- Ensure this uses the latest version, especially when running standard recipes -->\n <version>2.8.0</version>\n </dependency>\n <dependency>\n <groupId>com.atlassian.platform</groupId>\n <artifactId>openrewrite-recipes</artifactId>\n <!-- Replace this with the latest version of the openrewrite-recipes dependency -->\n <version>${platform.version}</version>\n </dependency>\n </dependencies>\n </plugin>\n </plugins>\n </build>\n</profile>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nmvn clean install -DskipTests\n```\n\nExample:\n```bash\nmvn -Popenrewrite rewrite:run -Drewrite.activeRecipes=com.package.Recipe\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.479Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":51,"estimatedTokens":344}}233{"id":"doc-configuring_the_velocity_method_allowlist-086bbfec","source":"documentation","title":"Configuring the Velocity method allowlist","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/configuring-the-velocity-allowlist","text":"Example:\n```text\n<velocity-allowlist key=\"velocity-allowlist-dynamictasklist2\">\n <method>com.atlassian.confluence.extra.dynamictasklist2.NameRenderer#render(java.lang.String boolean)</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.Task#getAssignee()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.Task$Priority#toString()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.TaskList#getCompleteTasks()</method>\n <method>com.atlassian.confluence.extra.dynamictasklist2.model.TaskList#getName()</method>\n</velocity-allowlist>\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.479Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":18,"estimatedTokens":163}}234{"id":"doc-templating_in_javascript_with_soy-7d0dc474","source":"documentation","title":"Templating in JavaScript with Soy","url":"https://developer.atlassian.com/server/confluence/templating-in-javascript-with-soy/","text":"Example:\n```text\n{namespace Confluence.Templates.Example}\n/**\n * Renders a Hello message.\n * @param name the name of the user\n */\n{template .helloWorld}\n<div>Hello {$name}!</div>\n{/template}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<web-resource key=\"example-resources\" name=\"Example Resources\">\n...\n<transformation extension=\"soy\">\n<transformer key=\"soyTransformer\"/>\n</transformation> ...\n<resource type=\"download\" name=\"example-soy.js\" location=\"/soy/example.soy\"/>\n....\n</web-resource>\n```\n\nExample:\n```javascript\n// display a hello message on the page \nvar template = Confluence.Templates.Example.helloWorld({name: \"John Smith\"});\nAJS.messages.info(jQuery(\"body\"), {body: template, closeable: true});\n```\n\nExample:\n```text\n/**\n * A template for a dialog help link\n * @param href key of the doc link url\n */\n{template .helpLink}\n<div class=\"dialog-help-link\">\n<a href=\"{$href}\" target=\"_blank\">{getText('help.name')}</a>\n</div>\n{/template}\n```\n\nExample:\n```text\n/**\n * Move page dialog help link\n */\n{template .helpLink}\n{call Confluence.Templates.Dialog.helpLink}\n{param href: docLink('help.moving.page') /}\n{/call}\n{/template}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.479Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":63,"estimatedTokens":291}}235{"id":"doc-internationalising_your_plugin-bedc3f0d","source":"documentation","title":"Internationalising your plugin","url":"https://developer.atlassian.com/server/framework/atlassian-sdk/internationalising-your-plugin/","text":"Example:\n```text\ncom.example.plugin.fruit.basket.label=Your Fruit Basket\ncom.example.plugin.fruit.apple=Apple\ncom.example.plugin.fruit.banana=Banana\ncom.example.plugin.fruit.pear=Pear\ncom.example.plugin.fruit.basket.add.button=Add Fruit\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nsrc/main/resources/com/example/mypluginname/mypluginprops.properties\n```\n\nExample:\n```bash\nsrc/main/resources/com/example/mypluginname/mypluginprops_de_DE.properties\n```\n\nExample:\n```text\n<atlassian-plugin>\n ...\n <resource type=\"i18n\" name=\"mypluginprops\" location=\"com.example.mypluginname\"/>\n ...\n</atlassian-plugin>\n```\n\nExample:\n```text\n<atlassian-plugin>\n ...\n <web-item>\n <label>Add Fruit</label>\n <link>/pages/addfruittobasket.action?pageId=$page.id</link>\n </web-item>\n ...\n</atlassian-plugin>\n```\n\nExample:\n```text\n<atlassian-plugin>\n ...\n <web-item>\n <label key=\"com.example.plugin.fruit.basket.add.button\"/>\n <link>/pages/addfruittobasket.action?pageId=$page.id</link>\n </web-item>\n\n <resource type=\"i18n\" name=\"i18n\" location=\"com.example.mypluginname.MyPluginName\"/>\n ...\n</atlassian-plugin>\n```\n\nExample:\n```text\ncom.example.plugin.fruit.basket.add.button=Add Fruit\n```\n\nExample:\n```text\ngetText(\"com.example.plugin.fruit.basket.add.button\")\n```\n\nExample:\n```text\ncom.example.plugin.fruit.basket.contains=Fruit basket item count: {0}\n```\n\nExample:\n```text\nList<String> fruits=Arrays.asList(\"apple\",\"banana\",\"apricot\");\n return getText(\"com.example.plugin.fruit.basket.contains\",new String[]{String.valueOf(fruits.size())});\n```\n\nExample:\n```text\npublic class AddFruitMacro extends BaseMacro {\n\n private final I18nResolver i18n;\n\n public void setI18nResolver(I18nResolver i18n) {\n this.i18n = i18n;\n }\n\n public String execute(Map params, String body, RenderContext context) {\n return \"<button>\" + i18n.getText(\"com.example.plugin.fruit.basket.add.button\") + \"</button>\";\n }\n}\n```\n\nExample:\n```text\n<component-import key=\"i18nResolver\" interface=\"com.atlassian.sal.api.message.I18nResolver\"/>\n```\n\nExample:\n```text\n<p>\n $i18n.getText(\"com.example.plugin.fruit.basket.contains\", [$fruits.size()])\n</p>\n<button>\n $i18n.getText(\"com.example.plugin.fruit.basket.add.button\")\n</button>\n```\n\nExample:\n```text\ncom.example.plugin.fruit.basket.contains = This occurrence of %sprint% will be replaced, but this sprint will not.\n```\n\nExample:\n```text\n<project>\n ...\n <build>\n ...\n </build>\n ...\n <reporting>\n <plugins>\n <plugin>\n <groupId>org.codehaus.mojo</groupId>\n <artifactId>l10n-maven-plugin</artifactId>\n <version>1.0-SNAPSHOT</version>\n <configuration>\n <locales>\n <locale>de</locale>\n <locale>es</locale>\n <locale>fr</locale>\n ...\n </locales>\n </configuration>\n </plugin>\n </plugins>\n </reporting>\n ...\n <pluginRepositories>\n <pluginRepository>\n <id>codehaus.org</id>\n <name>CodeHaus Plugin Snapshots</name>\n <url></url>\n <releases>\n <enabled>false</enabled>\n </releases>\n <snapshots>\n <enabled>true</enabled>\n </snapshots>\n </pluginRepository>\n </pluginRepositories>\n</project>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.480Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":160,"estimatedTokens":820}}236{"id":"doc-forge_bridge_apis-8e08909c","source":"documentation","title":"Forge bridge APIs","url":"https://developer.atlassian.com/platform/forge/custom-ui-bridge/bridge/","text":"Example:\n```javascript\nimport { invoke } from '@forge/bridge';\n\ninvoke('getText', { example: 'my-invoke-variable' }).then(setData);\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.480Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":14,"estimatedTokens":44}}237{"id":"doc-submit_your_data_center_app_to_atlassian_marketp-516f0021","source":"documentation","title":"Submit your Data Center app to Atlassian Marketplace","url":"https://developer.atlassian.com/platform/marketplace/dc-apps-submitting-your-app","text":"Example:\n```text\n<param name=\"atlassian-data-center-status\">compatible</param>\n<param name=\"atlassian-data-center-compatible\">true</param>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n <param name=\"atlassian-data-center-status\">compatible</param>\n <param name=\"atlassian-data-center-compatible\">true</param>\n</plugin-info>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.481Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":24,"estimatedTokens":136}}238{"id":"doc-bundling_extra_dependencies_in_an_obr-389c6e90","source":"documentation","title":"Bundling extra dependencies in an OBR","url":"https://developer.atlassian.com/display/DOCS/Bundling+extra+dependencies+in+an+OBR","text":"Example:\n```text\n<properties>\n <my.library.version>1.0</my.library.version>\n ....\n</properties>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<dependencies>\n <dependency>\n <groupId>my.company.whatever</groupId>\n <artifactId>my-library</artifactId>\n <version>${my.library.version}</version>\n <scope>provided</scope>\n </dependency>\n ....\n</dependencies>\n```\n\nExample:\n```text\n<build>\n <plugins>\n <plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>confluence-maven-plugin</artifactId>\n <!-- use the latest version of the SDK -->\n <version>3.2.4</version>\n <extensions>true</extensions>\n <configuration>\n <productVersion>${atlassian.product.version}</productVersion>\n <testResourcesVersion>${atlassian.product.data.version}</testResourcesVersion>\n <!-- Specify what to bundle in the OBR -->\n <pluginDependencies>\n <pluginDependency>\n <groupId>my.company.library</groupId>\n <artifactId>my-library</artifactId>\n </pluginDependency>\n </pluginDependencies>\n <instructions>\n <!-- Specify what package to include. Ensure that any packages from OBRs are also listed. -->\n <Import-Package>\n my.company.library;version=\"${my.library.version}\",\n ....\n </Import-Package>\n <CONF_COMM/>\n ....\n </instructions>\n </configuration>\n </plugin>\n ....\n </plugins>\n ....\n</build>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.482Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":65,"estimatedTokens":474}}239{"id":"doc-oauth_2_0_3lo_apps-5c3d4d31","source":"documentation","title":"OAuth 2.0 (3LO) apps","url":"https://developer.atlassian.com/cloud/confluence/oauth-2-authorization-code-grants-3lo-for-apps/","text":"Example:\n```text\nhttps://auth.atlassian.com/authorize?\n audience=api.atlassian.com&\n client_id=YOUR_CLIENT_ID&\n scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&\n redirect_uri=https://YOUR_APP_CALLBACK_URL&\n state=YOUR_USER_BOUND_VALUE&\n response_type=code&\n prompt=consent\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{\"grant_type\": \"authorization_code\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"code\": \"YOUR_AUTHORIZATION_CODE\",\"redirect_uri\": \"https://YOUR_APP_CALLBACK_URL\"}'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/oauth/token/accessible-resources \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"read:servicedesk-request\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:confluence-content\",\n \"read:confluence-content.all\",\n \"manage:confluence-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url <request URL> \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/ex/jira/11223344-a1b2-3b33-c444-def123456789/rest/api/2/project \\\n --header 'Authorization: Bearer aBCxYz654123' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --header 'Authorization: Bearer <access_token>' \\\n --url 'https://api.atlassian.com/oauth/token/accessible-resources'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{ \"grant_type\": \"refresh_token\", \"client_id\": \"YOUR_CLIENT_ID\", \"client_secret\": \"YOUR_CLIENT_SECRET\", \"refresh_token\": \"YOUR_REFRESH_TOKEN\" }'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"refresh_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/me \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n{\n \"account_type\": \"atlassian\",\n \"account_id\": \"112233aa-bb11-cc22-33dd-445566abcabc\",\n \"email\": \"mia@example.com\",\n \"name\": \"Mia Krystof\",\n \"picture\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/112233aa-bb11-cc22-33dd-445566abcabc/1234abcd-9876-54aa-33aa-1234dfsade9487ds\",\n \"account_status\": \"active\",\n \"nickname\": \"mkrystof\",\n \"zoneinfo\": \"Australia/Sydney\",\n \"locale\": \"en-US\",\n \"extended_profile\": {\n \"job_title\": \"Designer\",\n \"organization\": \"mia@example.com\",\n \"department\": \"Design team\",\n \"location\": \"Sydney\"\n }\n}\n```\n\nExample:\n```typescript\nconst response = await fetch(`https://auth.atlassian.com/oauth/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json'\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: this.refreshToken\n })\n });\n \n const json = response.json();\n \n this.accessToken = json.access_token;\n \n if(json.refresh_token) { \n this.refreshToken = json.refresh_token;\n }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.484Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":246,"estimatedTokens":1465}}240{"id":"doc-oauth_2_0_3lo_apps-881e8b4e","source":"documentation","title":"OAuth 2.0 (3LO) apps","url":"https://developer.atlassian.com/cloud/jira/platform/oauth-2-authorization-code-grants-3lo-for-apps/","text":"Example:\n```text\nhttps://auth.atlassian.com/authorize?\n audience=api.atlassian.com&\n client_id=YOUR_CLIENT_ID&\n scope=REQUESTED_SCOPE_ONE%20REQUESTED_SCOPE_TWO&\n redirect_uri=https://YOUR_APP_CALLBACK_URL&\n state=YOUR_USER_BOUND_VALUE&\n response_type=code&\n prompt=consent\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{\"grant_type\": \"authorization_code\",\"client_id\": \"YOUR_CLIENT_ID\",\"client_secret\": \"YOUR_CLIENT_SECRET\",\"code\": \"YOUR_AUTHORIZATION_CODE\",\"redirect_uri\": \"https://YOUR_APP_CALLBACK_URL\"}'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/oauth/token/accessible-resources \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"read:servicedesk-request\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name\",\n \"url\": \"https://your-domain.atlassian.net\",\n \"scopes\": [\n \"write:confluence-content\",\n \"read:confluence-content.all\",\n \"manage:confluence-configuration\"\n ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url <request URL> \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/ex/jira/11223344-a1b2-3b33-c444-def123456789/rest/api/2/project \\\n --header 'Authorization: Bearer aBCxYz654123' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```bash\ncurl --header 'Authorization: Bearer <access_token>' \\\n --url 'https://api.atlassian.com/oauth/token/accessible-resources'\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\" ],\n \"avatarUrl\": \"https:\\/\\/site-admin-avatar-cdn.prod.public.atl-paas.net\\/avatars\\/240\\/flag.png\"\n }\n]\n```\n\nExample:\n```json\n[\n {\n \"id\": \"8594f221-9797-5f78-1fa4-485e198d7cd0\",\n \"name\": \"Site name 2\",\n \"url\": \"https://your-domain2.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/koala.png\"\n },\n {\n \"id\": \"1324a887-45db-1bf4-1e99-ef0ff456d421\",\n \"name\": \"Site name 1\",\n \"url\": \"https://your-domain1.atlassian.net\",\n \"scopes\": [\n \"write:jira-work\",\n \"read:jira-user\",\n \"manage:jira-configuration\"\n ],\n \"avatarUrl\": \"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/flag.png\"\n }\n]\n```\n\nExample:\n```bash\ncurl --request POST \\\n --url 'https://auth.atlassian.com/oauth/token' \\\n --header 'Content-Type: application/json' \\\n --data '{ \"grant_type\": \"refresh_token\", \"client_id\": \"YOUR_CLIENT_ID\", \"client_secret\": \"YOUR_CLIENT_SECRET\", \"refresh_token\": \"YOUR_REFRESH_TOKEN\" }'\n```\n\nExample:\n```json\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{\n \"access_token\": <string>,\n \"refresh_token\": <string>,\n \"expires_in\": <expiry time of access_token in second>,\n \"scope\": <string>\n}\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url https://api.atlassian.com/me \\\n --header 'Authorization: Bearer ACCESS_TOKEN' \\\n --header 'Accept: application/json'\n```\n\nExample:\n```json\n{\n \"account_type\": \"atlassian\",\n \"account_id\": \"112233aa-bb11-cc22-33dd-445566abcabc\",\n \"email\": \"mia@example.com\",\n \"name\": \"Mia Krystof\",\n \"picture\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/112233aa-bb11-cc22-33dd-445566abcabc/1234abcd-9876-54aa-33aa-1234dfsade9487ds\",\n \"account_status\": \"active\",\n \"nickname\": \"mkrystof\",\n \"zoneinfo\": \"Australia/Sydney\",\n \"locale\": \"en-US\",\n \"extended_profile\": {\n \"job_title\": \"Designer\",\n \"organization\": \"mia@example.com\",\n \"department\": \"Design team\",\n \"location\": \"Sydney\"\n }\n}\n```\n\nExample:\n```typescript\nconst response = await fetch(`https://auth.atlassian.com/oauth/token`, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json'\n },\n body: JSON.stringify({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: this.refreshToken\n })\n });\n \n const json = response.json();\n \n this.accessToken = json.access_token;\n \n if(json.refresh_token) { \n this.refreshToken = json.refresh_token;\n }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.487Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":18,"totalLines":246,"estimatedTokens":1465}}241{"id":"doc-java_apis-b44f9498","source":"documentation","title":"Java APIs","url":"https://developer.atlassian.com/server/jira/platform/java-apis/","text":"Example:\n```text\n<dependency>\n <groupId>com.atlassian.jira</groupId>\n <artifactId>jira-api</artifactId>\n <version>${atlassian.product.version}</version>\n <scope>provided</scope>\n</dependency>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n/**\n * Returns the logged in user.\n *\n * @deprecated Use {@link #getLoggedInUser()} instead. Since v4.3\n */\npublic User getRemoteUser()\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.488Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":27,"estimatedTokens":102}}242{"id":"doc-oauth-931d8d96","source":"documentation","title":"OAuth","url":"https://developer.atlassian.com/server/jira/platform/oauth/","text":"Example:\n```bash\nopenssl genrsa -out jira_privatekey.pem 1024\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nopenssl req -newkey rsa:1024 -x509 -key jira_privatekey.pem -out jira_publickey.cer -days 365\n```\n\nExample:\n```bash\nopenssl pkcs8 -topk8 -nocrypt -in jira_privatekey.pem -out jira_privatekey.pcks8\n```\n\nExample:\n```bash\nopenssl x509 -pubkey -noout -in jira_publickey.cer > jira_publickey.pem\n```\n\nExample:\n```text\n* Change `JIRA_HOME` to the URL of your Jira site. Ours is `http://localhost:8080`.\n```\n\nExample:\n```text\n* Change `PRIVATE_KEY` to the private key in your `jira_privatekey.pcks8` file.\n\nFor us, the `DEFAULT_PROPERTY_VALUES` field looks like the following:\n\n``` java\nprivate final static Map<String, String> DEFAULT_PROPERTY_VALUES = ImmutableMap.<String, String>builder()\n .put(JIRA_HOME, \"http://localhost:8080\")\n .put(CONSUMER_KEY, \"OauthKey\")\n .put(PRIVATE_KEY, \"AKJsdaSLKFlkadfdjfsjfsdkjfksjf...\")\n .build();\n```\n```\n\nExample:\n```text\n``` bash\nmvn clean compile assembly:single\n```\n\nYou'll see a message like the following in terminal when the client is built:\n\n``` bash\n[INFO] --------------------------------------\n[INFO] BUILD SUCCESS\n[INFO] --------------------------------------\n[INFO] Total time: 2.995 s\n[INFO] Finished at: 2019-03-27T15:43:45+11:00\n[INFO] Final Memory: 25M/588M\n[INFO] --------------------------------------\n```\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar requestToken\n```\n\nExample:\n```bash\nToken: ec3dj4byySM5ek3XW7gl7f4oc99obAlo\nToken Secret: OhONj0eF7zhXAMKZLbD2Rd3x7Dmxjy0d\nRetrieved request token. go to https://jira101.atlassian.net/plugins/servlet/oauth/authorize?oauth_token=ec3dj4byySM5ek3XW7gl7f4oc99obAlo to authorize it\n```\n\nExample:\n```bash\nAccess Approved\nYou have successfully authorized 'Example JIRA app'. Your verification code is 'qTJkPi'. You will need to\nenter this exact text when prompted. You should write this value down before closing the browser window.\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar accessToken qTJkPi\n```\n\nExample:\n```bash\nAccess Token: W1jjOV4sq2iEqxO4ZYZTJVdgbjtKc2ye\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar request {URL for operation}>\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar request http://localhost:8080/rest/api/latest/project\n```\n\nExample:\n```json\n[\n {\n \"expand\": \"description,lead,url,projectKeys\",\n \"self\": \"http://localhost:8080/rest/api/2/project/10101\",\n \"id\": \"10101\",\n \"key\": \"SP\",\n \"name\": \"A second project\",\n \"avatarUrls\": {\n \"48x48\": \"http://localhost:8080/secure/projectavatar?avatarId=10324\",\n \"24x24\": \"http://localhost:8080/secure/projectavatar?size=small&avatarId=10324\",\n \"16x16\": \"http://localhost:8080/secure/projectavatar?size=xsmall&avatarId=10324\",\n \"32x32\": \"http://localhost:8080/secure/projectavatar?size=medium&avatarId=10324\"\n },\n \"projectTypeKey\": \"business\"\n },\n {\n \"expand\": \"description,lead,url,projectKeys\",\n \"self\": \"http://localhost:8080/rest/api/2/project/10100\",\n \"id\": \"10100\",\n \"key\": \"TP\",\n \"name\": \"Test project\",\n \"avatarUrls\": {\n \"48x48\": \"http://localhost:8080/secure/projectavatar?avatarId=10324\",\n \"24x24\": \"http://localhost:8080/secure/projectavatar?size=small&avatarId=10324\",\n \"16x16\": \"http://localhost:8080/secure/projectavatar?size=xsmall&avatarId=10324\",\n \"32x32\": \"http://localhost:8080/secure/projectavatar?size=medium&avatarId=10324\"\n },\n \"projectTypeKey\": \"business\"\n }\n]\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.489Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":16,"totalLines":139,"estimatedTokens":891}}243{"id":"doc-oauth_1_0a_for_rest_apis_deprecated-50dc37e1","source":"documentation","title":"OAuth 1.0a for REST APIs (Deprecated)","url":"https://developer.atlassian.com/cloud/jira/platform/jira-rest-api-oauth-authentication/","text":"Example:\n```bash\ncd atlassian-oauth-examples/java\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nmvn clean compile assembly:single\n```\n\nExample:\n```javascript\njava -jar OAuthTutorialClient-1.0.jar requestToken\n```\n\nExample:\n```bash\nopenssl genrsa -out jira_privatekey.pem 1024\nopenssl req -newkey rsa:1024 -x509 -key jira_privatekey.pem -out jira_publickey.cer -days 365\nopenssl pkcs8 -topk8 -nocrypt -in jira_privatekey.pem -out jira_privatekey.pcks8\nopenssl x509 -pubkey -noout -in jira_publickey.cer > jira_publickey.pem\n```\n\nExample:\n```bash\nToken: ec3dj4byySM5ek3XW7gl7f4oc99obAlo\nToken Secret: OhONj0eF7zhXAMKZLbD2Rd3x7Dmxjy0d\nRetrieved request token. go to https://jira101.atlassian.net/plugins/servlet/oauth/authorize?oauth_token=ec3dj4byySM5ek3XW7gl7f4oc99obAlo to authorize it\n```\n\nExample:\n```javascript\nAccess Approved\nYou have successfully authorized 'Example Jira app'. Your verification code is 'qTJkPi'. You will need to enter this exact text when prompted. You should write this value down before closing the browser window.\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar accessToken qTJkPi\n```\n\nExample:\n```bash\nAccess Token: W1jjOV4sq2iEqxO4ZYZTJVdgbjtKc2ye\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar request <URL for GET method>\n```\n\nExample:\n```bash\njava -jar OAuthTutorialClient-1.0.jar request https://example-dev1.atlassian.net/rest/api/latest/issue/JJ-2\n```\n\nExample:\n```javascript\n{\n \"expand\": \"renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/latest/issue/10300\">https://example-dev1.atlassian.net/rest/api/latest/issue/10300</a>\",\n \"id\": \"10300\",\n \"fields\": {\n \"issuetype\": {\n \"avatarId\": 10803,\n \"name\": \"Bug\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/issuetype/1\">https://example-dev1.atlassian.net/rest/api/2/issuetype/1</a>\",\n \"description\": \"A problem which impairs or prevents the functions of the product.\",\n \"id\": \"1\",\n \"iconUrl\": \"<a href=\"https://example-dev1.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10803&avatarType=issuetype\">https://example-dev1.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10803&avatarType=issuetype</a>\",\n \"subtask\": false\n },\n \"timespent\": null,\n \"project\": {\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://example-dev1.atlassian.net/secure/projectavatar?pid=10200&avatarId=10700\">https://example-dev1.atlassian.net/secure/projectavatar?pid=10200&avatarId=10700</a>\",\n \"24x24\": \"<a href=\"https://example-dev1.atlassian.net/secure/projectavatar?size=small&pid=10200&avatarId=10700\">https://example-dev1.atlassian.net/secure/projectavatar?size=small&pid=10200&avatarId=10700</a>\",\n \"16x16\": \"<a href=\"https://example-dev1.atlassian.net/secure/projectavatar?size=xsmall&pid=10200&avatarId=10700\">https://example-dev1.atlassian.net/secure/projectavatar?size=xsmall&pid=10200&avatarId=10700</a>\",\n \"32x32\": \"<a href=\"https://example-dev1.atlassian.net/secure/projectavatar?size=medium&pid=10200&avatarId=10700\">https://example-dev1.atlassian.net/secure/projectavatar?size=medium&pid=10200&avatarId=10700</a>\"\n },\n \"name\": \"Jira Junior\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/project/10200\">https://example-dev1.atlassian.net/rest/api/2/project/10200</a>\",\n \"id\": \"10200\",\n \"key\": \"JJ\"\n },\n \"fixVersions\": [],\n \"aggregatetimespent\": null,\n \"resolution\": null,\n \"customfield_10500\": null,\n \"customfield_10700\": \"com.atlassian.servicedesk.plugins.approvals.internal.customfield.ApprovalsCFValue@c8d588\",\n \"resolutiondate\": null,\n \"workratio\": -1,\n \"lastViewed\": \"2016-08-01T11:23:39.481+1000\",\n \"watches\": {\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/watchers\">https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/watchers</a>\",\n \"isWatching\": true,\n \"watchCount\": 1\n },\n \"created\": \"2013-05-29T13:56:24.224+1000\",\n \"customfield_10020\": null,\n \"customfield_10021\": \"Not started\",\n \"priority\": {\n \"name\": \"Major\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/priority/3\">https://example-dev1.atlassian.net/rest/api/2/priority/3</a>\",\n \"iconUrl\": \"<a href=\"https://example-dev1.atlassian.net/images/icons/priorities/major.svg\">https://example-dev1.atlassian.net/images/icons/priorities/major.svg</a>\",\n \"id\": \"3\"\n },\n \"customfield_10300\": null,\n \"customfield_10102\": null,\n \"labels\": [],\n \"customfield_10016\": null,\n \"customfield_10017\": null,\n \"customfield_10018\": null,\n \"customfield_10019\": null,\n \"timeestimate\": null,\n \"aggregatetimeoriginalestimate\": null,\n \"versions\": [],\n \"issuelinks\": [],\n \"assignee\": {\n \"emailAddress\": \"alana@<a href=\"http://example.com\">example.com</a>\",\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=48\">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=48</a>\",\n \"24x24\": \"<a href=\"https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=24\">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=24</a>\",\n \"16x16\": \"<a href=\"https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=16\">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=16</a>\",\n \"32x32\": \"<a href=\"https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=32\">https://secure.gravatar.com/avatar/b259e2a7fd37a83b02015192ee247e96?d=mm&s=32</a>\"\n },\n \"displayName\": \"Alana Example\",\n \"name\": \"alana\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/user?username=alana\">https://example-dev1.atlassian.net/rest/api/2/user?username=alana</a>\",\n \"active\": true,\n \"timeZone\": \"Australia/Sydney\",\n \"key\": \"alana\"\n },\n \"updated\": \"2016-08-01T11:23:38.022+1000\",\n \"status\": {\n \"name\": \"Open\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/status/1\">https://example-dev1.atlassian.net/rest/api/2/status/1</a>\",\n \"description\": \"The issue is open and ready for the assignee to start work on it.\",\n \"iconUrl\": \"<a href=\"https://example-dev1.atlassian.net/images/icons/statuses/open.png\">https://example-dev1.atlassian.net/images/icons/statuses/open.png</a>\",\n \"id\": \"1\",\n \"statusCategory\": {\n \"colorName\": \"blue-gray\",\n \"name\": \"To Do\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/statuscategory/2\">https://example-dev1.atlassian.net/rest/api/2/statuscategory/2</a>\",\n \"id\": 2,\n \"key\": \"new\"\n }\n },\n \"components\": [],\n \"timeoriginalestimate\": null,\n \"description\": \"The logo is currently a light cerise. I'd like to see it with a deep pink color.\",\n \"customfield_10012\": null,\n \"customfield_10013\": null,\n \"customfield_10014\": null,\n \"timetracking\": {},\n \"customfield_10015\": null,\n \"customfield_10600\": null,\n \"customfield_10006\": \"10\",\n \"customfield_10601\": null,\n \"customfield_10007\": [\n \"com.atlassian.greenhopper.service.sprint.Sprint@dc6300[id=1,rapidViewId=<null>,state=CLOSED,name=Sprint 1,goal=<null>,startDate=2013-07-26T11:31:09.530+10:00,endDate=2013-08-09T11:31:09.530+10:00,completeDate=2013-07-26T11:31:46.489+10:00,sequence=1]\",\n \"com.atlassian.greenhopper.service.sprint.Sprint@6b3e17[id=2,rapidViewId=<null>,state=ACTIVE,name=Sprint 2,goal=<null>,startDate=2013-08-22T11:35:33.759+10:00,endDate=2013-12-12T11:35:00.000+11:00,completeDate=<null>,sequence=2]\"\n ],\n \"customfield_10008\": null,\n \"attachment\": [],\n \"aggregatetimeestimate\": null,\n \"summary\": \"Jira Junior logo is not pink enough\",\n \"creator\": {\n \"emailAddress\": \"admin@<a href=\"http://example.com\">example.com</a>\",\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>\",\n \"24x24\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>\",\n \"16x16\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>\",\n \"32x32\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>\"\n },\n \"displayName\": \"Administrator\",\n \"name\": \"admin\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/user?username=admin\">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>\",\n \"active\": true,\n \"timeZone\": \"Australia/Sydney\",\n \"key\": \"admin\"\n },\n \"subtasks\": [],\n \"reporter\": {\n \"emailAddress\": \"admin@<a href=\"http://example.com\">example.com</a>\",\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>\",\n \"24x24\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>\",\n \"16x16\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>\",\n \"32x32\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>\"\n },\n \"displayName\": \"Administrator\",\n \"name\": \"admin\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/user?username=admin\">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>\",\n \"active\": true,\n \"timeZone\": \"Australia/Sydney\",\n \"key\": \"admin\"\n },\n \"customfield_10000\": null,\n \"aggregateprogress\": {\n \"total\": 0,\n \"progress\": 0\n },\n \"customfield_10001\": null,\n \"customfield_10200\": \"0|10001s:\",\n \"customfield_10002\": null,\n \"customfield_10003\": null,\n \"customfield_10400\": null,\n \"environment\": null,\n \"duedate\": null,\n \"progress\": {\n \"total\": 0,\n \"progress\": 0\n },\n \"comment\": {\n \"total\": 1,\n \"comments\": [{\n \"author\": {\n \"emailAddress\": \"admin@<a href=\"http://example.com\">example.com</a>\",\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>\",\n \"24x24\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>\",\n \"16x16\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>\",\n \"32x32\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>\"\n },\n \"displayName\": \"Administrator\",\n \"name\": \"admin\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/user?username=admin\">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>\",\n \"active\": true,\n \"timeZone\": \"Australia/Sydney\",\n \"key\": \"admin\"\n },\n \"created\": \"2013-06-04T16:11:24.505+1000\",\n \"updateAuthor\": {\n \"emailAddress\": \"admin@<a href=\"http://example.com\">example.com</a>\",\n \"avatarUrls\": {\n \"48x48\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=48</a>\",\n \"24x24\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=24</a>\",\n \"16x16\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=16</a>\",\n \"32x32\": \"<a href=\"https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32\">https://secure.gravatar.com/avatar/4beac5df66a475580809e0?d=mm&s=32</a>\"\n },\n \"displayName\": \"Administrator\",\n \"name\": \"admin\",\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/user?username=admin\">https://example-dev1.atlassian.net/rest/api/2/user?username=admin</a>\",\n \"active\": true,\n \"timeZone\": \"Australia/Sydney\",\n \"key\": \"admin\"\n },\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/issue/10300/comment/10100\">https://example-dev1.atlassian.net/rest/api/2/issue/10300/comment/10100</a>\",\n \"id\": \"10100\",\n \"body\": \"Hi [~william] Jira is *super fun* (see JJ-1). Alana is going to fix the logo.\",\n \"updated\": \"2013-06-12T21:55:34.882+1000\"\n }],\n \"maxResults\": 1,\n \"startAt\": 0\n },\n \"votes\": {\n \"hasVoted\": false,\n \"self\": \"<a href=\"https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/votes\">https://example-dev1.atlassian.net/rest/api/2/issue/JJ-2/votes</a>\",\n \"votes\": 0\n }\n },\n \"key\": \"JJ-2\"\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n126\n127\n128\n129\n130\n131\n132\n133\n134\n135\n136\n137\n138\n139\n140\n141\n142\n143\n144\n145\n146\n147\n148\n149\n150\n151\n152\n153\n154\n155\n156\n157\n158\n159\n160\n161\n162\n163\n164\n165\n166\n167\n168\n169\n170\n171\n172\n173\n174\n175\n176\n177\n178\n179\n180\n181\n182\n183\n184\n185\n186\n187\n188\n189\n190\n191\n192\n193\n194\n195\n196\n197\n198\n199\n200\n201\n202\n203\n204\n205\n206\n207\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.491Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":484,"estimatedTokens":3603}}244{"id":"doc-content_properties_in_the_rest_api-823bab73","source":"documentation","title":"Content properties in the REST API","url":"https://developer.atlassian.com/server/confluence/content-properties-in-the-rest-api/","text":"Example:\n```bash\n# Retrieves content properties associated with a piece of content with ID 12345\ncurl -u admin:admin -X GET \"http://localhost:8080/confluence/rest/api/content/12345/property\" | python -mjson.tool\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n# Stores a JSON document under the key \"myprop\" against content with ID 12345\ncurl -i -u admin:admin -X POST -H \"Content-Type: application/json\" \\\n-d '{ \"key\" : \"myprop\", \"value\" : \n{\n \"id\": \"507f1f77bcf86cd799439011\",\n \"editDate\": \"2000-01-01T11:00:00.000+11:00\",\n \"description\": \"If you have any questions please address them to admin@example.com\",\n \"content\": {\n \"likes\": 5,\n \"tags\": [\"cql\", \"confluence\"]\n }\n}\n }' http://localhost:8080/confluence/rest/api/content/12345/property\n```\n\nExample:\n```bash\n# Removes JSON document associated with key \"myprop from content with ID 12345\ncurl -i -u admin:admin -X DELETE \"http://localhost:8080/confluence/rest/api/content/12345/property/myprop\"\n```\n\nExample:\n```bash\n# fetch properties at the same time as fetching content, note the expand=metadata.properties.myprop\ncurl -u admin:admin -X GET \n \"http://localhost:8080/confluence/rest/api/content/12345?expand=metadata.properties.myprop\" | python -mjson.tool\n{ \n id: \"12345\",\n type: \"page\",\n status: \"current\",\n title: \"New in the platform team? Read me first\",\n metadata: {\n _expandable: {\n currentuser: \"\",\n labels: \"\",\n properties: {\n myprop: {\n \"id\": \"507f1f77bcf86cd799439011\",\n \"editDate\": \"2000-01-01T11:00:00.000+11:00\",\n \"description\": \"If you have any questions please address them to admin@example.com\",\n \"content\": {\n \"likes\": 5,\n \"tags\": [\"cql\", \"confluence\"]\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```javascript\n{\n \"id\": \"507f1f77bcf86cd799439011\",\n \"editDate\": \"2000-01-01T11:00:00.000+11:00\",\n \"description\": \"If you have any questions please address them to admin@example.com\",\n \"content\": {\n \"likes\": 5,\n \"tags\": [\"cql\", \"confluence\"]\n }\n}\n```\n\nExample:\n```text\n<content-property-index-schema key=\"module-key\">\n <key property-key=\"attachments\">\n <extract path=\"id\" type=\"string\" />\n <extract path=\"description\" type=\"text\" />\n <extract path=\"editDate\" type=\"date\" />\n </key>\n <key property-key=\"metadata\">\n <extract path=\"content.tags\" type=\"string\" />\n <extract path=\"content.likes\" type=\"number\" />\n </key>\n</content-property-index-schema>\n```\n\nExample:\n```text\ncontent.property[attachments].editDate >= 2001-01-01\ncontent.property[attachments].description ~ \"questions\"\ncontent.property[metadata].content.tags IN (\"cql\", \"help\")\ncontent.property[metadata].content.likes <= 5\n```\n\nExample:\n```text\n<search-body-property key=\"moduleKey\" content-type=\"com.company.product.plugins.pluginName:customType\" content-property=\"attachments\" />\n<search-body-property key=\"moduleKeyTwo\" content-type=\"myCustomType\" content-property=\"metadata\" />\n```\n\nExample:\n```bash\n# Stores a JSON document under the key \"myprop\" against content with ID 12345\ncurl -i -u admin:admin -X POST -H \"Content-Type: application/json\" \\\n-d '{ \"key\" : \"attachments\", \"value\" : \n \"This text will be added to the content body field\"\n }' http://localhost:8080/confluence/rest/api/content/123456789/property\n```\n\nExample:\n```bash\n# Stores a JSON document under the key \"myprop\" against content with ID 12345\ncurl -i -u admin:admin -X POST -H \"Content-Type: application/json\" \\\n-d '{ \"key\" : \"metadata\", \"value\" : \n{\n \"description\": \"The whole json value be added to the content body field\",\n \"content\": {\n \"likes\": 5,\n \"tags\": [\"cql\", \"confluence\"]\n }\n}\n }' http://localhost:8080/confluence/rest/api/content/987654321/property\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.493Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":132,"estimatedTokens":992}}245{"id":"doc-rovo_agent-dcde6cfe","source":"documentation","title":"Rovo Agent","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/rovo-agent/","text":"Example:\n```text\nmodules {}\n└─ rovo:agent []\n ├─ key (string) [Mandatory]\n ├─ name (string) [Mandatory]\n ├─ description (string) [Optional]\n ├─ icon (string) [Optional]\n ├─ prompt (string) [Mandatory]\n ├─ conversationStarters [] [Optional]\n │ └─ conversationStarter (string)\n ├─ actions [] [Optional]\n │ └─ action (string)\n └─ followUpPrompt (string) [Optional]\n\nresources []\n└─ key (string) [Mandatory]\n└─ path (string) [Mandatory]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n rovo:agent: \n - key: risk-agent\n name: \"My Risk Register Assistant\"\n description: A Rovo Agent that helps you manage your project risks\n icon: resource:example-resource;icons/risk-agent.svg\n prompt: |\n You are a helpful assistant that helps users manage their project risks. \n You can retrieve risks from the risk register, create new risks and update existing ones.\n conversationStarters:\n - Fetch my active project risks\n - Fetch my highest priority risks\n - Create a new risk\n actions:\n - fetch-all-risks\n - fetch-risk-by-priority\n - create-risk\n - update-risk\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```text\nmodules:\n rovo:agent: \n - key: risk-agent\n name: \"My Risk Register Assistant\"\n description: A Rovo Agent that helps you manage your project risks\n icon: resource:example-resource;icons/risk-agent.svg\n prompt: resource:agent-resource;prompts/agent-prompt.txt\n conversationStarters:\n - Fetch my active project risks\n - Fetch my highest priority risks\n - Create a new risk\n actions:\n - fetch-all-risks\n - fetch-risk-by-priority\n - create-risk\n - update-risk\nresources:\n - key: example-resource\n path: static/hello-world/build\n - key: agent-resource\n path: resource/agent\n```\n\nExample:\n```text\nYou are a helpful assistant that helps users manage their project risks. \nYou can retrieve risks from the risk register, create new risks and update existing ones.\n```\n\nExample:\n```text\nYou are an expert project manager tasked with managing risks for a project.\n```\n\nExample:\n```text\nYou can help with the following jobs:\n\nA. Reviewing high priority risks\nB. Generating executive reports on project risks\nC. Updating the risk register with new risks\nD. Evaluating the impact of risks on project objectives\n```\n\nExample:\n```text\nI'll separate the instructions for each job with a. '---' on a new line, followed by the job title.\n---\nA. Reviewing high priority risks\nWhen asked, you can help teams to review high priority risk and critique it based on likelihood, impact, and mitigation.\nTo do this, follow these steps:\n\n--- \nB. Generating executive reports on project risks\nTo do this, follow these steps:\n```\n\nExample:\n```text\nReviewing high priority risks\nWhen asked, you can help teams to review high priority risk and critique it based on likelihood, impact, and mitigation.\n\nTo do this, follow these steps:\n1. Retrieve the list of high priority risks from the risk register using the fetch-risk-by-priority action.\n2. Critique each risk based on likelihood, impact, and mitigation.\n3. Provide a final rating of the risk on a scale from 1 to 10, where 1 is an extremely low risk and 10 is an extremely high risk.\n```\n\nExample:\n```text\n* Use emoji to highlight what the rating score for each risk. Use this emoji scoring\n🔴 - Red circle emoji to highlight if there is a high risk (8-10). \n🟡 - Yellow circle emoji to highlight if there is a medium risk (4-7)\n🟢 - Green circle emoji - to reflect low risks (1-3).\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.493Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":130,"estimatedTokens":921}}246{"id":"doc-entity_properties-511db0c4","source":"documentation","title":"Entity properties","url":"https://developer.atlassian.com/server/jira/platform/entity-properties/","text":"Example:\n```bash\ncurl -X PUT \\\n -H \"Content-type: application/json\" \\\n -d '{\"content\":\"Test if works on Jira Cloud\", \"completed\" : 1}' \\\n https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/tasks\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl -X GET https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/\n```\n\nExample:\n```javascript\n{\n \"keys\" : [\n {\n \"key\" : \"tasks\",\n \"self\" : \"https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/tasks\"\n }\n ]\n}\n```\n\nExample:\n```bash\ncurl -X DELETE https://jira-instance1.net/rest/api/2/issue/ENPR-4/properties/tasks\n```\n\nExample:\n```text\n<index-document-configuration \n entity-key=\"IssueProperty\" \n key=\"jira-issue-tasklist-indexing\">\n <key property-key=\"tasks\">\n <extract path=\"content\" type=\"text\" />\n </key>\n</index-document-configuration>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.494Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":48,"estimatedTokens":218}}247{"id":"doc-forge_remote_essentials-be1a6ad7","source":"documentation","title":"Forge Remote essentials","url":"https://developer.atlassian.com/platform/forge/remote/essentials/","text":"Example:\n```text\nx-b3-traceid: a523b7549f0b88c9\nx-b3-spanid: 2a2436c64727923f\nauthorization: Bearer ${FIT}\nx-forge-oauth-system: ${OAuth-System-Token}\nx-forge-oauth-user: ${OAuth-User-Token}\nheader1: value ## Set custom headers when invoking your remote from a UI module\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"app\": {\n \"id\": \"ari:cloud:ecosystem::app/8db33809-1f32-48bb-8c52-5877dab48107\",\n \"version\": \"16\",\n \"appVersion\": \"16.0.1\",\n \"installationId\": \"ari:cloud:ecosystem::installation/0a3a7799-53ae-4a5b-9e7e-03338980abb5\",\n \"apiBaseUrl\": \"https://api.stg.atlassian.com/ex/confluence/d0d52620-3203-4cfa-8db5-f2587155f0dd\",\n \"installation\": {\n \"id\": \"ari:cloud:ecosystem::installation/0a3a7799-53ae-4a5b-9e7e-03338980abb5\",\n \"contexts\": [\n {\n \"name\": \"confluence\",\n \"apiBaseUrl\": \"https://api.stg.atlassian.com/ex/confluence/d0d52620-3203-4cfa-8db5-f2587155f0dd\",\n \"cloudId\": \"d0d52620-3203-4cfa-8db5-f2587155f0dd\"\n },\n {\n \"name\": \"bitbucket\",\n \"apiBaseUrl\": \"https://api.bitbucket.org\",\n \"workspaceId\": \"b7ea02c9-0871-4858-a2a6-a190d7de977c\"\n }\n ]\n },\n \"environment\": {\n \"type\": \"DEVELOPMENT\",\n \"id\": \"ari:cloud:ecosystem::environment/8db33809-1f32-48bb-8c52-5877dab48107/aa911f10-c54b-4b93-9e27-dd2947840b9e\"\n },\n \"module\": {\n \"type\": \"xen:macro\",\n \"key\": \"forge-remote-app-boot\"\n },\n \"license\": {\n \"active\": true,\n \"billingPeriod\": \"MONTHLY\",\n \"capabilitySet\": \"capabilityAdvanced\",\n \"ccpEntitlementId\": \"5e176cc2-6fa0-3c7b-8fc4-302443a16e86\",\n \"ccpEntitlementSlug\": \"X-3SH-1A6-33A-AS0\",\n \"isEvaluation\": false,\n \"subscriptionEndDate\": \"2026-12-08T00:00:00.000Z\",\n \"supportEntitlementNumber\": null,\n \"trialEndDate\": \"2027-06-08T00:00:00.000Z\",\n \"type\": \"commercial\",\n \"modes\": [\"USER_ACCESS\"]\n }\n },\n \"context\": {\n \"localId\": \"4654fa12-4c7c-4792-95a9-6019edb27953\",\n \"cloudId\": \"d0d52620-3203-4cfa-8db5-f2587155f0dd\",\n \"moduleKey\": \"forge-remote-app-boot\",\n \"siteUrl\": \"https://pbray2.jira-dev.com\",\n \"extension\": {\n \"type\": \"macro\",\n \"content\": {\n \"id\": \"5341185\"\n },\n \"space\": {\n \"key\": \"~655362312d3308895442b0aa38771a10c88656\",\n \"id\": \"65538\"\n },\n \"isEditing\": false,\n \"references\": []\n }\n },\n \"principal\": \"655362:312d3308-8954-42b0-aa38-771a10c88656\",\n \"icLabel\": \"orange\",\n \"aud\": \"ari:cloud:ecosystem::app/8db33809-1f32-48bb-8c52-5877dab48107\",\n \"iss\": \"forge/invocation-token\",\n \"iat\": 1700175149,\n \"nbf\": 1700175149,\n \"exp\": 1700175174,\n \"jti\": \"d8a496253ec8c18a54631e4c82cbedd5d0ae8570\"\n}\n```\n\nExample:\n```text\n@Component\npublic class FITValidator {\n\n @Value(\"${jwks.endpoint:https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json}\")\n private String jwksUrl;\n\n @Value(\"${appId}\")\n private String appId;\n\n public void validate(String invocationToken) throws InvalidJwtException {\n var httpsJwks = new HttpsJwks(jwksUrl);\n var httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJwks);\n var jwtConsumer = new JwtConsumerBuilder()\n .setVerificationKeyResolver(httpsJwksKeyResolver)\n .setExpectedAudience(appId)\n .setExpectedIssuer(\"forge/invocation-token\")\n .build();\n\n jwtConsumer.process(invocationToken);\n }\n}\n```\n\nExample:\n```javascript\nexport const validateContextToken = async (invocationToken, appId) => {\n const jwksUrl =\n \"https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json\";\n const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));\n\n const payload = await jose.jwtVerify(invocationToken, JWKS, {\n audience: appId,\n });\n return payload;\n};\n```\n\nExample:\n```javascript\nexport default [\n {\n appId: \"ari:cloud:ecosystem::app/COMMERCIAL_APP_ID\",\n isolatedCloud: {\n jwksUrlTemplate: \"https://some-domain.{IC_LABEL}.net/some-path.json\",\n storageUrlTemplate: \"https://some-domain.{IC_LABEL}.net/some-path\",\n },\n },\n];\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.495Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":144,"estimatedTokens":1033}}248{"id":"doc-access_rest_apis_exposed_by_a_forge_app_preview-cfb9778e","source":"documentation","title":"Access REST APIs exposed by a Forge app (Preview)","url":"https://developer.atlassian.com/platform/forge/access-rest-apis-exposed-by-a-forge-app/","text":"Example:\n```bash\ncurl --request GET \\\n --url <REQUEST_URL> \\\n --header \"Authorization: Bearer ACCESS_TOKEN\" \\\n --header \"Accept: application/json\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nhttps://api.atlassian.com/svc/confluence/a12bc345-678d-9e1f-ghi0-1jkl112131m4/apps/zy2x11w1-0v1u-9876-ts54-3r210qponmlk_3aaa01b0-02cc-1d00-3eee-1f01g001h1i0/getEmployeeName\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url \"https://api.atlassian.com/svc/confluence/a12bc345-678d-9e1f-ghi0-1jkl112131m4/apps/zy2x11w1-0v1u-9876-ts54-3r210qponmlk_3aaa01b0-02cc-1d00-3eee-1f01g001h1i0/getEmployeeName\" \\\n --header \"Authorization: Bearer ACCESS_TOKEN\" \\\n --header \"Accept: application/json\" \\\n --header \"id: 1234\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.496Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":29,"estimatedTokens":182}}249{"id":"doc-calling_atlassian_app_apis_from_a_remote-9f680731","source":"documentation","title":"Calling Atlassian app APIs from a remote","url":"https://developer.atlassian.com/platform/forge/remote/calling-product-apis/","text":"Example:\n```json\n{\n \"app\": {\n \"apiBaseUrl\": \"https://api.atlassian.com/ex/confluence/4c822e2f-510f-48b9-b8d0-8419d0932949\",\n \"installationId\": \"ari:cloud:ecosystem::installation/...\",\n \"id\": \"ari:cloud:ecosystem::app/...\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n'use strict'\nimport fetch from 'node-fetch';\n\nexport async function fetchFromConfluence(token, apiBaseUrl) {\n const headers = {\n Accept: 'application/json',\n Authorization: `Bearer ${token}`\n }\n return await fetch(`${apiBaseUrl}/wiki/rest/api/content`, { headers });\n}\n```\n\nExample:\n```text\npublic ResponseEntity<String> getContent(final String token, String apiBaseUrl) {\n\n final HttpHeaders headers = new HttpHeaders();\n headers.setBearerAuth(token);\n\n final HttpEntity<String> entity = new HttpEntity<>(null, headers);\n\n final ResponseEntity<String> response =\n restTemplate.exchange(apiBaseUrl + \"/wiki/rest/api/content\",\n HttpMethod.GET, entity, String.class);\n\n logger.info(\"Response statusCode={}\", response.getStatusCode());\n\n return response;\n}\n```\n\nExample:\n```text\nmutation forge_remote_offlineUserAuthToken($input: OfflineUserAuthTokenInput!) {\n offlineUserAuthToken(input: $input) {\n success\n errors {\n message\n }\n authToken {\n token\n ttl\n }\n }\n}\n```\n\nExample:\n```text\n{\n contextIds: [\"<context ARI of the installation>\"],\n userId: \"<account ID being impersonated>\"\n}\n```\n\nExample:\n```javascript\nconst query = \"<as above>\";\n\nasync getTokenForUser(systemToken, contextAri, userAccountId) {\n const response = await fetch(\"https://api.atlassian.com/graphql\", {\n method: \"POST\",\n headers: {\n accept: \"application/json\",\n \"content-type\": \"application/json\",\n authorization: `Bearer ${systemToken}`,\n },\n body: JSON.stringify({\n query,\n variables: {\n input: {\n contextIds: [contextAri],\n userId: userAccountId,\n },\n },\n }),\n });\n\n if (!response.ok) {\n throw new Error(`HTTP error: ${response.status}`);\n }\n\n const json = await response.json();\n\n if (!json.data.offlineUserAuthToken.success) {\n throw new Error(`GraphQL error: ${json.data.offlineUserAuthToken.errors}`);\n }\n\n // returns { token: \"<token>\", ttl: <token TTL> }\n return json.data.offlineUserAuthToken.authToken;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.497Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":113,"estimatedTokens":594}}250{"id":"doc-forge_bridge_apis-50209218","source":"documentation","title":"Forge bridge APIs","url":"https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/bridge/","text":"Example:\n```javascript\nimport { invoke } from '@forge/bridge';\n\ninvoke('getText', { example: 'my-invoke-variable' }).then(setData);\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.497Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":14,"estimatedTokens":44}}251{"id":"doc-display_conditions-a7d22ad1","source":"documentation","title":"Display conditions","url":"https://developer.atlassian.com/platform/forge/manifest-reference/display-conditions/","text":"Example:\n```text\njira:issuePanel:\n- key: hello-world-panel\n function: issue-panel-function\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n displayConditions:\n issueType: Bug\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\njira:issuePanel:\n- key: hello-world-panel\n function: issue-panel-function\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n displayConditions:\n or:\n canDeleteAllComments: true\n and:\n projectKey: TEST\n not:\n issueType: Epic\n```\n\nExample:\n```text\njira:issuePanel:\n- key: hello-world-panel\n function: issue-panel-function\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n displayConditions:\n and:\n isLoggedIn: true\n or:\n hasGlobalPermission: permission1 # WRONG - error manifest.yml failed to parse content - Map keys must be unique valid-yaml-required\n hasGlobalPermission: permission2 # WRONG - error manifest.yml failed to parse content - Map keys must be unique valid-yaml-required\n```\n\nExample:\n```text\njira:issuePanel:\n- key: hello-world-panel\n function: issue-panel-function\n title: Hello world!\n icon: https://developer.atlassian.com/platform/forge/images/issue-panel-icon.svg\n displayConditions:\n and:\n isLoggedIn: true\n or:\n hasGlobalPermission: permission1\n and: \n hasGlobalPermission: permission2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.497Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":65,"estimatedTokens":384}}252{"id":"doc-calling_a_remote_backend_from_a_forge_frontend-bb1a3f86","source":"documentation","title":"Calling a remote backend from a Forge frontend","url":"https://developer.atlassian.com/platform/forge/remote/calling-from-frontend/","text":"Example:\n```text\nremotes:\n - key: my-remote\n baseUrl: https://my-backend.example.com\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n endpoint:\n - key: my-remote-endpoint\n remote: my-remote # Must match the key in your remotes entry\n auth:\n appUserToken:\n enabled: true\n appSystemToken:\n enabled: true\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: my-issue-panel\n resource: main\n resolver:\n endpoint: my-remote-endpoint # Must match the key in your endpoint entry\n title: My Remote Panel\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: my-remote-macro\n resource: main\n resolver:\n endpoint: my-remote-endpoint # References the endpoint below\n title: My Remote Macro\n description: A macro that calls a remote backend.\n endpoint:\n - key: my-remote-endpoint\n remote: my-remote # References the remote below\n auth:\n appUserToken:\n enabled: true\n appSystemToken:\n enabled: true\nresources:\n - key: main\n path: static/hello-remote-world/build\npermissions:\n scopes:\n - read:app-system-token\n - read:app-user-token\n - read:confluence-content.summary # Include scopes for the Atlassian APIs you intend to call\nremotes:\n - key: my-remote\n baseUrl: https://my-backend.example.com\n```\n\nExample:\n```javascript\nimport { invokeRemote } from '@forge/bridge';\n\nasync function fetchData() {\n try {\n // Call different paths on the same remote backend\n const users = await invokeRemote({ path: '/users', method: 'GET' });\n const tasks = await invokeRemote({ path: '/tasks', method: 'GET' });\n // use users and tasks\n } catch (error) {\n console.error('Remote invocation failed:', error.message);\n }\n}\n```\n\nExample:\n```text\nremotes:\n - key: auth-service\n baseUrl: https://auth.example.com\n - key: data-service\n baseUrl: https://data.example.com\n```\n\nExample:\n```javascript\nimport { requestRemote } from '@forge/bridge';\n\n// Call different remote backends by key\nconst authResponse = await requestRemote('auth-service', {\n path: '/verify',\n method: 'GET',\n});\n\nconst dataResponse = await requestRemote('data-service', {\n path: '/records',\n method: 'GET',\n});\n```\n\nExample:\n```javascript\nimport { invokeRemote } from '@forge/bridge';\n\ntry {\n const res = await invokeRemote({\n path: '/tasks/?team=Forge',\n method: 'GET',\n });\n console.log(`Tasks: ${JSON.stringify(res.body)}`);\n} catch (error) {\n console.error('Remote invocation failed:', error.message);\n}\n```\n\nExample:\n```javascript\nimport { requestRemote } from '@forge/bridge';\n\nconst response = await requestRemote('my-remote', {\n path: '/tasks/?team=Forge',\n method: 'GET',\n});\n\nif (!response.ok) {\n throw new Error(`Request failed: ${response.status}`);\n}\n\nconst data = await response.json();\nconsole.log(`Tasks: ${JSON.stringify(data)}`);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.498Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":142,"estimatedTokens":732}}253{"id":"doc-bitbucket_git_operations_from_a_remote-2e04842b","source":"documentation","title":"Bitbucket git operations from a remote","url":"https://developer.atlassian.com/platform/forge/remote/bitbucket-git-operations/","text":"Example:\n```text\ngit clone https://x-token-auth:{x-forge-oauth-system token}@bitbucket.org/{workspace}/{repository}.git\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\ngit push --repo https://x-token-auth:{x-forge-oauth-user token}@bitbucket.org/{workspace}/{repository}.git\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.498Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":73}}254{"id":"doc-add_scopes_to_call_an_atlassian_rest_api-b7660994","source":"documentation","title":"Add scopes to call an Atlassian REST API","url":"https://developer.atlassian.com/platform/forge/add-scopes-to-call-an-atlassian-rest-api/","text":"Example:\n```bash\nforge lint\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nThe linter checks the app code for known errors. Warnings are problems you should\nfix, but that won't stop the app code from building.\nPress Ctrl+C to cancel.\n\n/src/index.jsx\n 10:57 warning Confluence endpoint: GET /api/content requires\n \"read:confluence-content.summary\" scope permission-scope-required\n\n14:56 warning Confluence endpoint: GET /api/content requires\n \"read:confluence-content.summary\" scope permission-scope-required\n\n19:51 warning Jira endpoint: GET /rest/api/3/user requires\n \"read:jira-user\" scope permission-scope-required\n\n⚠ 3 problems (0 errors, 3 warnings)\n Run forge lint --fix to automatically fix 0 errors and 3 warnings.\n```\n\nExample:\n```bash\nforge lint --fix\n```\n\nExample:\n```bash\n✔ Fixed 0 errors and 3 warnings\n\nRun forge lint to review outstanding errors and warnings\n```\n\nExample:\n```text\npermissions:\n scopes:\n - write:confluence-content\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```bash\n┌───────────────┬──────────────────────────────┬──────────────────┬─────────────┐\n│ Environment │ Site │ Atlassian app │ Scopes │\n├───────────────┼──────────────────────────────┼──────────────────┼─────────────┤\n│ ❯ development │ example-dev.atlassian.net │ Jira │ Latest │\n│ development │ example-dev.atlassian.net │ Confluence │ Latest │\n│ production │ example.atlassian.net │ Confluence │ Out-of-date │\n└───────────────┴──────────────────────────────┴──────────────────┴─────────────┘\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.499Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":72,"estimatedTokens":416}}255{"id":"doc-rotating_an_oauth_2_0_client_id_and_secret-c6a589e2","source":"documentation","title":"Rotating an OAuth 2.0 client ID and secret","url":"https://developer.atlassian.com/platform/forge/rotating-an-oauth-2.0-client-id-and-secret/","text":"Example:\n```bash\nforge deploy\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge providers configure\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.499Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":31}}256{"id":"doc-calling_a_remote_backend_from_a_forge_function-bc92b17b","source":"documentation","title":"Calling a remote backend from a Forge function","url":"https://developer.atlassian.com/platform/forge/remote/calling-from-function/","text":"Example:\n```text\nmodules:\n trigger:\n - key: update-issue\n function: main\n events:\n - avi:jira:updated:issue\n function:\n - key: main\n handler: index.run\nremotes:\n - key: remote-app-node\n baseUrl: https://forge-remote-refapp-nodejs.services.atlassian.com\n operations:\n - compute\n auth:\n appSystemToken:\n enabled: true\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { invokeRemote } from \"@forge/api\";\n\nconst res = await invokeRemote('remote-app-node', {\n path: `/tasks/?team=Forge`,\n method: 'GET'\n});\n\nif (!res.ok) {\n throw new Error(`invokeRemote failed: ${res.status}`);\n}\n\nconst json = await res.json();\nconsole.log(`Tasks: ${JSON.stringify(json)}`);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.500Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":45,"estimatedTokens":186}}257{"id":"doc-set_up_remotes_for_data_residency_realm_pinning-8eb8c63a","source":"documentation","title":"Set up remotes for data residency realm pinning","url":"https://developer.atlassian.com/platform/forge/remote/remote-realm-pinning/","text":"Example:\n```text\nremotes:\n - key: remote-backend\n baseUrl:\n default: \"https://backend.example.com\"\n US: \"https://us-backend.example.com\"\n EU: \"https://eu-backend.example.com\"\n operations:\n - storage\n storage:\n inScopeEUD: true\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.500Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":21,"estimatedTokens":81}}258{"id":"doc-support_data_residency_realm_migrations_for_forg-da9d066e","source":"documentation","title":"Support data residency realm migrations for Forge Remote","url":"https://developer.atlassian.com/platform/forge/remote/remote-realm-migration/","text":"Example:\n```text\nmodules:\n migration:dataResidency:\n key: dare\n remote: remote-backend\n path: /migration\n maxMigrationDurationHours: 6\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"startTime\": \"2021-12-26T00:00:00.000Z\",\n \"endTime\": \"2021-12-27T00:00:00.000Z\",\n \"location\": \"EU\",\n \"migrationId\": 57500\n}\n```\n\nExample:\n```text\nGET /status?migrationId=57500\n```\n\nExample:\n```json\n{\n \"status\": \"failed\",\n \"errorResponseCode\": \"E0004\"\n}\n```\n\nExample:\n```json\n{\n\"status\": \"ready-to-commit\"\n}\n```\n\nExample:\n```json\n{\n\"migrationId\": 57500\n}\n```\n\nExample:\n```json\n{\n \"errorResponseCode\": \"E0004\"\n}\n```\n\nExample:\n```text\n...\n maxMigrationDurationHours: 0.5\n ...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.501Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":68,"estimatedTokens":175}}259{"id":"doc-add_content_security_and_egress_controls-c4388566","source":"documentation","title":"Add content security and egress controls","url":"https://developer.atlassian.com/platform/forge/add-content-security-and-egress-controls/","text":"Example:\n```text\npermissions:\n external:\n fetch:\n backend:\n - '*.example-dev.com'\n - remote: remote-backend\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - '*.example-dev.com'\n```\n\nExample:\n```text\npermissions:\n external:\n fonts:\n - 'https://www.example-dev.com/fonts.css'\n```\n\nExample:\n```text\npermissions:\n external:\n styles:\n - 'https://www.example-dev.com/stylesheet.css'\n```\n\nExample:\n```text\npermissions:\n external:\n frames:\n - 'https://www.example-dev.com/embed/page'\n```\n\nExample:\n```text\npermissions:\n external:\n images:\n - address: https://www.example-dev.com/image.png\n```\n\nExample:\n```text\npermissions:\n external:\n media:\n - 'https://www.example-dev.com/video.mp4'\n```\n\nExample:\n```text\npermissions:\n external:\n scripts:\n - 'https://www.example-dev.com/script.js'\n```\n\nExample:\n```text\npermissions:\n content:\n styles:\n - \"unsafe-inline\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n content:\n styles:\n - 'unsafe-inline'\n external:\n images:\n - address: *.giphy.com\n```\n\nExample:\n```text\n<html>\n <body>\n <p style=\"color: blue; font-size: 46px;\">Hello world!</p>\n <img src=\"https://media1.giphy.com/media/d2jioMTLON9bDogE/giphy.gif\" />\n </body>\n</html>\n```\n\nExample:\n```bash\n/Users/agrant/my-apps/hello-world-app/manifest.yml\n38:11 error Invalid 'external.fetch.client' permission in the manifest.yml file - 'https://example.com?test=key'. Learn more about permissions at: http://go.atlassian.com/forge-permissions. valid-permissions-required\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```bash\n┌───────────────┬──────────────────────────────┬────────────────┬─────────────┐\n│ Environment │ Site │ Atlassian app │ Scopes │\n├───────────────┼──────────────────────────────┼────────────────┼─────────────┤\n│ ❯ development │ example-dev.atlassian.net │ Jira │ Latest │\n│ development │ example-dev.atlassian.net │ Confluence │ Latest │\n│ production │ example.atlassian.net │ Confluence │ Out-of-date │\n└───────────────┴──────────────────────────────┴────────────────┴─────────────┘\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.503Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":125,"estimatedTokens":567}}260{"id":"doc-runtime_egress_permissions-2dde95c9","source":"documentation","title":"Runtime egress permissions","url":"https://developer.atlassian.com/platform/forge/runtime-egress-permissions/","text":"Example:\n```text\npermissions:\n external:\n fetch:\n backend:\n - '*.ingest.sentry.io'\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: '*.example-dev.com'\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\n/Users/agrant/my-apps/hello-world-app/src/index.js\n14:30 error The domain https://www.google-analytics.com is not included in the external permissions of your app manifest egress-permission-required\n\nX 1 issue (1 error, 0 warnings)\n Run forge lint --fix to automatically fix 1 error and 0 warnings.\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - 'https://www.google-analytics.com'\n```\n\nExample:\n```javascript\nimport api from '@forge/api';\n// your google analytics config\nconst measurement_id = `G-XXXXXXXXXX`;\nconst api_secret = `<secret_value>`;\nconst sendAnalytics = async () => {\n return await api.fetch(\n `https://www.google-analytics.com` +\n `/mp/collect?measurement_id=${measurement_id}` +\n `&api_secret=${api_secret}`,\n {\n body: JSON.stringify({\n // your analytics payload here\n })\n }\n )\n};\n\nsendAnalytics().then(() => console.log('success'));\n```\n\nExample:\n```bash\n/Users/agrant/my-apps/hello-world-app/manifest.yml\n38:11 error Invalid 'external.fetch.backend' permission in manifest.yml file - 'https://example.com?test=key'. Learn about permissions at: http://go.atlassian.com/e-permissions. valid-permissions-required\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```bash\n┌───────────────┬──────────────────────────────┬────────────────┬─────────────┐\n│ Environment │ Site │ Atlassian app │ Scopes │\n├───────────────┼──────────────────────────────┼────────────────┼─────────────┤\n│ ❯ development │ example-dev.atlassian.net │ Jira │ Latest │\n│ development │ example-dev.atlassian.net │ Confluence │ Latest │\n│ production │ example.atlassian.net │ Confluence │ Out-of-date │\n└───────────────┴──────────────────────────────┴────────────────┴─────────────┘\n```\n\nExample:\n```text\npermissions:\n external:\n configurable:\n enabled: true\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.503Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":97,"estimatedTokens":567}}261{"id":"doc-in_scope_end_user_data-658ef4c1","source":"documentation","title":"In-scope End-User Data","url":"https://developer.atlassian.com/platform/forge/in-scope-end-user-data/","text":"Example:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: '*.example-analytics.com'\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: '*.example-dev.com'\n category: analytics\n inScopeEUD: true # inScopeEUD value was previously false\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: '*.example-dev.com'\n category: analytics\n inScopeEUD: true # inScopeEUD value was previously false, with all values previously false\n - address: '*.example-prod.com'\n category: analytics\n inScopeEUD: false # no change in value\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: '*.example-dev.com'\n category: analytics\n inScopeEUD: true # no change in value\n - address: '*.example-prod.com'\n category: analytics\n inScopeEUD: true # inScopeEUD value was previously false\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.504Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":57,"estimatedTokens":278}}262{"id":"doc-tenant_data_isolation_in_forge_apps-8ed39ce1","source":"documentation","title":"Tenant data isolation in Forge apps","url":"https://developer.atlassian.com/platform/forge/tenant-data-isolation/","text":"Example:\n```javascript\n// UNSAFE: module-level cache shared across all tenants\nconst cache = {};\n\nexport async function handler(context) {\n\n if (cache['currentUser']) {\n // This could return another tenant's user data!\n return cache['currentUser'];\n }\n\n const response = await api.asUser().requestJira('/rest/api/3/myself');\n const user = await response.json();\n\n cache['currentUser'] = user; // Leaks to the next invocation\n return user;\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// UNSAFE: shared object that accumulates data from multiple tenants\nconst issueCache = {};\n\nexport async function onIssueCreated({ payload, context }) {\n // Issue keys are NOT globally unique - the same key can exist\n // in different tenants' Jira instances\n issueCache[payload.issue.key] = payload.issue;\n}\n```\n\nExample:\n```javascript\n// SAFE: all state is local to the function invocation\nexport async function handler(context) {\n // Fetch fresh data every invocation - no cross-tenant risk\n const response = await api.asUser().requestJira('/rest/api/3/myself');\n const user = await response.json();\n return user;\n}\n```\n\nExample:\n```javascript\n// SAFE: cache partitioned by cloudId (tenant identifier)\nconst cache = {};\n\nexport async function handler(context) {\n const { cloudId } = context;\n\n if (cache[cloudId]?.currentUser) {\n return cache[cloudId].currentUser;\n }\n\n const response = await api.asUser().requestJira('/rest/api/3/myself');\n const user = await response.json();\n\n // Scope the cached value to this specific tenant\n if (!cache[cloudId]) {\n cache[cloudId] = {};\n }\n cache[cloudId].currentUser = user;\n\n return user;\n}\n```\n\nExample:\n```javascript\nimport { kvs } from '@forge/kvs';\n\n// SAFE: Forge Storage is scoped per installation (per tenant)\nexport async function handler(context) {\n const cachedUser = await kvs.get('currentUser');\n if (cachedUser) {\n return cachedUser;\n }\n\n const response = await api.asUser().requestJira('/rest/api/3/myself');\n const user = await response.json();\n\n await kvs.set('currentUser', user);\n return user;\n}\n```\n\nExample:\n```javascript\n// SAFE: read-only configuration constant - not tenant data\nconst MAX_RETRIES = 3;\nconst API_VERSION = 'v3';\n\nexport async function handler(context) {\n // MAX_RETRIES and API_VERSION are the same for all tenants\n // and are never modified\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.505Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":106,"estimatedTokens":597}}263{"id":"doc-access_to_forge_apps_for_unlicensed_users-2ba89fe3","source":"documentation","title":"Access to Forge apps for unlicensed users","url":"https://developer.atlassian.com/platform/forge/access-to-forge-apps-for-unlicensed-users/","text":"Example:\n```javascript\nconst secretKey = await kvs.getSecret(`github-secret:${context.accountId}`)\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:portalFooter:\n - key: hello-world-panel\n resource: main\n resolver:\n function: resolver\n viewportSize: medium\n unlicensedAccess:\n - anonymous\n - customer\n```\n\nExample:\n```javascript\nresolver.define('getAccountType', (req) => {\n return req.context.accountType;\n});\n```\n\nExample:\n```bash\n${module} does not support the following Forge properties - 'unlicensedAccess'\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:queuePage:\n - key: hello-world-panel\n resource: main\n resolver:\n function: resolver\n viewportSize: medium\n unlicensedAccess:\n - anonymous\n - customer\n```\n\nExample:\n```bash\n$ forge deploy\nerror jiraServiceManagement:queuePage does not support the following Forge properties - 'unlicensedAccess' valid-document-required\n```\n\nExample:\n```bash\nProperty \"unlicensedAccess\" in ${moduleKey} must be an array of one or more of the\nfollowing values: [unlicensed, customer, anonymous]\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:portalUserMenuAction:\n - key: hello-world-panel\n resource: main\n resolver:\n function: resolver\n viewportSize: medium\n unlicensedAccess:\n - anonymous\n - customer\n```\n\nExample:\n```bash\n$ forge deploy --verbose\n Upload URL is valid\n Found manifest file\n Manifest is a valid YAML\n Property \"unlicensedAccess\" in hello-world-panel-portal-user must be an array of one or more of the following values: [unlicensed, customer]\n\nError: Deployment failed\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.506Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":89,"estimatedTokens":433}}264{"id":"doc-build_runs_on_atlassian_apps-960c3b8b","source":"documentation","title":"Build Runs on Atlassian apps","url":"https://developer.atlassian.com/platform/forge/runs-on-atlassian-apps/","text":"Example:\n```text\npermissions:\n scopes:\n - read:content-details:confluence\n - read:content.property:confluence\n - write:content.property:confluence\n external:\n fetch:\n backend:\n - address: '*.example-dev.com'\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - 'https://backend.example.com'\n images:\n - address: '*.atlassian.com' # non-analytics egress\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: 'baconipsum.com'\n category: analytics\n inScopeEUD: true # in-scope end user data egress\n```\n\nExample:\n```text\nremotes:\n - key: remote-backend\n baseUrl: \"https://backend.example.com\"\n operations:\n - compute\n - fetch\n - other\n```\n\nExample:\n```bash\nforge eligibility\n```\n\nExample:\n```bash\nThe version of your app [2.14.0] that's deployed to [development] is eligible\nfor the Runs on Atlassian program.\n```\n\nExample:\n```bash\nThe version of your app [2.16.0] that's deployed to [development] is not eligible\nfor the Runs on Atlassian program.\n- App is using remote services\n- App has Connect modules\n```\n\nExample:\n```bash\nThe version of your app [2.14.0] that was just deployed to [development] is eligible\nfor the Runs on Atlassian program.\n```\n\nExample:\n```bash\nThe version of your app [2.16.0] that was just deployed to [development]\nis not eligible for the Runs on Atlassian program. Run forge eligibility to know more.\nTo know more about Runs on Atlassian, go to: https://go.atlassian.com/runs-on-atlassian\n```\n\nExample:\n```bash\nApp [2.16.0] in [development] is not eligible for Runs on Atlassian.\n- App is using remote services\n- App has Connect modules\n```\n\nExample:\n```bash\nforge eligibility --major-version [version]\n```\n\nExample:\n```bash\nThe version of your app [1.18.0] that's deployed to [development] is eligible\nfor the Runs on Atlassian program.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.511Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":105,"estimatedTokens":502}}265{"id":"doc-using_forge_cli_via_a_development_container-d2290711","source":"documentation","title":"Using Forge CLI via a development container","url":"https://developer.atlassian.com/platform/forge/enterprise/use-forge-cli-via-a-development-container/","text":"Example:\n```json\n{\n \"name\": \"Atlassian Forge Development Container Sample Config\",\n \"image\": \"atlassian/forge-devcontainer:latest\",\n \"features\":{},\n \"customizations\": {\n \"vscode\": {\n \"extensions\": [\n \"dbaeumer.vscode-eslint\",\n \"esbenp.prettier-vscode\"\n ],\n \"settings\": {\n \"terminal.integrated.shell.linux\": \"/bin/bash\"\n }\n }\n },\n \"remoteUser\": \"node\"\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ndocker pull atlassian/forge-devcontainer:latest\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd your-app-name\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nsudo mkdir -p /etc/systemd/system/docker.service.d\nsudo nano /etc/systemd/system/docker.service.d/http-proxy.conf\n```\n\nExample:\n```text\n[Service]\nEnvironment=\"HTTP_PROXY=http://proxy.example.com:8080\"\nEnvironment=\"HTTPS_PROXY=http://proxy.example.com:8080\"\nEnvironment=\"NO_PROXY=localhost,127.0.0.1,.example.com\"\n```\n\nExample:\n```bash\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n\nExample:\n```bash\nsudo systemctl show --property=Environment docker\n```\n\nExample:\n```text\n[wsl2]\nkernelCommandLine = \"sysctl.net.ipv4.tcp_keepalive_time=60 net.ipv4.tcp_keepalive_intvl=60 net.ipv4.tcp_keepalive_probes=6\"\n```\n\nExample:\n```bash\nsudo mkdir -p /etc/docker\nsudo nano /etc/docker/daemon.json\n```\n\nExample:\n```json\n{\n \"proxies\": {\n \"default\": {\n \"httpProxy\": \"http://proxy.example.com:8080\",\n \"httpsProxy\": \"http://proxy.example.com:8080\",\n \"noProxy\": \"localhost,127.0.0.1,.example.com\"\n }\n }\n}\n```\n\nExample:\n```bash\nsudo service docker restart\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.511Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":108,"estimatedTokens":416}}266{"id":"doc-use_the_forge_cli_on_a_corporate_network-0255e59b","source":"documentation","title":"Use the Forge CLI on a corporate network","url":"https://developer.atlassian.com/platform/forge/enterprise/use-forge-cli-on-corporate-network/","text":"Example:\n```bash\nnpm config set registry ${yourRegistry}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nrequest to https://api.atlassian.com/graphql failed, reason: read ECONNRESET`\n```\n\nExample:\n```bash\nnpm config set proxy ${yourProxyServer}\n```\n\nExample:\n```bash\nnpm config set proxy http://proxy.example.com:8888\n```\n\nExample:\n```bash\nnpm install -g global-agent\n```\n\nExample:\n```bash\nexport GLOBAL_AGENT_HTTP_PROXY=${yourProxyServer}\n```\n\nExample:\n```javascript\nrequire('global-agent/bootstrap');\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.516Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":42,"estimatedTokens":130}}267{"id":"doc-export_app_resource_usage-3a96c48f","source":"documentation","title":"Export app resource usage","url":"https://developer.atlassian.com/platform/forge/developer-space/export-app-resource-usage/","text":"Example:\n```text\nquery AppResourceUsageFlat(\n $appId: ID!\n $filters: DevConsoleAppResourceUsageFiltersInput!\n) {\n ecosystem {\n devConsole {\n appResourceUsage(\n appId: $appId\n filters: $filters\n ) {\n ... on DevConsoleAppResourceUsageFlatResponse {\n resourceUsage {\n period\n resolution\n tokensConsumed\n tokenUnit\n }\n pagination {\n page\n pageSize\n }\n error {\n message\n identifier\n extensions {\n errorType\n statusCode\n }\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n{\n \"appId\": \"<your app id>\",\n \"filters\": {\n \"resource\": \"FUNCTION_COMPUTE\",\n \"interval\": {\n \"start\": \"2025-09-01T00:00:00Z\",\n \"end\": \"2025-09-30T23:59:59Z\"\n },\n \"contextAris\": [\"ari:cloud:jira::site/12345\"],\n \"page\": 1\n }\n}\n```\n\nExample:\n```text\n{\n \"resourceUsage\": [\n {\n \"period\": \"2025-09-01\",\n \"resolution\": \"ONE_DAY\",\n \"tokensConsumed\": \"1.024000\",\n \"tokenUnit\": \"GB-seconds\"\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"pageSize\": 100\n },\n \"error\": null\n}\n```\n\nExample:\n```text\nquery AppResourceUsageGroupedByContext(\n $appId: ID!\n $filters: DevConsoleAppResourceUsageFiltersInput!\n $groupBy: DevConsoleGroupBy!\n) {\n ecosystem {\n devConsole {\n appResourceUsage(\n appId: $appId\n filters: $filters\n groupBy: $groupBy\n ) {\n ... on DevConsoleAppResourceUsageGroupedResponse {\n resourceUsage {\n period\n resolution\n groups {\n groupBy\n groupValue\n tokensConsumed\n tokenUnit\n }\n }\n pagination {\n page\n pageSize\n }\n error {\n message\n identifier\n extensions {\n errorType\n statusCode\n }\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\n{\n \"appId\": \"<your app id>\",\n \"filters\": {\n \"resource\": \"FUNCTION_COMPUTE\",\n \"interval\": {\n \"start\": \"2025-09-01T00:00:00Z\",\n \"end\": \"2025-09-30T23:59:59Z\"\n },\n \"contextAris\": [\"ari:cloud:jira::site/12345\"],\n \"page\": 1\n },\n \"groupBy\": \"CONTEXT_ARI\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.516Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":141,"estimatedTokens":601}}268{"id":"doc-monitor_sql-a710733c","source":"documentation","title":"Monitor SQL","url":"https://developer.atlassian.com/platform/forge/monitor-sql-metrics/","text":"Example:\n```javascript\nconst createDBobjects = migrationRunner\n .enqueue('v001_create_users_table', CREATE_USERS_TABLE)\n .enqueue('v002_create_books_table', CREATE_BOOKS_TABLE)\n .enqueue('v003_create_authors_table', CREATE_AUTH_TABLE)\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.519Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":15,"estimatedTokens":71}}269{"id":"doc-monitor_custom_metrics-cc1d9f81","source":"documentation","title":"Monitor custom metrics","url":"https://developer.atlassian.com/platform/forge/monitor-custom-metrics/","text":"Example:\n```bash\nnpm install @forge/metrics\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nimport { internalMetrics } from '@forge/metrics';\n```\n\nExample:\n```typescript\nexport const run = async (payload, context) => {\n const counter = internalMetrics.counter('jira-actions-success'); // Name should exactly match as registered in developer console\n\n const jiraIssues = await fetch(...);\n counter.incr(); // Increment by 1\n // Or increment by a specific value\n counter.incrBy(value);\n for (const issue of jiraIssues) {\n ....\n }\n};\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nnpm install @forge/bridge\n```\n\nExample:\n```typescript\nimport { frontendCustomMetrics } from '@forge/bridge';\n```\n\nExample:\n```typescript\nconst onButtonClick = () => {\n const counter = frontendCustomMetrics.counter('jira-actions-success'); // Name should exactly match as registered in developer console\n\n counter.incr(); // Increment by 1\n // Or increment by a specific value\n counter.incrBy(5);\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.520Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":58,"estimatedTokens":256}}270{"id":"doc-monitor_usage_metrics_and_costs-84c785fc","source":"documentation","title":"Monitor usage metrics and costs","url":"https://developer.atlassian.com/platform/forge/monitor-usage-metrics/","text":"Example:\n```text\nCompute in GB-sec = (Lambda Execution Time in seconds * 0.5) GB seconds\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nStorage writes in GB = Total Data Written to Storage in GB\n```\n\nExample:\n```text\nStorage reads in GB = Total Data Read from Storage in GB\n```\n\nExample:\n```text\nLog writes in GB = Total Data Written to Logs in GB\n```\n\nExample:\n```text\nSQL compute duration (in hours) = Total execution time of all SQL queries (in hours)\n```\n\nExample:\n```text\nSQL compute requests = Total number of SQL queries executed\n```\n\nExample:\n```text\nSQL data stored (in GB) = Total data stored in Forge SQL storage (in GB)\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.520Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":42,"estimatedTokens":163}}271{"id":"doc-command_install-40f7751f","source":"documentation","title":"Command: install","url":"https://developer.atlassian.com/platform/forge/cli-reference/install/","text":"Example:\n```text\nUsage: forge install [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n--app-id-override <appId> App ID to use (skips reading from manifest)\n-e, --environment [environment] specify the environment (see your default\n environment by running forge settings list)\n-s, --site [site] site URL (example.atlassian.net)\n-p, --product [Atlassian app] Atlassian app (Jira, Confluence, Compass,\n Bitbucket)\n-d, --demo-site install onto your demo site instead of a\n site passed with --site\n--upgrade [target] upgrade an existing installation (allowed\n values: all (default), code)\n--confirm-scopes skip confirmation of scopes for the app\n before installing or upgrading the app\n (default: false)\n-l, --license [license] specify the license value for the app\n (allowed values: active, standard,\n advanced, inactive, and trial)\n--license-modes [licenseModes...] specify the list of license mode value for\n the app (allowed values: user-access)\n--users-with-access [user...] specify the list of Atlassian Account\n IDs(aaid) for users who have access to the\n app\n--major-version <majorVersion> specify the major version to install\n--non-interactive run the command without input prompts\n-h, --help display help for command\n```\n\nExample:\n```text\nlist [options] list app installations\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.522Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":46,"estimatedTokens":479}}272{"id":"doc-export_app_logs-7c90af75","source":"documentation","title":"Export app logs","url":"https://developer.atlassian.com/platform/forge/export-app-logs/","text":"Example:\n```text\nhttps://api.atlassian.com/v1/app/logs/${appId}?environmentId=${envId}&startDate=${startDate}&endDate=${endDate}&level={level}&message={message}&installationContext={installationContext}&cursor=${cursor}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nGET\n```\n\nExample:\n```text\n- `appId`: string, Required, Id of the forge App.\n```\n\nExample:\n```text\n- `200 OK`: Successful response. Returns paginated logs.\n- `400 Bad Request`: Request failed with status code 400.\n- `401 Unauthorized`: Unauthorized.\n- `404 Not Found`: Request failed with status code 404.\n- `429 Too many requests`: Request has been rate limited.\n- `500 Internal Server Error`: Request failed with status code 500.\n```\n\nExample:\n```text\n// Please replace `email`, `appId`, `envId` and `<api_token>` with your actual values.\n// This code will fetch data for the last 5 minutes and\n// if the response contains a cursor, it will do a subsequent fetch with the new cursor.\n// This will continue until no more cursors are returned.\n\n// Define necessary variables\nconst email = \"<email>\";\nconst appId = \"<appId>\";\nconst envId = \"<envId>\";\nconst searchMsg = \"example_search_text\";\nconst siteId1 = \"<site_id_1>\"; // for eg: ari:cloud:confluence::site/089a1455-4ea0-122a-b70c-5b17360f047d\nconst siteId2 = \"<site_id_2>\"; // for eg: ari:cloud:jira::site/4eecb4e0-22cc-4e18-bad1-b58a154be343\nconst api_token = \"<api_token>\";\n\n// Get current date/time and subtract 5 minutes for startDate\nconst now = new Date();\nconst endDate = new Date(now.getTime() - 1 * 60000); // 1 minute ago\nconst startDate = new Date(now.getTime() - 6 * 60000); // 6 minutes ago\nlet cursor = null;\n\n// Function to fetch logs\nconst fetchLogs = async (startDate, endDate, cursor) => {\n try {\n const url = `https://api.atlassian.com/v1/app/logs/${appId}` +\n `?environmentId=${envId}` +\n `&startDate=${startDate.toISOString()}` +\n `&endDate=${endDate.toISOString()}` +\n `&level=INFO&level=ERROR` +\n `&message=${searchMsg}` +\n `&installationContext=${siteId1}&installationContext=${siteId2}` +\n `${cursor ? `&cursor=${cursor}` : \"\"}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Basic ${Buffer.from(`${email}:${api_token}`).toString(\"base64\")}`,\n Accept: \"application/json\",\n },\n });\n\n console.log(`Response: ${response.status} ${response.statusText}`);\n\n const data = await response.json();\n \n // export your logs to the external monitoring tool\n console.log(data);\n\n\n // if data.cursor exists, fetch next data\n if (data.cursor) {\n await fetchLogs(startDate, endDate, data.cursor);\n }\n } catch (err) {\n console.error(err);\n }\n};\n\n// Call fetchLogs function\nfetchLogs(startDate, endDate, cursor);\n```\n\nExample:\n```text\n{\n \"appLogs\": [\n {\n \"timeUnixNano\": \"1707821444939000000\",\n \"severityNumber\": 30,\n \"severityText\": \"INFO\",\n \"body\": {\n \"stringValue\": \"This is simple log message\"\n },\n \"traceId\": \"3e1c350520934cbeb20b7d54d56bee2c\",\n \"spanId\": \"6c7f6ad7436700c1\",\n \"attributes\": [\n {\n \"key\": \"appId\",\n \"value\": {\n \"stringValue\": \"yibeb59-d217-58d3-a3a7-0a888b3bc5ef\"\n }\n },\n {\n \"key\": \"environmentId\",\n \"value\": {\n \"stringValue\": \"0129990-850f-1a19-a013-12cdefe2fa19\"\n }\n },\n {\n \"key\": \"invocationId\",\n \"value\": {\n \"stringValue\": \"e1f88a1e-1b59-1511-adfb-e080972d5d89\"\n }\n },\n {\n \"key\": \"installationContext\",\n \"value\": {\n \"stringValue\": \"ari:cloud:confluence::site/089a1455-4ea0-122a-b70c-5b17360f047d\"\n }\n },\n {\n \"key\": \"appVersion\",\n \"value\": {\n \"stringValue\": \"1.206.0\"\n }\n },\n {\n \"key\": \"functionKey\",\n \"value\": {\n \"stringValue\": \"updateStatusTitle\"\n }\n },\n {\n \"key\": \"moduleType\",\n \"value\": {\n \"stringValue\": \"core:function\"\n }\n },\n {\n \"key\": \"arguments\",\n \"value\": {\n \"stringValue\": \"[{\\\"randomData\\\":0.6341547823420093}]\"\n }\n },\n {\n \"key\": \"licenseState\",\n \"value\": {\n \"stringValue\": \"Active\"\n }\n },\n {\n \"key\": \"edition\",\n \"value\": {\n \"stringValue\": \"Standard\"\n }\n }\n ]\n }\n ],\n \"cursor\": \"someString\"\n}\n```\n\nExample:\n```text\nResources:\n # IAM Role for Lambda execution\n MyLambdaExecutionRole:\n Type: AWS::IAM::Role\n Properties:\n AssumeRolePolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Principal:\n Service: lambda.amazonaws.com\n Action: sts:AssumeRole\n Path: \"/\"\n Policies:\n - PolicyName: S3AccessPolicy\n PolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Action:\n - s3:GetObject\n Resource: arn:aws:s3:::my-s3-bucket/*\n - Effect: Allow\n Action:\n - logs:CreateLogGroup\n - logs:CreateLogStream\n - logs:PutLogEvents\n Resource: arn:aws:logs:*:*:*\n \n # Lambda Function\n MyLambdaFunction:\n Type: AWS::Lambda::Function\n Properties:\n FunctionName: MyLambdaFunction\n Runtime: nodejs16.x\n Handler: index.handler\n Role: !GetAtt MyLambdaExecutionRole.Arn\n Code:\n S3Bucket: my-s3-bucket\n S3Key: my-function-package.zip\n Layers:\n - !Ref OTelLambdaLayer\n Timeout: 60 # Timeout set to 1 minute\n Environment:\n Variables:\n OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/otel-collector-config.yaml\n\n # Event Rule for Lambda Invocation\n MyLambdaInvocationRule:\n Type: \"AWS::Events::Rule\"\n Properties:\n Description: Invoke Lambda every 5 minutes\n ScheduleExpression: \"rate(5 minutes)\"\n State: ENABLED\n Targets:\n - Arn: !GetAtt MyLambdaFunction.Arn\n Id: MyLambdaInvoke\n\n # Permissions for Lambda Invocation\n PermissionForEventsToInvokeLambda:\n Type: \"AWS::Lambda::Permission\"\n Properties:\n FunctionName: !GetAtt MyLambdaFunction.Arn\n Action: \"lambda:InvokeFunction\"\n Principal: events.amazonaws.com\n SourceArn: !GetAtt MyLambdaInvocationRule.Arn\n```\n\nExample:\n```text\nResources:\n # IAM Role for Lambda execution\n MyLambdaExecutionRole:\n Type: AWS::IAM::Role\n Properties:\n AssumeRolePolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Principal:\n Service: lambda.amazonaws.com\n Action: sts:AssumeRole\n Path: \"/\"\n Policies:\n - PolicyName: S3AccessPolicy\n PolicyDocument:\n Version: '2012-10-17'\n Statement:\n - Effect: Allow\n Action:\n - s3:GetObject\n Resource: arn:aws:s3:::my-s3-bucket/*\n - Effect: Allow\n Action:\n - logs:CreateLogGroup\n - logs:CreateLogStream\n - logs:PutLogEvents\n Resource: arn:aws:logs:*:*:*\n \n # Lambda Layer\n OTelLambdaLayer:\n Type: AWS::Lambda::LayerVersion\n Properties:\n LayerName: OTelLambdaLayer\n Description: My OTEL Lambda layer\n Content:\n S3Bucket: my-s3-bucket\n S3Key: my-layer-package.zip\n CompatibleRuntimes:\n - nodejs16.x\n\n # Lambda Function\n MyLambdaFunction:\n Type: AWS::Lambda::Function\n Properties:\n FunctionName: MyLambdaFunction\n Runtime: nodejs16.x\n Handler: index.handler\n Role: !GetAtt MyLambdaExecutionRole.Arn\n Code:\n S3Bucket: my-s3-bucket\n S3Key: my-function-package.zip\n Layers:\n - !Ref OTelLambdaLayer\n Timeout: 60 # Timeout set to 1 minute\n Environment:\n Variables:\n OPENTELEMETRY_COLLECTOR_CONFIG_FILE: /var/task/otel-collector-config.yaml\n\n # Event Rule for Lambda Invocation\n MyLambdaInvocationRule:\n Type: \"AWS::Events::Rule\"\n Properties:\n Description: Invoke Lambda every 5 minutes\n ScheduleExpression: \"rate(5 minutes)\"\n State: ENABLED\n Targets:\n - Arn: !GetAtt MyLambdaFunction.Arn\n Id: MyLambdaInvoke\n\n # Permissions for Lambda Invocation\n PermissionForEventsToInvokeLambda:\n Type: \"AWS::Lambda::Permission\"\n Properties:\n FunctionName: !GetAtt MyLambdaFunction.Arn\n Action: \"lambda:InvokeFunction\"\n Principal: events.amazonaws.com\n SourceArn: !GetAtt MyLambdaInvocationRule.Arn\n```\n\nExample:\n```text\nreceivers:\n otlp:\n protocols:\n http:\n \nexporters:\n datadog:\n api: \n key: \"<API key>\"\n\nservice:\n pipelines:\n logs:\n receivers: [otlp]\n exporters: [datadog]\n```\n\nExample:\n```text\nFROM otel/opentelemetry-collector-contrib:latest\n\n# Copy the collector configuration file into the container\nCOPY otel-collector-config.yaml /etc/otel-collector-config.yaml\n\n# Start the collector with the specified configuration file\nCMD [\"--config=/etc/otel-collector-config.yaml\"]\n```\n\nExample:\n```text\nconst logs = {\n resourceLogs: [\n {\n \"resource\": {\n \"attributes\": [\n {\n \"key\": \"service.name\",\n \"value\": {\n \"stringValue\": \"my.service\"\n }\n }\n ]\n },\n \"scopeLogs\": [\n {\n \"scope\": {\n \"name\": \"my.library\",\n \"version\": \"1.0.0\",\n \"attributes\": [\n {\n \"key\": \"my.scope.attribute\",\n \"value\": {\n \"stringValue\": \"some scope attribute\"\n }\n }\n ]\n },\n logRecords: \"<Place appLogs received from REST API Call>\",\n },\n ],\n },\n ],\n};\n```\n\nExample:\n```text\ncurl --location --request POST 'localhost:4318/v1/logs' \\\n--header 'Content-Type: application/json' \\\n--data <logs>'\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.527Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":402,"estimatedTokens":2789}}273{"id":"doc-use_environment_specific_flags-ca0c4434","source":"documentation","title":"Use environment-specific flags","url":"https://developer.atlassian.com/platform/forge/feature-flags/how-to-environment-specific/","text":"Example:\n```javascript\nconst featureFlags = new FeatureFlags();\nawait featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n installContext: context?.installContext,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"new-feature\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\nExample:\n```bash\nforge deploy\nforge deploy --environment staging\nforge deploy --environment production\n```\n\nExample:\n```javascript\nconst isDebugMode = featureFlags.checkFlag(user, \"debug-mode\", false);\nif (isDebugMode) {\n console.log(\"Debug info:\", { context, user });\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.527Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":52,"estimatedTokens":252}}274{"id":"doc-target_specific_users_or_organizations-312d46fc","source":"documentation","title":"Target specific users or organizations","url":"https://developer.atlassian.com/platform/forge/feature-flags/how-to-target-users/","text":"Example:\n```javascript\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n installContext: context?.installContext,\n },\n attributes: {\n installContext: context?.installContext,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"enterprise-feature\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n accountId: context?.principal?.accountId,\n },\n attributes: {\n accountId: context?.principal?.accountId,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"beta-feature\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\nExample:\n```javascript\nimport { getAppContext } from \"@forge/api\";\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const { license: appLicense, environmentType } = getAppContext();\n\n // Determine the license value\n let licenseValue = \"INACTIVE\";\n if (appLicense?.trialEndDate && new Date(appLicense.trialEndDate) > new Date()) {\n licenseValue = \"TRIAL\";\n } else if (appLicense?.active) {\n licenseValue = \"ACTIVE\";\n }\n\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n installContext: context?.installContext,\n },\n attributes: {\n installContext: context?.installContext,\n license: licenseValue, // \"ACTIVE\", \"INACTIVE\", or \"TRIAL\"\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"premium-feature\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\nExample:\n```javascript\nlet capabilitySetValue = \"capabilityStandard\";\nif (appLicense?.capabilitySet === \"capabilityAdvanced\") {\n capabilitySetValue = \"capabilityAdvanced\";\n}\n\nconst user = {\n identifiers: { installContext: context?.installContext },\n attributes: {\n installContext: context?.installContext,\n capabilitySet: capabilitySetValue, // \"capabilityStandard\" or \"capabilityAdvanced\"\n }\n};\n```\n\nExample:\n```javascript\nconst user = {\n identifiers: {\n installContext: context?.installContext,\n },\n attributes: {\n region: \"EU\", // custom string attribute\n issueCount: 4, // custom numeric attribute\n }\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.528Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":123,"estimatedTokens":719}}275{"id":"doc-roll_out_to_a_percentage_of_users-914c94b2","source":"documentation","title":"Roll out to a percentage of users","url":"https://developer.atlassian.com/platform/forge/feature-flags/how-to-percentage-rollouts/","text":"Example:\n```javascript\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n installContext: context?.installContext,\n },\n attributes: {\n installContext: context?.installContext,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"my-feature-flag\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nresolver.define('getFeature', async ({ context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n accountId: context?.principal?.accountId,\n },\n attributes: {\n accountId: context?.principal?.accountId,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"my-feature-flag\", false);\n featureFlags.shutdown();\n return isEnabled;\n});\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.528Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":57,"estimatedTokens":305}}276{"id":"doc-create_an_app_supporting_rolling_releases-de49e3e3","source":"documentation","title":"Create an app supporting rolling releases","url":"https://developer.atlassian.com/platform/forge/rolling-releases-tutorial/","text":"Example:\n```bash\n# Install the latest version of CLI\nnpm i -g @forge/cli@latest\n\n# Ensure you are authenticated by running \nforge whoami\n\n# If not authenticated then login to authenticate\nforge login\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```bash\nforge create\n# ? Enter a name for your app: rolling-release-confluence\n# ? Select an Atlassian app or platform tool: Confluence\n# ? Select a category: UI Kit\n# ? Select a template: confluence-macro\n```\n\nExample:\n```bash\ncd rolling-release-confluence\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: rolling-release-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: Hello World!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\npermissions:\n # Enable rolling releases for the app\n enforcement: app-managed\n```\n\nExample:\n```javascript\nimport { invoke, view } from '@forge/bridge';\n```\n\nExample:\n```javascript\nconst [context, setContext] = useState({});\n\nuseEffect(() => {\n const fetchContext = async () => {\n setContext(await view.getContext());\n };\n fetchContext();\n}, []);\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke, view } from '@forge/bridge';\n\nconst App = () => {\n const [data, setData] = useState(null);\n const [context, setContext] = useState({});\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n useEffect(() => {\n const fetchContext = async () => {\n setContext(await view.getContext());\n };\n fetchContext();\n }, []);\n\n return (\n <>\n <Text>Hello world!</Text>\n <Text>Installed app version: {context.appVersion}</Text>\n <Text>{data ? data : 'Loading...'}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\npermissions:\n enforcement: app-managed\n scopes:\n # added a new scope\n - read:space:confluence\n```\n\nExample:\n```javascript\nimport ForgeReconciler, { Text, CodeBlock } from '@forge/react';\nimport { invoke, view, requestConfluence } from '@forge/bridge';\n```\n\nExample:\n```javascript\n// Added Component to fetch all spaces and render them\nconst ShowSpaces = () => {\n const [isLoading, setIsLoading] = useState(true);\n const [spaces, setSpaces] = useState([]);\n\n useEffect(() => {\n const fetchSpaces = async () => {\n try {\n setIsLoading(true);\n const response = await requestConfluence(`/wiki/api/v2/spaces`, {\n headers: {\n Accept: \"application/json\",\n },\n });\n const data = await response.json();\n setSpaces(data.results || []);\n } catch (error) {\n console.error('Failed to fetch spaces:', error);\n setSpaces([]);\n } finally {\n setIsLoading(false);\n }\n };\n\n fetchSpaces();\n }, []);\n\n if (isLoading) {\n return <Text>Loading spaces...</Text>;\n }\n\n return <CodeBlock text={JSON.stringify(spaces, undefined, 2)} language=\"json\" />;\n};\n```\n\nExample:\n```javascript\nreturn (\n <>\n <Text>Hello world!</Text>\n <Text>Installed app version: {context.appVersion}</Text>\n {/* Show all the spaces in the instance */}\n <ShowSpaces />\n <Text>{data ? data : 'Loading...'}</Text>\n </>\n);\n```\n\nExample:\n```javascript\nimport ForgeReconciler, { Text, CodeBlock, usePermissions } from '@forge/react';\n```\n\nExample:\n```javascript\n// Check if read:space:confluence permission is granted\nconst { hasPermission, isLoading: permissionsLoading } = usePermissions({\n scopes: [\"read:space:confluence\"],\n});\n```\n\nExample:\n```javascript\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text, CodeBlock, usePermissions } from '@forge/react';\nimport { invoke, view, requestConfluence } from '@forge/bridge';\n\n// Added Component to fetch all spaces and render them\nconst ShowSpaces = () => {\n const [isLoading, setIsLoading] = useState(true);\n const [spaces, setSpaces] = useState([]);\n\n useEffect(() => {\n const fetchSpaces = async () => {\n try {\n setIsLoading(true);\n const response = await requestConfluence(`/wiki/api/v2/spaces`, {\n headers: {\n Accept: \"application/json\",\n },\n });\n const data = await response.json();\n setSpaces(data.results || []);\n } catch (error) {\n console.error('Failed to fetch spaces:', error);\n setSpaces([]);\n } finally {\n setIsLoading(false);\n }\n };\n\n fetchSpaces();\n }, []);\n\n if (isLoading) {\n return <Text>Loading spaces...</Text>;\n }\n\n return <CodeBlock text={JSON.stringify(spaces, undefined, 2)} language=\"json\" />;\n};\n\nconst App = () => {\n const [data, setData] = useState(null);\n const [context, setContext] = useState({});\n\n // Check if read:space:confluence permission is granted\n const { hasPermission, isLoading: permissionsLoading } = usePermissions({\n scopes: [\"read:space:confluence\"],\n });\n\n useEffect(() => {\n invoke('getText', { example: 'my-invoke-variable' }).then(setData);\n }, []);\n\n useEffect(() => {\n const fetchContext = async () => {\n setContext(await view.getContext());\n };\n fetchContext();\n }, []);\n\n return (\n <>\n {/* Update the text to signify a backwards compatible change that you wished to ship in older installations */}\n <Text>Hello new world!</Text>\n <Text>Installed app version: {context.appVersion}</Text>\n {permissionsLoading ? (\n <Text>Loading...</Text>\n ) : (\n /* Skip calling show spaces if permission is not granted */\n hasPermission && <ShowSpaces />\n )}\n <Text>{data ? data : \"Loading...\"}</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nnpm install --save @forge/api@latest\n```\n\nExample:\n```javascript\nimport Resolver from '@forge/resolver';\nimport { permissions } from '@forge/api';\n\nconst resolver = new Resolver();\n\nresolver.define('getText', async () => {\n if (!permissions.hasScope('read:space:confluence')) {\n return 'The read:space:confluence scope is not approved yet.';\n }\n\n return 'The read:space:confluence scope is approved.';\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```bash\nforge install --upgrade code\n```\n\nExample:\n```bash\nforge install list\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```bash\nforge uninstall\n```\n\nExample:\n```bash\nforge install --major-version <major-version>\n```\n\nExample:\n```bash\nforge install --major-version 2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.530Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":26,"totalLines":342,"estimatedTokens":1720}}277{"id":"doc-tutorial_dark_mode_switcher_confluence_app_with_-9d4aacb7","source":"documentation","title":"Tutorial: Dark mode switcher Confluence app with feature flags","url":"https://developer.atlassian.com/platform/forge/feature-flags/dark-mode-feature-flag/","text":"Example:\n```bash\nnpm install @forge/bridge@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```text\ndark-mode-switcher/\n├── manifest.yml\n├── package.json\n└── src/\n ├── index.js\n ├── frontend/\n │ └── index.jsx\n └── resolvers/\n └── index.js\n```\n\nExample:\n```text\n# manifest.yml\nmodules:\n macro:\n - key: switch-dark-mode\n resource: main\n render: native\n resolver:\n function: resolver\n title: switch-dark-mode\n adfExport:\n function: export-key\n function:\n - key: resolver\n handler: index.handler\n - key: export-key\n handler: macroExport.exportFunction\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs22.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/<your-app-id>\n```\n\nExample:\n```javascript\n// src/frontend/index.jsx\nimport React, { useEffect, useState } from \"react\";\nimport ForgeReconciler, { Box, Button, Text, Stack } from \"@forge/react\";\nimport { view, FeatureFlags } from \"@forge/bridge\";\n\nconst App = () => {\n const [refresh, setRefresh] = useState(false);\n const [isDarkModeEnabled, setIsDarkModeEnabled] = useState(null);\n const [darkUiActive, setDarkUiActive] = useState(false);\n\n const onRefresh = () => {\n setRefresh(true);\n };\n\n const onToggleDarkMode = () => {\n setDarkUiActive((prev) => !prev);\n };\n\n useEffect(() => {\n void view.theme.enable();\n }, []);\n\n useEffect(() => {\n const initializeFeatureFlags = async () => {\n const { accountId, cloudId, environmentType } = await view.getContext();\n\n const user = {\n attributes: {\n installContext: `ari:cloud:confluence::site/${cloudId}`,\n },\n identifiers: {\n accountId: accountId,\n },\n };\n\n const config = {\n environment: environmentType.toLowerCase(),\n };\n\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize(user, config);\n\n const result = featureFlags.checkFlag(\"is_dark_mode_enabled\");\n setIsDarkModeEnabled(result);\n\n if (!result) {\n setDarkUiActive(false);\n }\n };\n\n initializeFeatureFlags();\n setRefresh(false);\n }, [refresh]);\n\n if (isDarkModeEnabled === null) {\n return <Text>Loading…</Text>;\n }\n\n return (\n <Box\n padding=\"space.200\"\n backgroundColor={darkUiActive ? \"color.background.neutral.bold\" : undefined}\n >\n <Stack space=\"space.200\" alignInline=\"center\">\n <Button onClick={onRefresh}>Refresh</Button>\n {isDarkModeEnabled ? (\n <Text color={darkUiActive ? \"color.text.inverse\" : \"color.text\"}>\n Dark mode feature is enabled\n </Text>\n ) : (\n <Text color={darkUiActive ? \"color.text.inverse\" : \"color.text\"}>\n Dark mode feature is disabled\n </Text>\n )}\n <Text color={darkUiActive ? \"color.text.inverse\" : \"color.text\"}>\n Dark mode UI is {darkUiActive ? \"on\" : \"off\"}\n </Text>\n {isDarkModeEnabled ? (\n <Button onClick={onToggleDarkMode}>\n {darkUiActive ? \"Switch to light mode\" : \"Switch to dark mode\"}\n </Button>\n ) : null}\n </Stack>\n </Box>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n);\n```\n\nExample:\n```bash\nforge deploy\nforge install --upgrade\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.531Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":161,"estimatedTokens":855}}278{"id":"doc-rolling_releases-93d44e6c","source":"documentation","title":"Rolling releases","url":"https://developer.atlassian.com/platform/forge/rolling-releases/","text":"Example:\n```text\napp:\n id: ...\npermissions:\n enforcement: app-managed\n scopes:\n - ...\n external:\n - ...\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnpm install --save @forge/react\n```\n\nExample:\n```typescript\nimport { usePermissions } from '@forge/react';\n\nconst MyComponent = () => {\n const { hasPermission, isLoading, missingPermissions, error } = usePermissions({\n scopes: ['read:confluence-content', 'write:confluence-content'],\n external: {\n fetch: {\n backend: ['https://api.example.com'],\n client: ['https://cdn.example.com']\n },\n images: ['https://images.example.com'],\n fonts: ['https://fonts.googleapis.com']\n }\n });\n\n if (isLoading) return <Text>Loading...</Text>;\n if (error) return <Text>Error: {error.message}</Text>;\n if (!hasPermission) {\n return <Text>Missing: {JSON.stringify(missingPermissions)}</Text>;\n }\n \n return <Text>All permissions granted!</Text>;\n};\n```\n\nExample:\n```bash\nnpm install --save @forge/bridge\n```\n\nExample:\n```typescript\nimport { checkPermissions } from '@forge/bridge';\n\nconst App = () => {\n const [hasPermission, setHasPermission] = useState(false);\n const [missingPermissions, setMissingPermissions] = useState(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState(null);\n useEffect(() => {\n checkPermissions({\n scopes: ['read:jira-work'],\n external: {\n fetch: {\n backend: ['https://example.com/something'],\n },\n },\n }).then(({granted, missing}) => {\n setHasPermission(granted);\n setMissingPermissions(missing);\n setIsLoading(false);\n }).catch(err => {\n setError(err);\n setIsLoading(false);\n });\n }, []);\n\n if (isLoading) return <div>Loading...</div>;\n if (error) return <div>Error: {error.message}</div>;\n if (!hasPermission) {\n return <div>Missing: {JSON.stringify(missingPermissions)}</div>;\n }\n \n return <div>All permissions granted!</div>;\n};\n```\n\nExample:\n```bash\nnpm install --save @forge/api@latest\n```\n\nExample:\n```javascript\nimport { permissions } from '@forge/api';\n\nconst isPermitted = permissions.hasScope('write:confluence-content')\n```\n\nExample:\n```typescript\nimport { permissions } from '@forge/api';\n\nconst isPermitted = permissions.canFetchFrom('backend', 'https://api.example.com')\n```\n\nExample:\n```typescript\nimport { permissions } from '@forge/api';\n\nconst isPermitted = permissions.canLoadResource('images', 'https://api.example.com/image.png')\n```\n\nExample:\n```javascript\nimport { permissions } from '@forge/api';\n\nconst { granted, missing } = permissions.hasPermission({ \n scopes: ['write:confluence-content'],\n external: {\n fetch: {\n backend: [\"https://api.example.com\", \"https://blah.com\", \"https://www.google.com\"]\n },\n images: [\"https://images.example.com\", \"https://cdn.example.com\"],\n }\n})\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: jira-issue-trigger-filtering-by-expression-for-bug-issue-type\n function: main\n events:\n - avi:jira:created:issue\n filter:\n expression: \"event.permissions.scopes.includes('write:jira-work') && event.issue.fields?.issueType.name == 'Bug'\"\n function:\n - key: main\n handler: index.run\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: jira-issue-trigger-filtering-by-expression-for-bug-issue-type\n function: main\n events:\n - avi:jira:created:issue\n filter:\n expression: \"(event.permissions.external?.fetch?.backend || []).includes('*.example.com')\"\n function:\n - key: main\n handler: index.run\npermissions:\n enforcement: app-managed\n external:\n fetch:\n backend:\n - \"*.example.com\"\n```\n\nExample:\n```bash\n# Install with permission version 2, code version 2 (coupled)\nforge install --major-version 2\n\n# Upgrade only code to version 4 (decoupled state: permissions v2, code v4)\nforge install --upgrade code --major-version 4\n\n# Upgrade both code and permissions to version 4 (coupled again)\nforge install --upgrade --major-version 4\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.532Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":14,"totalLines":182,"estimatedTokens":1023}}279{"id":"doc-feature_flags_server_side_sdk-b52e8603","source":"documentation","title":"Feature flags server-side SDK","url":"https://developer.atlassian.com/platform/forge/feature-flags/feature-flags-sdk/","text":"Example:\n```bash\nnpm install @forge/feature-flags@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nnew FeatureFlags()\n```\n\nExample:\n```typescript\ninitialize(config: FeatureFlagConfig): Promise<void>\n```\n\nExample:\n```typescript\ncheckFlag(user: FeatureFlagUser, flagName: string, defaultValue?: boolean): boolean\n```\n\nExample:\n```typescript\ngetFlag(user: FeatureFlagUser, flagId: string): FlagEvaluationDetails | undefined\n```\n\nExample:\n```typescript\ngetAllFlagIds(): string[]\n```\n\nExample:\n```typescript\nshutdown(): void\n```\n\nExample:\n```typescript\ninterface FeatureFlagUser {\n attributes?: Record<string, string | number>;\n identifiers?: {\n installContext?: string;\n accountId?: string;\n };\n}\n```\n\nExample:\n```typescript\ninterface FlagEvaluationDetails {\n flagId: string;\n name: string;\n value: boolean;\n matchedRule: string | null;\n reason: 'override' | 'rule_match' | 'default' | 'disabled';\n}\n```\n\nExample:\n```javascript\nimport { getAppContext } from \"@forge/api\";\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nexport const handler = async (payload, context) => {\n const { appVersion, license: appLicense, environmentType } = getAppContext();\n\n // Resolve license value\n let licenseValue = \"INACTIVE\";\n if (appLicense?.trialEndDate && new Date(appLicense.trialEndDate) > new Date()) {\n licenseValue = \"TRIAL\";\n } else if (appLicense?.active) {\n licenseValue = \"ACTIVE\";\n }\n\n // Resolve capabilitySet value\n const capabilitySetValue = appLicense?.capabilitySet === \"capabilityAdvanced\"\n ? \"capabilityAdvanced\"\n : \"capabilityStandard\";\n\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n accountId: context?.principal?.accountId,\n },\n attributes: {\n installContext: context?.installContext,\n accountId: context?.principal?.accountId,\n appVersion,\n license: licenseValue,\n capabilitySet: capabilitySetValue,\n }\n };\n\n const isEnabled = featureFlags.checkFlag(user, \"new-feature\", false);\n\n // Get evaluation details for a specific flag (useful for debugging)\n const flagDetails = featureFlags.getFlag(user, \"new-feature\");\n if (flagDetails) {\n console.log(`Flag \"${flagDetails.name}\" evaluated to ${flagDetails.value} (reason: ${flagDetails.reason})`);\n }\n\n // Get all available flag IDs\n const allFlagIds = featureFlags.getAllFlagIds();\n console.log(\"Available flags:\", allFlagIds);\n\n featureFlags.shutdown();\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.533Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":119,"estimatedTokens":643}}280{"id":"doc-feature_flags_client_sdk-8558ab4f","source":"documentation","title":"Feature flags client SDK","url":"https://developer.atlassian.com/platform/forge/feature-flags/feature-flags-client-sdk/","text":"Example:\n```bash\nyarn add @forge/bridge\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nnew FeatureFlags()\n```\n\nExample:\n```typescript\ninitialize(user: FeatureFlagUser, config?: ForgeFeatureFlagConfig): Promise<void>\n```\n\nExample:\n```typescript\ncheckFlag(flagName: string, defaultValue?: boolean): boolean\n```\n\nExample:\n```typescript\nisInitialized(): boolean\n```\n\nExample:\n```typescript\nshutdown(): void\n```\n\nExample:\n```typescript\ninterface FeatureFlagUser {\n attributes?: Record<string, string | number>;\n identifiers?: {\n installContext?: string;\n accountId?: string;\n };\n}\n```\n\nExample:\n```typescript\ninterface ForgeFeatureFlagConfig {\n environment?: 'development' | 'staging' | 'production';\n}\n```\n\nExample:\n```typescript\nconst { environmentType } = await view.getContext();\nconst config = {\n environment: environmentType.toLowerCase(), // \"development\", \"staging\", or \"production\"\n};\n```\n\nExample:\n```typescript\nimport { useEffect, useState } from \"react\";\nimport { view, FeatureFlags } from \"@forge/bridge\";\n\nconst App = () => {\n const [isFeatureEnabled, setIsFeatureEnabled] = useState(false);\n\n useEffect(() => {\n const initializeFeatureFlags = async () => {\n const { accountId, cloudId, environmentType } = await view.getContext();\n\n const user = {\n attributes: {\n installContext: `ari:cloud:confluence::site/${cloudId}`,\n },\n identifiers: {\n accountId: accountId,\n },\n };\n\n const config = {\n environment: environmentType.toLowerCase(),\n };\n\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize(user, config);\n\n const enabled = featureFlags.checkFlag(\"is-feature-enabled\");\n setIsFeatureEnabled(enabled);\n };\n\n initializeFeatureFlags();\n }, []);\n\n return (\n <>\n {isFeatureEnabled ? (\n <Text>Feature is enabled!</Text>\n ) : (\n <Text>Feature is not enabled.</Text>\n )}\n </>\n );\n};\n```\n\nExample:\n```typescript\nfeatureFlags.shutdown();\n\nconst updatedUser = {\n attributes: {\n installContext: `ari:cloud:confluence::site/${cloudId}`,\n issueId: \"<JIRA_ISSUE_ID>\",\n },\n identifiers: {\n accountId: accountId,\n },\n};\n\nawait featureFlags.initialize(updatedUser, { environment: \"production\" });\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.534Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":127,"estimatedTokens":577}}281{"id":"doc-feature_flags_core_concepts-71e23ba9","source":"documentation","title":"Feature flags core concepts","url":"https://developer.atlassian.com/platform/forge/feature-flags/concepts/","text":"Example:\n```javascript\nconst { environmentType } = context;\nawait featureFlags.initialize({\n environment: environmentType?.toLowerCase() || \"development\"\n});\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.534Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":15,"estimatedTokens":51}}282{"id":"doc-getting_started_with_forge-373f9640","source":"documentation","title":"Getting started with Forge","url":"https://developer.atlassian.com/platform/forge/installing-forge-on-macos/","text":"Example:\n```bash\nnvm install --lts\nnvm use --lts\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nnode --version\n```\n\nExample:\n```bash\nnpm config set unsafe-perm true\n```\n\nExample:\n```bash\nsudo rm -rf ~/Library/Preferences/@forge\n```\n\nExample:\n```bash\nnpm install -g @forge/cli\n```\n\nExample:\n```bash\nforge --version\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd <your-app-dir>\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nAllow Forge to collect CLI usage and error reporting information?\n```\n\nExample:\n```bash\n✔ Logged in as Mia Krystof\n```\n\nExample:\n```bash\nread FORGE_EMAIL\n# Enter email\nread -s FORGE_API_TOKEN\n# Enter your Atlassian API scoped token (will not be displayed)\nexport FORGE_EMAIL FORGE_API_TOKEN\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.535Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":15,"totalLines":83,"estimatedTokens":210}}283{"id":"doc-page_bookmark_confluence_app_with_feature_flags-1575a8cc","source":"documentation","title":"Page bookmark Confluence app with Feature flags","url":"https://developer.atlassian.com/platform/forge/feature-flags/page-bookmark-feature-flag/","text":"Example:\n```bash\nnpm install @forge/feature-flags@latest @forge/kvs@latest @forge/api@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```text\npage-bookmark-app/\n├── manifest.yml \n├── package.json \n├── node_modules/ \n└── src/ \n ├── index.js \n ├── frontend/ \n │ └── index.jsx \n └── resolvers/ \n └── index.js\n```\n\nExample:\n```javascript\n// src/frontend/index.jsx\nimport React from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\n\nconst App = () => {\n return (\n <>\n <Text>Hello world!</Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```text\n# manifest.yml\nmodules:\n macro:\n - key: page-bookmark-hello-world-macro\n resource: main\n render: native\n resolver:\n function: resolver\n title: Page Bookmarks\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\npermissions:\n scopes:\n - read:confluence-content.summary\n - storage:app\napp:\n runtime:\n name: nodejs22.x\n memoryMB: 256\n architecture: arm64\n id: ari:cloud:ecosystem::app/<Replace with your App Id>\n```\n\nExample:\n```javascript\n// src/frontend/index.jsx\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { \n Text, \n Button, \n useProductContext,\n Box,\n Stack,\n Lozenge\n} from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const context = useProductContext();\n const [isBookmarked, setIsBookmarked] = useState(false);\n const [showBookmarkFeature, setShowBookmarkFeature] = useState(false);\n const [loading, setLoading] = useState(true);\n\n // Check bookmark status when feature is enabled\n useEffect(() => {\n // Wait for context to be available before checking feature flag\n if (!context) return;\n\n const pageId = context?.extension?.content?.id;\n if (pageId) {\n invoke('checkBookmark', { pageId }).then(({isEnabled, bookmark}) => {\n setIsBookmarked(bookmark);\n setShowBookmarkFeature(isEnabled);\n setLoading(false);\n });\n } else {\n setLoading(false);\n setShowBookmarkFeature(false);\n }\n }, [context]);\n\n // Handle bookmark toggle\n const toggleBookmark = async () => {\n const pageId = context?.extension?.content?.id;\n if (!pageId) return;\n\n const newStatus = await invoke('toggleBookmark', { pageId });\n setIsBookmarked(newStatus);\n };\n\n if (loading) {\n return (\n <Box padding=\"space.100\">\n <Text appearance=\"subtle\">Loading...</Text>\n </Box>\n );\n }\n\n // If feature flag is disabled, show educational message\n if (!showBookmarkFeature) {\n return (\n <Box padding=\"space.150\">\n <Text appearance=\"subtle\">\n The bookmark feature is controlled by a feature flag. Enable it in Developer Console to activate.\n </Text>\n </Box>\n );\n }\n\n // Feature flag is enabled - show fully functional bookmark UI\n return (\n <Box padding=\"space.150\">\n <Stack space=\"space.150\" alignInline=\"start\">\n <Button \n onClick={toggleBookmark}\n appearance={isBookmarked ? \"primary\" : \"default\"}\n iconBefore={isBookmarked ? \"star-filled\" : \"star\"}\n >\n {isBookmarked ? \"Bookmarked\" : \"Bookmark\"}\n </Button>\n {isBookmarked && (\n <Lozenge appearance=\"success\" isBold>\n Saved\n </Lozenge>\n )}\n </Stack>\n </Box>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\n// src/resolvers/index.js\nimport Resolver from '@forge/resolver';\nimport { getAppContext } from '@forge/api';\nimport { kvs } from '@forge/kvs';\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nconst resolver = new Resolver();\n\n// Helper function to check if feature flag is enabled\nasync function isBookmarkFeatureEnabled(context) {\n try {\n const { environmentType } = getAppContext();\n \n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: environmentType?.toLowerCase() || \"development\" \n });\n\n const user = {\n identifiers: {\n accountId: context?.accountId,\n },\n attributes: {\n installContext: context?.installContext\n }\n };\n\n // IMPORTANT: Replace \"enable_page_bookmarks\" with your Flag ID from Developer Console\n // if you used a different name when creating the flag\n return featureFlags.checkFlag(user, \"enable_page_bookmarks\");\n } catch (error) {\n console.error(\"Feature flag check failed:\", error);\n return false;\n }\n}\n\n// Toggle bookmark for a page\nresolver.define('toggleBookmark', async (req) => {\n const { context, payload } = req;\n const { pageId } = payload;\n const accountId = context.accountId;\n\n // Check feature flag before processing\n const isEnabled = await isBookmarkFeatureEnabled(context);\n if (!isEnabled) {\n console.log(\"Bookmark feature is disabled\");\n return false;\n }\n\n // Get user's bookmarks from storage (Key-Value Store from @forge/kvs)\n const storageKey = `bookmarks_${accountId}`;\n const bookmarks = (await kvs.get(storageKey)) || [];\n\n // Toggle bookmark status\n if (bookmarks.includes(pageId)) {\n // Remove bookmark\n const updatedBookmarks = bookmarks.filter(id => id !== pageId);\n await kvs.set(storageKey, updatedBookmarks);\n return false;\n } else {\n // Add bookmark\n await kvs.set(storageKey, [...bookmarks, pageId]);\n return true;\n }\n});\n\n// Check if a page is bookmarked\nresolver.define('checkBookmark', async (req) => {\n const { context, payload} = req;\n const { pageId } = payload;\n const accountId = context.accountId;\n\n // Check feature flag before processing\n const isEnabled = await isBookmarkFeatureEnabled(context);\n if (!isEnabled) {\n return {isEnabled, bookmark : false};\n }\n\n // Check if page is in user's bookmarks\n const storageKey = `bookmarks_${accountId}`;\n const bookmarks = (await kvs.get(storageKey)) || [];\n return { isEnabled, bookmark : bookmarks.includes(pageId)};\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n// src/index.js\nexport { handler } from './resolvers';\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.536Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":280,"estimatedTokens":1622}}284{"id":"doc-tutorial_your_first_feature_flag-888b385b","source":"documentation","title":"Tutorial: Your first feature flag","url":"https://developer.atlassian.com/platform/forge/feature-flags/featureflags-quickstart","text":"Example:\n```bash\ncd your-forge-app\nnpm install @forge/feature-flags@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// src/resolvers/index.js\nimport Resolver from '@forge/resolver';\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nconst resolver = new Resolver();\n\nresolver.define('getFlagValue', async ({ payload, context }) => {\n const featureFlags = new FeatureFlags();\n await featureFlags.initialize({\n environment: context?.environmentType?.toLowerCase() || \"development\"\n });\n\n const user = {\n identifiers: {\n installContext: context?.installContext\n },\n attributes: {\n installContext: context?.installContext\n }\n };\n\n return featureFlags.checkFlag(user, payload?.flag, false);\n});\n\nexport const handler = resolver.getDefinitions();\n```\n\nExample:\n```javascript\n// src/frontend/index.jsx\nimport React, { useEffect, useState } from 'react';\nimport ForgeReconciler, { Text } from '@forge/react';\nimport { invoke } from '@forge/bridge';\n\nconst App = () => {\n const [showPremium, setShowPremium] = useState(false);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n invoke('getFlagValue', { flag: 'show_premium_welcome' })\n .then((value) => {\n setShowPremium(value);\n setLoading(false);\n })\n .catch(() => {\n setShowPremium(false);\n setLoading(false);\n });\n }, []);\n\n if (loading) {\n return <Text>Loading...</Text>;\n }\n\n return (\n <>\n <Text>Hello world!</Text>\n {showPremium && (\n <Text appearance=\"success\">Welcome to Premium Features!</Text>\n )}\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```bash\nforge deploy\nforge install --upgrade\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.537Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":92,"estimatedTokens":444}}285{"id":"doc-client_sdk_vs_server_side_sdk-efabb66e","source":"documentation","title":"Client SDK vs server-side SDK","url":"https://developer.atlassian.com/platform/forge/feature-flags/client-vs-server-sdk/","text":"Example:\n```typescript\n// Client SDK: no resolver needed\nimport { view, FeatureFlags } from \"@forge/bridge\";\n\nconst featureFlags = new FeatureFlags();\nconst { accountId, cloudId, environmentType } = await view.getContext();\n\nawait featureFlags.initialize(\n { identifiers: { accountId }, attributes: { installContext: `ari:cloud:confluence::site/${cloudId}` } },\n { environment: environmentType.toLowerCase() }\n);\n\nconst showNewUI = featureFlags.checkFlag(\"new-dashboard-ui\", false);\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// Server-side SDK: in a resolver\nimport { FeatureFlags } from \"@forge/feature-flags\";\n\nconst featureFlags = new FeatureFlags();\nawait featureFlags.initialize({ environment: environmentType?.toLowerCase() || \"development\" });\n\nconst user = { identifiers: { accountId: context?.principal?.accountId } };\nconst isEnabled = featureFlags.checkFlag(user, \"new-backend-logic\", false);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.537Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":35,"estimatedTokens":234}}286{"id":"doc-build_a_jira_service_management_hello_world_app-80fbe439","source":"documentation","title":"Build a Jira Service Management hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-jira-service-management","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n jiraServiceManagement:queuePage:\n - key: hello-world-app-hello-world-queue-page\n resource: main\n resolver:\n function: resolver\n render: native\n title: hello-world-app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.538Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":83,"estimatedTokens":299}}287{"id":"doc-log_in_with_atlassian_account-c31931d4","source":"documentation","title":"Log in with Atlassian account","url":"https://developer.atlassian.com/cloud/admin-hub/on-call/ic-environment/","text":"Atlassian JavaScript load error We tried to load scripts but something went wrong. Please make sure that your network settings allow you to download scripts from the following ://id-frontend.prod-east.frontend.public.atl-paas.net\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.538Z","totalSectionsIncluded":1,"totalCodeBlocksIncluded":0,"totalLines":3,"estimatedTokens":61}}288{"id":"doc-build_a_jira_hello_world_app-2f478dab","source":"documentation","title":"Build a Jira hello world app","url":"https://developer.atlassian.com/platform/forge/build-a-hello-world-app-in-jira","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-app-hello-world-issue-panel\n resource: main\n resolver:\n function: resolver\n render: native\n title: hello-world-app\n icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n runtime:\n name: nodejs24.x\n memoryMB: 256\n architecture: arm64\n id: '<your app id>'\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.540Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":88,"estimatedTokens":337}}289{"id":"doc-app_properties_api-555b2efe","source":"documentation","title":"App properties API","url":"https://developer.atlassian.com/cloud/confluence/app-properties-api/","text":"Example:\n```json\n{\n \"keys\" : [\n {\n \"key\" : \"first_key\",\n \"self\" : \"https://your-site.atlassian.net/wiki/rest/atlassian-connect/1/addons/my-add-on/properties/first_key\"\n },\n {\n \"key\" : \"another_key\",\n \"self\" : \"https://your-site.atlassian.net/wiki/rest/atlassian-connect/1/addons/my-add-on/properties/another_key\"\n }\n ]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"key\" : \"abcd\",\n \"value\" : true,\n \"self\" : \"https://your-site.atlassian.net/wiki/rest/atlassian-connect/1/addons/my-add-on/properties/abcd\"\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.540Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":32,"estimatedTokens":150}}290{"id":"doc-build_an_app_compatible_with_confluence_and_jira-d6a81135","source":"documentation","title":"Build an app compatible with Confluence and Jira","url":"https://developer.atlassian.com/platform/forge/build-an-app-compatible-with-confluence-and-jira","text":"Example:\n```bash\nnpm install -g @forge/cli@latest\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\ncd hello-world-app\n```\n\nExample:\n```bash\nhello-world-app\n├── README.md\n├── manifest.yml\n├── package-lock.json\n├── package.json\n└── src\n ├── frontend\n │ └── index.jsx\n ├── index.js\n └── resolvers\n └── index.js\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: hello world app\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n compatibility:\n confluence:\n required: true\n```\n\nExample:\n```text\nmodules:\n macro:\n - key: hello-world-app-hello-world\n resource: main\n render: native\n resolver:\n function: resolver\n title: Forge app for Mia\n description: Inserts hello world!\n function:\n - key: resolver\n handler: index.handler\nresources:\n - key: main\n path: src/frontend/index.jsx\napp:\n id: '<your app id>'\n compatibility:\n confluence:\n required: true\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge tunnel\n```\n\nExample:\n```bash\nTunnel redirects requests you make to your local machine. This occurs for any Atlassian site where your app is installed in the specific development environment. You will not see requests from other users.\nPress Ctrl+C to cancel.\n\n\n=== Running forge lint...\nNo issues found.\n\n=== Bundling code...\n✔ Functions bundled.\n✔ Resources bundled.\n\nListening for requests...\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.541Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":115,"estimatedTokens":426}}291{"id":"doc-declare_marketing_assets_for_server_apps-cccc5f1e","source":"documentation","title":"Declare marketing assets for server apps","url":"https://developer.atlassian.com/platform/marketplace/declaring-marketing-assets-for-server-apps","text":"Example:\n```text\n<logo image=\"images/01_logo.jpg\"/>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<banner image=\"images/02_banner.jpg\"/>\n```\n\nExample:\n```text\n<highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n</highlights>\n```\n\nExample:\n```text\n<screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n</screenshots>\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<atlassian-plugin-marketing>\n <!--Describe names and versions of compatible products -->\n <compatibility>\n <product name=\"jira\" min=\"4.0\" max=\"4.4.1\"/>\n <product name=\"bamboo\" min=\"3.0\" max=\"3.1\"/>\n </compatibility>\n <!-- Describe your app logo and banner. The banner is only displayed in the UPM. -->\n <logo image=\"images/01_logo.jpg\"/>\n <banner image=\"images/02_banner.jpg\"/>\n <!-- Describe highlight and cropped highlight image assets, in order. -->\n <highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n </highlights>\n <!-- Describe additional screenshots you'd like listed on your app listing. -->\n <screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n </screenshots>\n</atlassian-plugin-marketing>\n```\n\nExample:\n```javascript\n<vendor name=\"Atlassian Plugin Generator\" url=\"http://example.com\"/>\n```\n\nExample:\n```javascript\n<param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"vendor-icon\">images/vendorIcon.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n```\n\nExample:\n```javascript\n<param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<plugin-info>\n <description>Description of Example Marketing for Confluence</description>\n <version>1.0</version>\n <vendor name=\"Your Name Here\" url=\"http://example.com\"/>\n <param name=\"atlassian-licensing-enabled\">true</param>\n <param name=\"plugin-icon\">images/pluginIcon.jpg</param>\n <param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n <param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n <param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n</plugin-info>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.543Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":12,"totalLines":102,"estimatedTokens":721}}292{"id":"doc-user_privacy_guide_for_app_developers-3bf7b58c","source":"documentation","title":"User privacy guide for app developers","url":"https://developer.atlassian.com/cloud/jira/platform/user-privacy-developer-guide/","text":"Example:\n```json\n{\n\"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"updatedAt\": \"2017-05-27T16:22:09.000Z\"\n }, {\n \"accountId\": \"account-id-b\",\n \"updatedAt\": \"2017-04-27T16:23:32.000Z\"\n }, {\n \"accountId\": \"account-id-c\",\n \"updatedAt\": \"2017-02-27T16:22:11.000Z\"\n }]\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"status\": \"closed\"\n }, {\n \"accountId\": \"account-id-c\",\n \"status\": \"updated\"\n }]\n}\n```\n\nExample:\n```json\n{\n \"errorType\": \"string\",\n \"errorMessage\": \"string\"\n}\n```\n\nExample:\n```json\n{\n\"accounts\": [{\n \"accountId\": \"account-id-a\",\n \"updatedAt\": \"2018-10-25T23:08:51.382Z\"\n }, {\n \"accountId\": \"account-id-b\",\n \"updatedAt\": \"2018-10-25T23:14:44.231Z\"\n }, {\n \"accountId\": \"account-id-c\",\n \"updatedAt\": \"2018-12-01T02:44:21.020Z\"\n }]\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.544Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":60,"estimatedTokens":217}}293{"id":"doc-connect_app_descriptor-6e2fd960","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/jira/platform/add-on-descriptor/","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://issues.example.com\",\n \"displayUrlServicedeskHelpCenter\": \"https://support.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.545Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":99,"estimatedTokens":466}}294{"id":"doc-build_agc_compatible_forge_apps-e0228669","source":"documentation","title":"Build AGC-compatible Forge apps","url":"https://developer.atlassian.com/platform/framework/agc/guides/build-agc-compatible-forge-apps/","text":"Example:\n```bash\nexport FORGE_GRAPHQL_GATEWAY=https://api.atlassian-us-gov-mod.com/graphql\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge login\n```\n\nExample:\n```text\n✔ Logged in as John Doe.\n```\n\nExample:\n```bash\nexport FORGE_GRAPHQL_GATEWAY=https://api.atlassian.com/graphql\nforge login\n```\n\nExample:\n```text\napp:\n# id: ari:cloud:ecosystem::app/5c3ed5a5-721d-4fb8-b283-a3e22ec28d0a # Standard cloud app ID\nid: ari:cloud:ecosystem::app/b8703e30-dff5-409e-a0c1-cf4a6aa5e7f0 # AGC app ID\n```\n\nExample:\n```bash\nforge register\n```\n\nExample:\n```bash\ncd my-agc-app\n```\n\nExample:\n```bash\n✔ Registered my-agc-app\n```\n\nExample:\n```bash\nforge create\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```bash\nforge deploy -e staging\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.547Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":13,"totalLines":70,"estimatedTokens":196}}295{"id":"doc-timebomb_licenses_for_testing_server_apps_on_the-17010fec","source":"documentation","title":"Timebomb licenses for testing server apps on the Atlassian Marketplace","url":"https://developer.atlassian.com/platform/marketplace/timebomb-licenses-for-testing-server-apps","text":"Example:\n```bash\nAAABCA0ODAoPeNpdj01PwkAURffzKyZxZ1IyUzARkllQ24gRaQMtGnaP8VEmtjPNfFT59yJVFyzfu\nbkn796Ux0Bz6SmbUM5nbDzj97RISxozHpMUnbSq88poUaLztFEStUN6MJZ2TaiVpu/YY2M6tI6sQ\nrtHmx8qd74EZ+TBIvyUU/AoYs7jiE0jzknWQxMuifA2IBlUbnQ7AulVjwN9AaU9atASs69O2dNFU\n4wXJLc1aOUGw9w34JwCTTZoe7RPqUgep2X0Vm0n0fNut4gSxl/Jcnj9nFb6Q5tP/Ueu3L+0PHW4g\nhZFmm2zZV5k6/95CbR7Y9bYGo/zGrV3Ir4jRbDyCA6vt34DO8p3SDAsAhQnJjLD5k9Fr3uaIzkXK\nf83o5vDdQIUe4XequNCC3D+9ht9ZYhNZFKmnhc=X02dh\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uQZOS7KEIUWMtpNJqGi9bfKUb4S3ZD7T/XgrqwYPv9\nmbezGTeRXn0NK8cZRHlPGZRHEW0SEsaMh6SFGxlVOeURlGCdbRRFaAFetCGdo2vFdI36KHRHRhLV\nr7dg8kPGztsgjNyY0Cexal0IELOw4DNA85J1svGj4xwxgOZrOzsciYrp3qY0Eep0AFKrCD77JQ5j\nTapN6PyNb5mw5Dc1BKVndwWrpHWKonkCUwP5j4Vye28DF422yh42O3ugoTxZ7KcagzsBt9Rf+AP8\nk/O90V56mAl24HPttkyL7L1b+1Etnut19BqB4sa0FkRXpHCm+ooLfz9wRfgrX9WMCwCFAkWHvhJC\ndutS3LcZ46iYgICDPQqAhQL76vdT4AYTQXBwl/wbw/MtQrP4w==X02dt\n```\n\nExample:\n```bash\nAAABEA0ODAoPeNp9UE1Pg0AUvO+v2MSbCc0uoYeScChC1FhLU6Gaxsvr+ko3wi7ZD7T/XoTqwYPv9\nmbezGTeVXnytBCOsohyHrN5HHG6yUoaMh6SDK0wsnNSq6RE62gjBSqL9KgN7RpfS0XfsMdGd2gsW\nfv2gKY4VnbYEs7IjUH4FmfgMAk5DwO2CDgneQ+NH5nEGY9ksrKz6xkIJ3uc0EeQyqECJTD/7KQ5j\nzaZN6PyNeZsGFKYGpS0k9vSNWCtBEWe0PRo7rMkvV2UwUu1i4KH/f4uSBl/JqupxsBW6l3pD/WD/\nJNzuSjPHa6hHfh8l6+KTb79rZ1Ce9B6i612uKxROZuEc7LxRpzA4t8ffAHYfn9KMCwCFEErfsC77\n7XOAsdjoKVRM24pJL3+AhRbORJTyFn7+5BUotohYeGfCqYgkA==X02dt\n```\n\nExample:\n```bash\nAAAA9A0ODAoPeNpdj0FPwkAQhe/7KzbxZlLSXTEGkj2AbYBoSxO2SriNdcCNZdrMbqv8e5HqheP7X\nubLvJusIZkBSzWRSk3VwzQeyyKxUsdKiwR9xa4NriFj0QdZuwrJo9w3LNu6OziS79hj3bTIXuTd8\nQ15vS/9ORkVi0dG+D1OIKDRSuko1pG+F2kPdXdpTOAOxaDyo9sRVMH1ONAMHAUkoArT79bx6aIp7\npZizQcg5wfDLNTgvQMSG+QeeZWY+WJio235Mo6edrtlNI/Vq3geXj+3JX1S80X/5Mr9R+2pxRyOa\nGy6sat8IYqOqw/weL3lB7zIa8swLAIULU/BlyZoOVbLPEVGAVAio9Yh3t4CFFBJJd00fLnkkzgex\nV+T6NsiPVpgX02ck\n```\n\nExample:\n```bash\nAAAA9A0ODAoPeNpdj0FrwkAQhe/7KxZ6K0SySyso7KGaoNImBty0xduYjnZpnITZTVr/fa2xF4/ve\n8zHvLusIZkBSzWRSk3VeKrHskis1LHSIkFfsWuDa8hY9EHWrkLyKPcNy7buDo7kB/ZYNy2yF3l33\nCGv96U/J6NiMWeEv+MEAhqtlI5iHelHkfZQd5fGBO5QDCo/uh9BFVyPA83AUUACqjD9aR2fLpoiX\noo1H4CcHwxPoQbvHZDYIPfIq8TMFhMbvZevD9HzdruMZrF6Ey/D6+e2pC9qvumf3Liv1J5azOGIx\nqYbu8oXoui4+gSPt1t+Ab4Oa8gwLAIUbz98vdLFn/2d+epiSRUkE1spDVICFGIao7aIxBdxQdEoM\no/wg3GIMhntX02ck\n```\n\nExample:\n```bash\nAAABCQ0ODAoPeNpdj8FPgzAYxe/9K5p4M2FpcYtuSQ9DyLaoQFxRs9s3/GCN0JK2oPvvnUM97Pjey\n/vlvSt56GlWesqmlLPF7HbB7mgeSxoyHpIYXWlV55XRQqLztFElaoe0MpZ2TV8rTd9xwMZ0aB1J+\n3aPNqsKd1KCM3JvEX7KMXgUIedhwOYB5yQZoOnPiaigcUhGlptcT6D0akDhbY/kCZT2qEGXmHx1y\nh7PnPxmTTJbg1ZuRCx9A84p0GSLdkC7iUW0msvgrXiZBg+73TqIGH8lj+P2U1roD20+9Z9zwf515\nbHDFFoUMtnKTbr6fxdBuzfmGVvjcVmj9k6EM5L3tjyAw8ur38lTdxIwLAIUOawOiPVfH6zNRA/o/\nSZKylVzOfgCFG0JSQn0He9aMn3zrNE/fg625LgFX02dh\n```\n\nExample:\n```bash\nAAABBw0ODAoPeNpdjzFvgzAUhHf/CkvdKhHZkA6J5CEUlERtCSqmrbK90Ae1CjayDW3+fdMglox3p\n/t0dycHpJkZKQ8pW62X0ZozmheShoxHJEFXWdV7ZbSQ6DxtVYXaIa2NpX07NErTTxyxNT1aR7KhO\n6E91KW7KMFD8mgR/ssJeBQh51HAWRAyko7QDtdE1NA6JBPLLe4XUHk1ovB2QPICSnvUoCtMf3tlz\n1dOznbkYBvQyk2IjW/BOQWaFGhHtPtExNuVDD7Kt2XwdDzugpjxd/I8bb+kpf7W5kfPzg17fhFDd\nzLmFTvjcdOg9k6ED3NHnnvMoEMh00Lusy3JB1t9gcPbq3/qq3cVMCwCFEJl98Z/glYXAJfalw64u\nyFp+3zWAhRYkVu511dtvnuBWgWES2DhAbIkpg==X02dh\n```\n\nExample:\n```bash\nAAABCA0ODAoPeNpdj8FrwjAYxe/5KwK7DSpJK4hCDnYtKttame0c3j7j1y6sTUqSdvO/n7PbDh7fe\n7wf793t8URz6SmLKJ8totmCTek2KWjIeEgSdNKqziujRYHO00ZJ1A5pZSztmr5Wmp5wwMZ0aB3J+\nvaINq9Kd1EiJA8W4aebgEcRch4GbB5wRtIBmv6aiAoah2REucn9BKRXAwpveyTPoLRHDVpi+tUpe\n75yttGa5LYGrdyIWPoGnFOgyQ7tgHaTiHg1L4K38nUaPB4O6yBmfE+exumXtNQf2nzqP+eG/esW5\nw4zaFEU6a7YZKv/czG0R2NesDUelzVq7wQn297Kd3B4+/Qb7dx2mjAtAhUAkCqinKy6DbTosPd7S\nmDta0BxH8ECFBU82orr2+LLHzvHmq2Xr3pvQc8mX02dh\n```\n\nExample:\n```bash\nAAABDQ0ODAoPeNpdj01Lw0AQhu/7Kxa8CVt204KmsIfG1rZoP7CJSm/TOK2LyW6Y3UT7740JFRHmM\nu/LPMNzdU+Gr4C4vOUyHo/UeBTz7S7lkVRDNkWfk6mCcVan6AMvTI7WIz864lVRn4zlb9hg4Sokz\n9Z1eUDaHDPfblpJdkcIP8dTCKgjpYZCRqLlzhoo6q7RgWpkPcoPrgeQB9Ngn67A2IAWbI6zr8rQu\ncOo+EYKqdphGzqBNb4HTUIB3huwbIfUIC2nOpnHqXjNnkfiYb9fiESqF/bYG7RtZj+s+7SX5M+L7\nXBxSdNzhWsoUaezXbpcz38dEygPzj1h6QJOTmhDJ7ytKX8Hj/+FvwHN/XffMCwCFHRCjzxhfiWzk\npYAfiOmIZ8IxGP2AhRErmAkWvxlDZEljComfwIA/wscjg==X02dp\n```\n\nExample:\n```bash\nAAABiQ0ODAoPeNp1kk9TwjAQxe/9FJnxXKYpeoCZHqCtgsqfgaIO4yWELURD0tm0KN/eWOjYdvD68\nvbtb3dzM9GKTBgS2iOU9n3a7/pkHiXE96jvbNhho3XnWXBQBuKtyIVWQTxN4sV8MV7GTirMHk5QO\nZJTBsG91eITvPdJBEeQOgN0uNRHwIYtLKWGa1ocNoCzdGUATUA9h2uVdhjPxRGCHAtw5gXyPTMQs\nRwCn1Lf9XzXv3NqwVN2gGCZDBYWstLj70zgqSyad0fVWPXgJaClGUfB8KGXuG+rl1v3ab0euUOPv\njofAlmD/XG8GJBY5YAZCtMa9Ze5MagVZAGKX/FVE4eyMDZtqrdgAq+19zJlWEr/Na0TXjkTx4KLj\nWzeKbyIjaAJE7aDYpa2tTSO+mvbCrBKo/ryate4Up9KfylnhjumhGEl0SCXzBjB1B9Q/QYhQulrH\n/fcue6svl1di8BwFFnZKAGTE3mGIalGksliJxTZVqTmvLF6fXxksjhzpkwaqP5s3fMDBMYhRDAtA\nhUAhcR3uL05YCxbclq7h1dNa+Nc+j4CFBrdN005oVlMN9yBlWeM4TlnrOhqX02j3\n```\n\nExample:\n```bash\nAAABCQ0ODAoPeNpdjzFPwzAUhHf/CktsSKnsUJBayUNDorYC0og6gLq9mpfUIrEj2wn031MaYOh4d\n7pPd1fy0NONCpRNKedzxub8jhappDHjMUnRK6e7oK0REn2gjVZoPNLKOto1fa0NfccBG9uh8yTv2\nz26TVX6kxKckXuH8FNOIaCIOY8jNos4J9kATX9ORAWNRzKy/OR6AiroAUVwPZIn0CagAaMw++q0O\n545xc2KbFwNRvsRsQgNeK/BkC26Ad06FclyJqO38mUaPex2qyhh/JU8jttPaWk+jP00f84F+9eVx\nw5zaFHIbCvX+fL/XQLt3tpnbG3ARY0meBHfkqJ36gAeL69+A7mJdwYwLAIUOvp5LMC8ygBti6ue+\nae0PwoyX8QCFBfM5Yqqy2BCrwExOXHECJskOitAX02dh\n```\n\nExample:\n```bash\nAAAB8w0ODAoPeNp9Uk2P2jAQvedXWOoNydmELVKLFKlL4u7SLglKQj+27cEkA3gb7GjssMu/rwnQl\ns9DDvHMvPfmvXmTN0BGfE08n3jdftfv927J/SgnXc9/58wRQC5UXQO6j6IAqYGVwgglAxbnLB2nw\n4w5cbOcAiaziQbUge85oZKGFybmSwjKmiMKvfjATcW1Fly6hVo64waLBdcQcQPBhot6Per5zo4lX\n9fQjofJaMTScHj3uC+x11rgup0b3z7sudiIi+oSWQa4AhxGweD+fU6/Tb68pZ+fnh7owPO/Os8Cu\nVujKpvCuJsfqtXMvHAE1+KKFQQGG3A+2cp412XJeQjSHLVkzVQXKOrWn/bljH/nNmslXPa30+nES\nU4/Jikdp0k0CfNhEtNJxmwhCBGsFSWZrolZANmhECYLVQISu9gzFIb8WBhT/+zf3MyVe2DOTbWdo\nLCd+OWSSBGpDCmFNiimjQGLLDQxihSNNmppU3Yd67c0ILksjhOxqsKU3eUsooPvG4kXUrli/MlF7\ndayEU7kb6lepJOxOLAf7XneFmkfCuCp95nh+LdwhfegL8E5l0LzNo4IVlApi0Vy0GZvs9O6b+vHZ\nxzBv0toB3Yuk5lCwuualHs8fSD0/3NqdZ48nBd+5bjYilfNdokZr6zmP7TmY5YwLAIUNq8MbmR8G\nfaV9ulfLz1K+3g9j1YCFDeq7aYROMQbwMIvHimNt7/bJCCIX02nj\n```\n\nExample:\n```bash\nAAAB3w0ODAoPeNqFUttu0zAYvs9TWOKuktOkbBJUisSamK2wHJQDhwEXrvN39Zbake109O1xk0ZjQ\ny0XvrD/w3fym7IDFNM98nzkzeaz2dy/QNdxiWae/8554Iq6rZJ1x4x7uGANascZ1KAfXcoM30FgV\nAdO0m1XoNJ1ZRt04HtOKIWxDQndQlC3VCmuNx+oaajWnAqXya1jj/v8cmpz1im2oRoiaiA4sMLeJ\nfZ859Y2Cw3lvoUeJEzjmOTh8up2LJHfLVf7fi57ezMyIjHlzSlKhSUBahkFi+v3Jf5WfbnAn+/ub\nvDC8786n6wB2dEMu5WGIIyCF2RPSzpD97zLZwaLbqWZ4q3hUgz4/apRfs37AklKkmf5siD/Qfonw\n8lkkqQl/pjmOMvTqArLZZrgqiC2EIQKrLM1Wu2R2QA6giIimKxBIYvyAMygHxtj2p/z6fReui+8n\njbDBIZh4peLIomENKjm2ii+6gzYzVwjIxHrtJFbS8t1bHzCgKCCvQ7YsgpzclWSCC++HyieCPlI1\naZciUchn4RTkCSwB1963uDRGC+owddU3VPBNe0NjWAHjWytyBK0GZU7vSG2/vqjRvAcUj9wFI7WU\niHatqge9+kB/O8PRXa06QbYNW0syh8ZAEsLMCwCFGL057N7jgFszjOK1RP20LSXbPDkAhR/Kf0Zh\nAzWSddIoCukypG76CReHQ==X02mq\n```\n\nExample:\n```bash\nAAABtQ0ODAoPeNp9kV9v0zAUxd/9Ka7EWyWnTmESqxSJNQlbxdJUTbLBgAfXuV0NqR3ZTqHfHjdpY\nVSCB7/4/jm/e86rR6wh4wdgE2Bsyq6nLITbrIQJC9+SRbdbo8k3lUVjo5CRWCvHhVvwHUZ1y42Rd\nvuOu4ZbK7kKhN4RodUm8D1yj5EzHZJlZ8SWW0y4w+i4lrIrykJyLwUqi+WhxX5fnGdZuornN/fnU\nvqzlebQzy1f353F04zL5l/qBZo9mnkSzW6vS/qxenhDPzw93dEZCx8HtBeyLyX7mpfiMSqHZkAvu\nrUVRrZOajX8jEajRV7S9/mKLld5UsXlPF/Qqkh9IYoNetYa1gdwW4STEqRK6BoNtEZ/Q+Hg89a59\nst0PH7WwV/042aYoDhMfA0g0aC0g1paZ+S6c+g3SwtOg+is0zufS0C8IZ5ZcSUuLfNU8Sq9KdOEz\nj4dEf8XWuG4+X36Cd47WanvSv9QpEgXkX/0ijGSm2eupOW9MQnusdGtv7BE685nk94NX7/M/TKFy\n/BPJjz4047bJyTBPyH0CqcO2GgDvG2hPgNYku550w1YG954il/X0fxXMC0CFQCRUd9kwqDYeFIFJ\nyQmlQPeMMYDLQIUYpH3kyyXea6e1PzAN2rpSuuUl4M=X02l1\n```\n\nExample:\n```bash\nAAABrQ0ODAoPeNp9kVFvmzAQx9/9KSztLZIJZIu0RkJqA6yNViAK0G3d+uDApXgjNrKPbPn2dYG0a\n6fuwS8+393v//O7vAMa8yN1PerOFrPZYn5GL+OczlzvI0m6/RZ0uisMaON7LgmURF5iwvfgVy3XW\npj6nGPDjRFcOqXaE4Pc1M61KEEayI8t9I+DNI6jTbC6uP73wd/FdafLmhsIOYL/yMDcOXM98p95Y\nyn60wp97PvW769OpFHMRfMWagb6AHoV+svLs5x9LW4+sM+3t1ds6XpfRkw7jwcgEbSPugOSdVtTa\ntGiUHK4mUwmSZqzT+mGrTdpWAT5Kk1YkUW24AcaLFBFt0eKNdARlUayVBVo2mr1E0qk32vE9sdiO\nr1XzgvEaTN0MBg67hwaKioV0koY1GLbIdjJwlBUtOwMqr39KYfY1JZZclm+9jLEsmbEAZ4CBJvoI\no9Ctvz2CP2GrRHe6irkL6l+S5JFiW8Pm7suSfU9l8LwXkwIB2hUaxPmYPAUm/Q2bP315w5MGXL95\nDmEZ839jFEE3SlNedvS6rTCkOjAm25YvOON3fMAVTj4nTAtAhRH4o+fI5MQ7xSh2mtA1bPJrq0WA\ngIVAIGperR8m2N0fl/GfUUJfQnd+T1aX02kk\n```\n\nExample:\n```bash\nAAABZw0ODAoPeNp9UV1PwjAUfe+vaOLzyDbBRJMlwlYjCY5lDN7v5gUbunZpOxR/vWUMPxC9adP23\nvac03OvihbpA5Y0GNHg5u46vBve0pgVNPTDkMx4hdJgsW8whRqjhK3YbJ6xnMQawXIlE7AYHe56f\nuAGKaEulRpAZfkOI6tbPKXSti5Rz9eT7jhTFYjxBqU1kReQU3FpUHeJnpm9NVzvO5IsHD6SRVuaS\nvPmQP0D/W+lbAei7bRGaxDm84UDhdjxoz4C9QjTJFrKrVSvkixQ71C7xNiFN3HhxS68xAXJWl29g\nMFzAxYsjdz0Rr5PEvwSW6CxVBw56FppeuCnRwHkCbhbJcjq148vupcJkJ1NF6s51spib27gk5515\naw9KAnJXG9AcnM0JcEdCtWgpp3E3oYLvn7vvpLWtZg53SJqzJajrt730m3uwQowhoMcVKo+Q2HPv\nKNkacHyLJ8u2Amp69l/QB/3JOtvMCwCFA0F6wrOfvxukthYbD4doBefiu/mAhQVD9z9LF1gkNh6j\nBnqKXDvBjxVog==X02hl\n```\n\nExample:\n```bash\nAAABrw0ODAoPeNp9kl9vmzAUxd/9KSztLZIJpKvURUJaA7SNViAKsD9d++DATeKN2JZ9yZZvPxeSZ\na3UPfjFx/f4d479ruyApvxA/YD6k+nkYjq5ordpSSd+cEWybrcCk68rC8aGgU8iJZHXmPEdhI3mx\ngi7/cix5dYKLr1a7Yhuu42Q1ht57qDYQ4imA7LoTL3lFmKOED57M/+S+QG5FzVIC+VBQ28a5WmaL\nKP59f1JSn5rYQ793OLi7kSQpFy0byEUYPZg5nE4u/1Qsq/V5/fs08PDHZv5wRdSdCtbG6FRKDmwn\nYkL5AbBDNuj0SjLS3aTL9limcdVVM7zjFVF4oQwMuCIGro6UNwCPbLSRNaqAUO1UT+gRvp9i6gfp\n+PxRnkvGMftMMFgmHjyaKyoVEgbYdGIVYfgnIWlqGjdWVQ79wQecbElguSyfl2Mo4qWyXWZxGz27\nRnxjXKOqK6dSv6U6pckRZKFbrFL3ye52XApLO/biWEPrdIuTwkWTyFJn93pr9/y3KNTeATyb5Uxn\nCvvrY7p6VoZyrWmzekm+4/Lf/5GsudtN0CueeuY/gBrQ/OdMC0CFQCBdUokGEPDQeApZwmK47VRc\n7hv9wIUHoAl+9VxpP1eW0/g1SHbc5S4p5M=X02ko\n```\n\nExample:\n```bash\nAAABtQ0ODAoPeNp9kl9v2jAUxd/zKSztDckhQevEkCK1JFmH1iQVSfan6x5McgFvwbbsazq+/UwCZ\nZvUWvKLj+/x757rN5UFkrEDCUISTGaT6SycktusIpMgnHq53a1AF+vagDZRGHixFMgazNkOolYxr\nbnZXjPsmDGcCb+RO091dsOF8Ue+u8j3EKG24N1b3WyZgYQhREdvGlzRIPTueAPCQHVQ0JvGRZaly\n3hxc3eW0t+K60Nfl1jNkEvxOHsXuHWmSTPGu5dwStB70Iskmt++r+jX+vNb+unh4SOdB+EXr7Qr0\n2iujqYD54W+RKYR9HA8Go3yoqIfiiW9XxZJHVeLIqd1mTohijU4upasDgS3QE7cJBWNbEETpeVPa\nJB83yKqx9l4vJH+P4zjbqigMFT88EkiiZBIWm5Q85VFcM7cEJSksQblzo3D91zbAkEw0bwWkiOMl\n+lNlSZ0/u2I+0JQJ2yXVC1+CfkkvDLNI7fplXMp9IYJbnrnKIE9dFK53ioweG7Y63Nw+v8zvmTqF\nBaDeI41gUv8vdUpCbKWmjClSHt+yfzl8sqfSfesswPkmnWO6Q9infuvMCwCFBh3Bec/J0qyMOEW5\n11M1DJ8Dy3JAhQmDJx2Rpx4bJdsyMk2A1S5fY3FVQ==X02l1\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.548Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":19,"totalLines":211,"estimatedTokens":2500}}296{"id":"doc-create_a_server_app_package-a670147c","source":"documentation","title":"Create a server app package","url":"https://developer.atlassian.com/platform/marketplace/creating-a-server-app-package","text":"Example:\n```text\n<modelVersion>4.0.0</modelVersion>\n<groupId>com.example.plugins.tutorial</groupId>\n<artifactId>tutorial-plugin-marketing</artifactId>\n<version>1.0-SNAPSHOT</version>\n\n<organization>\n <name>Example Company</name>\n <url>http://www.example.com/</url>\n</organization>\n\n<name>tutorial-plugin-marketing</name>\n<description>This is the com.example.plugins.tutorial:tutorial-plugin-marketing plugin for Atlassian Jira.</description>\n<packaging>atlassian-plugin</packaging>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<properties>\n <amps.version>3.7.2</amps.version>\n <confluence.data.version>3.5</confluence.data.version>\n <confluence.version>4.0</confluence.version>\n</properties>\n```\n\nExample:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n\n <param name=\"plugin-icon\">images/pluginIcon.jpg</param>\n <param name=\"plugin-logo\">images/pluginLogo.jpg</param>\n <param name=\"plugin-banner\">images/pluginBanner.jpg</param>\n <param name=\"vendor-icon\">images/vendorIcon.jpg</param>\n <param name=\"vendor-logo\">images/vendorLogo.jpg</param>\n </plugin-info>\n ...\n\n</atlassian-plugin>\n```\n\nExample:\n```text\n<atlassian-plugin-marketing>\n \n <!--Describe names and versions of compatible products -->\n <compatibility>\n <product name=\"jira\" min=\"4.0\" max=\"4.4.1\"/>\n <product name=\"bamboo\" min=\"3.0\" max=\"3.1\"/>\n </compatibility>\n \n <!-- Describe your app logo and banner. The banner is only displayed in the UPM. -->\n <logo image=\"images/01_logo.jpg\"/>\n <banner image=\"images/02_banner.jpg\"/>\n \n <!-- Describe highlight and cropped highlight image assets, in order. -->\n <highlights>\n <highlight image=\"images/03_highlight1_ss.jpg\" cropped=\"images/04_highlight1_cropped.jpg\"/>\n <highlight image=\"images/05_highlight2_ss.jpg\" cropped=\"images/06_highlight2_cropped.jpg\"/>\n <highlight image=\"images/07_highlight3_ss.jpg\" cropped=\"images/08_highlight3_cropped.jpg\"/>\n </highlights>\n \n <!-- Describe additional screenshots you'd like listed on your app listing. -->\n <screenshots>\n <screenshot image=\"images/09_addl_ss1.jpg\"/>\n <screenshot image=\"images/10_addl_ss2.jpg\"/>\n <screenshot image=\"images/11_addl_ss3.jpg\"/>\n </screenshots>\n \n</atlassian-plugin-marketing>\n```\n\nExample:\n```bash\nManifest-Version: 1.0\nArchiver-Version: Plexus Archiver\nCreated-By: Apache Maven\nBuilt-By: manthony\nBuild-Jdk: 1.6.0_31\nAtlassian-Build-Date: 2012-05-08T17:01:14-0700\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.551Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":93,"estimatedTokens":712}}297{"id":"doc-trigger-e996a0e2","source":"documentation","title":"Trigger","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/trigger/","text":"Example:\n```javascript\nimport { asApp } from \"@forge/api\";\nconst payload = {\n type: \"trigger\",\n data: {\n events: [\n \"avi:jira:updated:issue\"\n ],\n \"endpoint\": \"some-endpoint\"\n }\n}\nconst response = await asApp().requestAtlassian(`/forge/installation/v2/dynamic/module/`, {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'POST',\n body: JSON.stringify(payload),\n});\nconst body = await response.text(); \nconsole.log(`Response: ${response.status} ${body}`);\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { asApp } from \"@forge/api\";\nconst key = \"unique-module-key\";\nconst payload = {\n type: \"trigger\",\n data: {\n events: [\n \"avi:jira:updated:issue\"\n ],\n \"endpoint\": \"some-endpoint\"\n }\n}\nconst response = await asApp().requestAtlassian(`/forge/installation/v2/dynamic/module/${key}`, {\n headers: {\n 'Content-Type': 'application/json'\n },\n method: 'PUT',\n body: JSON.stringify(payload)\n});\nconst body = await response.text(); \nconsole.log(`Response: ${response.status} ${body}`);\n```\n\nExample:\n```javascript\nexport async function myTriggerFunction(event, context) {\n // Handle the event\n}\n```\n\nExample:\n```text\nmodules:\n trigger:\n - key: issue-updated-trigger-with-ignore-self-and-expression\n function: main\n events:\n - avi:jira:updated:issue\n filter:\n appIsLicensed: true\n ignoreSelf: true\n expression: event.issue.fields?.issueType.name == 'Bug'\n onError: RECEIVE_AND_LOG\n function:\n - key: main\n handler: index.run\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.552Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":79,"estimatedTokens":392}}298{"id":"doc-useproductcontext-09c25a17","source":"documentation","title":"useProductContext","url":"https://developer.atlassian.com/platform/forge/ui-kit/hooks/use-product-context/","text":"Example:\n```javascript\nimport { useProductContext } from \"@forge/react\";\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport React from \"react\";\nimport ForgeReconciler, {\n Code,\n Heading,\n Text,\n useProductContext,\n} from \"@forge/react\";\n\nconst App = () => {\n const context = useProductContext();\n\n return (\n <>\n <Heading as=\"h3\">Product context</Heading>\n <Text>\n Module key from context:\n <Code>{context?.moduleKey}</Code>\n </Text>\n </>\n );\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```typescript\nfunction useProductContext(): ProductContext | undefined;\n\ninterface ProductContext {\n accountId?: string;\n cloudId?: string;\n workspaceId?: string;\n extension: ExtensionData;\n license?: LicenseDetails;\n localId: string;\n locale: string;\n moduleKey: string;\n siteUrl: string;\n timezone: string;\n theme?: {\n colorMode: string;\n light: string;\n dark: string;\n spacing: string;\n [key: string]: string;\n };\n}\n\ninterface ExtensionData {\n [k: string]: any;\n}\n\ninterface LicenseDetails {\n active: boolean;\n billingPeriod: string;\n ccpEntitlementId: string;\n ccpEntitlementSlug: string;\n isEvaluation: boolean;\n subscriptionEndDate: string | null;\n supportEntitlementNumber: string | null;\n trialEndDate: string | null;\n type: string;\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.552Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":84,"estimatedTokens":348}}299{"id":"doc-scheduled_trigger-19ab31a7","source":"documentation","title":"Scheduled trigger","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/scheduled-trigger/","text":"Example:\n```text\nmodules:\n scheduledTrigger:\n - key: example-scheduled-trigger\n function: my-scheduled-function\n interval: hour # Runs hourly\n filter: # Optional. Skip invocations for unlicensed sites.\n appIsLicensed: true\n function:\n - key: my-scheduled-function\n handler: index.trigger\n timeoutSeconds: 60 #Optional. Maximum time (in seconds) this function can run when triggered by a schedule or as an async event consumer. Range: 1–900 seconds.\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\n// index.js\n\nexport const trigger = ({ context }) => {\n console.log(\"Scheduled trigger invoked\");\n console.log(context);\n // Add your business logic here\n};\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.552Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":33,"estimatedTokens":180}}300{"id":"doc-working_with_the_pom-1e216b6a","source":"documentation","title":"Working with the POM","url":"https://developer.atlassian.com/x/CQEr","text":"Example:\n```text\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\n <modelVersion>4.0.0</modelVersion>\n <groupId>com.example.plugins.tutorial.confluence</groupId>\n <artifactId>HelloWorld</artifactId>\n <version>1.0-SNAPSHOT</version>\n <organization>\n <name>Example Company</name>\n <url>http://www.example.com/</url>\n </organization>\n <name>HelloWorld</name>\n <description>This is the com.example.plugins.tutorial.confluence:HelloWorld plugin for Atlassian Confluence.</description>\n <packaging>atlassian-plugin</packaging>\n <dependencies>\n <dependency>\n <groupId>junit</groupId>\n <artifactId>junit</artifactId>\n <version>4.10</version>\n <scope>test</scope>\n </dependency>\n <dependency>\n <groupId>com.atlassian.confluence</groupId>\n <artifactId>confluence</artifactId>\n <version>${confluence.version}</version>\n <scope>provided</scope>\n </dependency>\n <!-- WIRED TEST RUNNER DEPENDENCIES -->\n <dependency>\n <groupId>com.atlassian.plugins</groupId>\n <artifactId>atlassian-plugins-osgi-testrunner</artifactId>\n <version>${plugin.testrunner.version}</version>\n <scope>test</scope>\n </dependency>\n <dependency>\n <groupId>javax.ws.rs</groupId>\n <artifactId>jsr311-api</artifactId>\n <version>1.1.1</version>\n <scope>provided</scope>\n </dependency>\n <dependency>\n <groupId>com.google.code.gson</groupId>\n <artifactId>gson</artifactId>\n <version>2.2.2-atlassian-1</version>\n </dependency>\n </dependencies>\n <build>\n <plugins>\n <plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>confluence-maven-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n <productVersion>${confluence.version}</productVersion>\n <productDataVersion>${confluence.data.version}</productDataVersion>\n <allowGoogleTracking>false</allowGoogleTracking>\n </configuration>\n </plugin>\n <plugin>\n <artifactId>maven-compiler-plugin</artifactId>\n <configuration>\n <source>1.8</source>\n <target>1.8</target>\n </configuration>\n </plugin>\n </plugins>\n </build>\n <properties>\n <confluence.version>7.13.0</confluence.version>\n <confluence.data.version>7.13.0</confluence.data.version>\n <amps.version>8.2.3</amps.version>\n </properties>\n</project>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nDefine value for groupId: : com.example.plugins.tutorial.confluence\nDefine value for artifactId: : HelloWorld\nDefine value for version: [1.0-SNAPSHOT]: \nDefine value for package: [com.example.plugins.tutorial.confluence]:\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n\n ...\n <dependencies>\n ...\n </dependencies>\n <build>\n <plugins>\n <plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>confluence-maven-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n <productVersion>${confluence.version}</productVersion>\n <productDataVersion>${confluence.data.version}</productDataVersion>\n <allowGoogleTracking>false</allowGoogleTracking>\n <httpPort>${httpPort.value}</httpPort>\n </configuration>\n </plugin>\n ...\n ...\n <properties>\n <httpPort.value>2000</httpPort.value>\n <confluence.version>7.13.0</confluence.version>\n <confluence.data.version>7.13.0</confluence.data.version>\n <amps.version>8.2.3</amps.version>\n </properties>\n</project>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.552Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":133,"estimatedTokens":1152}}301{"id":"doc-osgi_spring_and_the_plugin_framework-568f986d","source":"documentation","title":"OSGi, Spring and the Plugin framework","url":"https://developer.atlassian.com/display/DOCS/OSGi%2C+Spring+and+the+Plugin+Framework","text":"Example:\n```text\nKey: \"com.foo.MyInterface\"\nInstance: com.foo.MyService\nProperties:\nname => foo\nsomeProperty => someValue\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nManifest-Version: 1.0\nBundle-ManifestVersion: 2\nBundle-SymbolicName: org.foo.Example\nBundle-Version: 1.0\nImport-Package: org.bar;version=\"1.3.0\"\nExport-Package: org.foo.api\nBundle-ClassPath: .,META-INF/lib/foo.jar\n```\n\nExample:\n```text\n<osgi:service id=\"fooService\"\n ref=\"foo\"\n interface=\"com.myapp.Foo\"/>\n```\n\nExample:\n```text\n<!-- bunch of XML namespace stuff skipped -->\n<beans>\n<bean name=\"foo\" class=\"com.myapp.FooImpl\"/>\n<osgi:service id=\"fooService\" ref=\"foo\"\n interface=\"com.myapp.Foo\"/>\n</beans>\n```\n\nExample:\n```text\n<!-- bunch of XML namespace stuff skipped -->\n<beans>\n<osgi:reference id=\"foo\"\n interface=\"com.myapp.Foo\"/>\n</beans>\n```\n\nExample:\n```text\nRequire-Bundle: org.otherPlugin;bundleversion=\"[3.2.0,4.0.0)\"\n\nImport-Package: org.otherPlugin.api;version=\"[3.2.0,4.0.0)\"\n```\n\nExample:\n```text\n<component-import key=\"foo\" interface=\"org.Foo\" />\n```\n\nExample:\n```text\n<component key=\"foo\" public=\"true\"\nclass=\"org.bar.FooImpl\" interface=\"org.foo.Foo\" />\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.553Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":71,"estimatedTokens":291}}302{"id":"doc-forge_bridge_view-39d59245","source":"documentation","title":"Forge bridge view","url":"https://developer.atlassian.com/platform/forge/custom-ui-bridge/view/","text":"Example:\n```typescript\nfunction close(): Promise<void>;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nview.close();\n```\n\nExample:\n```javascript\nfunction onClose(payload: () => Promise<void>): Promise<void>;\n```\n\nExample:\n```typescript\nimport { view } from \"@forge/bridge\";\n\nview.onClose(async () => save());\n```\n\nExample:\n```typescript\nfunction submit(payload: mixed): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst onSubmit = async () => {\n const fieldValue = \"new-value\";\n try {\n return await view.submit(fieldValue);\n } catch (e) {\n // Handle the error\n }\n};\n```\n\nExample:\n```typescript\nfunction getContext(): Promise<Context>;\n\ninterface Context {\n accountId?: string;\n cloudId?: string;\n extension: ExtensionData;\n license?: LicenseDetails;\n localId: string;\n locale: string;\n moduleKey: string;\n siteUrl: string;\n timezone: string;\n}\n\ninterface ExtensionData {\n [k: string]: any;\n}\n\ninterface LicenseDetails {\n active: boolean;\n billingPeriod: string;\n ccpEntitlementId: string;\n ccpEntitlementSlug: string;\n isEvaluation: boolean;\n subscriptionEndDate: string | null;\n supportEntitlementNumber: string | null;\n trialEndDate: string | null;\n type: string;\n}\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst context = await view.getContext();\n```\n\nExample:\n```typescript\ntype Dispatch = (...args: any[]) => void;\nfunction getFrameDispatch<T extends Dispatch>(): Promise<T>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst dispatch = await view.getFrameDispatch();\n```\n\nExample:\n```typescript\ntype LocationDescriptor = {\n pathname: string;\n search?: string;\n hash?: string;\n state?: any;\n};\ntype UnlistenCallback = () => void;\ntype Action = \"POP\" | \"PUSH\" | \"REPLACE\";\n\nfunction createHistory(): Promise<{\n action: Action;\n location: LocationDescriptor;\n push(path: string, state?: any): void;\n push(location: LocationDescriptor): void;\n replace(path: string, state?: any): void;\n replace(location: LocationDescriptor): void;\n go(n: number): void;\n goBack(): void;\n goForward(): void;\n listen(\n listener: (location: LocationDescriptor, action: Action) => void,\n ): UnlistenCallback;\n}>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst history = await view.createHistory();\n\n// e.g. URL begins as http://example.atlassian.net/example/apps/abc/123\n\nhistory.push(\"/page-1\");\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123/page-1\n\nhistory.push(\"/page-2\");\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123/page-2\n\nhistory.go(-2);\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123\n```\n\nExample:\n```typescript\nfunction refresh(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nview.refresh();\n```\n\nExample:\n```typescript\nfunction enable(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nawait view.theme.enable();\n```\n\nExample:\n```javascript\n// Example usage in a React app\n// Make sure to call enable() before the app is mounted for tokens to be available before the first render\nview.theme.enable().then(() => {\n ReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById(\"root\"),\n );\n});\n```\n\nExample:\n```typescript\nfunction changeWindowTitle(newTitle: string): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nawait view.changeWindowTitle(\"New title\");\n```\n\nExample:\n```typescript\nfunction emitReadyEvent(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\nimport { useEffect, useState } from \"react\";\n\nconst MyMacro = () => {\n const [data, setData] = useState();\n\n useEffect(() => {\n const loadData = async () => {\n const result = await fetchData();\n setData(result);\n // Perform any other operations your macro requires\n await view.emitReadyEvent(); // Notify that the macro is ready\n };\n\n loadData();\n }, []);\n\n return <div>{data ? \"Macro content loaded\" : \"Loading...\"}</div>;\n};\n\nexport default MyMacro;\n```\n\nExample:\n```typescript\ninterface FullContext {\n accountId?: string;\n cloudId?: string;\n workspaceId?: string;\n extension: ExtensionData;\n environmentId: string;\n environmentType: EnvironmentType;\n license?: LicenseDetails;\n localId: string;\n}\n\nfunction createAdfRendererIframeProps(\n context: FullContext,\n iframeId?: string,\n): Promise<{\n id: string;\n src: string;\n onLoad: () => void;\n}>;\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from \"react\";\nimport { view } from \"@forge/bridge\";\n\nfunction App() {\n // Get the contents of the bodied macro\n const [context, setContext] = useState(null);\n const [iframe, setIframe] = useState(null);\n\n useEffect(() => {\n view.getContext().then(setContext);\n }, []);\n\n useEffect(() => {\n if (!context) return;\n // Generate properties for embedded content wrapper\n // Pass in the embedded contents to the function\n // macroBody - any Valid ADF document can be passed in.\n view.createAdfRendererIframeProps(context).then(({ id, src, onLoad }) =>\n // Required iframe props\n // id - id of the iframe so the wrapper knows where to send the embedded macro contents\n // src - src of the embedded content renderer\n // onLoad - sends embedded macro contents once iframe has initialised\n // You can apply any styling you like to the iframe\n setIframe(<iframe id={id} src={src} onLoad={onLoad} />),\n );\n }, [context]);\n return (\n <div>\n <h1>Embedded content</h1>\n {/* Render the embedded content where you want in your app */}\n {iframe || \"Loading embedded content...\"}\n </div>\n );\n}\nexport default App;\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.555Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":24,"totalLines":290,"estimatedTokens":1473}}303{"id":"doc-extend_ui_with_custom_options-e901daa4","source":"documentation","title":"Extend UI with custom options","url":"https://developer.atlassian.com/platform/forge/custom-ui/","text":"Example:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n resource: example-resource\n title: Hello world!\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html>\n <body>\n <div>Hello, world!</div>\n </body>\n</html>\n```\n\nExample:\n```text\n<!DOCTYPE html>\n<html>\n <body>\n <div>Hello, world!</div>\n <img src=\"./images/image.png\"></img>\n </body>\n</html>\n```\n\nExample:\n```javascript\n// This is the UI Kit part of the app, e.g., `src/frontend/index.jsx` for a Forge app\n\nimport React, { useEffect } from 'react';\nimport ForgeReconciler, { Frame } from '@forge/react';\nimport { events } from '@forge/bridge';\n\nconst App = () => {\n // add your resource to the Frame component\n return <Frame resource=\"example-resource\" />;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\n// This is the UI Kit part of the app, e.g., `src/frontend/index.jsx` for a Forge app\n\nimport React, { useEffect } from 'react';\nimport ForgeReconciler, { Frame } from '@forge/react';\nimport { events } from '@forge/bridge';\n\nconst App = () => {\n useEffect(() => {\n // Send a message to the Frame component\n setTimeout(() => {\n events.emit('MY_FRAME_RESOURCE_DATA', { msg: 'hello' });\n }, 2000);\n }, []);\n return <Frame resource=\"example-frame-resource\" />;\n};\n\nForgeReconciler.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>\n);\n```\n\nExample:\n```javascript\n// This is the Frame resource part of the app, e.g., `resources/frame-app/src/App.jsx` for a Forge app\n\nimport React, {useEffect, useState } from 'react';\nimport { events } from \"@forge/bridge\";\n\nfunction App() {\n const [msg, setMsg] = useState('');\n\n useEffect(() => {\n let subscription = null;\n const registerMessage = async () => {\n subscription = await events.on('MY_FRAME_RESOURCE_DATA', ({ msg }) => {\n // This will be called whenever the message is sent from UI Kit\n setMsg(msg);\n });\n };\n registerMessage();\n return () => {\n if (subscription) {\n subscription.then(({ unsubscribe }) => unsubscribe());\n }\n };\n }, [setMsg]);\n\n return <b>{msg}</b>;\n}\n```\n\nExample:\n```text\nicon: resource:<resource key>;<relative path to resource>\n```\n\nExample:\n```text\nmodules:\n jira:issuePanel:\n - key: hello-world-panel\n title: Hello world!\n icon: resource:example-resource;icons/issue-panel.svg\nresources:\n - key: example-resource\n path: static/hello-world/build\n```\n\nExample:\n```text\nicon: https://example.com/icon.png\n```\n\nExample:\n```javascript\nimport { invoke } from \"@forge/bridge\";\n\ninvoke(\"exampleFunctionKey\", { example: \"my-invoke-variable\" }).then(setData);\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.556Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":11,"totalLines":144,"estimatedTokens":701}}304{"id":"doc-configuring_the_plugin_descriptor-f5570048","source":"documentation","title":"Configuring the plugin descriptor","url":"https://developer.atlassian.com/x/KAAN","text":"Example:\n```text\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\" />\n </plugin-info>\n</atlassian-plugin>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<atlassian-plugin key=\"${project.groupId}.${project.artifactId}\" name=\"${project.name}\" plugins-version=\"2\">\n <plugin-info>\n <description>${project.description}</description>\n <version>${project.version}</version>\n <vendor name=\"${project.organization.name}\" url=\"${project.organization.url}\"/>\n </plugin-info>\n <resource type=\"i18n\" name=\"i18n\" location=\"com.atlassian.samples.tutorial.myJiraPlugin\"/>\n <customfield-type name=\"My Custom Field\" i18n-name-key=\"my-custom-field.name\" key=\"my-custom-field\" \n class=\"com.atlassian.samples.tutorial.jira.customfields.MyCustomField\">\n <description key=\"my-custom-field.description\">The My Custom Field Plugin</description>\n <resource name=\"view\" type=\"velocity\" location=\"/templates/customfields/my-custom-field/view.vm\"/>\n <resource name=\"edit\" type=\"velocity\" location=\"/templates/customfields/my-custom-field/edit.vm\"/>\n </customfield-type>\n</atlassian-plugin>\n```\n\nExample:\n```text\n<resource type=\"velocity\" name=\"template\" location=\"com/example/plugin/template.vm\"/>\n<resource type=\"i18n\" name=\"i18n\" location=\"resources/exampleplugin\" />\n<resource type=\"download\" name=\"style.css\" location=\"com/example/plugin/style.css\">\n <property key=\"content-type\" value=\"text/css\"/>\n</resource>\n```\n\nExample:\n```text\n<plugin-info>\n <description>A macro which displays Google maps within a Confluence page.</description>\n <vendor name=\"Atlassian\" url=\"http://www.atlassian.com/\"/>\n <version>0.1</version>\n <param name=\"configure.url\">/admin/plugins/gmaps/configurePlugin.action</param>\n</plugin-info>\n```\n\nExample:\n```text\n<macro name=\"gmap\" class=\"com.atlassian.confluence.ext.gmaps.GmapsMacro\" key=\"gmap\">\n <description>The individual map macro.</description>\n <param name=\"configure.url\">/admin/plugins/gmaps/configureMacro.action</param>\n</macro>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.558Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":64,"estimatedTokens":587}}305{"id":"doc-forge_bridge_view-b49df16c","source":"documentation","title":"Forge bridge view","url":"https://developer.atlassian.com/platform/forge/apis-reference/ui-api-bridge/view/","text":"Example:\n```typescript\nfunction close(): Promise<void>;\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nview.close();\n```\n\nExample:\n```javascript\nfunction onClose(payload: () => Promise<void>): Promise<void>;\n```\n\nExample:\n```typescript\nimport { view } from \"@forge/bridge\";\n\nview.onClose(async () => save());\n```\n\nExample:\n```typescript\nfunction submit(payload: mixed): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst onSubmit = async () => {\n const fieldValue = \"new-value\";\n try {\n return await view.submit(fieldValue);\n } catch (e) {\n // Handle the error\n }\n};\n```\n\nExample:\n```typescript\nfunction getContext(): Promise<Context>;\n\ninterface Context {\n accountId?: string;\n cloudId?: string;\n extension: ExtensionData;\n license?: LicenseDetails;\n localId: string;\n locale: string;\n moduleKey: string;\n siteUrl: string;\n timezone: string;\n}\n\ninterface ExtensionData {\n [k: string]: any;\n}\n\ninterface LicenseDetails {\n active: boolean;\n billingPeriod: string;\n ccpEntitlementId: string;\n ccpEntitlementSlug: string;\n isEvaluation: boolean;\n subscriptionEndDate: string | null;\n supportEntitlementNumber: string | null;\n trialEndDate: string | null;\n type: string;\n}\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst context = await view.getContext();\n```\n\nExample:\n```typescript\ntype Dispatch = (...args: any[]) => void;\nfunction getFrameDispatch<T extends Dispatch>(): Promise<T>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst dispatch = await view.getFrameDispatch();\n```\n\nExample:\n```typescript\ntype LocationDescriptor = {\n pathname: string;\n search?: string;\n hash?: string;\n state?: any;\n};\ntype UnlistenCallback = () => void;\ntype Action = \"POP\" | \"PUSH\" | \"REPLACE\";\n\nfunction createHistory(): Promise<{\n action: Action;\n location: LocationDescriptor;\n push(path: string, state?: any): void;\n push(location: LocationDescriptor): void;\n replace(path: string, state?: any): void;\n replace(location: LocationDescriptor): void;\n go(n: number): void;\n goBack(): void;\n goForward(): void;\n listen(\n listener: (location: LocationDescriptor, action: Action) => void,\n ): UnlistenCallback;\n}>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nconst history = await view.createHistory();\n\n// e.g. URL begins as http://example.atlassian.net/example/apps/abc/123\n\nhistory.push(\"/page-1\");\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123/page-1\n\nhistory.push(\"/page-2\");\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123/page-2\n\nhistory.go(-2);\n// this updates the URL to http://example.atlassian.net/example/apps/abc/123\n```\n\nExample:\n```typescript\nfunction refresh(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nview.refresh();\n```\n\nExample:\n```typescript\nfunction enable(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nawait view.theme.enable();\n```\n\nExample:\n```javascript\n// Example usage in a React app\n// Make sure to call enable() before the app is mounted for tokens to be available before the first render\nview.theme.enable().then(() => {\n ReactDOM.render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n document.getElementById(\"root\"),\n );\n});\n```\n\nExample:\n```typescript\nfunction changeWindowTitle(newTitle: string): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\n\nawait view.changeWindowTitle(\"New title\");\n```\n\nExample:\n```typescript\nfunction emitReadyEvent(): Promise<void>;\n```\n\nExample:\n```javascript\nimport { view } from \"@forge/bridge\";\nimport { useEffect, useState } from \"react\";\n\nconst MyMacro = () => {\n const [data, setData] = useState();\n\n useEffect(() => {\n const loadData = async () => {\n const result = await fetchData();\n setData(result);\n // Perform any other operations your macro requires\n await view.emitReadyEvent(); // Notify that the macro is ready\n };\n\n loadData();\n }, []);\n\n return <div>{data ? \"Macro content loaded\" : \"Loading...\"}</div>;\n};\n\nexport default MyMacro;\n```\n\nExample:\n```typescript\ninterface FullContext {\n accountId?: string;\n cloudId?: string;\n workspaceId?: string;\n extension: ExtensionData;\n environmentId: string;\n environmentType: EnvironmentType;\n license?: LicenseDetails;\n localId: string;\n}\n\nfunction createAdfRendererIframeProps(\n context: FullContext,\n iframeId?: string,\n): Promise<{\n id: string;\n src: string;\n onLoad: () => void;\n}>;\n```\n\nExample:\n```javascript\nimport React, { useState, useEffect } from \"react\";\nimport { view } from \"@forge/bridge\";\n\nfunction App() {\n // Get the contents of the bodied macro\n const [context, setContext] = useState(null);\n const [iframe, setIframe] = useState(null);\n\n useEffect(() => {\n view.getContext().then(setContext);\n }, []);\n\n useEffect(() => {\n if (!context) return;\n // Generate properties for embedded content wrapper\n // Pass in the embedded contents to the function\n // macroBody - any Valid ADF document can be passed in.\n view.createAdfRendererIframeProps(context).then(({ id, src, onLoad }) =>\n // Required iframe props\n // id - id of the iframe so the wrapper knows where to send the embedded macro contents\n // src - src of the embedded content renderer\n // onLoad - sends embedded macro contents once iframe has initialised\n // You can apply any styling you like to the iframe\n setIframe(<iframe id={id} src={src} onLoad={onLoad} />),\n );\n }, [context]);\n return (\n <div>\n <h1>Embedded content</h1>\n {/* Render the embedded content where you want in your app */}\n {iframe || \"Loading embedded content...\"}\n </div>\n );\n}\nexport default App;\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.559Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":24,"totalLines":290,"estimatedTokens":1473}}306{"id":"doc-scopes_for_connect_apps-6e8acfc4","source":"documentation","title":"Scopes for Connect apps","url":"https://developer.atlassian.com/cloud/jira/platform/scopes/","text":"Example:\n```json\n{\n \"baseUrl\": \"http://my-app.com\",\n \"key\": \"atlassian-connect-app\",\n \"scopes\": [\n \"read\", \"write\"\n ],\n \"modules\": {}\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.559Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":19,"estimatedTokens":51}}307{"id":"doc-connect_app_descriptor-1cdf8efa","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/jira/platform/app-descriptor/","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://issues.example.com\",\n \"displayUrlServicedeskHelpCenter\": \"https://support.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.559Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":99,"estimatedTokens":466}}308{"id":"doc-permissions-3698f2e7","source":"documentation","title":"Permissions","url":"https://developer.atlassian.com/platform/forge/manifest-reference/permissions/","text":"Example:\n```text\npermissions:\n scopes:\n - \"read:confluence-content.summary\"\n - \"write:jira-work\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\npermissions:\n scopes: []\n```\n\nExample:\n```text\npermissions:\n scopes:\n read:confluence-content.summary:\n allowImpersonation: true\n write:confluence-content:\n allowImpersonation: false\n write:jira-work: {}\n```\n\nExample:\n```text\npermissions:\n content:\n scripts:\n - \"unsafe-hashes\"\n```\n\nExample:\n```text\npermissions:\n content:\n styles:\n - \"unsafe-inline\"\n```\n\nExample:\n```text\n^(\\*\\.)?[.a-zA-Z0-9_\\-\\/:~#%]+$\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - \"*.example-dev.com\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - \"*.example-dev.com\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - address: \"*.example-dev.com\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - address: \"*.example-dev.com\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - \"https://api.example.com\"\nremotes:\n - key: api-backend\n baseUrl: \"https://api.example.com\"\n operations:\n - fetch\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n backend:\n - remote: remote-backend\nremotes:\n - key: remote-backend\n baseUrl: \"https://example-dev.io\"\n operations:\n - fetch\n```\n\nExample:\n```text\npermissions:\n external:\n fonts:\n - \"https://www.example-dev.com/fonts.css\"\n```\n\nExample:\n```text\npermissions:\n external:\n styles:\n - \"https://www.example-dev.com/stylesheet.css\"\n```\n\nExample:\n```text\npermissions:\n external:\n frames:\n - \"https://www.example-dev.com/embed/page\"\n```\n\nExample:\n```text\npermissions:\n external:\n fetch:\n client:\n - address: \"*\"\n```\n\nExample:\n```text\npermissions:\n external:\n images:\n - address: https://www.example-dev.com/image.png\n```\n\nExample:\n```text\npermissions:\n external:\n media:\n - \"https://www.example-dev.com/video.mp4\"\n```\n\nExample:\n```text\npermissions:\n external:\n scripts:\n - \"https://www.example-dev.com/script.js\"\n```\n\nExample:\n```text\npermissions:\n external:\n scripts:\n - address: \"https://www.example-dev.com/script.js\"\n category: analytics\n inScopeEUD: false\n```\n\nExample:\n```text\npermissions:\n sandbox:\n - \"allow-storage-access-by-user-activation\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.560Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":22,"totalLines":195,"estimatedTokens":647}}309{"id":"doc-connect_app_descriptor-551d87ec","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/jira/platform/add-on-descriptor","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://issues.example.com\",\n \"displayUrlServicedeskHelpCenter\": \"https://support.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.561Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":99,"estimatedTokens":466}}310{"id":"doc-sales_promotions-b25c1c88","source":"documentation","title":"Sales promotions","url":"https://developer.atlassian.com/platform/marketplace/sales-promotions","text":"Example:\n```bash\nhttps://promo.atlassian.com/9SO6RI\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n9SO6RI\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.566Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":17,"estimatedTokens":31}}311{"id":"doc-web_trigger-12df2d2e","source":"documentation","title":"Web trigger","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/web-trigger","text":"Example:\n```bash\nforge webtrigger create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: no-egress-web-trigger\n function: no-egress-function\n urlFormat: v2\n response:\n type: static\n outputs:\n - key: status-ok\n statusCode: 200\n contentType: application/json\n body: '{\"body\": \"Allowed static response\"}'\n - key: status-error\n statusCode: 403\n contentType: text/plain\n body: 'Error: Forbidden'\n```\n\nExample:\n```javascript\n// no-egress-function.js\nexport async function noEgressFunction() {\n return {\n outputKey: \"status-ok\"\n };\n}\n```\n\nExample:\n```text\nmodules:\n webtrigger:\n - key: egress-web-trigger\n function: egress-function\n urlFormat: v2\n response: # Dynamic web triggers do not need to define a response field and are dynamic by default.\n type: dynamic\n```\n\nExample:\n```javascript\n// egress-function.js\nexport async function egressFunction() {\n const response = await fetch(\"https://api.example.com/data\");\n const data = await response.json(); \n return {\n statusCode: 200,\n contentType: \"application/json\",\n body: JSON.stringify(data)\n };\n}\n```\n\nExample:\n```text\nhttps://4a6d16a1-bf25-4ddb-9a1a-3a781c11af3d.hello.atlassian-dev.net/x1/XUBR5WnG2Hk2V52APDdGaRSDm\n```\n\nExample:\n```text\nhttps://788926e4-e61e-473a-a1d0-b27e0b1ec96f.webtrigger.atlassian.app/public/XUBR5WnG2Hk2V52APDdGaRSDm\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.567Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":77,"estimatedTokens":375}}312{"id":"doc-understanding_jwt_for_connect_apps-2e0f9aa3","source":"documentation","title":"Understanding JWT for Connect apps","url":"https://developer.atlassian.com/cloud/jira/platform/understanding-jwt-for-connect-apps/","text":"Example:\n```javascript\nPOST https://<my-dev-environment>.atlassian.net/jira/rest/api/2/issue/AC-1/attachments\n\"Authorization\" header value: \"JWT <jwt-token>\"\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nimport com.atlassian.jwt.core.writer.JsonSmartJwtJsonBuilder;\nimport com.atlassian.jwt.core.writer.JwtClaimsBuilder;\nimport com.atlassian.jwt.core.writer.NimbusJwtWriterFactory;\nimport com.atlassian.jwt.httpclient.CanonicalHttpUriRequest;\nimport com.atlassian.jwt.writer.JwtJsonBuilder;\nimport com.atlassian.jwt.writer.JwtWriterFactory;\n\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.http.HttpRequest;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.HashMap;\n\npublic class JWTSample {\n //the key from the app descriptor\n private String key = \"atlassian-connect-addon\";\n\n //the sharedsecret key received during the app installation handshake\n private String sharedSecret = \"this is not a secret that you should use in your code\";\n\n public HttpRequest createRequestWithJwt() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException {\n String method = \"GET\";\n String baseUrl = \"https://<my-dev-environment>.atlassian.net/\";\n String contextPath = \"/\";\n String apiPath = \"/rest/api/latest/serverInfo\";\n\n String jwt = createJwt(method, apiPath, contextPath);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(baseUrl + apiPath))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwt)\n .build();\n\n return request;\n\n }\n\n private String createJwt(String method, String apiPath, String contextPath) throws UnsupportedEncodingException, NoSuchAlgorithmException {\n\n long issuedAt = System.currentTimeMillis() / 1000L;\n long expiresAt = issuedAt + 180L;\n\n JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder()\n .issuedAt(issuedAt)\n .expirationTime(expiresAt)\n .issuer(key);\n\n CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method,\n apiPath, contextPath, new HashMap());\n JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical);\n\n JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory();\n String jwtbuilt = jwtBuilder.build();\n\n String jwtToken = jwtWriterFactory.macSigningWriter(SigningAlgorithm.HS256,\n sharedSecret).jsonToJwt(jwtbuilt);\n\n return jwtToken;\n }\n}\n```\n\nExample:\n```javascript\nimport com.atlassian.jwt.*;\nimport com.atlassian.jwt.core.http.JavaxJwtRequestExtractor;\nimport com.atlassian.jwt.core.reader.*;\nimport com.atlassian.jwt.exception.*;\nimport com.atlassian.jwt.reader.*;\nimport javax.servlet.http.HttpServletRequest;\nimport java.io.UnsupportedEncodingException;\nimport java.security.NoSuchAlgorithmException;\nimport java.util.Map;\n\npublic class JWTVerificationSample {\n\n public Jwt verifyRequest(HttpServletRequest request,\n JwtIssuerValidator issuerValidator,\n JwtIssuerSharedSecretService issuerSharedSecretService)\n throws UnsupportedEncodingException, NoSuchAlgorithmException,\n JwtVerificationException, JwtIssuerLacksSharedSecretException,\n JwtUnknownIssuerException, JwtParseException {\n JwtReaderFactory jwtReaderFactory = new NimbusJwtReaderFactory(\n issuerValidator, issuerSharedSecretService);\n JavaxJwtRequestExtractor jwtRequestExtractor = new JavaxJwtRequestExtractor();\n CanonicalHttpRequest canonicalHttpRequest\n = jwtRequestExtractor.getCanonicalHttpRequest(request);\n Map<String, ? extends JwtClaimVerifier> requiredClaims = JwtClaimVerifiersBuilder.build(canonicalHttpRequest);\n String jwt = jwtRequestExtractor.extractJwt(request);\n return jwtReaderFactory.getReader(jwt).readAndVerify(jwt, requiredClaims);\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n```\n\nExample:\n```javascript\nString jwtToken = ...;//e.g. extracted from the request\nString[] base64UrlEncodedSegments = jwtToken.split('.');\nString base64UrlEncodedHeader = base64UrlEncodedSegments[0];\nString base64UrlEncodedClaims = base64UrlEncodedSegments[1];\nString signature = base64UrlEncodedSegments[2];\nString header = base64Urldecode(base64UrlEncodedHeader);\nString claims = base64Urldecode(base64UrlEncodedClaims);\n```\n\nExample:\n```javascript\n{\n \"alg\": \"HS256\",\n \"typ\": \"JWT\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:15489595\",\n \"iat\": 1386898951,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"exp\": 1386899131\n}\n```\n\nExample:\n```text\nuKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\nimport com.nimbusds.jose.JOSEException;\nimport com.nimbusds.jose.JWSObject;\nimport com.nimbusds.jose.JWSVerifier;\nimport com.nimbusds.jwt.JWTClaimsSet;\nimport net.minidev.json.JSONObject;\n\npublic JWTClaimsSet read(String jwt, JWSVerifier verifier) throws ParseException, JOSEException\n{\n JWSObject jwsObject = JWSObject.parse(jwt);\n\n if (!jwsObject.verify(verifier))\n {\n throw new IllegalArgumentException(\"Fraudulent JWT token: \" + jwt);\n }\n\n JSONObject jsonPayload = jwsObject.getPayload().toJSONObject();\n return JWTClaimsSet.parse(jsonPayload);\n}\n```\n\nExample:\n```json\n{\n \"kid\": \"0e50fccb-239d-4991-a5db-dc850ba3f236\",\n \"typ\": \"JWT\",\n \"alg\": \"RS256\"\n}\n```\n\nExample:\n```text\nhttps://connect-install-keys.atlassian.com/0e50fccb-239d-4991-a5db-dc850ba3f236\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jose.crypto.*;\nimport com.nimbusds.jwt.*;\n\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;\nimport org.bouncycastle.openssl.PEMParser;\nimport org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;\n\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.security.interfaces.RSAPublicKey;\n\npublic boolean verify(String jwt, String privateKey) {\n try (Reader reader = new StringReader(privateKey)) {\n PEMParser pemParser = new PEMParser(reader);\n Object object = pemParser.readObject();\n\n SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(object);\n JcaPEMKeyConverter converter = new JcaPEMKeyConverter();\n\n RSAPublicKey rsaPublicKey = (RSAPublicKey) converter.getPublicKey(pub);\n SignedJWT signedJWT = SignedJWT.parse(jwt);\n\n JWSVerifier verifier = new RSASSAVerifier(rsaPublicKey);\n\n return signedJWT.verify(verifier);\n } catch (Exception e) {\n // log and handle exception\n return false;\n }\n}\n```\n\nExample:\n```text\nimport com.nimbusds.jose.*;\nimport com.nimbusds.jwt.*;\nimport java.net.URI;\n\nprivate static final String APP_BASE_URL = \"https://example.com/path/\";\n\npublic boolean verifyAudienceClaim(String jwt) {\n // `aud` claim should match your app baseUrl defined in the descriptor file.\n final JWSObject jwsObject = JWSObject.parse(jwt);\n JWTClaimsSet claims = JWTClaimsSet.parse(jwsObject.getPayload().toString());\n String audienceClaim = claims.getAudience().stream().findFirst().orElse(\"\");\n URI appBaseUrl = URI.create(APP_BASE_URL);\n return audienceClaim.equals(appBaseUrl.toString());\n}\n```\n\nExample:\n```javascript\nqsh = `sign(canonical-request)`\ncanonical-request = `canonical-method + '&' + canonical-URI + '&' + canonical-query-string`\n```\n\nExample:\n```javascript\n\"https://<my-dev-environment>.atlassian.net/path/to/service?zee_last=param&repeated=parameter 1&first=param&repeated=parameter 2\"\n```\n\nExample:\n```javascript\n\"GET&/path/to/service&first=param&repeated=parameter%201,parameter%202&zee_last=param\"\n```\n\nExample:\n```javascript\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo\n```\n\nExample:\n```javascript\n<base64url-encoded header>.<base64url-encoded claims>.<base64url-encoded signature>\n```\n\nExample:\n```json\n{\n \"typ\":\"JWT\",\n \"alg\":\"HS256\"\n}\n```\n\nExample:\n```json\n{\n \"iss\": \"jira:1234567\",\n \"iat\": 1300819370,\n \"exp\": 1300819380,\n \"qsh\": \"8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9\",\n \"sub\": \"123456:1234abcd-1234-abcd-1234-1234abcd1234\"\n}\n```\n\nExample:\n```text\npublic class JwtClaims {\n protected String iss;\n protected long iat;\n protected long exp;\n protected String qsh;\n protected String sub;\n // + getters/setters/constructors\n}\n\n[...]\n\npublic class JwtHeader {\n protected String alg;\n protected String typ;\n // + getters/setters/constructors\n}\n\n[...]\n\nimport static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;\nimport static org.apache.commons.codec.binary.Hex.encodeHexString;\nimport java.io.UnsupportedEncodingException;\nimport java.security.*;\nimport javax.crypto.*;\nimport javax.crypto.spec.SecretKeySpec;\nimport com.google.gson.Gson;\n\npublic class JwtBuilder {\n\n public static String generateJWTToken(String requestUrl, String canonicalUrl,\n String key, String sharedSecret)\n throws NoSuchAlgorithmException, UnsupportedEncodingException,\n InvalidKeyException {\n\n JwtClaims claims = new JwtClaims();\n claims.setIss(key);\n claims.setIat(System.currentTimeMillis() / 1000L);\n claims.setExp(claims.getIat() + 180L);\n\n claims.setQsh(getQueryStringHash(canonicalUrl));\n String jwtToken = sign(claims, sharedSecret);\n return jwtToken;\n }\n\n private static String sign(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n String signingInput = getSigningInput(claims, sharedSecret);\n String signed256 = signHmac256(signingInput, sharedSecret);\n return signingInput + \".\" + signed256;\n }\n\n private static String getSigningInput(JwtClaims claims, String sharedSecret)\n throws InvalidKeyException, NoSuchAlgorithmException {\n JwtHeader header = new JwtHeader();\n header.alg = \"HS256\";\n header.typ = \"JWT\";\n Gson gson = new Gson();\n String headerJsonString = gson.toJson(header);\n String claimsJsonString = gson.toJson(claims);\n String signingInput = encodeBase64URLSafeString(headerJsonString\n .getBytes())\n + \".\"\n + encodeBase64URLSafeString(claimsJsonString.getBytes());\n return signingInput;\n }\n\n private static String signHmac256(String signingInput, String sharedSecret)\n throws NoSuchAlgorithmException, InvalidKeyException {\n SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), \"HmacSHA256\");\n Mac mac = Mac.getInstance(\"HmacSHA256\");\n mac.init(key);\n return encodeBase64URLSafeString(mac.doFinal(signingInput.getBytes()));\n }\n\n private static String getQueryStringHash(String canonicalUrl)\n throws NoSuchAlgorithmException,UnsupportedEncodingException {\n MessageDigest md = MessageDigest.getInstance(\"SHA-256\");\n md.update(canonicalUrl.getBytes(\"UTF-8\"));\n byte[] digest = md.digest();\n return encodeHexString(digest);\n }\n }\n\n[...]\n\npublic class Sample {\n public HttpRequest getUrlSample() throws Exception {\n String requestUrl =\n \"https://<my-dev-environment>.atlassian.net/rest/atlassian-connect/latest/license\";\n String canonicalUrl = \"GET&/rest/atlassian-connect/latest/license&\";\n String key = \"...\"; //from the app descriptor\n //and received during installation handshake\n String sharedSecret = \"...\"; //received during installation Handshake\n\n String jwtToken = JwtBuilder.generateJWTToken(\n requestUrl, canonicalUrl, key, sharedSecret);\n\n HttpRequest request = HttpRequest.newBuilder()\n .uri(new URI(requestUrl))\n .header(\"Content-Type\", \"application/json\")\n .header(\"Authorization\", \"JWT \" + jwtToken)\n .build();\n\n return request;\n }\n}\n```\n\nExample:\n```text\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.569Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":23,"totalLines":533,"estimatedTokens":3191}}313{"id":"doc-command_eligibility-a2a93e91","source":"documentation","title":"Command: eligibility","url":"https://developer.atlassian.com/platform/forge/cli-reference/eligibility/","text":"Example:\n```text\nUsage: forge eligibility [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-e, --environment [environment] specify the environment (see your default\n environment by running forge settings list)\n--non-interactive run the command without input prompts\n-v, --major-version [version] specify a major version\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.569Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":22,"estimatedTokens":127}}314{"id":"doc-capacity_planning_rest_api-adf7dfee","source":"documentation","title":"Capacity Planning REST API","url":"https://developer.atlassian.com/cloud/capacity-planning/","text":"Example:\n```text\nhttps://api.atlassian.com/ex/capacity-planning/{cloudId}/v1\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\ncurl --request GET \\\n --url \"https://api.atlassian.com/ex/capacity-planning/{cloudId}/v1/contributions?contributorDataId={contributorAri}&workDataId={workAri}&startDate=2026-01-01&endDate=2026-12-31\" \\\n --user \"your-email@example.com:{API_TOKEN}\" \\\n --header \"Accept: application/json\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.574Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":20,"estimatedTokens":108}}315{"id":"doc-command_create-c1989986","source":"documentation","title":"Command: create","url":"https://developer.atlassian.com/platform/forge/cli-reference/create/","text":"Example:\n```text\nUsage: forge create [options] [name]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-t, --template <template name> specify the template to use\n-d, --directory <directory name> specify the directory to create (uses the template name by default)\n--personal register as a personal app\n-s, --developer-space-id <Developer Space id> specify the Developer Space id to use\n-y, --accept-terms automatically accept terms and conditions in non-interactive mode\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.575Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":23,"estimatedTokens":180}}316{"id":"doc-graphql_api-df7daf16","source":"documentation","title":"GraphQL API","url":"https://developer.atlassian.com/cloud/compass/graphql/","text":"Example:\n```text\nquery example {\n tenantContexts(hostNames:[\"your-domain.atlassian.net\"]) {\n cloudId\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nquery example {\n jira {\n allJiraProjects(cloudId: \"a436116f-02ce-4520-8fbb-7301462a1674\", filter: {types: [SOFTWARE]}) {\n pageInfo {\n hasNextPage\n }\n edges {\n node {\n key\n name\n opsgenieTeamsAvailableToLinkWith {\n pageInfo {\n hasNextPage\n }\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\nquery example {\n me {\n user {\n ... on AtlassianAccountUser {\n accountId\n accountStatus\n name\n picture\n }\n }\n }\n}\n```\n\nExample:\n```text\nquery example_with_no_data {\n user(accountId: \"notanid\") {\n ... on AtlassianAccountUser {\n name\n }\n }\n}\n\n# returns\n\n{\n \"data\": {\n \"user\": null\n }\n}\n```\n\nExample:\n```text\nmutation example {\n devOps {\n ariGraph {\n createRelationships (input: {\n \"from\": \"ari:cloud:jira:a436116f-02ce-4520-8fbb-7301462a1674:project/1001\",\n \"to\": \"ari:cloud:confluence:a436116f-02ce-4520-8fbb-7301462a1674:space/456\",\n \"type\": \"project-documentation-space\",\n \"updatedAt\": \"2022-12-22T00:03:12.239Z\"\n } ) {\n success\n errors {\n message\n }\n }\n }\n }\n}\n```\n\nExample:\n```text\nquery example_with_no_data {\n user(accountId: \"notandid\") {\n ... on AtlassianAccountUser {\n name\n notAValidField\n }\n }\n}\n\n# returns\n\n{\n \"errors\": [\n {\n \"message\": \"Validation error of type FieldUndefined: Field 'notAValidField' in type 'AtlassianAccountUser' is undefined @ 'user/notAValidField'\",\n \"locations\": [\n {\n \"line\": 5,\n \"column\": 7\n }\n ],\n \"extensions\": {\n \"errorSource\": \"GRAPHQL_GATEWAY\",\n \"classification\": \"ValidationError\"\n }\n }\n ],\n \"extensions\": {\n \"gateway\": {\n \"request_id\": \"d8765c8a6d500a3f\",\n \"invokedServices\": []\n }\n }\n}\n```\n\nExample:\n```text\nX-ExperimentalApi: DevOpsBeta\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.575Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":145,"estimatedTokens":557}}317{"id":"doc-command_custom_scopes-58b1051a","source":"documentation","title":"Command: custom-scopes","url":"https://developer.atlassian.com/platform/forge/cli-reference/custom-scopes/","text":"Example:\n```text\nUsage: forge custom-scopes [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nlist [options] List custom scopes\ncreate [options] Submit custom scopes for creation\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.575Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":24,"estimatedTokens":81}}318{"id":"doc-command_developer_spaces-cae5f285","source":"documentation","title":"Command: developer-spaces","url":"https://developer.atlassian.com/platform/forge/cli-reference/developer-spaces/","text":"Example:\n```text\nUsage: forge developer-spaces [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nlist [options] lists all Developer Spaces\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.575Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":23,"estimatedTokens":69}}319{"id":"doc-get_started_building_compass_apps_compass_atlass-7ed3c294","source":"documentation","title":"Get started building Compass apps | Compass | Atlassian documentation","url":"https://developer.atlassian.com/cloud/compass/integrations/get-started-integrating-with-Compass/","text":"Example:\n```bash\nforge create\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\nforge deploy\n```\n\nExample:\n```bash\nforge install\n```\n\nExample:\n```text\npermissions:\n scopes:\n - \"read:component:compass\"\n - \"write:component:compass\"\n```\n\nExample:\n```bash\nforge install --upgrade\n```\n\nExample:\n```text\npermissions:\n scopes: \"storage:app\"\n```\n\nExample:\n```javascript\nquery getComponent($componentId: ID!) {\n compass {\n component(id: $componentId) {\n __typename\n ... on CompassComponent {\n name\n type\n }\n }\n }\n}\n```\n\nExample:\n```json\n{ \"componentId\": \"your-component-id-here\" }\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.576Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":9,"totalLines":61,"estimatedTokens":158}}320{"id":"doc-command_containers-9d2b3dd6","source":"documentation","title":"Command: containers","url":"https://developer.atlassian.com/platform/forge/cli-reference/containers/","text":"Example:\n```text\nUsage: forge containers [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\ncreate [options] create new container definition\nget-login [options] get login password for the image repository\ndelete [options] delete container definition along with the associated\n image repository and all of its images\ndocker-login [options] authenticate to container registry\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.576Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":27,"estimatedTokens":142}}321{"id":"doc-command_deploy-47857f3c","source":"documentation","title":"Command: deploy","url":"https://developer.atlassian.com/platform/forge/cli-reference/deploy/","text":"Example:\n```text\nUsage: forge deploy [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-f, --no-verify disable pre-deployment checks\n-v, --major-version [version] specify a major version to update (Preview)\n-t, --tag <tag> specify a build tag to deploy (from forge\n build)\n--inspect save application bundle for inspection\n--skip-rollout Skip rolling release rollout after deployment\n--approve <rule...> list of validation rules to approve\n-e, --environment [environment] specify the environment (see your default\n environment by running forge settings list)\n--non-interactive run the command without input prompts\n-h, --help display help for command\n```\n\nExample:\n```text\nlist [options] list app deployments\n```\n\nExample:\n```text\nforge deploy -e staging --no-verify\n```\n\nExample:\n```text\nforge deploy --tag 3f6f3d\n```\n\nExample:\n```text\nforge deploy --tag 3f6f3d -e production\n```\n\nExample:\n```text\nforge deploy -e production\nDeploying your app to the production environment.\nPress Ctrl+C to cancel.\n\nRunning forge lint...\n\n./manifest.yml\n0:0 approval This deployment triggers a major version upgrade in the production environment because: Change due to scope modification.\nFor more information, see: https://go.atlassian.com/forge-major-app-version-upgrades MAJOR_VERSION_RULE\n\n⚠ 1 issue (0 error, 0 warnings, 1 approval)\n Issue found is not automatically fixable with forge lint.\n \nThe deployment failed due to 1 approval requested. Run forge deploy --approve MAJOR_VERSION_RULE to acknowledge and proceed.\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.576Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":8,"totalLines":66,"estimatedTokens":454}}322{"id":"doc-the_jira_software_cloud_rest_api-597b98fc","source":"documentation","title":"The Jira Software Cloud REST API","url":"https://developer.atlassian.com/cloud/jira/software/rest/intro/","text":"Example:\n```text\nhttp://host:port/context/rest/api-name/api-version/resource-name\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nhttps://jira.atlassian.net/rest/agile/latest/board/123\n```\n\nExample:\n```text\nhttp://host:port/context/rest/api-name/api-version/resource-name?startAt=0&maxResults=10\n```\n\nExample:\n```javascript\n{\n \"startAt\" : 0,\n \"maxResults\" : 10,\n \"total\": 200,\n \"values\": [\n { /* result 0 */ },\n { /* result 1 */ },\n { /* result 2 */ }\n ]\n}\n```\n\nExample:\n```javascript\ncustomfield_11458\": [\n \"com.atlassian.greenhopper.service.sprint.Sprint@1bf75fd[id=1,rapidViewId=1,state=CLOSED,name=Sprint 1,goal=Sprint 1 goal,startDate=2016-06-06T21:30:53.537+10:00,endDate=2016-06-20T21:30:00.000+10:00,completeDate=2016-06-06T21:30:57.523+10:00,sequence=1]\",\n \"com.atlassian.greenhopper.service.sprint.Sprint@1689feb[id=2,rapidViewId=1,state=FUTURE,name=Sprint 2,goal=Sprint 2 goal,startDate=<null>,endDate=<null>,completeDate=<null>,sequence=2]\"\n]\n```\n\nExample:\n```javascript\n\"customfield_10021\": {\n \"1\": [\n \"com.atlassian.greenhopper.service.sprint.Sprint@1bf75fd[id=1,rapidViewId=1,state=CLOSED,name=Sprint 1,goal=Sprint 1 goal,startDate=2016-06-06T21:30:53.537+10:00,endDate=2016-06-20T21:30:00.000+10:00,completeDate=2016-06-06T21:30:57.523+10:00,sequence=1]\",\n \"com.atlassian.greenhopper.service.sprint.Sprint@1689feb[id=2,rapidViewId=1,state=FUTURE,name=Sprint 2,goal=Sprint 2 goal,startDate=<null>,endDate=<null>,completeDate=<null>,sequence=2]\"\n ],\n \"2\": [\n {\n \"id\": 1,\n \"name\": \"Sprint 1\",\n \"state\": \"closed\",\n \"boardId\": 1\n },\n {\n \"id\": 2,\n \"name\": \"Sprint 2\",\n \"state\": \"future\",\n \"boardId\": 1\n }\n ]\n}\n```\n\nExample:\n```javascript\n\"customfield_10021\": 2\n```\n\nExample:\n```javascript\n\"customfield_11458\": \"EPIC-1\"\n```\n\nExample:\n```javascript\n\"customfield_11410\": \"Epic Name\"\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.577Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":10,"totalLines":83,"estimatedTokens":498}}323{"id":"doc-command_lint-c4a97a62","source":"documentation","title":"Command: lint","url":"https://developer.atlassian.com/platform/forge/cli-reference/lint/","text":"Example:\n```text\nUsage: forge lint [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n--fix attempt to automatically fix any issues\n encountered (default: false)\n-e, --environment [environment] specify the environment (see your default\n environment by running forge settings list)\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.577Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":22,"estimatedTokens":127}}324{"id":"doc-command_manifest-c2df1a3b","source":"documentation","title":"Command: manifest","url":"https://developer.atlassian.com/platform/forge/cli-reference/manifest/","text":"Example:\n```text\nUsage: forge manifest [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nrender [options] render the app manifest after applying environment\n transformations\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.577Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":24,"estimatedTokens":83}}325{"id":"doc-command_feedback-cf9de5c0","source":"documentation","title":"Command: feedback","url":"https://developer.atlassian.com/platform/forge/cli-reference/feedback/","text":"Example:\n```text\nUsage: forge feedback [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.577Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":18,"estimatedTokens":46}}326{"id":"doc-log_in_with_atlassian_account-3d7876d2","source":"documentation","title":"Log in with Atlassian account","url":"https://developer.atlassian.com/cloud/backup-management-apis/","text":"Atlassian JavaScript load error We tried to load scripts but something went wrong. Please make sure that your network settings allow you to download scripts from the following ://id-frontend.prod-east.frontend.public.atl-paas.net\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.578Z","totalSectionsIncluded":1,"totalCodeBlocksIncluded":0,"totalLines":3,"estimatedTokens":61}}327{"id":"doc-about_the_atlassian_a2a_gateway-9824a676","source":"documentation","title":"About the Atlassian A2A Gateway","url":"https://developer.atlassian.com/cloud/rovo-a2a/","text":"Example:\n```text\nExternal Agent\n → Agent Card discovery (/.well-known/agent.json)\n → OAuth 2.0 authentication\n → JSON-RPC request (message/stream)\n → Rovo agent\n → Atlassian Cloud APIs (Jira, Confluence, etc.)\n → Task response\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.578Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":18,"estimatedTokens":77}}328{"id":"doc-how_to_adopt_forge_from_connect-f071bcf1","source":"documentation","title":"How to adopt Forge from Connect","url":"https://developer.atlassian.com/platform/adopting-forge-from-connect/how-to-adopt/","text":"Example:\n```text\nnpx @atlassian/connect-to-forge@latest convert --type <jira|confluence> --url https://website.com/path/to/descriptor.json\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\nConversion begun for 'Existing Connect App': \n\n- Moved all lifecycle events into connectModules.confluence:lifecycle. \n- Moved 5 modules into connectModules in the Manifest \n- Converted 3 connect scopes into correct format in manifest. \n\nForge manifest generated and saved to manifest.yml\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.579Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":23,"estimatedTokens":124}}329{"id":"doc-function-8450bf18","source":"documentation","title":"Function","url":"https://developer.atlassian.com/platform/forge/manifest-reference/modules/function/","text":"Example:\n```text\nmodules:\n function:\n - key: main\n handler: index.run\n runtime:\n memoryMB: 1024\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```typescript\nexport interface ProviderReference {\n provider: string;\n requiredScopes?: string[];\n}\n```\n\nExample:\n```text\nfunction:\n - key: google-macro\n handler: index.google_macro\n providers:\n auth:\n - provider: google\n requiredScopes:\n - \"https://www.googleapis.com/auth/userinfo.profile\"\n - \"https://www.googleapis.com/auth/userinfo.email\"\n - key: dropbox-macro\n handler: index.dropbox_macro\n providers:\n auth:\n - dropbox\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.579Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":43,"estimatedTokens":168}}330{"id":"doc-automatic_plugin_reinstallation_with_quickreload-6c5bdc5b","source":"documentation","title":"Automatic plugin reinstallation with QuickReload","url":"https://developer.atlassian.com/docs/developer-tools/automatic-plugin-reinstallation-with-quickreload","text":"Example:\n```text\n<build>\n <plugins>\n <plugin>\n <groupId>com.atlassian.maven.plugins</groupId>\n <artifactId>jira-maven-plugin</artifactId>\n <version>${amps.version}</version>\n <extensions>true</extensions>\n <configuration>\n ...\n <enableQuickReload>true</enableQuickReload>\n ...\n </configuration>\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```bash\n$ atlas-mvn package\n```\n\nExample:\n```bash\n$ mvn package\n```\n\nExample:\n```bash\n#\n# Add directories to this file, one on each line, and the QuickReload will monitor them\n# and automatically load them as soon as they are compiled and assembled.\n#\n# You can use -Dquickreload.dirs=dir1,dir2,dir3 on the JVM command line to also specify directories\n# to monitor.\n#\n# relative path to plugin\nsome/path/test-plugin\n\n# absolute path to plugin\n/Users/me/src/atlassian/servicedesk/master/plugin\n```\n\nExample:\n```bash\n-Dquickreload.tracked=dir1,dir2,dir3\n```\n\nExample:\n```bash\nprop1=123;prop2=xyz;prop3 : /some/path\n```\n\nExample:\n```bash\n[INFO] [talledLocalContainer] 2014-05-08 18:10:14,332 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [labs.plugins.quickreload.PluginInstaller]\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] v\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] Starting Quick Reload - '/Users/bbaker/src/atlassian/plugins/quickreload/test-plugin/target/quickreload-test-plugin-1.3-SNAPSHOT.jar'....\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] 2014-05-08 18:10:14,335 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [atlassian.plugin.loaders.ScanningPluginLoader] No plugins found to be installed\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,752 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [atlassian.plugin.manager.DefaultPluginManager] Found dependent enabled plugins for uninstalled plugin 'com.atlassian.labs.plugins.quickreload.test': []. Disabling...\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,752 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [atlassian.plugin.manager.DefaultPluginManager] Updating plugin 'com.atlassian.labs.plugins.quickreload.test' to 'com.atlassian.labs.plugins.quickreload.test'\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,752 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [atlassian.plugin.manager.DefaultPluginManager] Disabling com.atlassian.labs.plugins.quickreload.test\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,757 Timer-1 WARN [plugins.quickreload.test.Launcher]\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] Quick Reload Test Plugin is going out of existence!!\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,760 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [atlassian.plugin.loaders.ScanningPluginLoader] Removed plugin com.atlassian.labs.plugins.quickreload.test\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,777 ThreadPoolAsyncTaskExecutor::Thread 56 WARN anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [plugins.quickreload.test.Launcher]\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] Quick Reload Test Plugin is coming into existence!!\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] 2014-05-08 18:10:16,785 http-bio-2990-exec-26 INFO anonymous 1090x6739x1 - 0:0:0:0:0:0:0:1 /rest/quickreload/1/install [labs.plugins.quickreload.PluginInstaller]\n[INFO] [talledLocalContainer] ^\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer] |\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] A watched plugin never boils!\n[INFO] [talledLocalContainer]\n[INFO] [talledLocalContainer] Quick Reload Finished (2453 ms) - '/Users/bbaker/src/atlassian/plugins/quickreload/test-plugin/target/quickreload-test-plugin-1.3-SNAPSHOT.jar'\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/plugins/servlet/qr\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/api\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/bundles\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/bundles/{bundleId}\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/services\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/plugins\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/plugins/{pluginKey}\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/plugin/enabled/{pluginKey}\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/plugin/enabled/{moduleKey}\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/events\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/tracked\n```\n\nExample:\n```bash\nhttp://localhost:2990/jira/rest/qr/1.0/batching\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.580Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":20,"totalLines":157,"estimatedTokens":1379}}331{"id":"doc-command_assistant-130ab44b","source":"documentation","title":"Command: assistant","url":"https://developer.atlassian.com/platform/forge/cli-reference/assistant/","text":"Example:\n```text\nUsage: forge assistant [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\non [options] <name> [⚠️ experimental feature] enable AI assistant for error\n analysis (specify name: rovo or gemini)\n When errors occur during Forge command execution, error\n details will be sent to your AI agent to help you\n understand and resolve issues.\noff [options] disable AI assistant for error analysis\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.580Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":28,"estimatedTokens":159}}332{"id":"doc-command_autocomplete-0d26beb3","source":"documentation","title":"Command: autocomplete","url":"https://developer.atlassian.com/platform/forge/cli-reference/autocomplete/","text":"Example:\n```text\nUsage: forge autocomplete [options] [install|uninstall]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nforge autocomplete install\n```\n\nExample:\n```text\nforge autocomplete uninstall\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.580Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":5,"totalLines":28,"estimatedTokens":77}}333{"id":"doc-connect_app_descriptor-b2a9c454","source":"documentation","title":"Connect app descriptor","url":"https://developer.atlassian.com/cloud/confluence/connect-app-descriptor/","text":"Example:\n```json\n{\n \"modules\": {},\n \"key\": \"my-app-key\",\n \"name\": \"My Connect App\",\n \"description\": \"A connect app that does something\",\n \"vendor\": {\n \"name\": \"My Company\",\n \"url\": \"http://www.example.com\"\n },\n \"links\": {\n \"self\": \"http://www.example.com/connect/jira/atlassian-connect.json\"\n },\n \"lifecycle\": {\n \"installed\": \"/installed\",\n \"uninstalled\": \"/uninstalled\"\n },\n \"baseUrl\": \"http://www.example.com/connect/jira\",\n \"authentication\": {\n \"type\": \"jwt\"\n },\n \"enableLicensing\": true,\n \"scopes\": [\n \"read\",\n \"write\"\n ],\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```json\n{\n \"authentication\": {\n \"type\": \"jwt\"\n }\n}\n```\n\nExample:\n```json\n{\n \"installed\": \"/installed\",\n \"enabled\": \"/enabled\",\n \"disabled\": \"/disabled\",\n \"uninstalled\": \"/uninstalled\"\n}\n```\n\nExample:\n```json\n{\n \"key\": \"installed-addon-key\",\n \"clientKey\": \"unique-client-identifier\",\n \"sharedSecret\": \"a-secret-key-not-to-be-lost\",\n \"serverVersion\": \"server-version\",\n \"pluginsVersion\": \"version-of-connect\",\n \"baseUrl\": \"https://example.atlassian.net\",\n \"displayUrl\": \"https://docs.example.com\",\n \"productType\": \"jira\",\n \"description\": \"Atlassian Jira at https://example.atlassian.net\",\n \"serviceEntitlementNumber\": \"SEN-number\",\n \"entitlementId\": \"Entitlement-Id\",\n \"entitlementNumber\": \"Entitlement-Number\",\n \"eventType\": \"installed\",\n \"installationId\": \"ari:cloud:ecosystem::installation/uuid-of-forge-installation-identifier\"\n}\n```\n\nExample:\n```json\n{\n \"vendor\": {\n \"name\": \"Atlassian\",\n \"url\": \"http://www.atlassian.com\"\n }\n}\n```\n\nExample:\n```json\n{\n \"cloudAppMigration\": {\n \"migrationWebhookPath\" : \"/app-migration-events-webhook\"\n }\n}\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.581Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":7,"totalLines":98,"estimatedTokens":449}}334{"id":"doc-command_site-a614f45d","source":"documentation","title":"Command: site","url":"https://developer.atlassian.com/platform/forge/cli-reference/site/","text":"Example:\n```text\nUsage: forge site [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nprovision [options] provision a demo site for development and testing\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.581Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":23,"estimatedTokens":75}}335{"id":"doc-command_logs-f96b4dd3","source":"documentation","title":"Command: logs","url":"https://developer.atlassian.com/platform/forge/cli-reference/logs/","text":"Example:\n```text\nUsage: forge logs [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-e, --environment [environment] specify the environment (see your default\n environment by running forge settings list)\n-i, --invocation <invocation> view logs for a given invocation ID\n-n, --limit <limit> number of log lines to return\n-s, --since <since> view logs since the specified time. valid\n formats: YYYY-MM-DD, ISO 8061 timestamp or\n a relative time (e.g: 5m, 10h, 2d)\n-g, --grouped group logs by invocation ID (default:\n false)\n-c, --containerKey <containerKey> view logs for a given container\n--instance <instance> view logs for a given container instance\n--serviceKey <serviceKey> view logs for a given service\n-f, --functionKey <functionKey> view logs for a given function\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.581Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":31,"estimatedTokens":283}}336{"id":"doc-server_app_licensing_validation_rules-322ef343","source":"documentation","title":"Server app licensing validation rules","url":"https://developer.atlassian.com/platform/marketplace/server-app-license-validation-rules","text":"Example:\n```text\nif (licenseManager.getLicense().isDefined())\n{\n PluginLicense license = licenseManager.getLicense().get();\n if (license.getError().isDefined())\n {\n // handle license error scenario\n // (e.g., expiration or user mismatch) \n }\n else\n {\n // handle valid license scenario\n }\n} \nelse\n{\n // handle unlicensed scenario\n}\n```\n\nExample:\n```text\n1\n2\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.582Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":2,"totalLines":28,"estimatedTokens":104}}337{"id":"doc-command_version-130193fd","source":"documentation","title":"Command: version","url":"https://developer.atlassian.com/platform/forge/cli-reference/version/","text":"Example:\n```text\nUsage: forge version [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nbulk-upgrade [options] upgrades installations from one major version to\n another version.\ndetails [options] returns the details of a specific version of the app. Details include:\n - egress\n - analytics\n - policies\n - scopes\n - connect keys\n - functions\n - remotes\n - modules\n - license\ncompare [options] returns the details of two versions of the app for comparison. Details include:\n - deployment date\n - egress\n - analytics\n - policies\n - scopes\n - connect keys\n - functions\n - remotes\n - modules\n - license\nlist [options] returns a summary of all major versions of the app. Summary includes:\n - version number\n - deployment date\n - egress\n - analytics\n - policies\n - scopes\n - connect keys\n - functions\n - remotes\n - modules\n - license\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.582Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":57,"estimatedTokens":256}}338{"id":"doc-command_providers-27bce007","source":"documentation","title":"Command: providers","url":"https://developer.atlassian.com/platform/forge/cli-reference/providers/","text":"Example:\n```text\nUsage: forge providers [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nconfigure [options] [providerKey] configure provider credentials\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.582Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":23,"estimatedTokens":82}}339{"id":"doc-command_variables-c347f562","source":"documentation","title":"Command: variables","url":"https://developer.atlassian.com/platform/forge/cli-reference/variables/","text":"Example:\n```text\nUsage: forge variables [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nset [options] [key] [value] set an environment variable\nunset [options] <key> remove an environment variable\nlist [options] list the environment variables\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.582Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":25,"estimatedTokens":107}}340{"id":"doc-command_storage-8653dcce","source":"documentation","title":"Command: storage","url":"https://developer.atlassian.com/platform/forge/cli-reference/storage/","text":"Example:\n```text\nUsage: forge storage [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nentities [options] manage indexes for custom entities present in your app\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.582Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":23,"estimatedTokens":77}}341{"id":"doc-command_settings-ae54197e","source":"documentation","title":"Command: settings","url":"https://developer.atlassian.com/platform/forge/cli-reference/settings/","text":"Example:\n```text\nUsage: forge settings [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nlist [options] list Forge CLI settings\nset [options] <setting> <boolean|string> update Forge CLI setting (choices: usage-analytics, default-environment, seasonal-effects, proxy)\ndelete [options] <setting> Delete a Forge CLI setting. Allowed settings: proxy\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":25,"estimatedTokens":144}}342{"id":"doc-command_module-626b6085","source":"documentation","title":"Command: module","url":"https://developer.atlassian.com/platform/forge/cli-reference/module/","text":"Example:\n```text\nUsage: forge module [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\nadd [options] [Preview] add a module to your Forge App. ⚠️ This\n will modify your app files (manifest.yml, source\n files, and package.json).\nlist [options] [Preview] list available module templates\nshow [options] [moduleType] [Preview] show details about a module template\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":27,"estimatedTokens":152}}343{"id":"doc-command_tunnel-3587137a","source":"documentation","title":"Command: tunnel","url":"https://developer.atlassian.com/platform/forge/cli-reference/tunnel/","text":"Example:\n```text\nUsage: forge tunnel [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-e, --environment [environment] specify the environment (see your default environment by running forge settings list)\n-d, --debug enable debugger mode\n-f,--debugFunctionHandlers <debugFunctionHandlers...> list of function handlers declared on manifest to debug, separated by space. This option must be specified if debug mode is enabled. It is only used for Node runtime debugger\n-p,--debugStartingPort [debugStartingPort] starting port to use for debugging, multiple handlers will get subsequent ports. It is only used for Node runtime debugger (default: \"9229\")\n-h,--debugHost [debugHost] host to bind the Node runtime debuggers to, default is 127.0.0.1 (default: \"127.0.0.1\")\n-n, --no-verify disable pre-tunnel checks\n--help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":24,"estimatedTokens":284}}344{"id":"doc-command_uninstall-e284e3b7","source":"documentation","title":"Command: uninstall","url":"https://developer.atlassian.com/platform/forge/cli-reference/uninstall/","text":"Example:\n```text\nUsage: forge uninstall [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-s, --site [site] site URL (example.atlassian.net)\n-p, --product [Atlassian app] Atlassian app (Jira, Confluence, Compass, Bitbucket)\n-e, --environment [environment] specify the environment (see your default environment by running forge settings list)\n--batch select up to 10 installations to uninstall (default: false)\n-i, --installation-id <installationId> specify the installation ID\n--app-id-override <appId> App ID to use (skips reading from manifest)\n-h, --help display help for command\n```\n\nExample:\n```text\nforge uninstall --site example.atlassian.net\n```\n\nExample:\n```text\nforge uninstall --batch\n```\n\nExample:\n```text\nforge uninstall --batch --environment staging\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":39,"estimatedTokens":242}}345{"id":"doc-command_whoami-2971c220","source":"documentation","title":"Command: whoami","url":"https://developer.atlassian.com/platform/forge/cli-reference/whoami/","text":"Example:\n```text\nUsage: forge whoami [options]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":3,"totalLines":18,"estimatedTokens":46}}346{"id":"doc-command_environments-7cdaae3e","source":"documentation","title":"Command: environments","url":"https://developer.atlassian.com/platform/forge/cli-reference/environments/","text":"Example:\n```text\nUsage: forge environments [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\ncreate [options] create a new development environment\nlist [options] view all environments for this app\ndelete [options] delete an existing development environment\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":4,"totalLines":25,"estimatedTokens":100}}347{"id":"doc-command_webtrigger-dec01306","source":"documentation","title":"Command: webtrigger","url":"https://developer.atlassian.com/platform/forge/cli-reference/webtrigger/","text":"Example:\n```text\nUsage: forge webtrigger [options] [command]\n```\n\nExample:\n```text\n1\n2\n```\n\nExample:\n```text\n--verbose enable verbose mode\n-h, --help display help for command\n```\n\nExample:\n```text\ncreate [options] get a web trigger URL\nlist [options] list webtrigger URLs\ndelete [options] delete a webtrigger URL\n```\n\nExample:\n```text\nforge webtrigger create\n```\n\nExample:\n```text\nforge webtrigger create --functionKey <function-key-from-manifest>\n```\n\n<|endofdoc|>","metadata":{"transformedAt":"2026-08-18T15:16:38.583Z","totalSectionsIncluded":0,"totalCodeBlocksIncluded":6,"totalLines":35,"estimatedTokens":122}}348 