Back to blog

Easy Programmatic SEO with WordPress

easy programmatic SEO with wordpress featured

Vilius Dumcius

Last updated -

How to

In This Article

Ready to get started?

Register now

Programmatic SEO is an effective strategy to build new sites or improve existing ones. You can use it to create useful pages for your visitors at scale, with no human input.

And WordPress is a great tool to execute this strategy.

With WordPress, programmatic SEO is made easy since you have a lot of plugins and functions to help you implement it. You don’t need to build it all from scratch - you can do it with just a couple of lines of code, or even no code at all.

Today, you will learn how to implement programmatic pages in your WordPress site using a plugin or code snippets.

If you are just starting out your journey with programmatic SEO, this is actually the fourth post of our programmatic SEO series. In it, we are creating a programmatic SEO site from scratch. Here are the four main topics we are covering in this series:

  • A programmatic SEO blueprint
    What is programmatic SEO, why you should do it, how to plan your site, and how to research keywords.  
  • How to perform automated web scraping in any language
    How to pick the right programming language and libraries depending on your target sites, how to set up the backend for automated web scraping, and the best tools for web scraping.  
  • How to build a Facebook scraper
    Here, we dive into how to collect data from our target site, how to collect data from other sites (such as Amazon), how to process data, and how to deal with errors.  
  • Easy programmatic SEO with WordPress
    In the last part of the series, you are going to learn how to use your scraped data to build a WordPress site.

Let’s get started!

The Two Ways of Implementing Programmatic Pages in WordPress

You can create programmatic sites using WordPress through two different methods. You can:

  • Create new pages programmatically as posts, pages, or custom post types
  • Use variables and rewrite rules to display as if you were creating multiple pages.

The difference between these two methods in WordPress is simple. The first method is easier to implement (almost no setup) but harder to update. That’s because you’d need to find each DB entry in WordPress to update them.

Also, since these are actual entries for a custom post type, you’ll benefit from the WordPress caching, and you don’t need to worry too much about it.

On the other hand, the second method is harder to implement but much easier to maintain. In this case, as your database updates with new scraped data, your programmatic pages are updated since they get data directly from the source.

In this case, you must take care of performance and optimization yourself or use a caching plugin.

Your Programmatic SEO Site

Just a quick recap about the programmatic SEO series. You have found a good niche with lots of keywords that are easy to target . Ideally, they have a high volume as well, but it’s okay if they don’t. In our example, we’ve found the Facebook marketplace [city] cluster. There are millions of searches for terms related to that.

Then, you find out how you can provide value to users searching these terms at scale. In our example, we are building a site that shows specific pre-filtered products, and we show listings for these products in a specific city. This saves users from searching through many listings that might not contain relevant data for them.

Therefore, the landing page for a page about Facebook marketplace in a city starts like this, showing our pre-filtered products that are available in that city:

programmatic SEO landing page mockup

And then, if you click on any of the products, you see the product details along with the listings, like this:

programmatic SEO improved landing page mockup

After you have a clear idea of your site’s objectives, you can start planning your scraping process. You can find the best tool to do it, list the data points you need, design your database structure, and start scraping data.

That’s what we did in the previous steps of this series.

Now that everything else is ready, it’s time to present this data to our visitors.

Let’s see how you can do it on a WordPress site.

Implementing the Programmatic SEO Pages With a Plugin

The logic behind using a plugin to create programmatic pages is to let the plugin load a list of items you want to create. Some plugins do it just once, and others can do it on a schedule.

So far, we have a table with our scraped data in the same MySQL database as our site. Therefore, you’d need a plugin to read this table, which can be quite challenging since most of them read data from spreadsheets.

But you can turn the scraped data table into spreadsheets and let the importer plugin do the rest.

You could use a command like this one to export tables programmatically:

SELECT * FROM scrapedData  
INTO OUTFILE '\tmp\export.csv'  
ESCAPED BY ‘”  ‘  

You could run it using a bash script or a PHP script and schedule it to run occasionally using cron.
Then, you can use a WordPress plugin such as WP All Import to read this CSV file on a schedule.
The tricky part here is updating the content after WP All import has created it.
Since there is no relation between a specific page and your scraped data entries, you’d probably need to delete the programmatic pages and recreate them whenever you need to add more data.
But there’s a simpler way to do all of this that will better suit our project. Let’s explore this other method.

Implementing the Programmatic SEO Pages by Creating Custom Template Files

The core of this method is to just load variables in a WordPress template page.

There are many ways to go about it. You could use custom post types, posts pages, template files, and more.

This is the implementation path you are using:

  • Create a new WordPress page for all programmatic items (for example /marketplace);
  • Create a new URL rewrite endpoint for two variables: city and product;
  • Detect if any of these variables are present, and load data from the SQL table if that’s the case.

Therefore, the URL bar holds all variables we need to generate the programmatic pages. First, there’s the page itself /marketplace. So when visitors go to mysite.com/marketplace, the page we have created in the wp-admin is loaded.

Next, there’s information about the city or product. You just need to read data from URLs that contain /city/[cityname]. For example, if someone goes to mysite.com/marketplace/city/new-york you know that you need to load data about New York from your SQL table.

Regarding products, they are tied to a specific city. In this case, you could use both variables in your URL or just one at a time, for example:

  • /marketplace/city/new-york/product/iphone-14
  • /marketplace/product/iphone-14-in-new-york

Notice the first example uses /city/[cityname] and /product/[productname]. On the other hand, the second example uses just /product/[product + city]. The first example is harder to implement since you need to use a custom add_rewrite_rule to handle it. So let’s create our site following the second method.

To register these URL variables in WordPress, you just need one function. It’s called add_rewrite_endpoint. You can add this code to a child theme in the functions.php file:

add_rewrite_endpoint( ‘city’, EP_PAGES );  
add_rewrite_endpoint( 'product', EP_PAGES );

This tells WordPress to store /city/[variable] in the $city query variable. The same goes for /product, which is in the $product query variable. Go to wp-admin > settings > permalinks and hit save again to flush the rewrite rules and include these items.

Make a copy of your theme’s page.php file in your child theme. Rename it to template-marketplace.php. And add this code at the beginning of that file:

    Template Name: Full Width
    The template for displaying marketplace cities and products

Now go to your wp-admin > pages and add a new one. Select this new template for it:

WordPress template

The actual code of this file depends on what you have in your theme at the moment, but let’s assume it’s a simple loop like this one:

<?php get_header(); ?>

    <div id="primary">
        <main id="main" class="site-main" role="main">

            <?php while ( have_posts() ) : the_post(); ?>

                    //content goes here
            <?php endwhile; // end of the loop. ?>

        </main><!-- #main -->
    </div><!-- #primary -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

Then let’s add some code to replace the “content goes here” comment. First, you need to read the URL variables, and you can use the get_query_vari for that:

$city = get_query_var( 'city' );
$product = get_query_var( 'product' );

Then, you need to check if one of these is present, connect to the DB, and load data for it.

If this is a /city/[cityname] page we need to:

  • Get the city data from scraped_facebook_marketplaces;
  • Get all listings from the scraped_city_product table, where the city ID is the one we have now, grouped by product_ID;
    • Left join it with the product details from the scraped_products table.

Use this code snippet to do it:

    $city = get_query_var( 'city' );

    if ( ! empty($city) ) {
        global $wpdb;

        //gets the current city
        $results = $wpdb->get_results( "SELECT * FROM scraped_facebook_marketplaces WHERE name='$city' LIMIT 1 ", ARRAY_A );

        if ( ! empty($results) ) {
            //if there is a city, let's load the listings
            foreach ( $results as $result ) {

                $cityID = $result['id'];

                //output city data code goes here
                $products = $wpdb->get_results( "SELECT * FROM scraped_city_product a LEFT JOIN ( SELECT * from scraped_products ) b ON a.product_ID = b.ID WHERE a.City_ID='$cityID' GROUP BY a.product_ID", ARRAY_A );

                if ( ! empty ( $products ) ) {
                    //if there are products, let's list them

                    foreach ($products as $product) {
                        //output product list code goes here

                } else {
                    echo "No products found";
        } else {
            echo "City not found";


Please notice that this code does not prevent SQL injection. That would be too complex for this example. Make sure to use $wpdb->prepare or similar methods on the city variable if you want to do it.

Now you can play around with outputs for the city data and the product lists using data from the arrays. The /product pages use very similar logic, but we need to extract the cityname from the product variable, and then we load products where the product ID and city ID are the ones you are looking for.

So it looks like this:

  • Extract the cityname from /product/
  • Check the city ID for that name
  • Extract the product id for that product name
  • Get all items that have the city ID and product ID requested

And here is the code to do it:

 $product = get_query_var( 'product' );
    $product = explode( "-in-", $product );

    $productName = $product[0];
    $cityName = $product[1];

    $results = $wpdb->get_results( "SELECT * FROM scraped_products WHERE name='$productName' LIMIT 1 ", ARRAY_A );

    if ( ! empty($results) ) {
        //if there are products, get the ID
        $productID = $results[0]['ID'];
    } else {
        //if there aren't, ID is zero
        $productID = 0;

    $results = $wpdb->get_results( "SELECT * FROM scraped_facebook_marketplaces WHERE name='$cityName' LIMIT 1 ", ARRAY_A );

    if ( ! empty($results) ) {
        //if there are products, get the ID
        $cityID = $results[0]['ID'];
    } else {
        //if there aren't, ID is zero
        $cityID = 0;

    if ( ! empty($cityID) && ! empty($productID) ) {
        $products = $wpdb->get_results( "SELECT * FROM scraped_city_product WHERE City_ID='$cityID' AND product_ID='$productID' ", ARRAY_A );

        if ( ! empty($products) ) {
            // products output code goes here
        } else {
            echo "No results found";
    } else {
        echo "No results found";

This code snippet uses explode to turn the product string into an array. So a product slug like “iphone-11-in-new-york” turns into two variables, “iphone-11” and “new-york”.

Then, the code looks for the correct ID in each of the tables and performs a search using both cityId and productID.
Now your programmatic page is ready to be used.
Just make sure to use a caching plugin, such as W3 Total Cache , to reduce the server load on the programmatic pages.

Final Thoughts

Today we learned how you could combine programmatic SEO with WordPress. You saw how you could create programmatic pages using different methods and how you could do it in a real example. Now you are ready to build your own programmatic SEO sites, no matter how complex you want them to be.

We hope you’ve enjoyed it, and see you again next time!


Why is one of the URL endpoints ignored when there are multiple variables?

If you try to use the add_rewrite_endpoint function and add more than one query variable at once, WordPress just loads all data for the first variable it finds. For example, /city/new-york/product/iphone-11 is understood as:

  • $city = “new-york/product/iphone-11”
  • $product = “” (empty)

There is no way to fix this with add_rewrite_endpoint alone, and you’ll need to use more complex rewrite rules using add_rewrite_rule.

Why do I get a 404 error with custom rewrite endpoints?

Sometimes you may get a 404 error with custom rewrite endpoints. This usually happens because you use the same query variable as other rewrite rules. For example, if you have a WooCommerce site, it already registers the /product/ variable. So you can’t use it in your own code. You need to change the WooCommerce variable or use a different one in your functions.

Why am I getting invalid product names and city names?

Make sure that “-in-” is never part of a product name. Otherwise this would break both the product and city search on that page.

If your product contains “-in-” as part of its name, use a method to replace it in the URL, and add a code exception in your searches or displays.

For example, if the product name is “Jack in the box”, you can use a dot while registering the URL for it (jack-in.-the-box) or something else that won’t break the -in- explode.

Create account


Vilius Dumcius

Product Owner

With six years of programming experience, Vilius specializes in full-stack web development with PHP (Laravel), MySQL, Docker, Vue.js, and Typescript. Managing a skilled team at IPRoyal for years, he excels in overseeing diverse web projects and custom solutions. Vilius plays a critical role in managing proxy-related tasks for the company, serving as the lead programmer involved in every aspect of the business. Outside of his professional duties, Vilius channels his passion for personal and professional growth, balancing his tech expertise with a commitment to continuous improvement.

Learn more about Vilius Dumcius
Share on

Related articles

Want to learn how IPRoyal can assist you in customizing Proxies on a larger scale?