Assign custom workflow when a project is created

Hi everyone,

I want to automate my Jira with a custom plugin. This is what I’m trying to accomplish:
When a project is created, the project has the custom workflow (CustomWF) and all its components (post functions, conditions, etc…) automatically available.

Currently, I’m able to get the CustomWF from a .jwb file when exporting from an existing workflow in Jira. However, all custom plugin configurations on transitions are removed with the following message:

The items from the following plugins were removed from the workflow:
 - explugin

Transition: Start Progress

Plugin post functions removed from this transition:
 - Create Branch (explugin)

Transition: Done

System post functions removed from this transition:
 - Update Issue Field

Transition: Done

Plugin post functions removed from this transition:
 - Trigger Job (explugin)

System post functions removed from this transition:
 - Update Issue Field

Transition: Reopen

System post functions removed from this transition:
 - Update Issue Field

Transition: Reopen and start progress

System post functions removed from this transition:
 - Update Issue Field

I was able to get the complete configuration (including all custom post functions) with a project XML export, however this only works for existing projects. All new projects built with CustomWF must manually set up these custom post functions for transitions.

How can I associate post functions with a project at creation time, if possible?

Thank you for your help! I’ve been combing through the forums with almost no luck :<

A very minimal tutorial for the Jira Project Blueprint plugin is here:

It doesn’t cover nearly enough. The gist here is that you can also execute JAVA code in the Post-Creation Project Hook. That’s where you’d do additional input such as creating a new workflow and adding non-standard items to it.

Ah I see what you mean! I have a few post functions already created using the atlassian-create-jira-plugin-module command, I just need to add them to transition states (To Do > In Progress, for ex.) in a new workflow. I’ll go ahead and start looking into that. It’s possible to get the post functions I’ve created in that new workflow, right?

Thank you so much for your help!

I’ve included one of my Project Hooks, the java code that is executed after the project is created:

public class TaskTrackingHook extends EmptyAddProjectHook {
    private final JiraHookHelper helper;

    public TaskTrackingHook(JiraHookHelper helper) {
        this.helper = helper;

    public ValidateResponse validate(ValidateData data) {
        return ValidateResponse.create();
    public ConfigureResponse configure(ConfigureData data) {
        ConfigureResponse configureResponse = ConfigureResponse.create();

        FieldScreen resolutionScreen = helper.getOrCreateScreen("Transition: Resolution", ImmutableList.of("resolution"));

        JiraWorkflow simpleworkflow = helper.createDraft(data.createdWorkflows().get("SIMPLEWORKFLOW"));
        FieldScreen defaultScreen = data.createdScreens().get("TASKDEFAULTSCREEN");
        helper.addCustomFieldToScreen(defaultScreen, "Epic Link", null);
        helper.addScreenToActionName(simpleworkflow, resolutionScreen, "Closed");
        helper.addFunctionToWorkflow(simpleworkflow, Constants.FUNCTION.UPDATE_ISSUE_FIELD, "Open");

        JiraWorkflow taskworkflow = helper.createDraft(data.createdWorkflows().get("TASKWORKFLOW"));
        helper.addScreenToActionName(taskworkflow, resolutionScreen, "Closed");
        helper.addFunctionToWorkflow(taskworkflow, Constants.FUNCTION.UPDATE_ISSUE_FIELD, "Open");

        helper.associatePriorityScheme(data.project(), "Standard Priority Scheme");

        helper.createOrAssociatePermissionScheme(data.project(), "Standard Permission Scheme");
        helper.updateProjectType(data.project(), "software");

        return configureResponse;


Obviously this is dependent on my ‘helper’ class, so I included a snippet of that as well:

    public JiraWorkflow createDraft(JiraWorkflow workflow) {
        if (!workflow.isActive()) {
            return workflow;
        if (!workflow.isDraftWorkflow() && workflow.hasDraftWorkflow()) {
            return workflowManager.getDraftWorkflow(workflow.getName());
        return workflowManager.createDraftWorkflow(authenticationContext.getLoggedInUser(), workflow.getName());

    public void saveWorkflow(JiraWorkflow workflow) {
        if (workflow.isDraftWorkflow()) {

    public void addScreenToActionName(JiraWorkflow workflow, FieldScreen fieldScreen, String actionName) {
        Collection<ActionDescriptor> actions = workflow.getActionsByName(actionName);
        for (ActionDescriptor action : actions) {
            action.setMetaAttributes(ImmutableMap.of("", fieldScreen.getId().toString()));


    public void addFunctionToWorkflow(JiraWorkflow workflow, Constants.FUNCTION functionEnum, String actionName) {
        switch (functionEnum) {
            case UPDATE_ISSUE_FIELD:
                FunctionDescriptor postFunction = functionFactory.updateIssueField("resolution", null);

                Collection<ActionDescriptor> actions = workflow.getActionsByName(actionName);
                for (ActionDescriptor action : actions) {
                    List postfunctions = action.getUnconditionalResult().getPostFunctions();
                    postfunctions.add(0, postFunction);


    public void addCustomFieldToScreen(FieldScreen fieldScreen, String fieldName, Integer positionId) {
        try {
            if (fieldScreen == null) {
                throw new IllegalStateException("fieldScreen was null.");

            CustomField customField =
                            .orElseThrow(() -> new IllegalArgumentException("Custom Field with name '" + fieldName + "' was not found."));

            FieldScreenTab tab =
                            .orElseThrow(() -> new IllegalStateException("No tab was found on screen provided."));

            if (positionId == null) {
            } else {
                tab.addFieldScreenLayoutItem(customField.getId(), positionId);
        } catch (IllegalArgumentException | IllegalStateException e) {
            log.error("Failed to add field to screen: {}", e.getMessage());

Good luck, we’re all counting on you.

Sorry, was pulled away for another task for a bit. Thank you for your code snippet! I’m trying to see how to add my custom post functions but am not sure how to go about calling those classes. I followed the plugin tutorial for post-functions and have the following:

public class CreateRepo extends AbstractJiraFunctionProvider 
public class CreateRepoFunctionFactory extends AbstractWorkflowPluginFactory implements WorkflowPluginFunctionFactory

I’m not sure how to add the postfunctions I made already to this code (in a different directory) I see that you used FunctionDescriptor. Would I have to do something like the following:

CreateRepo createRepoPostFunction = new CreateRepo(); /
FunctionDescriptor fd = ... ?
/* add post function to list of post functions of a certain action */

Thank you again! If you know of any good resources, please let me know. I’m a junior dev working on this project and no one else in my company knows Jira server development.

Okay, some progress made! I was able to add a postfunction using the following code:

fd.getArgs().put("", CreateGitRepo.class.getName());

However, this is the result:

I can’t make changes, is this because I’m not calling it or the function factory associated with it correctly?

Did you make any progress? I am stuck in a similar situation to yours here in this thread. How do you create a FunctionDescriptor to add your already defined postfunction?

I figured it out how to add the post function. I created a new factory like this:

public class NoInputFunctionDescriptorFactory {

    public static FunctionDescriptor makeFunctionDescriptor(Class<? extends FunctionProvider> clazz) {
        FunctionDescriptor functionDescriptor = DescriptorFactory.getFactory().createFunctionDescriptor();
        Map conditionArgs = functionDescriptor.getArgs();
        conditionArgs.put("", clazz.getName());
        return functionDescriptor;

And used it to get my FunctionDescriptor like this:

FunctionDescriptor createPostFunction = NoInputFunctionDescriptorFactory.makeFunctionDescriptor(CreateIssuesPostFunction.class);

Except from a small error in my function, everything works like charm now. Thank you for your code @sfbehnke, it pointed me into the right direction.

1 Like