As a Google Chrome user, you’ve probably used some extensions in that browser. Have you ever wondered how they are built or if you could build one?
This article guides you through the process of creating a Chrome extension, specifically one that uses React and the Kinsta API to manage plugins on WordPress sites hosted with Kinsta.
What is a Chrome extension?
A Chrome extension is a program installed in the Chrome browser and enhances its functionality. Extensions can range from simple icon buttons in the toolbar to fully integrated features that interact deeply with your browsing experience.
How to create a Chrome extension
Creating a Chrome extension is similar to developing a web application, but it requires a JSON-formatted file called manifest.json. This file acts as the backbone of the extension, dictating its settings, permissions, and functionalities you wish to include.
To start, create a folder that will hold all your extension files. Next, create a manifest.json file in the folder.
A basic manifest.json file for a Chrome extension includes key properties that define the extension’s basic settings. Below is an example of a manifest.json file that includes the necessary fields to make it work:
{
"manifest_version": 3,
"name": "My Chrome extension",
"version": "1.0",
"description": "Here is a description for my Chrome extension."
}
You can load and test this as an unpacked extension to Chrome. Navigate to chrome://extensions
in your browser and toggle Developer mode, then click the Load Unpacked button. This will open a file browser, and you can select the directory you created for your extension.
When you click the extension icon, nothing will happen because you have not created a user interface.
Create a user interface (popup) for your Chrome extension
Like with every web application, the user interface (UI) of your extension uses HTML to structure the content, CSS to style it, and JavaScript to add interactivity.
Let’s create a basic UI using all of these files. Start by creating an HTML file (popup.html). This file defines the structure of your UI elements, such as text, headings, images, and buttons. Add the following code:
Hello World
Hello World!
My first Chrome Extension
The code above creates a heading, paragraph, and button. The CSS and JavaScript files are also linked. Now, add some styles in the popup.css file:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background-color: aliceblue;
padding: 20px;
}
Next, in the popup.js file, add an event listener to the button so that when it is clicked, an alert is displayed:
const sayHelloBtn = document.getElementById('sayHello');
sayHelloBtn.addEventListener('click', async () => {
let tab = await chrome.tabs.query({ active: true });
chrome.scripting.executeScript({
target: { tabId: tab[0].id },
function: () => alert('Hello from the extension!'),
});
});
This JavaScript code retrieves the current active tab and uses the Chrome Scripting API to execute a script that displays an alert with a greeting message when the Say Hello button is clicked. This introduces basic interactivity to your Chrome extension.
With these steps, you have set up a simple popup UI for your Chrome extension that includes basic text, styling, and functionality.
Finally, you need to enable the popup file in the manifest.json file by adding some permissions:
{
. . . ,
"action": {
"default_popup": "popup.html"
},
"permissions": [
"scripting",
"tabs"
],
"host_permissions": [
"http://*/*",
"https://*/*"
]
}
In the configuration above, the default_popup
key specifies that popup.html will be the default UI when the user interacts with the extension. The permissions
array includes scripting
and tabs
, which are crucial for the extension to interact with the tabs and use the browser’s scripting features.
The host_permissions
array specifies which sites your extension can interact with. The patterns http://*/*
and https://*/*
indicate that your extension can interact with all websites accessed over HTTP and HTTPS protocols.
With these settings in your manifest.json file, your Chrome extension is properly configured to display a popup and execute scripts.
Reload your Chrome extension
With these changes effected in your local folder, you need to update the unpacked folder loaded to Chrome. To do this, open the Chrome extensions page, find your extension, and click the reload icon.
You can then click the extension icon, and a popup will appear. When you click the Say Hello button, an alert will appear.
You now have a basic knowledge of how to begin building a Chrome extension. There is more that can be done. You can manipulate your site’s UI, make API requests, retrieve data from URLs to perform specific operations, and more.
How to create a Chrome extension with React
As we mentioned earlier, creating a Chrome extension is similar to building a web application. You can use popular web frameworks like React.
For React, the manifest.json file is created in the public folder. This folder is used for static assets you do not want to be processed by Webpack (or similar bundlers that React might use under the hood in tools like Create React App).
When you build your React application, the build process copies all contents of the public folder into the dist folder. Here is how to create a Chrome extension with React:
- Create a new React application. You can use the local development environment Vite by running the following command in your terminal:
npm create vite@latest
Next, give your project a name and select React as the framework. Once this is done, navigate into the project folder and install the dependencies:
cd
npm install
- In your React project’s public folder, create a manifest.json file. Add the following configurations:
{
"manifest_version": 3,
"name": "React Chrome extension",
"description": "Chrome extension built with React",
"version": "0.1.0",
"action": {
"default_popup": "index.html"
},
"permissions": [
"tabs"
],
"host_permissions": [
"http://*/*",
"https://*/*"
]
}
The configuration for a Chrome extension includes an action
object that sets index.html as the default popup when the extension icon is clicked. This is the static HTML file generated when you build your React application.
- Develop the React application. Feel free to make API requests, style them as you wish, use React Hooks, and more.
- When you are done building the UI of the extension, run the build command in React (
npm run build
). All your assets, including your manifest.json file, React-generated index.html, and others, are moved into the dist or build folder. - Finally, load your extension into Chrome. Navigate to
chrome://extensions/
and reload your extension.
Creating a Chrome extension to manage your site’s plugins with Kinsta API
This is what the Chrome extension you’ll build will look like:
When clicked, the extension displays a list of sites with outdated plugins on your MyKinsta account. You can see a list of the plugins and click the View in MyKinsta button to navigate to the site’s Themes & Plugins page, where you can update each plugin.
Let’s explore how to create the Chrome extension.
Understanding the Kinsta API
The Kinsta API is a powerful tool that allows you to interact programmatically with Kinsta services like hosted WordPress sites. It can help automate various tasks related to WordPress management, including site creation, retrieving site information, getting a site’s status, browsing and restoring backups, and more.
To use Kinsta’s API, you must have an account with at least one WordPress site, application, or database in MyKinsta. You must also generate an API key to authenticate and access your account.
To generate an API key:
- Go to your MyKinsta dashboard.
- Navigate to the API Keys page (Your name > Company settings > API Keys).
- Click Create API Key.
- Choose an expiration or set a custom start date and number of hours for the key to expire.
- Give the key a unique name.
- Click Generate.
After creating an API key, copy it and store it somewhere safe (using a password manager is recommended). You can generate multiple API keys, which will be listed on the API Keys page. If you need to revoke an API key, click the Revoke button.
Manage your site’s plugins with Kinsta API and React
Let’s start by developing a user interface in React, which will then be transformed into a Chrome extension. This guide assumes basic familiarity with React and API interaction.
Setting up the environment
Firstly, in the App.jsx file, define a constant for the Kinsta API URL to avoid redundancy in your code:
const KinstaAPIUrl="https://api.kinsta.com/v2";
For security, store sensitive data such as your API key and Kinsta company ID in a .env.local file to keep them secure and out of your source code:
VITE_KINSTA_COMPANY_ID=YOUR_COMPANY_ID
VITE_KINSTA_API_KEY=YOUR_API_KEY
Fetch data with Kinsta API
In the App.jsx file, you need to make several requests to the Kinsta API to retrieve information about sites and their plugins.
- Retrieve company sites: Begin by fetching a list of sites associated with your Kinsta company account. Use the company ID in a GET request, which returns an array of site details.
const getListOfCompanySites = async () => { const query = new URLSearchParams({ company: import.meta.env.VITE_KINSTA_COMPANY_ID, }).toString(); const resp = await fetch(`${KinstaAPIUrl}/sites?${query}`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, }); const data = await resp.json(); const companySites = data.company.sites; return companySites; }
- Fetch environment data for each site: For each site, retrieve the environments, which include the environment ID necessary for further requests. This involves mapping over each site and making an API call to the
/sites/${siteId}/environments
endpoint.const companySites = await getListOfCompanySites(); // Get all environments for each site const sitesEnvironmentData = companySites.map(async (site) => { const siteId = site.id; const resp = await fetch(`${KinstaAPIUrl}/sites/${siteId}/environments`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, }); const data = await resp.json(); const environments = data.site.environments; return { id: siteId, name: site.display_name, environments: environments, }; });
- Retrieve plugins for each site environment: Finally, use the environment ID to fetch plugins for each site. This step involves a mapping function and an API call to the
/sites/environments/${environmentId}/plugins
endpoint for each environment.// Wait for all the promises to resolve const sitesData = await Promise.all(sitesEnvironmentData); // Get all plugins for each environment const sitesWithPlugin = sitesData.map(async (site) => { const environmentId = site.environments[0].id; const resp = await fetch( `${KinstaAPIUrl}/sites/environments/${environmentId}/plugins`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, } ); const data = await resp.json(); const plugins = data.environment.container_info; return { env_id: environmentId, name: site.name, site_id: site.id, plugins: plugins, }; });
You can now put all of these requests together into a function that is used to return the final array of sites with basic details about each site and its plugins:
const getSitesWithPluginData = async () => { const getListOfCompanySites = async () => { const query = new URLSearchParams({ company: import.meta.env.VITE_KINSTA_COMPANY_ID, }).toString(); const resp = await fetch(`${KinstaAPIUrl}/sites?${query}`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, }); const data = await resp.json(); const companySites = data.company.sites; return companySites; } const companySites = await getListOfCompanySites(); // Get all environments for each site const sitesEnvironmentData = companySites.map(async (site) => { const siteId = site.id; const resp = await fetch(`${KinstaAPIUrl}/sites/${siteId}/environments`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, }); const data = await resp.json(); const environments = data.site.environments; return { id: siteId, name: site.display_name, environments: environments, }; }); // Wait for all the promises to resolve const sitesData = await Promise.all(sitesEnvironmentData); // Get all plugins for each environment const sitesWithPlugin = sitesData.map(async (site) => { const environmentId = site.environments[0].id; const resp = await fetch( `${KinstaAPIUrl}/sites/environments/${environmentId}/plugins`, { method: 'GET', headers: { Authorization: `Bearer ${import.meta.env.VITE_KINSTA_API_KEY}`, }, } ); const data = await resp.json(); const plugins = data.environment.container_info; return { env_id: environmentId, name: site.name, site_id: site.id, plugins: plugins, }; }); // Wait for all the promises to resolve const sitesWithPluginData = await Promise.all(sitesWithPlugin); return sitesWithPluginData; }
Displaying site data
Create a state with the useState
hook to store sites with outdated plugin(s). The useEffect
hook will also call the getSitesWithPluginData()
method and extract the site details when the component is mounted.
In the useEffect
hook, create a function that will loop through each site to filter out sites with outdated plugins and then store them in the state:
const [sitesWithOutdatedPlugin, setSitesWithOutdatedPlugin] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const checkSitesWithPluginUpdate = async () => {
const sitesWithPluginData = await getSitesWithPluginData();
const sitesWithOutdatedPlugin = sitesWithPluginData.map((site) => {
const plugins = site.plugins.wp_plugins.data;
const outdatedPlugins = plugins.filter((plugin) => plugin.update === "available");
if (outdatedPlugins.length > 0) {
const kinstaDashboardPluginPageURL = `https://my.kinsta.com/sites/plugins/${site.site_id}/${site.env_id}?idCompany=${import.meta.env.VITE_KINSTA_COMPANY_ID}`;
return {
name: site.name,
plugins: outdatedPlugins,
url: kinstaDashboardPluginPageURL,
};
}
});
setSitesWithOutdatedPlugin(sitesWithOutdatedPlugin);
checkSitesWithPluginUpdate();
setIsLoading(false);
}, []);
In the code above, you notice that the loading state is also created and set to true
by default. This will be used to control how data is displayed. When all the data is loaded, we set it to false
.
Below is a markup to render the site data and plugins within your UI.
import { useEffect, useState } from "react"
import KinstaLogo from './assets/kinsta-logo.png'
import PluginPage from './components/PluginsPage'
function App() {
// load the data from the API
return (
className="title-section">
className="info-box">
Get quick information about your site plugins that need update.
{isLoading ? (
Loading...
) : (
<>
The following sites have plugins that need to be updated.
{sitesWithOutdatedPlugin.map((site, index) => {
return (
);
})}
>
)}
)
}
export default App
The code includes a header with a logo and an informational paragraph. The content of the UI is conditionally rendered based on the isLoading
state. If data is still loading, it displays a loading message. Once data is loaded, it presents the data about the sites and any plugins requiring updates.
You will also notice a component: PluginPage
(PluginPage.jsx). This component is designed to display individual sites and their plugin details. It includes a functionality to toggle the visibility of the plugin details.
import { useState } from "react"
import { FaRegEye } from "react-icons/fa";
import { FaRegEyeSlash } from "react-icons/fa";
const PluginUse = (site) => {
const [viewPlugin, setViewPlugin] = useState(false);
return (
<>
{viewPlugin && (
{site.plugins.map((plugin, index) => {
return (
{plugin.name}
Current Version: {plugin.version}
Latest Version: {plugin.update_version}
);
})}
)}
>
)
}
export default PluginUse
Configure the manifest file
To transform your user interface and functionality into a Chrome extension, you need to configure the manifest.json file.
Create a manifest.json file in the public folder and paste the code below:
{
"manifest_version": 3,
"name": "Kinsta Plugins Manager - Thanks to Kinsta API",
"description": "This extension allows you to manage your WordPress site's plugin from Kinsta's MyKinsta dashboard via Kinsta API.",
"version": "0.1.0",
"icons": {
"48": "kinsta-icon.png"
},
"action": {
"default_popup": "index.html"
},
"permissions": [
"tabs"
],
"host_permissions": [
"https://my.kinsta.com/*"
]
}
Be sure to add the icon file to your public folder.
At this point, you can now run the build command (npm run build
) so all your assets, including your manifest.json file, React-generated index.html, and other files, are moved into the dist or build folder.
Next, navigate to chrome://extensions/
and load this as an unpacked extension to Chrome. Click the Load Unpacked button and select the directory you created for your extension.
Restrict extension to specific sites
You notice that this extension works at any time. We want it to work only when a user is navigated to the MyKinsta dashboard.
To do this, let’s adjust the App.jsx file. Create a state to store the active tab:
const [activeTab, setActiveTab] = useState(null);
Next, update the useEffect
Hook to define and invoke the getCurrentTab
function:
const getCurrentTab = async () => {
const queryOptions = { active: true, currentWindow: true };
const [tab] = await chrome.tabs.query(queryOptions);
setActiveTab(tab);
}
getCurrentTab();
The above code uses chrome.tabs.query
with specific query options to ensure it retrieves only the active tab in the current window. Once the tab is retrieved, it’s set as the active tab within the extension’s state.
Finally, implement a conditional rendering logic in your component’s return statement. This ensures that the plugin management UI appears only when the user is on the MyKinsta dashboard:
return (
{activeTab?.url.includes('my.kinsta.com') ? (
Get quick information about your site plugins that need update.
{isLoading ? (
Loading...
) : (
<>
The following {sitesWithPluginUpdate} sites have plugins that need to be updated.
{sitesWithOutdatedPlugin.map((site, index) => {
return (
);
})}
>
)}
) : (
This extension is only available on Kinsta Dashboard.
)}
)
After making the changes, rebuild your application and reload the Chrome extension. This will apply the new logic and restrictions.
Summary
In this article, you have learned the basics of creating a Chrome extension and how to create one with React. You’ve also learned how to create an extension that interacts with the Kinsta API.
As a Kinsta user, you can take advantage of the enormous potential and flexibility the Kinsta API brings as it helps you develop custom solutions to manage your sites, applications, and databases.
What endpoint of the Kinsta API have you been using a lot, and how have you used it? Share with us in the comment section!