Forge App Review
Deep pre-deploy review of Forge apps across Security, Architecture, Cost, Performance, and Triggers & Scheduling. Produces a severity-sorted issue list with actionable fixes.
Forge Pricing Reference
Forge uses a consumption-based pricing model. Charges only apply above free monthly allowances. Use this table to assess cost impact of findings:
| Capability | Billing Unit | Free Monthly Allowance | Overage Price (USD) |
|---|---|---|---|
| Functions: Duration | GB-seconds | 100,000 GB-seconds | $0.000025 / GB-second |
| KVS: Reads | GB read | 0.1 GB | $0.055 / GB |
| KVS: Writes | GB written | 0.1 GB | $1.090 / GB |
| Logs: Writes | GB written | 1 GB | $1.005 / GB |
| SQL: Compute duration | Hours | 1 hour | $0.143 / hour |
| SQL: Compute requests | Per 1M requests | 100,000 requests | $1.929 / 1M requests |
| SQL: Data stored | GB-hours | 730 GB-hours | $0.00076850 / GB-hour |
Key cost insight: KVS writes are ~20× more expensive than reads. Logging is ~$1/GB over the free tier. The cost formula for functions is: GB-seconds = (memoryMiB ÷ 1024) × duration in seconds.
Free capabilities (not billed): UI modules (UI Kit and Custom UI frontends run in the browser), Jira expressions, Forge Remote invocations (though the remote function runtime is billed), entity properties (stored by the product, not by Forge Storage).
Execution Mandate
When triggered, immediately:
- Read
manifest.yml— this is the source of truth for permissions, modules, egress, triggers, scheduled triggers, and function memory settings - Read
package.json— check dependencies, versions, scripts - Scan all resolver files in
src/— check patterns, error handling, data flow, API call patterns (N+1, missing fields, sequential calls), logging verbosity - Scan UI code (Custom UI or UI Kit) — check component patterns, bridge usage, whether API calls and logic could be moved to the frontend, product context usage, invoke patterns (chatty, per-render)
- Check for Forge Storage / Entity Store usage patterns — TTL strategy, write frequency, query vs iteration, entity properties vs KVS
- Check trigger and scheduling configuration — frequency, filtering, ignoreSelf, early exit, polling vs event-driven
- Check for Forge Remote usage or opportunities — compute offloading, trade-offs
- Compile all findings into a severity-sorted issue list
Do NOT ask the user what to review. Review everything. Do NOT modify any code unless explicitly asked. Do NOT skip categories — even if the app looks clean, confirm it explicitly.
Review Process
Step 1: Manifest Analysis (manifest.yml)
Read the manifest first. Extract:
- Permissions/scopes — list all
scopesandpermissionsentries - Modules — list all module types and their function key references
- Egress — check
app.connect.remotesorpermissions.external.fetch.backendURLs - Environment variables — check for
app.storageor environment variable declarations
Cross-reference every function key in modules against actual resolver resolver.define() calls.
Step 2: Dependencies (package.json)
Check:
- Node.js engine compatibility (Forge requires Node 18.x+)
- Unnecessary large dependencies (e.g.,
lodashwhen only one function is used,momentinstead of native Date ordayjs) - Missing
@forge/api,@forge/bridge, or@forge/uidepending on app type - Dev dependencies leaking into production
- Outdated
@forge/*packages
Step 3: Resolver Code
Read all files that contain resolver.define or Resolver imports. Check for:
- Error handling patterns
- API call patterns (
requestJira,requestConfluence,api.asUser(),api.asApp()) - Data validation and sanitization
- Storage operations
- External fetch calls
Step 4: UI Code
For UI Kit apps — scan for @forge/react imports, component usage, hooks.
For Custom UI apps — scan for @forge/bridge usage, invoke() calls, CSP compliance.
In both cases, check for:
- Frontend offloading opportunities: Are there resolvers that only do read-only
requestJira()/requestConfluence()calls that could instead use@forge/bridgedirectly from the browser? - Product context via resolver: Is the app invoking a resolver just to get issue/project/space key? Use
useProductContext()(UI Kit) orview.getContext()(Custom UI) instead. - Invoke on every render: Is
invoke()called without properuseEffectwith empty dependency array, causing re-invocation on every render? - Client-side logic: Is data formatting, sorting, filtering, or validation done in a resolver when it could run in the browser for free?
Step 5: Storage Analysis
Search for usage of:
storage.get,storage.set,storage.delete— check for unnecessary writes, short TTLs (KVS writes are ~20× more expensive than reads), and missing caching patternsstorage.query(Entity Store) — check for proper use of indexes,.where(), and.limit()instead of fetching all items and filtering in code- Entity properties (
requestJirato/properties/) — note these are free and stored by the product (not Forge Storage quota), suitable for small per-entity metadata (max 32 KB per property). Good for flags, markers, timestamps attached to Jira issues or Confluence pages. Queryable via JQL for Jira entity properties. Not suitable for sensitive data — visible to other apps and users via REST API. - Cache patterns — is app-level data that rarely changes (e.g., custom field IDs, project configs, workflow statuses) being fetched from APIs on every invocation? Should be cached in KVS with a TTL (1 hour+ preferred to minimize writes)
- Write amplification — a 1-minute TTL cache with 100 calls/hour causes ~60 writes/hour; a 1-hour TTL causes ~1 write/hour at ~60× less cost
Step 6: Trigger & Scheduling Analysis
Check the manifest for scheduledTrigger and trigger modules:
- Scheduled triggers: Is the interval appropriate? (
fiveMinutesis rarely justified — preferhour,day, orweek) - Polling vs events: Is a scheduled trigger polling for changes that could be caught by a product event trigger?
- Event filtering: Do product event triggers have
filter.expressionto limit invocations to relevant events? - ignoreSelf: If the app writes to entities and listens to events on those entities, is
filter.ignoreSelf: trueset? (Jira only) - Early exit: Do trigger handler functions check for work to do before running expensive operations?
- External polling: Could scheduled triggers polling external services be replaced with web triggers?
Step 7: Forge Remote Analysis
Check if the app uses Forge Remote (remotes: section in manifest):
- If present, note that Forge Remote offloads compute to an external backend — the Forge function is not executed for those calls, saving FaaS invocations. But the app loses "Runs on Atlassian" eligibility.
- If not present, check whether the app would benefit from Forge Remote:
- Compute-intensive operations (ML inference, image processing, complex report generation)
- Long-running operations that approach the 25-second function timeout
- Existing backend services the app duplicates logic from
- Large-scale storage needs exceeding Forge Storage limits
- Note: For most apps, staying on-platform is simpler. Only recommend Forge Remote when there's a genuine need.
Step 8: Compile Findings
Produce a single issue list sorted: Critical → Warning → Info.
Security Checks
| ID | Check | Severity | What to Look For |
|---|---|---|---|
| SEC-01 | Overly broad scopes | Critical | read:jira-work when only read:jira-work:jira (granular) is needed. Any write: scope that isn't actually used in code. Any manage: or admin: scope. |
| SEC-02 | Missing egress restrictions | Critical | Exter |