You don’t have to manage many of the 800 million WordPress sites on the web before you look for ways to launch new sites efficiently.

Cloning an existing WordPress configuration is one way to get up and running quickly, and customers of Kinsta’s Managed WordPress Hosting service know that’s easily done within our user-friendly MyKinsta dashboard.

What’s more, you can clone WordPress sites at scale using your favorite application development technologies and the Kinsta API. In this tutorial, we’re using that API and React, one of many popular JavaScript libraries, to show how that works.

What You’re Building

Here’s the scenario: You’re a WordPress development agency with one or more sites that can be used as starter templates. The React application for cloning WordPress sites we are building looks like this:

React application for cloning site with Kinsta API
React application for cloning site with Kinsta API.

Prerequisites

To follow this tutorial, you will need a basic understanding of HTML, CSS, and JavaScript and some familiarity with React. Also, you’ll need Node.js and npm (the Node Package Manager) or yarn installed on your computer. The focus of this project is building a WordPress-cloning application using React and the Kinsta API rather than the details of UI creation and styling.

Setting Up the Development Environment

You can create a React application from scratch and develop your own interface, or you can grab the Git starter template mentioned above by following these steps:

  1. Visit this project’s GitHub repository.
  2. Select Use this template > Create a new repository to copy the starter code into a repository within your GitHub account. (Check the box to include all branches.)
  3. Pull the repository to your local computer and switch to the starter-files branch using the command: git checkout starter-files
  1. Install the necessary dependencies by running the command npm install

Once the installation is complete, you can launch the project on your local computer with npm run start. This opens the project at http://localhost:3000/.

Understanding the Project Files

The src folder is the heart of a React application, as it contains the JavaScript needed by webpack. In the folder is App.js, where the two routes for this project are configured.

Within the src folder are the subfolder components and pages. The components folder contains reusable components, such as the Header.jsx and Footer.jsx, used in the Home.jsx and Operations.jsx pages.

Your focus here is implementing the logic in Home.jsx and Operations.jsx, since styling and routing can be found in our GitHub starter files.

Home.jsx has a form with two fields: the name of the site you are creating and a select field that lists the WordPress sites found in your MyKinsta account (this list is fetched via the Kinsta API).

When the form’s submit button (Clone site) is clicked, an object that contains an operation_id property is returned. This ID and display name will be passed as route parameters to Operations.jsx, where the status of the cloning operation is reported. The interface will also include links to access the WordPress admin login and the site’s home page.

Operations page showing links to WP admin and site
Operations page showing links to WP admin and site.

Using the Kinsta API to Clone a WordPress Site

Within Home.jsx, three API requests will be made to the Kinsta API. The first request is to get a list of sites on your Kinsta account. This will be stored in a state and then iterated to the select field. This request will be made immediately after the page renders using the useEffect hook.

The second and third requests are made once the Clone site button is clicked. The second request gets the environment ID of the site you want to clone. The third request uses that environment ID and the site’s display name to initiate cloning of the site.

Interacting With the Kinsta API in React

In this tutorial, you interact with two endpoints of the Kinsta API:

  • /sites: This can return a list of all sites, request a site environment ID, and finally clone an existing site.
  • /operations: This is used to get the operation status. For example, when the site cloning operation is in progress, you can use this endpoint to programmatically track the status of the operation to determine when it’s finished.

To interact with the Kinsta API, you need your company ID (can be found in MyKinsta under Company > Billing Details > Company ID) and an API key. Here’s how to create a Kinsta API key.

Once you have these credentials, it’s best to store them securely as environment variables in your React application. To set up the environment variables, create a .env file in the root folder of your project. Inside this file, add the following lines:

REACT_APP_KINSTA_COMPANY_ID = 'YOUR_COMPANY_ID' 
REACT_APP_KINSTA_API_KEY = 'YOUR_API_KEY'

To access these environment variables within your project, you can use the syntax process.env.THE_VARIABLE. For example, to access the REACT_APP_KINSTA_COMPANY_ID, you would use process.env.REACT_APP_KINSTA_COMPANY_ID.

Clone an Existing Site With Kinsta API

Let’s start by fetching the list of all sites when Home.jsx renders using the useEffect Hook and storing them in a state. To achieve this, import the useEffect and useState Hooks and create a state to store the array of sites that will be fetched:

import { useState, useEffect } from 'react';
const [sites, setSites] = useState([]);

Next, use the useEffect Hook to query the Kinsta API using the JavaScript Fetch API. First, create two constant variables to store the headers and the Kinsta API URL. This is done to avoid repetition since you will send more than one request to the Kinsta API on this page:

const KinstaAPIUrl = 'https://api.kinsta.com/v2';
const headers = useMemo(() => {
    return {
        Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
    };
}, []);

In the code above, the useMemo Hook memoizes the headers object so that it doesn’t need to be re-evaluated on every render since its value is constant. Now you can create the API request:

useEffect(() => {
    const fetchAllSites = async () => {
        const query = new URLSearchParams({
            company: process.env.REACT_APP_KINSTA_COMPANY_ID,
        }).toString();
        const resp = await fetch(
            `${KinstaAPIUrl}/sites?${query}`,
            {
                method: 'GET',
                headers
            }
        );
        const data = await resp.json();
        setSites(data.company.sites);
    };
    fetchAllSites();
}, [headers]);

In the code above, an asynchronous function fetchAllSites is created. Inside this function, you first define the query parameter (your company ID) fetched from your .env file. Then, you make a GET request to the /sites endpoint of the Kinsta API using the query parameter. The response is then stored in the sites state you created earlier. Finally, you call fetchAllSites to initiate the fetching process.

Let’s now incorporate the values stored in the sites state by looping through them to populate the select field. The display name will be shown to the user, while the site ID will be used as the option value. This way, when the form is submitted, the selected site’s ID can be used to query for environment details:

<div className="input-div">
    <label>Select a site</label>
    <span>Select the site you want to clone</span>
    <select className="form-control">
        <option> value=""></option>
        {sites && (
            sites.map((site) => {
                return (
                    <option> key={site.id} value={site.id}>{site.display_name}</option>
                )
            })
        )}
    </select>
</div>

Let’s proceed to handle the form submission and retrieve values from the form. To do this, you need to create state variables for each input field:

const [selectedSiteId, setSelectedSiteId] = useState('');
const [displayName, setDisplayName] = useState('');

Next, bind the form fields to their respective state values by adding the value and onChange attributes to each input element. This is what the form will look like:

<form>
    <div className="form-container">
        <div className="input-div">
            <label>Display name</label>
            <span>Helps you identify your site. Only used in MyKinsta and temporary domain</span>
            <input type="text" className="form-control" value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
        </div>
        <div className="input-div">
            <label>Select a site</label>
            <span>Select the site you want to clone</span>
            <select className="form-control" value={selectedSiteId} onChange={(e) => setSelectedSiteId(e.target.value)}>
                <option value=""></option>
                {sites && (
                    sites.map((site) => {
                        return (
                            <option key={site.id} value={site.id}>{site.display_name}</option>
                        )
                    })
                )}
            </select>
        </div>
        <button className='btn'>Clone Site</button>
    </div>
</form>

In the code above, each input element has the value attribute set to the corresponding state variable, and the onChange attribute is used to update the state value when the user interacts with the input fields.

To handle the form submission, attach an onSubmit method to the form element. For example:

<form> onSubmit={handleSubmission}>
    {/* form details */}
</form>

Define the handleSubmission method, which involves making two API requests to the Kinsta API. The first request retrieves the environment ID of the site to be cloned, and the second request performs the clone operation.

Let’s begin with retrieving the environment ID. Inside the handleSubmission method, create an asynchronous function to handle this request. The function will send a GET request to the /sites endpoint, appending the selected site’s ID, followed by the /environments endpoint:

const handleSubmission = async (e) => {
    e.preventDefault();
    const fetchEnvironmentId = async (siteId) => {
        const resp = await fetch(
            `${KinstaAPIUrl}/sites/${siteId}/environments`,
            {
                method: 'GET',
                headers
            }
        );
        const data = await resp.json();
        let envId = data.site.environments[0].id;
        return envId;
    }
    let environmentId = await fetchEnvironmentId(selectedSiteId);
}

Above, fetchEnvironmentId is an asynchronous function that sends a GET request to the Kinsta API. It fetches the environments of the selected site and extracts the environment ID from the response. The environment ID is stored in the envId variable and then returned. When calling the function, we assign its return value to the envId variable.

At this point, you can clone an existing site with the Kinsta API because you have the essential information about the source site: company ID, display name, and environment ID.

Within the handleSubmission method, create a function called cloneExistingSite to handle this API request. This request will be to the /sites/clone endpoint. Unlike the previous requests, the headers for this request are different because you need to specify the Content-Type as application/json. Additionally, this is a POST request, so you need to include a request body containing the payload you want to send to the API. Here’s how the request will be structured:

const handleSubmission = async (e) => {
    e.preventDefault();

    // fetch environment Id

    const cloneExistingSite = async (env_Id) => {
        const resp = await fetch(
            `${KinstaAPIUrl}/sites/clone`,
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
                },
                body: JSON.stringify({
                    company: `${process.env.REACT_APP_KINSTA_COMPANY_ID}`,
                    display_name: displayName,
                    source_env_id: env_Id,
                })
            }
        );
        const data = await resp.json();
        navigate(`/operations/${displayName}/${data.operation_id}`)
        console.log(data);
    }
    cloneExistingSite(environmentId);
}

In this code, the request body is constructed using JSON.stringify() to convert the payload object into a JSON string. The response is then stored in the data variable. Using the useNavigate method from the react-router-dom library the displayName and operation_id is passed as route parameters. Ensure you import the useNaviagte method and instantiate it:

// Import the required method 
import { useNavigate } from 'react-router-dom'; 

// Instantiate the useNavigate method 
const navigate = useNavigate();

Now, when you fill out the form and click the Clone site button, a new site will begin the cloning process, which would be visible in your MyKinsta dashboard. However, we want to track the site cloning operation programmatically within the custom UI. You handle this in Operations.jsx using the data sent via the route.

Implementing Operation Status Check With Kinsta API

In Operations.jsx, retrieve the operation ID from the route using the useParams method from react-router-dom. This ID will be used to make an API request whenever the Check Site Status button is clicked.

First, import the useParams method and use it to instantiate the displayName and operationId variables:

// Import the useParams library
import { useParams } from 'react-router-dom';

// Instantiate the paramters
const { displayName, operationId } = useParams();

Next, create a state to store the operation status when the request is made:

const [operationData, setOperationData] = useState({ message: "Operation in progress." });

In the code above, the state is initialized with a default message, which will be displayed until the Check Site Status button is clicked. Add an onClick event to the Check Site Status button and call the checkOperation method when the button is clicked:

<button> className='sm-btn' onClick={() => checkOperation()}>Check Site Status</button>

Now, create the checkOperation function to make the operation request to the Kinsta API. Store the headers and KinstaAPIUrl constants in variables, and then use them in the API request:

const KinstaAPIUrl = 'https://api.kinsta.com/v2';
const headers = useMemo(() => {
    return {
        Authorization: `Bearer ${process.env.REACT_APP_KINSTA_API_KEY}`
    };
}, []);

const checkOperation = async () => {
    const resp = await fetch(
        `${KinstaAPIUrl}/operations/${operationId}`,
        {
            method: 'GET',
            headers
        }
    );
    const data = await resp.json();
    setOperationData(data);
};

In the code above, a GET request is sent to the /operations endpoint with the operation ID, and the response is stored in the operationData state. Now, you can use the data within the markup:

<div className="services">
    <div className="details">
        <p>{operationData.message}..</p>
        <button> className='sm-btn' onClick={() => checkOperation()}>Check Site Status</button>
    </div>
</div>

Finally, the displayName data passed via the route will be used to construct the URL for the new site and the WordPress admin URL. Both links will open in a new tab.

<div className="details">
    <a href={`http://${displayName}.kinsta.cloud/wp-admin/`} target="_blank" rel="noreferrer" className='detail-link'>
        <p>Open WordPress admin</p>
        <FiExternalLink />
    </a>
    <a href={`http://${displayName}.kinsta.cloud/`} target="_blank" rel="noreferrer" className='detail-link'>
        <p>Open URL</p>
        <FiExternalLink />
    </a>
</div>

With these changes, Operations.jsx will retrieve the operation ID from the route, make an API request when the button is clicked, display the operation status, and provide links to the WordPress admin and site URL based on the displayName data.

Deploy Your Application To Kinsta

To deploy your application to Kinsta’s Application Hosting platform, you need to push the project to your preferred Git provider. When your project is hosted on either GitHub, GitLab, or Bitbucket, you can proceed to deploy to Kinsta.

To deploy your repository to Kinsta, follow these steps:

  1. Log in to or create your Kinsta account on the MyKinsta dashboard.
  2. On the left sidebar, click Applications and then click Add service.
  3. Select Application From the dropdown menu to deploy a React application to Kinsta.
  4. In the modal that appears, choose the repository you want to deploy. If you have multiple branches, you can select the desired branch and give a name to your application.
  5. Select one of the available data center locations from the list of 25 options.
  6. Kinsta automatically detects the start command for your application.

Finally, it’s not safe to push out API keys to public hosts like your Git provider. When hosting, you can add them as environment variables using the same variable name and value specified in the .env file.

environment variables
Set environment variables on MyKinsta when deploying.

Once you initiate the deployment of your application, it begins the process and typically completes within a few minutes. A successful deployment generates a link to your application, like https://clone-wp-site-12teh.kinsta.app/.

Summary

The Kinsta API offers the flexibility to create custom user interfaces for managing WordPress sites, including the ability to clone existing sites and manage various aspects of your WordPress environment.

In this article, you have learned how to develop an application that enables site cloning outside of MyKinsta.

How are you using Kinsta API? What features and endpoints would you like to see added to the API? Share them in the comment section!

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.