Forge’s EAP offers experimental features to selected users for testing and feedback purposes. These features are unsupported and not recommended for use in production environments. They are also subject to change without notice. For more details, see Forge EAP, Preview, and GA.
To sign up for this EAP, submit a ticket here.
Due to performance and scalability challenges in the upcoming milestone preview, we plan a phased rollout of apps. Initially, only a select number of apps will be permitted for use in production environments and distribution through the marketplace.
Both Rovo and Automation utilize the same Forge module core:action
.
However, the action
can be used in different contexts depending on the app's manifest configuration.
In subsequent sections, you can learn how to use the core:action
module in your Forge app to extend the Automation platform.
Users can select your app action from the Automation component picker. After adding an app action to a rule, users will configure, validate, and enable it.
When the rule gets executed, your action will get executed, too. Automation will pass along the user’s configuration when invoking your action’s handler function. If you notice an invalid configuration, you can fail the action and append error messages to the Automation audit log.
If your action ran successfully, control will be passed back to Automation, and the rule will continue execution.
1 2modules {} └─ automation:actionProvider [] ├─ key (string) [Mandatory] └─ actions (array) [Mandatory] └─ action [] ├─ key (string) [Mandatory] ├─ name (string) [Mandatory] ├─ function (string) [Mandatory] ├─ actionVerb (string) [Mandatory] ├─ description (string) [Mandatory] ├─ config {} [Optional] │ └─ resource (string) [Mandatory] │ └─ render (default | native) [Optional] ├─ inputs {} [Mandatory] │ └─ inputName {} │ ├─ title (string) [Mandatory] │ ├─ type (string) [Mandatory] │ ├─ required (boolean) [Mandatory] │ └─ description (string) [Optional] function [] └─ key (string) [Mandatory] └─ handler (string) [Mandatory] resources [] └─ key (string) [Mandatory] └─ path (string) [Mandatory]
Automation will only show actions that are referenced by an actionProvider
.
To make your actions visible in Automation, you must add them to an actionProvider
module.
The action
module supports both Automation and Rovo.
To control where the action should be shown, you need to opt into Automation explicitly by adding your action
to an actionProvider
.
Property | Type | Required | Description |
---|---|---|---|
key | string | Yes | A key for the action provider. Must be unique within the manifest. Regex: ^[a-zA-Z0-9_-]+$ |
actions | string[] | Yes | A list of references to action modules (defined next) |
The action
module allows you to configure an action.
Property | Type | Required | Description |
---|---|---|---|
key | string | Yes | A key for the action, which other modules can refer to. Must be unique within the manifest. Regex: ^[a-zA-Z0-9_-]+$ |
name | string | Yes | A human-friendly name for the action which will be displayed in the user interface. |
function or endpoint | string | Yes | A reference to the hosted Forge function that defines the behavior of this action. If you are using Forge remote then you can use an external API endpoint here. |
actionVerb | string | Yes | The verb that best represents your action: GET , CREATE , UPDATE , DELETE , TRIGGER .
This property is only used by Rovo right now. Unfortunately, you still need to provide a value until we make it optional.
|
description | string | Yes | Textual representation of the component's configuration state. |
config | config | Yes | Form to provide additional context during action invocation. |
inputs | inputs | Yes | The inputs for this action. |
config
propertyAfter adding an app action to a rule, users can configure it. This is done via a configuration form controlled by your app. See Action configuration below for more details.
The config
property allows you to choose between UI Kit and Custom UI.
Property | Type | Required | Description |
---|---|---|---|
resource | string | Required if using Custom UI or the latest version of UI Kit | A reference to the static resources entry that your context menu app wants to display. See resources |
render | native | Yes for UI Kit | Indicates the module uses UI Kit. |
inputs
propertyWhen your action is invoked, it receives a set of inputs, each of which is configured via the inputs
property.
Each input must have a unique user-defined name, referred to as inputName
, which acts as a parent container for the following properties: title
, type
, required
, and description
.
Property | Type | Required | Description |
---|---|---|---|
title | string | Yes | The name of the input. |
type | string | Yes | The data type of the input: string , integer , number , or boolean . |
required | string | Yes | True if the input is required. |
description | string | No | A short description of your action. |
Automation action module doesn't require any additional scopes to be added to the manifest.
Keep in mind that if your action communicates with the product API, it might need additional scopes. See Add scopes to call an Atlassian REST API for more details.
1 2modules: action: - key: log-time function: logTime name: Fetch timesheet by date actionVerb: CREATE description: | Retrieve a user's timesheet based on a date config: render: native resource: main-resource inputs: timesheetDate: title: Timesheet Date type: string required: true description: "The date that the user wants a timesheet for" function: - key: logTime handler: index.logTime - key: summary handler: index.summary resources: - key: main-resource path: src/frontend/fui.tsx
1 2modules: action: - key: log-time endpoint: logTime name: Log time actionVerb: CREATE description: | Log some time for the user against a Jira issue config: render: native resource: main-resource inputs: issueKey: title: Jira Issue Key type: string required: true description: "The jira issue to log time against" time: title: Time to log in minutes type: integer required: true description: "The number of minutes to log" endpoint: - key: logTime remote: timesheetapp route: path: log-time auth: appUserToken: enabled: true appSystemToken: enabled: false remote: - key: timesheetapp baseUrl: "https://backend.timesheetapp.com"
Users will be able to configure your action after they have added it to a rule. They do that by providing inputs via a configuration form.
That configuration form is built by you, using either UI Kit or Custom UI.
We recommend choosing UI Kit due to its simplicity and efficiency in capturing user inputs.
For a great user experience, it is important for the configuration form to interact properly with Automation. There are two areas to take care of:
Users should be able to make changes to your action, navigate to another one, and then get back to yours. Changes they have made should not be lost along the way.
Configuration state can be maintained in multiple ways prior to saving the rule:
Next
buttononChange
or onBlur
handlers implemented per input in the configuration form that will call view.submit(payload)
on each such event triggered.onChange
and onBlur
handlersBack
button won’t maintain the state, it is designed to undo the dirty state of the formWhen the user clicks the Next
button during rule creation/update, the Form’s onSubmit
will be triggered, allowing you to handle the form's validation and submission.
More information about the form can be found under Form component.
1 2import { view } from '@forge/bridge'; import ForgeReconciler, { Form, useForm, Text, useProductContext, ErrorMessage, TextArea, } from '@forge/react'; export const CommentForm = ({ context }) => { const formInstance = useForm({ defaultValues: context.extension, }); const { handleSubmit, register, getValues } = formInstance; const onSubmit = data => { view.submit(data); }; const onChangeHandler = (value) => { view.submit({ ...getValues(), ...value }); }; const { onChange: onCommentChange, ...commentRegisterProps } = register("comment", { required: { value: true, message: "Comment is required" }, }); return ( <Form onSubmit={handleSubmit(onSubmit)}> <Text>Comment</Text> <TextArea {...commentRegisterProps} onChange={e => { onCommentChange(e); onChangeHandler({ comment: e.target.value }); }} /> {formInstance.formState.errors.comment?.message && ( <ErrorMessage> {formInstance.formState.errors.comment?.message} </ErrorMessage> )} </Form> ); } export const App = () => { const context = useProductContext(); return !context ? <Text>Loading...</Text> : <CommentForm context={context} />; }; ForgeReconciler.render(<App />);
Custom UI necessitates additional setup. The code must be either bundled into your Forge app or served from an external CDN, ensuring appropriate Content Security Policy (CSP) configurations are in place.
To be able to submit the configuration form with the native Next
button - a subscription to an event is required, where the event listener callback should call the view.submit
.
The event is called AUTOMATION_ACTION_SUBMIT
.
It will notify your app that a configuration form was submitted.
This event is only available for Custom ui modules.
1 2import ReactDOM from 'react-dom/client'; import { useEffect, useRef, useState } from 'react'; import Form, { Field } from '@atlaskit/form'; import Textfield from '@atlaskit/textfield'; import { view, events } from '@forge/bridge'; export const App = () => { const [formData, setFormData] = useState(null); const formRef = useRef(null); useEffect(() => { view.getContext().then(({ extension }) => { setFormData(extension); }); const subscription = events.on('AUTOMATION_ACTION_SUBMIT', () => { formRef.current?.onSubmit(); }); return () => { subscription.then((sub) => sub.unsubscribe()); }; }, []); const onChangeHandler = value => { const updatedFormData = { ...formData, ...value }; view.submit(updatedFormData); setFormData(updatedFormData); }; const onSubmit = data => { view.submit(data); }; return ( <Form onSubmit={onSubmit}> {({ formProps }) => { formRef.current = formProps; return ( <form {...formProps}> <Field name="issueKey" label="Issue key" defaultValue={formData.issueKey} > {({ fieldProps: { onChange, ...restFieldProps } }) => ( <Textfield {...restFieldProps} onChange={e => { onChangeHandler({ [restFieldProps.name]: e.target.value }); onChange(e); }} /> )} </Field> </form> ); }} </Form> ); }; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);
In order to be able to save the rule in the Automation platform, the configuration needs to pass some basic validation checks.
inputs
declared in the manifest module
inputs
in the manifest will be rejected If you want to use Smart Values to pass dynamic configuration to your action, you can add a hidden input field to your form (make sure to declare it in the manifest).
Then set the value of the input field to the smart value, e.g. “{{version.name}}"
.
When Automation executes your action, it will pass on the resolved value.
When Automation executes your action, it invokes the referenced Forge function (or remote endpoint if you are using Forge remote).
A payload
object is passed into the function as an argument.
It includes the inputs defined for the action during the configuration inside the UI.
Here’s an example of a payload object, and how it can be accessed.
1 2"payload": { "projectKey": "CP", "issueKey": "CP-16", "comment": "hello world" }
The addComment
function from the previous example might look like this:
1 2export async function addComment(payload) { // Extract necessary information const projectKey = payload?.projectKey; const issueKey = payload?.issueKey; const comment = payload.comment || "This is a comment"; // Your code goes here ... }
asApp()
vs asUser()
Automation will always invoke your action as a particular user (currently the one configured under “Rule Actor”).
Therefore, you should always use asUser()
when making outbound requests.
Quoting from Forge’s Shared Responsibility Model:
You must use
asUser()
whenever you are performing an operation on behalf of a user. This ensures your app has at most the permissions of the calling user.
In the past, we recommended the use of asApp()
, because asUser()
wasn’t yet available.
This has now changed.
Please avoid using asApp()
, whenever possible.
At this point, your action won’t be able to contribute any data back to Automation. Return values from your handler function will be ignored, unless you return a special errors object (see section below).
If the user hasn’t provided valid inputs to your action, you can return a list of error messages. The rule will stop execution, the action will be marked as failed, and the rule owner will be notified by email. The rule owner can then navigate to the Audit Log, where they can then see the error messages you have provided.
Note: if your app throws an exception, the execution will be marked as failed too, but only a generic error message will be shown.
1 2export async function addCommentWithUsageError(payload) { const issueKey = payload?.issueKey; if (!issueKey) { return { errors: [ { message: `Payload provided with invalid issue key: ${issueKey}`, } ], }; } // continue execution }
An app icon will be used as an action icon visible in the automation flow configuration. If the app defines multiple actions, they will have the same icon.
An app icon can be configured in the Developer console:
When a user uninstalls your app, there might still be references to your app actions in their rules. These references will continue to exist (together with the documentation).
However, the action will be shown as an “unknown component”.
If the user chooses to reinstall the app, all the actions will appear again together with the previous configuration.
If your action appends error messages to Automation’s Audit log, these will be persisted in the Audit Log. There is no support for internationalization / translating these strings.
The default actor of a new rule is still a special “Automation for Jira” bot user.
This leads to a lot of confusion, because when running asUser()
, your app will run as that bot user.
That the bot user will soon disappear, though, because all Forge actions will soon require a Connection to be created (which will decide which user the action is run as).
To make forge tunnel
work for Automation actions, you should change the default actor Automation for Jira
of the rule to be the "Personal User Account" instead.
To access this configuration, click the "Rule details" button in the Rule configuration view.
After you have done that, you can run forge tunnel
and the action execution logs should be visible in the terminal.
Also code reloading should work as usual.
--no-verify
flag to bypass it.Rate this page: