Visualise your Workflow in Kontent.AI

Mar 5, 2025
Kontent.aiCMSContent modelling

Kontent.AI enables the creation of complex editorial workflows, but visualising them can be challenging. Transforming workflows into flow diagrams allows easy comparison with design plans. A LINQPad script can generate a dynamic Mermaid diagram, providing clear visualisation that can be integrated into documentation.

Kontent.AI provides the ability to build workflows that support the editorial process for your content.

While the editor for workflows lets us build something fairly complex, it can be difficult to - at a glance - understand how content can move through the workflow due to the linear layout of the workflow.

Kontent.AI's default linear view of Workflow

In a recent project, I wanted to be able to quickly determine if my workflows match what we had designed in our Miro board, but also be able to share documentation about the implemented workflow. As kontet.AIs doncumeantion on workflow says, a drawing prevents gray hairs. The simplest way I could think of to do this was to make it in to a flow diagram. 

I didn't want to have to keep manually updating the flow diagram each time there were some changes made to the workflow. My solution was to create a simple script in LINQPad that would create a Mermaid diagram on demand. 

Building the Solution

First, create a dictionary to store the steps in. This is just improve the readability of the code when we do lookups.

var workflows = await managementClient.ListWorkflowsAsync();
var workflow = workflows.First();

// Precompute steps in a dictionary for faster lookup
var stepDictionary = workflow.Steps.ToDictionary(s => s.Id, s => s.Codename);

Next, we create a dictionary of the colours in Kontent.AI. Without it, the output is quite bland. As the colour names in Kontent.AI are not just standard CSS colours, and because we also want to change the text colour we use this lookup.

var colorDictionary = new Dictionary<string, (string Fill, string Color)> {
	{ "gray", ("#6F6F6F", "#FFFFFF") },
	{ "red", ("#DB0000", "#FFFFFF")},
	{ "rose", ("#D9006F", "#FFFFFF")},
	{ "lightpurple", ("#DAB8FF", "#000000")},
	{ "darkpurple", ("#AD00FB", "#FFFFFF")},
	{ "darkblue", ("#006CDC", "#FFFFFF")},
	{ "lightblue", ("#B1C5FF", "#000000")},
	{ "skyblue", ("#72D1FF", "#000000")},
	{ "mintgreen", ("#00E293", "#000000")},
	{ "persiangreen", ("#007F51", "#FFFFFF")},
	{ "darkgreen", ("#4B7C00", "#FFFFFF")},
	{ "lightgreen", ("#89DC00", "#000000")},
	{ "yellow", ("#FFB865", "#000000")},
	{ "pink", ("#FE9A9A", "#000000")},
	{ "orange", ("#C54300", "#FFFFFF")},
	{ "brown", ("#996400", "#FFFFFF")},
};

With that in place - using LINQPad's Dump command - we then initialise the Mermaid diagram with flowchart TD to create a top-down flow chart, and then also write out the styles from our colour dictionary. In addition, because Published and Archived don't live in the Steps collection in the workflow, we'll add these manually to the output and create their won relationship. We've also got some methods in here to generate some convenient style names.

"flowchart TD".Dump();
foreach (var key in colorDictionary.Keys)
{
	$"classDef {key} fill:{colorDictionary[key].Fill},color:{colorDictionary[key].Color},stroke:none;".Dump();
}
"published(Published):::persiangreen;".Dump();
"archived(Archived):::gray;".Dump();
"published --> archived".Dump();

The final thing to do is to loop through the steps in the workflow. For each step we create a node with text and a style. For the node name, we just use the workflow step's codename

As the order of nodes in Mermaid does not matter, we also go ahead and list out all of the Transition To steps, building links using the codename from the dictionary.

foreach (var step in workflow.Steps)
{
	$"{step.Codename}(\"{step.Name}\"):::{step.Color.ToString().ToLowerInvariant()};".Dump();

	foreach (var transition in step.TransitionsTo)
	{
		string targetCodename = transition.Step.Id == workflow.PublishedStep.Id ? "published"
			: transition.Step.Id == workflow.ArchivedStep.Id ? "archived"
			: stepDictionary.TryGetValue(transition.Step.Id??Guid.Empty, out var nextStepCodename) ? nextStepCodename
			: "unknown"; // Fallback if step ID not found

		$"{step.Codename} --> {targetCodename}".Dump();
	}
}

The output from this I then copy and paste into a viewer for Mermaid diagrams. For example https://mermaid.js.org/ or Notion.

The output of this particular script is a simple flow diagram in Mermaid as follows:

flowchart TD
classDef style_gray fill:#6F6F6F,color:#FFFFFF,stroke:none;
classDef style_red fill:#DB0000,color:#FFFFFF,stroke:none;
classDef style_rose fill:#D9006F,color:#FFFFFF,stroke:none;
classDef style_lightpurple fill:#DAB8FF,color:#000000,stroke:none;
classDef style_darkpurple fill:#AD00FB,color:#FFFFFF,stroke:none;
classDef style_darkblue fill:#006CDC,color:#FFFFFF,stroke:none;
classDef style_lightblue fill:#B1C5FF,color:#000000,stroke:none;
classDef style_skyblue fill:#72D1FF,color:#000000,stroke:none;
classDef style_mintgreen fill:#00E293,color:#000000,stroke:none;
classDef style_persiangreen fill:#007F51,color:#FFFFFF,stroke:none;
classDef style_darkgreen fill:#4B7C00,color:#FFFFFF,stroke:none;
classDef style_lightgreen fill:#89DC00,color:#000000,stroke:none;
classDef style_yellow fill:#FFB865,color:#000000,stroke:none;
classDef style_pink fill:#FE9A9A,color:#000000,stroke:none;
classDef style_orange fill:#C54300,color:#FFFFFF,stroke:none;
classDef style_brown fill:#996400,color:#FFFFFF,stroke:none;
published(Published):::style_persiangreen;
archived(Archived):::style_gray;
published --> archived
classDef c_draft fill:red
draft("Draft"):::style_red;
draft --> translation_automation
draft --> proof_reading
draft --> proof_reading__admin_
classDef c_translation_automation fill:gray
translation_automation("Translation Automation"):::style_gray;
translation_automation --> ready_to_publish
translation_automation --> published
classDef c_proof_reading fill:darkpurple
proof_reading("Proof-reading"):::style_darkpurple;
proof_reading --> ready_to_translate
classDef c_proof_reading__admin_ fill:yellow
proof_reading__admin_("Proof Reading (Admin)"):::style_yellow;
proof_reading__admin_ --> ready_to_publish
classDef c_ready_to_translate fill:darkblue
ready_to_translate("Ready to Translate"):::style_darkblue;
ready_to_translate --> translation_submitted
classDef c_translation_submitted fill:gray
translation_submitted("Translation Submitted"):::style_gray;
translation_submitted --> translation_cancelled
translation_submitted --> translation_received
classDef c_translation_cancelled fill:gray
translation_cancelled("Translation Cancelled"):::style_gray;
translation_cancelled --> archived
classDef c_translation_received fill:gray
translation_received("Translation Received"):::style_gray;
translation_received --> translation_in_progress
translation_received --> translation_cancelled
classDef c_translation_in_progress fill:gray
translation_in_progress("Translation In Progress"):::style_gray;
translation_in_progress --> translation_complete
translation_in_progress --> translation_cancelled
classDef c_translation_complete fill:gray
translation_complete("Translation Complete"):::style_gray;
translation_complete --> ready_to_publish
translation_complete --> translation_cancelled
classDef c_ready_to_publish fill:mintgreen
ready_to_publish("Ready to publish"):::style_mintgreen;
ready_to_publish --> published

This is 10-15 minutes of effort, but it makes sharing a workflow for documentation much simpler, and makes the workflow easier to digest.