Configuration

This page explains the concepts and options available to configure Flowie. If you are just getting started, you can skip this page and come back later.

Flowie utilizes configuration as code using JavaScript/TypeScript to define it.

Configuration is defined via Flowie settings pages, which can be accessed from the repository or workspace settings.

A typical configuration looks like this:

import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"

import {
  const merge: OptionsPlugin<MergePluginOptions, false>merge,
  function noChangesRequested(): PluginDef<boolean> (+2 overloads)
Users get notified if pull requests have ‘Changes requested’ associated with any of the reviewers.
noChangesRequested
,
function noUnresolvedTasks(): PluginDef<boolean> (+2 overloads)
Users get notified when they have open pull request tasks
noUnresolvedTasks
,
const minimumBuilds: OptionsPluginNoDefault<MinimumBuildsPluginsOptions, false>minimumBuilds, const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
,
} from "flowie.app/plugins" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ function merge(options: MergePluginOptions): PluginDef<MergePluginOptions> (+2 overloads)merge({ MergePluginOptions.allow?: RulesOr<string[] | undefined>allow: ["Team leaders"], }), // Basic checks function noChangesRequested(): PluginDef<boolean> (+2 overloads)
Users get notified if pull requests have ‘Changes requested’ associated with any of the reviewers.
noChangesRequested
(),
function noUnresolvedTasks(): PluginDef<boolean> (+2 overloads)
Users get notified when they have open pull request tasks
noUnresolvedTasks
(),
function minimumBuilds(options: MinimumBuildsPluginsOptions): PluginDef<MinimumBuildsPluginsOptions> (+1 overload)minimumBuilds(1), function minimumApprovals(options: MinimumApprovalsPluginsOptions): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
(2),
], })

Scopes

The configurations can be defined at repository, project or workspace level. Configurations are inherited by all repositories in the project or workspace, and can be overridden by the repository configuration.

Plugins

Plugins are defined using the plugins property. They provide high level and common workflows by leveraging Flowie functionalities such as labels and checks combined.

Check the Plugins section of the documentation to find more about the plugins available.

Rules and conditions

Rules and conditions allow you to define different configurations based on the context of the pull request. This is useful, for example, to define different workflows for different branches.

import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"
import {const merge: OptionsPlugin<MergePluginOptions, false>merge, const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
} from "flowie.app/plugins"
import {function source(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditionsource, function otherwise(): trueotherwise} from "flowie.app/conditions" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ function minimumApprovals(...rules: Rules<MinimumApprovalsPluginsOptions | undefined>): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
([function source(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditionsource(/hotfix.*/), 0], [function otherwise(): trueotherwise, 2]),
function merge(...rules: Rules<MergePluginOptions | undefined>): PluginDef<MergePluginOptions> (+2 overloads)merge( [function source(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditionsource(/hotfix.*/), {MergePluginOptions.allow?: RulesOr<string[] | undefined>allow: ["RELEASE_MANAGERS_GROUP"]}], [function otherwise(): trueotherwise /* default options */] ), ], })

In the example above, we allow hotfix pull requests to be merged without approval, while all other pull requests require at least 2 approvals. It also requires that a member of the RELEASE_MANAGERS_GROUP group merges the pull request.

Examples:

  • Define a condition to use a different plugin configuration based on the target branch.
import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"
import {const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
} from "flowie.app/plugins"
import {function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget} from "flowie.app/conditions" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ // Require 2 approvals only when target is main function minimumApprovals(...rules: Rules<MinimumApprovalsPluginsOptions | undefined>): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
([function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"), 2]),
], })
  • Run with different options for different branches.
import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"
import {const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
} from "flowie.app/plugins"
import {function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget} from "flowie.app/conditions" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ function minimumApprovals(...rules: Rules<MinimumApprovalsPluginsOptions | undefined>): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
(
// Two reviewers for the main branch, [function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"), 2], // One reviewer for feature branches [function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget(/feature.*/), 1] ), ], })
  • With a catch-all clause.
import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"
import {const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
} from "flowie.app/plugins"
import {function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget, function otherwise(): trueotherwise} from "flowie.app/conditions" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ function minimumApprovals(...rules: Rules<MinimumApprovalsPluginsOptions | undefined>): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
(
[function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"), 3], [function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget(/feature.*/), 2], [function otherwise(): trueotherwise, 1] ), ], })

Rules

A Rule is a tuple that defines a Condition and a configuration value for the plugin, which is used when the condition is matched. You can define multiple rules for the same plugin; if no rule is matched, the plugin will not be applied.

A rule is defined as follows:

[condition, configuration]

multiple rules use the following syntax:

[condition, configuration], [condition, configuration], ...

if the plugin has a default configuration, you can omit it:

[condition], [condition, configuration], ...

import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"
import {const minimumApprovals: OptionsPluginNoDefault<MinimumApprovalsPluginsOptions, false>
Users get notified when pull requests don't have that number of approvals
minimumApprovals
} from "flowie.app/plugins"
import {function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget} from "flowie.app/conditions" function configure(config: Config | (() => Config)): voidconfigure({ Config.plugins?: PluginDef<unknown>[] | undefinedplugins: [ function minimumApprovals(...rules: Rules<MinimumApprovalsPluginsOptions | undefined>): PluginDef<MinimumApprovalsPluginsOptions> (+1 overload)
Users get notified when pull requests don't have that number of approvals
minimumApprovals
(
// Condition Configuration // | | // v v [function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"), 2], // <-------- Rule 1 [function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget(/feature.*/), 1] // <--- Rule 2 /*... more rules... ^--- If none of the conditions are matched, the plugin will not be applied. */ ), ], })

Conditions

Condition is a function that takes a context and returns a value. The most common conditions are built-in and can be imported from flowie.app/conditions.

Combining conditions

You can combine conditions logically:

import {const labels: Labelslabels, function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget, function source(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditionsource, const changeset: ChangesetConditionschangeset, function not(condition: ChainableCondition): ChainableConditionnot} from "flowie.app/conditions"

const labels: Labelslabels.Labels.has(label: string | LabelRef): ChainableCondition (+1 overload)has("Ready to merge").ChainableCondition.and: (also: Condition<boolean>) => ChainableConditionand(function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"))
const labels: Labelslabels.Labels.has(label: string | LabelRef): ChainableCondition (+1 overload)has("Ready to merge").ChainableCondition.or: (also: Condition<boolean>) => ChainableConditionor(function source(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditionsource("main"))
const labels: Labelslabels.Labels.hasAny(...labels: Array<string | LabelRef> & NonEmptyArray): ChainableCondition (+1 overload)hasAny("Ready to merge", "WIP").
ChainableCondition.and: ((also: Condition<boolean>) => ChainableCondition) & {
    not: (also: Condition<boolean>) => ChainableCondition;
}
and
.not: (also: Condition<boolean>) => ChainableConditionnot(function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main"))
const changeset: ChangesetConditionschangeset.ChangesetConditions.matches: (glob: string) => ChainableConditionmatches("**/*.md" /* <== Glob */) // All files matching e.g., documentation change only const changeset: ChangesetConditionschangeset.ChangesetConditions.allMatches: (glob: string) => ChainableConditionallMatches("**/*.md") // Reuse const const readyMain: ChainableConditionreadyMain = const labels: Labelslabels.Labels.has(label: string | LabelRef): ChainableCondition (+1 overload)has("Ready to merge").ChainableCondition.and: (also: Condition<boolean>) => ChainableConditionand(function target(...branches: Array<string | RegExp> & NonEmptyArray): ChainableConditiontarget("main")) // Negation function not(condition: ChainableCondition): ChainableConditionnot(const labels: Labelslabels.Labels.has(label: string | LabelRef): ChainableCondition (+1 overload)has("Ready to merge")) function not(condition: ChainableCondition): ChainableConditionnot(const readyMain: ChainableConditionreadyMain)

Branch restrictions

The branch.restrictions property allows customizing or disabling the branch permissions managed by Flowie in order to implement some of its features.

By default, when restrictions are enabled, Flowie will protect branches based on the branching model defined on Bitbucket. It will protect production, main and release branches, by only allowing pull request merge through Flowie, and no write, push, create, rewrite or delete. It also removes any extraneous configuration.

If you wish to keep other branch restrictions, or modify the default behavior your can use branch.restrictions:

import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"

function configure(config: Config | (() => Config)): voidconfigure({
  
Config.branches?: {
    restrictions?: boolean | "manual" | BranchRestrictionOptions;
} | undefined
branches
: {
restrictions?: boolean | "manual" | BranchRestrictionOptions | undefinedrestrictions: { /* It accepts a branch pattern or the branch types from the branching model that are prefixed: release, feature, hotfix and bugfix */ "main": { // Let CI group write to the branch BranchOptions.pushGroups?: (string[] & [unknown, ...unknown[]]) | "Everybody" | undefinedpushGroups: ["CI"], }, "release": { // Alternatively, use "Everybody" special value to allow any user // with write access BranchOptions.pushGroups?: (string[] & [unknown, ...unknown[]]) | "Everybody" | undefinedpushGroups: "Everybody", }, "feature": { // Enable reset for changes BranchOptions.resetRequestedChanges?: boolean | undefinedresetRequestedChanges: true, // Premium Bitbucket only features // resetApprovals: true // resetApprovalsKeepIfNoChanges: true }, // A custom rule, outside the branching model "my-branch-prefix/*": { BranchOptions.pushGroups?: (string[] & [unknown, ...unknown[]]) | "Everybody" | undefinedpushGroups: ["Developers"], BranchOptions.allowDeleting?: boolean | undefinedallowDeleting: true, BranchOptions.allowRewriting?: boolean | undefinedallowRewriting: true, }, }, }, })

Or completely disable it by setting restrictions to false.

Create branches

Typically, you’ll want to allow admins or scripts to create new branches under protected prefixes — for example, when creating new releases.

Bitbucket doesn’t support granular permissions for branch creation only. If you use pushGroups to grant write access, users can also accidentally commit or merge directly into the branch.

Flowie provides a safer workflow: branches are initially created under the new/ prefix, and Flowie then moves them to the appropriate protected prefix. This avoids the need to grant write access to the final destination.

To allow this, enable branch creation for the prefix using create: true

import {const configure: (config: Config | (() => Config)) => voidconfigure} from "flowie.app"

function configure(config: Config | (() => Config)): voidconfigure({
  
Config.branches?: {
    restrictions?: boolean | "manual" | BranchRestrictionOptions;
} | undefined
branches
: {
restrictions?: boolean | "manual" | BranchRestrictionOptions | undefinedrestrictions: {
release: {
    create: true;
}
release
: {
// Create new branches under 'new/release/1.0' // and Flowie will move it to 'release/1.0' BranchOptions.create?: true | undefinedcreate: true, }, }, }, })

Editor support

The script setting pages have support for auto-completion and validation using the Monaco editor. This is the same editor used in VS Code. Learn more about the editor features here.

Editor example

Why JavaScript?

Instead of inventing a new configuration language, Flowie leverages JavaScript/TypeScript to configure the workflow. This allows you to use the full power of a programming language to configure your workflow, which is especially useful for complex workflows and when using conditionals. Also, it can re-use all the tooling and knowledge already available.

This is a similar concept of tools like AWS CDK, Pulumi and CDK for Terraform.

You are not required to know JavaScript, as most of the configurations should be really simple, however, if you require mode advanced or custom configurations, you will probably benefit from it.