Forge Feature Flags is now available as part of Forge Early Access Program (EAP). To start testing this feature, sign up using this form.
Forge Feature Flags is an experimental feature offered to selected users for testing and feedback purposes. This feature is unsupported and subject to change without notice. Do not use Forge Feature Flags in apps that handle sensitive information and customer data. The Feature flags EAP is fully functional in development, staging, and production environments.
Note: Feature flags are not available in Atlassian Government Cloud or FedRAMP environments. See, Limitations.
This tutorial describes how to build a page bookmarking feature for Confluence using feature flags. You'll learn how to create a user-friendly bookmark button that allows users to save pages to their personal collection, with the entire feature controlled by a feature flag for safe rollout and testing.
By the end of this tutorial, you'll have a working Confluence macro that lets users bookmark pages, with the ability to enable or disable the feature instantly without redeployment.
This tutorial assumes you already have experience with basic Forge development. If you're new to Forge, complete the Build a hello world app in Confluence tutorial first.
To complete this tutorial, you need:
npm install -g @forge/cli)1 2npm install @forge/feature-flags-node@latest @forge/api@latest
Let's walk through building a practical feature: a page bookmarking system that allows users to save their favorite Confluence pages. This demonstrates how feature flags enable you to:
If you already have a hello world Confluence app, you can use it. Otherwise, create a new one:
1 2forge create
When prompted:
page-bookmark-appThis tutorial uses UI Kit (Forge React components). UI Kit provides pre-built components like Button, Text, and more, making it easier to build consistent Confluence integrations.
Your app folder structure should look like this:
1 2page-bookmark-app/ ├── manifest.yml ├── package.json ├── node_modules/ └── src/ ├── index.js ├── frontend/ │ └── index.jsx └── resolvers/ └── index.js
Your starting src/frontend/index.jsx should be a simple hello world:
1 2// src/frontend/index.jsx import React from 'react'; import ForgeReconciler, { Text } from '@forge/react'; const App = () => { return ( <> <Text>Hello world!</Text> </> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
Before implementing the bookmark feature, set up the feature flag that will control it.
Make sure you're logged in to the Atlassian Developer Console before proceeding.
Click Create flag.
Configure the flag:
enable page bookmarksControls the page bookmarking feature - displays bookmark button and manages bookmark storageinstallContext (targets specific Confluence installations)Click Confirm.
The Flag ID (enable_page_bookmarks) is automatically generated from the name and will be used in your code.
Important: Note down your Flag ID exactly as shown. You will need to use this exact ID in your resolver code in Step 3. The Flag ID must match exactly — if you used a different name, your Flag ID will be different (for example, "MyPageBookmark" becomes mypagebookmark).
After clicking Confirm, you'll be on the Setup page.
Configure the rule:
100% and Fail to 0%.Ensure the DEV environment is checked at the top of the rule card.
Click Save to apply your configuration.
Now that your feature flag is created, implement the bookmarking functionality in your app.
First, ensure your manifest has the necessary permissions:
1 2# manifest.yml modules: macro: - key: page-bookmark-hello-world-macro resource: main render: native resolver: function: resolver title: Page Bookmarks function: - key: resolver handler: index.handler resources: - key: main path: src/frontend/index.jsx permissions: scopes: - read:confluence-content.summary - storage:app app: runtime: name: nodejs22.x memoryMB: 256 architecture: arm64 id: ari:cloud:ecosystem::app/<Replace with your App Id>
Key permissions:
read:confluence-content.summary: Allows reading page informationstorage:app: Enables storing bookmark dataReplace your src/frontend/index.jsx with the bookmark feature controlled by a feature flag:
1 2// src/frontend/index.jsx import React, { useEffect, useState } from 'react'; import ForgeReconciler, { Text, Button, useProductContext, Box, Stack, Lozenge } from '@forge/react'; import { invoke } from '@forge/bridge'; const App = () => { const context = useProductContext(); const [isBookmarked, setIsBookmarked] = useState(false); const [showBookmarkFeature, setShowBookmarkFeature] = useState(false); const [loading, setLoading] = useState(true); // Check bookmark status when feature is enabled useEffect(() => { // Wait for context to be available before checking feature flag if (!context) return; const pageId = context?.extension?.content?.id; if (pageId) { invoke('checkBookmark', { pageId }).then(({isEnabled, bookmark}) => { setIsBookmarked(bookmark); setShowBookmarkFeature(isEnabled); setLoading(false); }); } else { setLoading(false); setShowBookmarkFeature(false); } }, [context]); // Handle bookmark toggle const toggleBookmark = async () => { const pageId = context?.extension?.content?.id; if (!pageId) return; const newStatus = await invoke('toggleBookmark', { pageId }); setIsBookmarked(newStatus); }; if (loading) { return ( <Box padding="space.100"> <Text appearance="subtle">Loading...</Text> </Box> ); } // If feature flag is disabled, show educational message if (!showBookmarkFeature) { return ( <Box padding="space.150"> <Text appearance="subtle"> The bookmark feature is controlled by a feature flag. Enable it in Developer Console to activate. </Text> </Box> ); } // Feature flag is enabled - show fully functional bookmark UI return ( <Box padding="space.150"> <Stack space="space.150" alignInline="start"> <Button onClick={toggleBookmark} appearance={isBookmarked ? "primary" : "default"} iconBefore={isBookmarked ? "star-filled" : "star"} > {isBookmarked ? "Bookmarked" : "Bookmark"} </Button> {isBookmarked && ( <Lozenge appearance="success" isBold> Saved </Lozenge> )} </Stack> </Box> ); }; ForgeReconciler.render( <React.StrictMode> <App /> </React.StrictMode> );
Key implementation details:
ForgeFeatureFlags SDK to check if the bookmark feature is enabledaccountId and installContext for proper feature targetingCreate or update src/resolvers/index.js to handle bookmark operations:
1 2// src/resolvers/index.js import Resolver from '@forge/resolver'; import { storage, getAppContext } from '@forge/api'; import { ForgeFeatureFlags } from "@forge/feature-flags-node"; const resolver = new Resolver(); // Helper function to check if feature flag is enabled async function isBookmarkFeatureEnabled(context) { try { const { environmentType } = getAppContext(); const featureFlags = new ForgeFeatureFlags(); await featureFlags.initialize({ environment: environmentType?.toLowerCase() || "development" }); const user = { identifiers: { accountId: context?.accountId, }, attributes: { installContext: context?.installContext } }; // IMPORTANT: Replace "enable_page_bookmarks" with your Flag ID from Developer Console // if you used a different name when creating the flag return featureFlags.checkFlag(user, "enable_page_bookmarks"); } catch (error) { console.error("Feature flag check failed:", error); return false; } } // Toggle bookmark for a page resolver.define('toggleBookmark', async (req) => { const { context, payload } = req; const { pageId } = payload; const accountId = context.accountId; // Check feature flag before processing const isEnabled = await isBookmarkFeatureEnabled(context); if (!isEnabled) { console.log("Bookmark feature is disabled"); return false; } // Get user's bookmarks from storage const storageKey = `bookmarks_${accountId}`; const bookmarks = await storage.get(storageKey) || []; // Toggle bookmark status if (bookmarks.includes(pageId)) { // Remove bookmark const updatedBookmarks = bookmarks.filter(id => id !== pageId); await storage.set(storageKey, updatedBookmarks); return false; } else { // Add bookmark await storage.set(storageKey, [...bookmarks, pageId]); return true; } }); // Check if a page is bookmarked resolver.define('checkBookmark', async (req) => { const { context, payload} = req; const { pageId } = payload; const accountId = context.accountId; // Check feature flag before processing const isEnabled = await isBookmarkFeatureEnabled(context); if (!isEnabled) { return {isEnabled, bookmark : false}; } // Check if page is in user's bookmarks const storageKey = `bookmarks_${accountId}`; const bookmarks = await storage.get(storageKey) || []; return { isEnabled, bookmark : bookmarks.includes(pageId)}; }); export const handler = resolver.getDefinitions();
Key backend features:
bookmarks_${accountId}Ensure your src/index.js exports the resolver handler:
1 2// src/index.js export { handler } from './resolvers';
Deploy and test your app:
Deploy your app:
1 2forge deploy
Install in development:
1 2forge install --upgrade
When prompted, select your Confluence site.
/ to open the macro menuWith the feature flag enabled (Pass: 100%), you should see:
Test the bookmark:
Test on multiple pages:
Now test the feature flag control:
enable_page_bookmarksCmd + Shift + R (Mac) or Ctrl + Shift + R (Windows)enable_page_bookmarks:
Congratulations! You've successfully built a page bookmarking feature controlled by feature flags. Here's what to explore next:
new-dashboard-layout, checkout-v2team-dashboard-redesignYou're now ready to build feature-flagged apps in Confluence! This bookmark feature demonstrates the power of feature flags for safe, controlled rollouts of new functionality.
Rate this page: