A case study: Webflow GPT

Meta descriptions? Yikes. Let’s have AI write those for us, shall we?

The problem

The wonderful thing about Webflow is its incredible flexibility; a user can shape their database in almost any fashion imaginable. Unfortunately, as the team at ZEAL was making changes to its Webflow site, all of its blog meta descriptions disappeared! ChatGPT, meet Webflow.

The solution

Bad things happen to good people and, yes, this is a fine example. At the time, ZEAL’s blog collection exceeded 100 articles. We could create new descriptions manually, reading through each blog and coming up with our most creative summaries. That, of course, would be a long process and we would forfeit the opportunity to help other teams facing similar predicaments.

To solve our problem, I created a JavaScript program with four core functions:

  1. Fetch each article
  2. Request OpenAI to generate a meta description based on the article data
  3. Post each new description to its corresponding article in Webflow
  4. Generate a report of the work completed

Fetching and sending articles

Altogether, the four core functions of this program require a great deal of asynchronous processes that are likely to get tangled with one another. For this reason, I instantiated a lightweight SQLite database to persist key data points that I could revisit after each step terminates. For example, for every article fetched from Webflow, the program stores its data in a posts table. Once that process completes, each article’s post_body is then sent to OpenAI to process.

Generating descriptions

The payload sent to OpenAI contains the modelprompttemperature, and max_tokens. The model for this program is text-davinci-003, a GPT-3.5 model that generates well for most tasks. The prompt asks OpenAI to read a specific blog post and generate a meta description with up to 150 characters. The temperature handles output variability, which in this case is set to a low value. Finally, max_tokens is set to 200 to ensure that the generated description has at least 150 characters.

Altogether, the payload sent to OpenAI looks like this:

generate.js
require("dotenv").config();
const { Configuration, OpenAIApi } = require("openai");

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

const generateMetaDescription = async (postBody) => {
  try {
    const response = await openai.createCompletion({
      model: "text-davinci-003",
      prompt: `Read this blog post and generate a meta description with up to 150 characters: ${postBody}`,
      temperature: 0.2,
      max_tokens: 200,
    });
    return response.data.choices;
  } catch (error) {
    console.error(`Error with OpenAI API request: ${error.message}`);
  }
};

module.exports = generateMetaDescription;

Results and limitations

The response from OpenAI is stored in the SQLite database and posted to Webflow. A comprehensive report of the work completed is then generated with time stamps, post IDs, and new descriptions. Below is a snippet of what reports include:

posts.csv
ID,Webflow ID,Post Name,Previous Meta Description,AI Meta
Description,Created At,Updated At 1,12345678abcdefg,Tailwind is a
popular library. Here is how to add it to RedwoodJS,,"Learn how
to use Tailwind with RedwoodJS to speed up your styling process,
decrease your CSS bundle size, and standardize your code. Tailwind
is a utility-first CSS framework that is compatible with React, Ruby
on Rails, Angular, and more. Customize Tailwind with ad hoc and
global changes to fit your project needs.",2023-07-17
17:07:50,2023-07-17 19:54:02

One limitation of the davinci-003 model is that it is not good at following character limits for generated text. Often, the new description would exceed the 150 character specification set in the prompt. One way around this is to send multiple requests to OpenAI for each article and choose the response with the least number of characters.

Another limitation is Webflow’s API throttle limits, which can quickly corrupt data sets. I overcame this issue by creating a wait function that returns a Promise based on a specified setTimeout function.

Although the program did yield favorable results, with some limitations, Agile iterations are a logical next step toward improving the application. I can write new user stories for additional use cases and refactor along the way to ensure that the codebase is ready for future changes,

View GitHub Repo

Like what you see?

Well, you made it this far. It kind of seems like we should chat about how I can add value to your team. Don’t you think? (And yes, I read every message that comes through and respond promptly). Download my resume and drop me a line. Talk soon! 👋🏽