Add Support for RichText List Items via API

When updating a RichText field via API, list items (bulleted or numbered lists) will show as blank lines. This is an extreme inconvenience that has been known for years.

Here are related posts in the WF community:

The ridiculous workaround: https://twitter.com/mhddngs/status/1306291136131031041

  • William Gen
  • Dec 5 2023
CMS
  • Aaron Dahlstrom commented
    02 Aug 18:19

    William's JavaScript function worked for me. At first it threw a syntax error in Zapier, so I tweaked the code a bit and added line breaks and indentation to make it more readable. Hope this helps others. Thanks, William!


    function markdownToHtml(markdown) {
    if (!markdown) return '';

    // Convert Markdown elements that should not be inside paragraphs
    let intermediateHtml = markdown
    .replace(/^### (.*$)/gim, '<h3>$1</h3>')
    .replace(/^## (.*$)/gim, '<h2>$1</h2>')
    .replace(/^# (.*$)/gim, '<h1>$1</h1>')
    .replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>') // Non-greedy match for bold
    .replace(/\*(.*?)\*/gim, '<em>$1</em>') // Non-greedy match for italic
    .replace(/\[(.*?)\]\((.*?)\)/gim, '<a href="$2">$1</a>'); // Links

    // Split the intermediate HTML by lines to handle lists and paragraphs separately
    let lines = intermediateHtml.split('\n');
    let html = '';
    let inList = false;
    let listType = null;
    let listLevel = 0;

    lines.forEach((line) => {
    let trimmedLine = line.trim();
    if (!trimmedLine) {
    if (inList) {
    // Close all open list tags if in list
    while (listLevel-- >= 0) {
    html += `</${listType}>`;
    }
    inList = false;
    }
    // Skip adding empty <p> tags
    } else {
    let unordered = trimmedLine.match(/^\s*-\s+(.*)/);
    let ordered = trimmedLine.match(/^\s*\d+\.\s+(.*)/);

    if (unordered || ordered) {
    let currentListType = unordered ? 'ul' : 'ol';
    let indentLevel = line.search(/\S/);

    if (!inList) {
    inList = true;
    listLevel = indentLevel;
    html += `<${currentListType}>`;
    } else if (currentListType !== listType || indentLevel !== listLevel) {
    if (indentLevel > listLevel) {
    html += `<${currentListType}>`;
    } else if (indentLevel < listLevel) {
    while (indentLevel < listLevel--) {
    html += `</${listType}>`;
    }
    html += `<${currentListType}>`;
    } else {
    html += `</${listType}><${currentListType}>`;
    }
    }
    listType = currentListType;
    listLevel = indentLevel;
    html += `<li>${unordered ? unordered[1] : ordered[1]}</li>`;
    } else {
    if (inList) {
    while (listLevel-- >= 0) {
    html += `</${listType}>`;
    }
    inList = false;
    }
    if (!line.startsWith('<h1>') && !line.startsWith('<h2>') && !line.startsWith('<h3>') && !line.startsWith('<a>') && !line.startsWith('<ul>') && !line.startsWith('<ol>')) {
    html += `<p>${line}</p>`;
    } else {
    html += line;
    }
    }
    }
    });

    // Ensure all lists are closed if the markdown ends in a list
    if (inList) {
    while (listLevel-- >= 0) {
    html += `</${listType}>`;
    }
    }

    return html;
    }

    // Zapier Code step
    let markdown = inputData.markdown;

    if (markdown) {
    let htmlText = markdownToHtml(markdown);
    output = { htmlOutput: htmlText };
    } else {
    output = { error: "No markdown input provided" };
    }
  • ContentPie ContentPie commented
    22 Jun 10:10

    Any updates on it?

  • William Gen commented
    29 Apr 20:06

    I found a solution

    Note: I use Airtable to manage my writers so this is an Airtable-specific solution

    I am no longer using Zapier or Make to convert markdown to HTML.

    I am using an automation flow to first convert the markdown to HTML (script below)

    Then, I send Zapier a webhook which triggers a Zap to publish to Webflow


    function markdownToHtml(markdown){if(!markdown)return'';
    // Convert Markdown elements that should not be inside paragraphslet intermediateHtml = markdown.replace(/^### (.*$)/gim,'<h3>$1</h3>').replace(/^## (.*$)/gim,'<h2>$1</h2>').replace(/^# (.*$)/gim,'<h1>$1</h1>').replace(/\*\*(.*?)\*\*/gim,'<strong>$1</strong>')// Non-greedy match for bold.replace(/\*(.*?)\*/gim,'<em>$1</em>')// Non-greedy match for italic.replace(/\[(.*?)\]\((.*?)\)/gim,'<a href="$2">$1</a>');// Links
    // Split the intermediate HTML by lines to handle lists and paragraphs separatelylet lines = intermediateHtml.split('\n');let html ='';let inList =false;let listType =null;let listLevel =0;
    lines.forEach((line)=>{let trimmedLine = line.trim();if(!trimmedLine){if(inList){// Close all open list tags if in listwhile(listLevel-->=0){ html +=`</${listType}>`;} inList =false;}// Skip adding empty <p> tags}else{let unordered = trimmedLine.match(/^\s*-\s+(.*)/);let ordered = trimmedLine.match(/^\s*\d+\.\s+(.*)/);
    if(unordered || ordered){let currentListType = unordered ?'ul':'ol';let indentLevel = line.search(/\S/);
    if(!inList){ inList =true; listLevel = indentLevel; html +=`<${currentListType}>`;}elseif(currentListType !== listType || indentLevel !== listLevel){if(indentLevel > listLevel){ html +=`<${currentListType}>`;}elseif(indentLevel < listLevel){while(indentLevel < listLevel--){ html +=`</${listType}>`;} html +=`<${currentListType}>`;}else{ html +=`</${listType}><${currentListType}>`;}} listType = currentListType; listLevel = indentLevel; html +=`<li>${unordered ? unordered[1]: ordered[1]}</li>`;}else{if(inList){while(listLevel-->=0){ html +=`</${listType}>`;} inList =false;}if(!line.startsWith('<h1>')&&!line.startsWith('<h2>')&&!line.startsWith('<h3>')&&!line.startsWith('<a>')&&!line.startsWith('<ul>')&&!line.startsWith('<ol>')){ html +=`<p>${line}</p>`;}else{ html += line;}}}});
    // Ensure all lists are closed if the markdown ends in a listif(inList){while(listLevel-->=0){ html +=`</${listType}>`;}}
    return html;}

    let inputConfig = input.config();let markdown = inputConfig.markdown;
    let htmlText = markdownToHtml(markdown);
    console.log(htmlText);// This will print the final HTML to the console
    output.set('htmlOutput', htmlText);


  • Victoria Chen-Englert commented
    26 Jan 17:55

    PLEASE add this support! I've been frustrated to no end. I'm trying to streamline the content creation with my team using Clickup, and I hit a wall because there is no way to import the text WITH the markdown from Clickup into Webflow using the API. I'm using make.com.

  • Fredo Corleone commented
    December 24, 2023 15:32

    So guys, is there a workaround? How do we bulk load via CMS and be able to show lists in the editor for our clients to edit? Thanks

  • Sean Horvath commented
    December 08, 2023 13:31

    Is there even a way to API with RTF — because I've been doing design workaround miracles to accomodate a PTF only design that doesn't look like it is lacking RTF.

    Point blank — it isn't sustainable in the long term and I fully think we should be loud about this

    • considering not using API is obviously not an option

    • considering API is about to overtake all of the internet

    • considering the user-adoption learning curve is just about at the point of anybody with no tech skills using AI to set up API — feasible now, but not in an ROI way that makes sense yet — but literally months and all these companies are... idk what they're even doing it is just radio silence.

    Like, you can learn all you need to know about API by learning from Youtube to build one GPT Assistant.

    Best luck I've had is yelling into the void of reddit until a startup looking to scale comes out of the woodwork offering to build you features.

    I wish I understood what Webflow was doing to retain customers but it's like, we're at the point where plans are being made because tech features are existent, and (unless you straight up tell them not to) customer service gaslights you and will never admit fault on their end. It's really the strangest business strategy I've seen in years.

    As for being able to design websites to the degree of customization as Webflow, I'd give it three months before there is a viable solution that requires zero knowledge. The solutions are half there on YouTube and have been for months. AI is only better at coding and translating code languages, and understanding our creative requests via prompts.

    It just seems like -- where is the logic? Unless Webflow is going to launch something epic in Q1, I genuinely don't understand how it isn't behind the curve by Q3 and dead by Q4.

    Exponential means exponential.

    BREATHE

    This is coming from months of not just Webflow being beyond difficult to deal with. I'm giving up Make and Zapier for a startup building all of what I had automated, in a way I can actually learn and scale, which Webflow doesn't accomodate (or seem to want to?).

    So if they don't care about us after we built a site. And they very soon won't be cutting edge enough for during site building to matter — this is going to be quite the tech revolution and impending industrial revolution. It seems like they're all just banking on things being far slower than anything has been. And I sort of get it. I've been told I'm scaling too fast right now, but it is the services available, not my production. And SCOOP then come the startups willing to offer arm and leg to use you as a client to simultaneously build out their product.


    WILD — wild is all it comes down too.


    knock knock helllooooo, Webflow, are you alive?