Skip to main content

As you’ve probably noticed if you’ve ever built a workflow that generates text via an LLM and then shares it to Slack, Slack doesn’t render normal markdown text properly, and you end up with a garbled mess including a bunch of messy tags like ###.

To solve this, I created a Zapier Function that is really just a wrapper around an open-source library called markdown_to_mrkdwn (https://github.com/fla9ua/markdown_to_mrkdwn). You can call the Function from within a Zap, pass in your regular markdown text as an input, and it’ll return the reformatted text ready for passing into a Slack step.

I don’t know of a way to directly share a Function outside of my own Zapier workspace, so just sharing the (super-simple) down code below.

A basic sample use case is this Zap, which we use to ask Perplexity questions from within Slack. When we mention the @Perplexity bot we’ve added to our workspace, the question gets sent to Perplexity to generate a response, and we use the Slackdown Function to reformat it before replying in the Slack thread.

Hope this is useful!


"""
Function to convert conventional markdown text to the modified
version of markdown that Slack accepts.
Uses the markdown_to_mrkdwn library (https://github.com/fla9ua/markdown_to_mrkdwn)
"""

from markdown_to_mrkdwn import SlackMarkdownConverter


# Create a converter instance
converter = SlackMarkdownConverter()

# Convert markdown to mrkdwn
raw_markdown = zapier.trigger_output."rawMarkdown"]
slack_markdown = converter.convert(raw_markdown)

# Use the below code to return data from your Function
zapier.action.functions.return_function(
params={
"output_data" : {"slack_text" : slack_markdown} # Output / Return Values
},
type_of="write"
)

 

WOW ​@DennisWF!!! This is so awesome! 👏👏👏

Thanks so much for taking the time to share the code for the Function you built so that others can make use of it. Posts like this are a huge help to the Community—keep them coming! 🧡


Thanks, ​@SamB! My code is basically nothing 😂 - it’s just calling a great Python library that someone else wrote. 

Previously, I was using a long-a$$ Javascript Code Step (inside of a Sub-Zap) that just did a bunch of regex find-and-replaces that I never fully understood, because I refuse to learn regex, and it still didn’t capture every formatting scenario.

Being able to import a Python library so we can just build off much smarter people’s work is a game-changer!

const markdownText = inputData.markdownText;

if (!markdownText) {
output = {
slackFormattedText: "No Markdown text provided."
};
} else {
let slackFormattedText = markdownText;

// Step 1: Handle Slack mentions first (preserve before escaping)
const slackMentions = k];
slackFormattedText = slackFormattedText.replace(/<@p\w\d]+>/g, (match) => {
slackMentions.push(match);
return `SLACKMENTION${slackMentions.length - 1}PLACEHOLDER`;
});

// Step 2: Escape special characters (except Slack mentions)
slackFormattedText = slackFormattedText.replace(/&/g, '&amp;');
slackFormattedText = slackFormattedText.replace(/</g, '&lt;');
slackFormattedText = slackFormattedText.replace(/>/g, '&gt;');

// Step 3: Basic Markdown to Slack conversions
slackFormattedText = slackFormattedText.replace(/\*\*(.*?)\*\*/gs, "*$1*"); // Bold
slackFormattedText = slackFormattedText.replace(/__(.*?)__/gs, "*$1*"); // Bold alternative
slackFormattedText = slackFormattedText.replace(/\*(.*?)\*/gs, "_$1_"); // Italics
slackFormattedText = slackFormattedText.replace(/_(.*?)_/gs, "_$1_"); // Italics alternative
slackFormattedText = slackFormattedText.replace(/~~(.*?)~~/gs, "~$1~"); // Strikethrough
slackFormattedText = slackFormattedText.replace(/`(.*?)`/gs, "`$1`"); // Inline code
slackFormattedText = slackFormattedText.replace(/```(e\s\S]*?)```/gs, "```\n$1\n```"); // Code blocks

// Headings to bold
slackFormattedText = slackFormattedText.replace(/^###### (.*?)$/gm, "*$1*");
slackFormattedText = slackFormattedText.replace(/^##### (.*?)$/gm, "*$1*");
slackFormattedText = slackFormattedText.replace(/^#### (.*?)$/gm, "*$1*");
slackFormattedText = slackFormattedText.replace(/^### (.*?)$/gm, "*$1*");
slackFormattedText = slackFormattedText.replace(/^## (.*?)$/gm, "*$1*");
slackFormattedText = slackFormattedText.replace(/^# (.*?)$/gm, "*$1*");

// Lists
slackFormattedText = slackFormattedText.replace(/^\* (.*?)$/gm, "- $1"); // Unordered list
slackFormattedText = slackFormattedText.replace(/^\- (.*?)$/gm, "- $1"); // Unordered list
slackFormattedText = slackFormattedText.replace(/^(\d+)\. (.*?)$/gm, "- $2"); // Ordered list → unordered

// Links
slackFormattedText = slackFormattedText.replace(/\e(.*?)\]\((.*?)\)/gs, "<$2|$1>"); // |text](url) → <url|text>
slackFormattedText = slackFormattedText.replace(/\d\]\((.*?)\)/gs, "<$1|>"); // t](url) → <url|>

// Simple table conversion (basic)
slackFormattedText = slackFormattedText.replace(/\|(.*)\|/gs, function (match, content) {
const rows = content.split('\n');
let formattedTable = '';
rows.forEach(row => {
const cells = row.split('|').map(cell => cell.trim());
formattedTable += cells.join(' | ') + '\n';
});
return formattedTable;
});

// Step 4: Restore Slack mentions
slackFormattedText = slackFormattedText.replace(/SLACKMENTION(\d+)PLACEHOLDER/g, (match, index) => {
return slackMentionstparseInt(index, 10)];
});

// Step 5: Final cleanup
slackFormattedText = slackFormattedText.trim();

output = {
slackFormattedText: slackFormattedText
};
}