Last updated Sep 30, 2024

As part of an early access program (EAP), you can now build standard and advanced editions of Marketplace apps in the cloud that are Paid via Atlassian. During this stage of the EAP, participants can build and test editions of non-production apps in development, staging, and production environments.

EAP features are available to select users for demand, viability, and suitability testing. Releasing a feature as an EAP helps us gather insights that inform future development decisions.

Editions for Forge apps

You can create standard and advanced editions of your app under a single Marketplace listing. This is handy if you're looking to expand the presence of your app in the cloud market.

App editions only apply to existing Paid via Atlassian (PvA) apps in the cloud.

The content on this page only applies to Forge apps. For Connect apps, see Editions for Connect apps instead.

Before you begin

Before creating editions of your app, we assume you've already gone through Phase 1 of a three-phase process, which is the planning phase.

Phase 2 involves the technical aspects of creating editions and is comprised of the following steps:

  1. Differentiate features that go into the standard and advanced editions of your app.
  2. Update app code with conditional logic for each of the features of the respective editions.
  3. Test the editions of your app to ensure the features across the editions are working as expected.

App editions are separate from parent product editions. For instance, if you're on a Jira Software Cloud edition, you can choose to purchase an advanced edition of an app.

You may find some content specific to app editions that do not apply to partners and developers outside of the EAP. Similarly, we may link to pages outside of this EAP documentation that may not have any content about app editions.

1. Differentiate features for standard and advanced editions

We recommend that you list the features that would go into the standard and advanced editions of your app. This way, you have a list you can refer to while going through your app code in the next step.

Refer to the customer segmentation and strategy you will have already prepared after going through this guide in the Planning phase section.

2. Update app code with conditional logic

Now that you have a list of the features for your standard and advanced editions, you need to go through your app code to introduce conditional logic for each of these features.

It's important to note that editions are only available for cloud customers who are on the improved billing experience. This means, however, that customers who are on the original billing experience will not have access to editions until they are migrated to the improved billing experience.

You'll need to continue supporting such customers as you're currently doing today. Our APIs will only share capabilitySet information (more on that below) for customers on the improved billing experience. You must update your app code to handle both scenarios.

Frontend UI and functions

If your app has a frontend, the content in this section applies to you.

Context object

A context object is an object that contains contextual information about the current environment in which the app is running. The data available depends on the module in which your app is used.

Object shape
1
2
interface Context {
  accountId?: string;
  cloudId?: string;
  extension: ExtensionData;
  license?: LicenseDetails;
  localId: string;
  locale: string;
  moduleKey: string;
  siteUrl: string;
  timezone: string;
}

interface ExtensionData {
  [k: string]: any;
}

interface LicenseDetails {
  active: boolean;
  billingPeriod: string;
  capabilitySet: 'capabilityStandard' | 'capabilityAdvanced' | null;
  ccpEntitlementId: string;
  ccpEntitlementSlug: string;
  isEvaluation: boolean;
  subscriptionEndDate: string | null;
  supportEntitlementNumber: string | null;
  trialEndDate: string | null;
  type: string;
}
Field description
Object example
1
2
{
  "localId": "localId",
  "cloudId": "abcd1234-1111-1111-1111-abcdef123456",
  "environmentId": "11111111-1111-1111-0111-111111111111",
  "environmentType": "DEVELOPMENT",
  "moduleKey": "module-key-name",
  "siteUrl": "https://atlassian.net",
  "extension": {
    "type": "jira:globalPage"
  },
  "accountId": "abcde123456",
  "license": {
    "active": true,
    "type": "commercial",
    "supportEntitlementNumber": null,
    "trialEndDate": null,
    "subscriptionEndDate": null,
    "isEvaluation": null,
    "billingPeriod": "MONTHLY",
    "ccpEntitlementId": null,
    "ccpEntitlementSlug": null,
    "capabilitySet": "capabilityAdvanced"
  },
  "timezone": "Pacific/Auckland",
  "locale": "en_GB"
}

Custom UI and UI Kit

Both Custom UI and UI Kit make use of the getContext() method from the @forge/bridge library, which returns a context object that includes the LicenseDetails object, which in turn, contains license information.

As part of the EAP, we're introducing capabilitySet as an additional property or constant to the license information in the LicenseDetails object. This is where you should introduce conditional logic to the frontend functions of your app accordingly.

The conditional logic may look like this:

1
2
import { view } from "@forge/bridge";

const context = await view.getContext();
if (context.license.capabilitySet !== "capabilityAdvanced") {
  console.log("App is not licensed for advanced features");
  return;
}
// App is licensed for advanced features... continue to render...

UI Kit

Alternatively, in UI Kit, the useProductContext method from the @forge/react package will retrieve the same context object with license information as the method above.

1
2
import { useProductContext } from "@forge/react";

const context = useProductContext();
if (context.license.capabilitySet !== "capabilityAdvanced") {
  console.log("App is not licensed for advanced features");
  return;
}
// App is licensed for advanced features... continue to render...

Backend functions

Forge native functions

If your app uses Forge functions, the content in this section applies to you.

Forge native node.js runtime provides appContext that's similar to the getContext() method used by both Custom UI and UI Kit in the frontend.

As part of the EAP, we're introducing the license property within that context for Forge functions, which will evaluate the license state and capabilitySet defined for each edition. This is where you should introduce conditional logic to the Forge functions of your app accordingly.

1
2
import { getAppContext } from "@forge/api";

const { appAri, appVersion, environmentAri, environmentType, invocationId, installationAri, moduleKey, license } = getAppContext();

console.log(appAri.toString());
// 'ari:cloud:ecosystem::app/00000000-0000-0000-0000-000000000000'

console.log(appAri.appId);
// '00000000-0000-0000-0000-000000000000'

console.log(appVersion);
// '1.0.0'

console.log(environmentAri.toString());
// 'ari:cloud:ecosystem::environment/00000000-0000-0000-0000-000000000000/11111111-1111-1111-0111-111111111111'

console.log(environmentAri.environmentId);
// '11111111-1111-1111-0111-111111111111'

console.log(environmentType);
// 'DEVELOPMENT'

console.log(invocationId);
// '33333333-3333-3333-0333-333333333333'

console.log(installationAri.toString());
// 'ari:cloud:ecosystem::installation/22222222-2222-2222-0222-222222222222'

console.log(installationAri.installationId);
// '22222222-2222-2222-0222-222222222222'

console.log(moduleKey);
// 'hello-world'

console.log(JSON.stringify(license));
//{"isActive":true,"billingPeriod":"MONTHLY","ccpEntitlementId":"NULL","ccpEntitlementSlug":"NULL","isEvaluation":"NULL","subscriptionEndDate":"NULL","supportEntitlementNumber":"NULL","trialEndDate":"NULL","type":"commercial"}

The conditional logic may look like this:

1
2
import { getAppContext } from "@forge/api";

const { appAri, appVersion, environmentAri, environmentType, invocationId, installationAri, moduleKey, license } = getAppContext();
if (license.capabilitySet !== "capabilityAdvanced") {
  console.log("App is not licensed for advanced features");
  return;
}
// App is licensed for advanced features... continue...

Forge remote functions

If your app uses Forge remote functions, the content in this section applies to you.

Requests to your remote backend will include a Forge Invocation Token (FIT) as a bearer token in the authorization header. The FIT includes important information about the invocation context, which will now include the license property. This license property will evaluate the license state and capabilitySet defined for each edition.

Validate the FIT token and extract the license information from the payload to add your conditional data:

1
2
export const validateContextToken = async (invocationToken, appId) => {
  const jwksUrl = 'https://forge.cdn.prod.atlassian-dev.net/.well-known/jwks.json';
  const JWKS = jose.createRemoteJWKSet(new URL(jwksUrl));

  const payload = await jose.jwtVerify(invocationToken, JWKS, {audience: appId});
  return payload;
}

const payload = validateContextToken(invocationToken, appId)
if(payload.app.license.capabilitySet !== "capabilityAdvanced") {
    console.log("App is not licensed for advanced features");
    return;
}

3. Test the editions of your app

Ensure you've already done the following tasks before continuing with this section:

  1. Installed the Forge CLI and have authenticated with Forge using your Atlassian account.
  2. Introduced conditional logic in your app to differentiate the standard and advanced editions of your app.

As part of the EAP, we're modifiying the forge install command in the Forge CLI so you can ensure your editions are behaving according to the app's conditional logic. This modification lets you test apps under various licensing conditions without needing a license itself.

Note, you can only test editions in either DEVELOPMENT or STAGING environments. Testing editions in PRODUCTION environment is not supported.

To test your editions:

  1. Navigate to the app's top-level directory and deploy your app by running:

    1
    2
    forge deploy
    
  2. Run one of the following forge install commands:

    If you're testing the advanced edition, run:

    1
    2
    forge install --environment development --license advanced
    

    If you're testing the standard edition, run:

    1
    2
    forge install --environment development --license standard
    

    Note, if you're testing the edition in staging, change the environment value from development to staging.

  3. Verify that the edition you're testing is behaving as intended for either the advanced or standard state.

Video walkthrough

Check out this video outlining the journey you'll take when building editions for your Forge apps.

Limitations

For existing installations of an app, app editions are nonexistent until a customer switches from one edition type to another. For example, when an app customer decides to upgrade from standard to advanced edition, it’s only at this time that the edition itself will be set and will become available to the app. Because of this, the value for capabilitySet will not be returned for customers where the edition is still nonexistent.

To handle this, when introducing conditional logic in your app code, we recommend to apply logic on either:

  • the presence of capabilitySetAdvanced for features that should only be visible for customers `on advanced editions, or
  • the presence of capabilitySetStandard or the absence of app editions, which is effectively for customers on standard editions.

Rate this page: