How to create a Chrome extension for GPT-3 questions with ReactJS

Cover image

I'm a big fan of using the OpenAI Playground, and I have numerous saved templates for generating landing page headlines and checking my grammar. In this walkthrough, we'll build a Chrome extension so you don't have to leave your browser to ask GPT-3 questions.

We'll build the extension with ReactJS as the view engine, rendering a popup with your logic.

If you're interested in following this blog post as a course, I'm working on turning this popular guide on Chrome extension development using GPT-3 into a course.

Sign up for our waitlist to be notified when it's available and have access to additional resources and guidance ➡️ https://gpt3-chrome-extension.carrd.co/


Disclaimer

Do not publish an app or an extension with any of your API keys; this tutorial is just a walkthrough of creating a Chrome extension. Don't expose unencrypted API keys/secrets/credentials if you're planning to publish an app or an extension.


Here's what we'll use:

1. ReactJS ⚛️
2. OpenAI SDK 🦾

And here's a summary of the steps we'll take:

1. Create a ReactJS app
2. Create extension updates
3. Install Material UI (optional)
4. Create the extension UI
5. Get OpenAI API key
6. Add OpenAI configuration
7. Create GPT-3 completion
8. Add favicon
9. Upload extension
10. UI fixes
11. Save the state (optional)
12. Ask away

Prerequisites:

- Node
- npm


1. Create a ReactJS app

Let's start with creating a new React app. To begin, run this command within your favorite directory on your computer:

npx create-react-app your-app-name

This will start the creation of a regular React app and can take a couple of minutes. When the installation is complete, cd into the folder your-app-name and start the app:

cd your-app-name
npm start

This will open a new browser window with this starting point:

React app starter

The next step is to install Material UI, which is a fully-loaded component library and design system https://mui.com/


2. Create extension updates

Now that we have a react app, we need to make 2 changes to the extension structure. The first step is to update the public/manifest.json file. The manifest describes what the source package includes and the extension's permissions.

Open public/manifest.json and change it to this structure:

{
    "manifest_version": 3,
    "name": "YOUR_EXTENSION_NAME",
    "description": "DESCRIPTION",
    "version": "1.0.0",
    "action": {
        "default_popup": "index.html",
        "default_title": "Open the popup"
    },
    "icons": {
        "16": "icon.png",
        "48": "icon.png",
        "128": "icon.png"
    },
    "permissions": [
        "scripting",
        "activeTab",
        "storage"
    ],
    "host_permissions": [
        "https://*/"
    ]
}

Then add the following snippet to your src/App.js file right at the very top:

/*global chrome*/

Great, we're ready to start building the app.


3. Install Material UI (optional)

I'll use Material UI to format the UI in this guide; it is a fully-loaded component library and design system with production-ready components.

Start by installing the library, and run this command in the folder your-app-name that got created earlier:

npm install @mui/material @emotion/react @emotion/styled

Let's also go ahead and install the icon package, it consists of SVG icons exported as React components:

npm install @mui/icons-material

Now that we have Material UI installed let's start building the extension app.


4. Create the extension UI

In this section, we'll build a super simple app with just a text field and a button. The button will call the OpenAI SDK and ask GPT-3 a question. The response is then made visible below the text field.

Change the title

Start by changing the title of our Chrome extension. Open up public/index.html and change the title tag to your own name:

 <title>GPT-3 Chrome extension</title>

Create React app comes with reloading and ES6 support, so you should already see the changes in the browser tab:

New title

Right, go ahead and remove the already written code in src/App.js and replace it with a Container and Grids. Your App.js should now look like this:

import React, {useState} from "react";
import "./App.css";

import { Box, Container, Grid, TextField } from "@mui/material";

function App() {
  return (
    <Container>
      <Box sx={{ width: "100%", mt: 4  }}>
        <Grid container>
          <Grid item xs={12}>
          </Grid>
        </Grid>
      </Box>
    </Container>
  );
}

export default App;

Continue with creating variables for the GPT-3 prompt:

const [prompt, setPrompt] = useState("")

Also, add this snippet to create a text field:

<TextField
  autoFocus
  fullWidth
  label="Your text"
  variant="outlined"
  multiline
  rows={4}
  margin="normal"
  value={prompt}
  onChange={(e) => {
    setPrompt(e.target.value);
  }}
/>

Create this function to handle the API call:

  async function handleSubmit() {
   return
  }

Then create a button calling the function:

<Button
  fullWidth
  disableElevation
  variant="contained"
  onClick={() => handleSubmit()}
>
  Submit
</Button>

The app should now look like this:

With all components

And here's all the code in App.js so far:

import React, { useState } from "react";
import "./App.css";

import { Box, Button, Container, Grid, TextField } from "@mui/material";

function App() {
  const [prompt, setPrompt] = useState("");

  async function handleSubmit() {}

  return (
    <Container>
      <Box sx={{ width: "100%", mt: 4 }}>
        <Grid container>
          <Grid item xs={12}>
            <TextField
              fullWidth
              autoFocus
              label="Your text"
              variant="outlined"
              multiline
              rows={4}
              margin="normal"
              value={prompt}
              onChange={(e) => {
                setPrompt(e.target.value);
              }}
            />
            <Button
              fullWidth
              disableElevation
              variant="contained"
              onClick={() => handleSubmit()}
            >
              Submit
            </Button>
          </Grid>
        </Grid>
      </Box>
    </Container>
  );
}

export default App;

Let's add another variable to track while the API call is made:

const [isLoading, setIsLoading] = useState(false)

Import an icon called AutoRenew:

import AutorenewIcon from '@mui/icons-material/Autorenew';

And add the icon to the button and add a disabled condition;

<Button
 fullWidth
 disableElevation
 variant="contained"
 disabled={isLoading}
 onClick={() => handleSubmit()}
 startIcon={
   isLoading && (
     <AutorenewIcon
       sx={{
         animation: "spin 2s linear infinite",
         "@keyframes spin": {
           "0%": {
             transform: "rotate(360deg)",
           },
           "100%": {
             transform: "rotate(0deg)",
           },
         },
       }}
     />
   )
 }
>
 Submit
</Button>

This snippet will make the icon spin while isLoading is true while the API is called.

Finally, let's add a Paper component under the button for the API response. Start with creating a new variable:

const [response, setResponse] = useState("")

And add a Paper component that gets populated once we have a response:

 <Grid item xs={12} sx={{mt:3}}>
  <Paper sx={{p:3}}>{response}</Paper>
</Grid>

We have a very simple UI finished, and we're ready to create the logic for sending an API request to OpenAI.


5. Get OpenAI API key

Before we go ahead and write the API call, let's get the OpenAI credentials needed for the API calls.

Go to https://beta.openai.com/, log in and click on your avatar and View API keys:

Open AI API keys

Then create a new secret key and save it for the request:

Create Open AI API key

Then click on Settings and save the Organization ID for your account:

Open AI organization ID

Now we have all the credentials needed to make an API request.


6. Add OpenAI configuration

Install the OpenAI Node library:

npm install openai

Then import Configuration and OpenAIApi from the library:

import { Configuration, OpenAIApi } from "openai";

Disclaimer

Do not publish an app or an extension with any of your API keys; this tutorial is just a walkthrough of creating a Chrome extension. Don't expose unencrypted API keys/secrets/credentials if you're planning to publish an app or an extension.


Initializing the library with the API key and organization id:

const configuration = new Configuration({
  apiKey: YOUR_OPENAI_API_KEY,
});

const openai = new OpenAIApi(configuration);

Now we're ready to create a completion.


7. Create GPT-3 completion

We're finally ready to finalize the handleSubmit function with the API call to GPT-3 completion.

Update the handleSubmit to this:

  async function handleSubmit() {
    setIsLoading(true);

    try {
      const completion = await openai.createCompletion({
        model: "text-davinci-002",
        prompt: prompt,
        max_tokens: 100,
      });
      setResponse(completion.data.choices[0].text);
      setIsLoading(false);
    } catch (e) {
      alert("Error: ", e);
      setIsLoading(false);
    }
  }

The response is saved in the response variable and shown right under the text field:

GPT-3 completion

Great, we have working logic to ask questions and show the results.

The last step is to create a favicon which will be the little icon for your Chrome extension, similar to these two:

Favicon example


8. Create a favicon

Let's create a favicon for our app, this will also be the little icon representing your Chrome extension.

Start by finding an image you like. I'm using this icon from https://icons8.com/ - remember to link to icons8.com if you're using icons for free. I have a monthly subscription which gives you 100 downloads/month.

Next up is to upload your image to https://realfavicongenerator.net/ - go through the steps and finally click on Generate your Favicons and HTML code:

Favicon generation

Click to download your package once it is generated:

Download favicon

Unzip the package and copy the file favicon.ico to your app folder public, this will replace the current favicon.ico:

Exchange current favicon

Also copy one of the larger png files to /public, name it icon.png as we wrote in the manifest earlier:

Add icon

If you refresh the browser, you should see the new favicon:

New favicon

Now that we have all the parts ready let's build and upload the extension to Chrome.


9. Upload extension

Start by running this command in the extension directory:

npm run build

The command will create a new /build folder in the root folder of your extension directory; this is the folder we'll upload to Chrome.

Once the build is completed, visit chrome://extensions in a new tab and click Load unpacked:

Load unpacked

Then select the /build folder that just got created:

Pick build

There you have it! If you click on your extension menu in Chrome, you'll see your little icon, click on it, and our extension popup window should look something like this:

Working extension

The only problem is that it is quite narrow. Let's fix that in the last section below.


10. UI fixes

Alrighty, the extension popup window is quite narrow, let's fix that. Open src/index.css and add width and height to the body tag:

body {
  width: 800px; /* Add width */
  height: 1000px; /* Add height */
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

That's all. Run npm run build again, then visit chrome://extensions/ and update the extension:

Update extension

The app should now be this wide:

Updated extension


11. Save the state (optional)

The app is now fully working. One thing you'll notice is that once you click away from the popup, all the input data will disappear:

Input not saved

Here's how you can save the current state in local variables. Start by saving the prompt to the local state.

Add this snippet to the onChange in your TextField component:

onChange={(e) => {
  setPrompt(e.target.value); // Keep
  chrome.storage.local.set({ prompt: e.target.value }); // Add this
}}

This snippet will store the prompt to the local variable prompt.

Except for only saving the variable in the Chrome local storage, we also want to be able to add this value to your own variable prompt if you close the popup.

Do this by adding the useEffect hook to the src/App.js file. Then add this snippet which looks for a variable prompt in the local Chrome variables and saves the value in your own variable prompt with useState:

useEffect(() => {
  try {
    chrome.storage.local.get(null, function (data) {
      if ("prompt" in data) {
        setPrompt(data.prompt);
      }
    });
  } catch (e) {
    console.log("Error due to local state");
  }
}, []);

The error message is for when you're running the app locally.

Now run npm run build again, open chrome://extensions/ and update your extension. Your prompt is now saved locally when you close the popup:

Input saved

That's everything, now you have a Chrome extension that works, and your input data is saved locally when you close your popup window. Let's try to ask some questions.


12. Ask away

Let's try the final app:

Final app


Summary

This was a quick and simple walkthrough to build your first Chrome extension.

Here are the steps we took:

1. Create a ReactJS app
2. Create extension updates
3. Install Material UI (optional)
4. Create the extension UI
5. Get OpenAI API key
6. Add OpenAI configuration
7. Create GPT-3 completion
8. Add favicon
9. Upload extension
10. UI fixes
11. Save the state (optional)
12. Ask away


Next steps

1. Repo with source code
Here is the repo with the source code if you'd like to implement this on your own ⬇️
https://github.com/norahsakal/create-gpt3-chrome-extension

2. Do you need help with building your own Chrome extension? Or do you have other questions?
I'm happy to help, don't hesitate to reach out ➡️ norah@quoter.se

3. Do you want this guide as a course?
I'm working on turning this popular blog post on Chrome extension development using GPT-3 into a course.

If you're interested, you can sign up for our waitlist to be notified when it's available and have access to additional resources and guidance ⬇️
https://gpt3-chrome-extension.carrd.co/


Get notified about new posts
Connect with me