DEV Community

Cover image for CLI Workflow for Generating WordPress Template Parts
Martijn Assie
Martijn Assie

Posted on

CLI Workflow for Generating WordPress Template Parts

Developer problem: "I need to create header/footer/sidebar for 15 client themes!!"

Manual approach:

  • Create header.php in each theme
  • Copy/paste navigation code
  • Create footer.php in each theme
  • Copy/paste widget areas
  • 4 hours of repetitive work!!

My CLI approach:

  • Bash script + WP-CLI
  • Generate all template parts automatically
  • Consistent structure across themes
  • Done in 5 minutes!!

Here's how to automate WordPress template part generation with WP-CLI:

The Template Parts Problem

Block themes require:

  • /parts/header.html
  • /parts/footer.html
  • /parts/sidebar.html
  • /parts/navigation.html

Classic themes require:

  • header.php
  • footer.php
  • sidebar.php
  • navigation.php

For 15 client themes:

  • 60 files to create manually
  • Copy/paste navigation menus
  • Copy/paste widget areas
  • Update theme-specific details
  • Massive time waste!!

Solution: WP-CLI + Bash Automation

The CLI Workflow

# Generate all template parts for a theme
./generate-template-parts.sh my-theme

# Output:
# ✓ Created /parts/header.html
# ✓ Created /parts/footer.html
# ✓ Created /parts/sidebar.html
# ✓ Created /parts/navigation.html
# Done in 8 seconds!!
Enter fullscreen mode Exit fullscreen mode

Setting Up WP-CLI

Install WP-CLI

# Download WP-CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

# Make executable
chmod +x wp-cli.phar

# Move to system path
sudo mv wp-cli.phar /usr/local/bin/wp

# Verify installation
wp --version
# WP-CLI 2.10.0
Enter fullscreen mode Exit fullscreen mode

Test WP-CLI Access

# Navigate to WordPress installation
cd /var/www/html

# List installed themes
wp theme list

# Output:
# +-----------------+----------+--------+---------+
# | name            | status   | update | version |
# +-----------------+----------+--------+---------+
# | twentytwentyfour| active   | none   | 1.0     |
# | my-theme        | inactive | none   | 1.0.0   |
# +-----------------+----------+--------+---------+
Enter fullscreen mode Exit fullscreen mode

Block Theme Template Parts Generator

Script: generate-block-parts.sh

#!/bin/bash

# Usage: ./generate-block-parts.sh my-theme

THEME_SLUG=$1
THEME_DIR="/var/www/html/wp-content/themes/${THEME_SLUG}"
PARTS_DIR="${THEME_DIR}/parts"

# Validate theme exists
if [ ! -d "$THEME_DIR" ]; then
    echo "Error: Theme '${THEME_SLUG}' not found!"
    exit 1
fi

# Create parts directory
mkdir -p "$PARTS_DIR"

echo "Generating template parts for theme: ${THEME_SLUG}"

# Generate header.html
cat > "${PARTS_DIR}/header.html" << 'EOF'
<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"20px","bottom":"20px"}}},"layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull" style="padding-top:20px;padding-bottom:20px">
    <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} -->
    <div class="wp-block-group">
        <!-- wp:site-logo {"width":120} /-->

        <!-- wp:group {"layout":{"type":"flex"}} -->
        <div class="wp-block-group">
            <!-- wp:navigation {"ref":123,"layout":{"type":"flex","setCascadingProperties":true,"justifyContent":"right"}} /-->

            <!-- wp:buttons -->
            <div class="wp-block-buttons">
                <!-- wp:button -->
                <div class="wp-block-button">
                    <a class="wp-block-button__link wp-element-button">Get Started</a>
                </div>
                <!-- /wp:button -->
            </div>
            <!-- /wp:buttons -->
        </div>
        <!-- /wp:group -->
    </div>
    <!-- /wp:group -->
</div>
<!-- /wp:group -->
EOF

echo "✓ Created ${PARTS_DIR}/header.html"

# Generate footer.html
cat > "${PARTS_DIR}/footer.html" << 'EOF'
<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"60px","bottom":"40px"}},"elements":{"link":{"color":{"text":"var:preset|color|base"}}}},"backgroundColor":"contrast","textColor":"base","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull has-base-color has-contrast-background-color has-text-color has-background has-link-color" style="padding-top:60px;padding-bottom:40px">
    <!-- wp:columns -->
    <div class="wp-block-columns">
        <!-- wp:column -->
        <div class="wp-block-column">
            <!-- wp:heading {"level":4} -->
            <h4>About</h4>
            <!-- /wp:heading -->

            <!-- wp:paragraph -->
            <p>Company description goes here.</p>
            <!-- /wp:paragraph -->
        </div>
        <!-- /wp:column -->

        <!-- wp:column -->
        <div class="wp-block-column">
            <!-- wp:heading {"level":4} -->
            <h4>Quick Links</h4>
            <!-- /wp:heading -->

            <!-- wp:navigation {"ref":456,"orientation":"vertical"} /-->
        </div>
        <!-- /wp:column -->

        <!-- wp:column -->
        <div class="wp-block-column">
            <!-- wp:heading {"level":4} -->
            <h4>Contact</h4>
            <!-- /wp:heading -->

            <!-- wp:paragraph -->
            <p>Email: info@example.com<br>Phone: (555) 123-4567</p>
            <!-- /wp:paragraph -->
        </div>
        <!-- /wp:column -->
    </div>
    <!-- /wp:columns -->

    <!-- wp:separator {"style":{"spacing":{"margin":{"top":"40px","bottom":"20px"}}}} -->
    <hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:40px;margin-bottom:20px"/>
    <!-- /wp:separator -->

    <!-- wp:group {"layout":{"type":"flex","flexWrap":"nowrap","justifyContent":"space-between"}} -->
    <div class="wp-block-group">
        <!-- wp:paragraph -->
        <p>© 2026 Company Name. All rights reserved.</p>
        <!-- /wp:paragraph -->

        <!-- wp:social-links -->
        <ul class="wp-block-social-links">
            <!-- wp:social-link {"url":"https://twitter.com/","service":"twitter"} /-->
            <!-- wp:social-link {"url":"https://facebook.com/","service":"facebook"} /-->
            <!-- wp:social-link {"url":"https://instagram.com/","service":"instagram"} /-->
        </ul>
        <!-- /wp:social-links -->
    </div>
    <!-- /wp:group -->
</div>
<!-- /wp:group -->
EOF

echo "✓ Created ${PARTS_DIR}/footer.html"

# Generate sidebar.html
cat > "${PARTS_DIR}/sidebar.html" << 'EOF'
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
    <!-- wp:heading {"level":3} -->
    <h3>Sidebar</h3>
    <!-- /wp:heading -->

    <!-- wp:search {"label":"Search","showLabel":false,"buttonText":"Search"} /-->

    <!-- wp:separator {"style":{"spacing":{"margin":{"top":"30px","bottom":"30px"}}}} -->
    <hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:30px;margin-bottom:30px"/>
    <!-- /wp:separator -->

    <!-- wp:latest-posts {"postsToShow":5,"displayPostDate":true,"displayFeaturedImage":true,"featuredImageSizeSlug":"thumbnail"} /-->

    <!-- wp:separator {"style":{"spacing":{"margin":{"top":"30px","bottom":"30px"}}}} -->
    <hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:30px;margin-bottom:30px"/>
    <!-- /wp:separator -->

    <!-- wp:categories /-->

    <!-- wp:separator {"style":{"spacing":{"margin":{"top":"30px","bottom":"30px"}}}} -->
    <hr class="wp-block-separator has-alpha-channel-opacity" style="margin-top:30px;margin-bottom:30px"/>
    <!-- /wp:separator -->

    <!-- wp:tag-cloud /-->
</div>
<!-- /wp:group -->
EOF

echo "✓ Created ${PARTS_DIR}/sidebar.html"

# Generate navigation.html
cat > "${PARTS_DIR}/navigation.html" << 'EOF'
<!-- wp:navigation {"ref":789,"layout":{"type":"flex","setCascadingProperties":true,"justifyContent":"center"}} /-->
EOF

echo "✓ Created ${PARTS_DIR}/navigation.html"

echo ""
echo "✅ All template parts generated successfully!"
echo "Location: ${PARTS_DIR}"
Enter fullscreen mode Exit fullscreen mode

Make Script Executable

chmod +x generate-block-parts.sh
Enter fullscreen mode Exit fullscreen mode

Run Script

./generate-block-parts.sh my-theme

[![ ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1w9tyjbhofyq8hpzhybn.webp)](https://www.elegantthemes.com/affiliates/idevaffiliate.php?id=53476&url=73342)

# Output:
# Generating template parts for theme: my-theme
# ✓ Created /var/www/html/wp-content/themes/my-theme/parts/header.html
# ✓ Created /var/www/html/wp-content/themes/my-theme/parts/footer.html
# ✓ Created /var/www/html/wp-content/themes/my-theme/parts/sidebar.html
# ✓ Created /var/www/html/wp-content/themes/my-theme/parts/navigation.html
#
# ✅ All template parts generated successfully!
Enter fullscreen mode Exit fullscreen mode

If you're building child themes for popular themes, check out my guide on Building a Child Theme for Avada: The Right Way.

Classic Theme Template Parts Generator

Script: generate-classic-parts.sh

#!/bin/bash

# Usage: ./generate-classic-parts.sh my-classic-theme

THEME_SLUG=$1
THEME_DIR="/var/www/html/wp-content/themes/${THEME_SLUG}"

if [ ! -d "$THEME_DIR" ]; then
    echo "Error: Theme '${THEME_SLUG}' not found!"
    exit 1
fi

echo "Generating classic template parts for theme: ${THEME_SLUG}"

# Generate header.php
cat > "${THEME_DIR}/header.php" << 'EOF'
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>

<div id="page" class="site">
    <header id="masthead" class="site-header">
        <div class="container">
            <div class="site-branding">
                <?php
                if (has_custom_logo()) {
                    the_custom_logo();
                } else {
                    ?>
                    <h1 class="site-title">
                        <a href="<?php echo esc_url(home_url('/')); ?>">
                            <?php bloginfo('name'); ?>
                        </a>
                    </h1>
                    <?php
                }
                ?>
            </div>

            <nav id="site-navigation" class="main-navigation">
                <?php
                wp_nav_menu(array(
                    'theme_location' => 'primary',
                    'menu_id'        => 'primary-menu',
                    'container'      => false,
                ));
                ?>
            </nav>
        </div>
    </header>

    <main id="primary" class="site-main">
EOF

echo "✓ Created ${THEME_DIR}/header.php"

[![ ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1w9tyjbhofyq8hpzhybn.webp)](https://www.elegantthemes.com/affiliates/idevaffiliate.php?id=53476&url=73342)

# Generate footer.php
cat > "${THEME_DIR}/footer.php" << 'EOF'
    </main><!-- #primary -->

    <footer id="colophon" class="site-footer">
        <div class="container">
            <div class="footer-widgets">
                <div class="footer-column">
                    <?php dynamic_sidebar('footer-1'); ?>
                </div>

                <div class="footer-column">
                    <?php dynamic_sidebar('footer-2'); ?>
                </div>

                <div class="footer-column">
                    <?php dynamic_sidebar('footer-3'); ?>
                </div>
            </div>

            <div class="site-info">
                <p>
                    &copy; <?php echo date('Y'); ?> 
                    <a href="<?php echo esc_url(home_url('/')); ?>">
                        <?php bloginfo('name'); ?>
                    </a>. 
                    All rights reserved.
                </p>
            </div>
        </div>
    </footer>
</div><!-- #page -->

<?php wp_footer(); ?>
</body>
</html>
EOF

echo "✓ Created ${THEME_DIR}/footer.php"

# Generate sidebar.php
cat > "${THEME_DIR}/sidebar.php" << 'EOF'
<aside id="secondary" class="widget-area">
    <?php if (is_active_sidebar('sidebar-1')) : ?>
        <?php dynamic_sidebar('sidebar-1'); ?>
    <?php else : ?>

        <section class="widget widget_search">
            <?php get_search_form(); ?>
        </section>

        <section class="widget widget_recent_entries">
            <h2 class="widget-title">Recent Posts</h2>
            <?php
            wp_get_archives(array(
                'type'            => 'postbypost',
                'limit'           => 5,
                'format'          => 'html',
                'show_post_count' => true
            ));
            ?>
        </section>

        <section class="widget widget_categories">
            <h2 class="widget-title">Categories</h2>
            <ul>
                <?php wp_list_categories(array(
                    'title_li' => '',
                    'show_count' => true
                )); ?>
            </ul>
        </section>

    <?php endif; ?>
</aside>
EOF

echo "✓ Created ${THEME_DIR}/sidebar.php"

# Generate navigation.php (template part)
cat > "${THEME_DIR}/navigation.php" << 'EOF'
<nav class="navigation pagination">
    <h2 class="screen-reader-text">Posts navigation</h2>
    <div class="nav-links">
        <?php
        global $wp_query;

        $big = 999999999;

        echo paginate_links(array(
            'base'      => str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
            'format'    => '?paged=%#%',
            'current'   => max(1, get_query_var('paged')),
            'total'     => $wp_query->max_num_pages,
            'prev_text' => '<span aria-hidden="true">&laquo;</span> Previous',
            'next_text' => 'Next <span aria-hidden="true">&raquo;</span>',
        ));
        ?>
    </div>
</nav>
EOF

echo "✓ Created ${THEME_DIR}/navigation.php"

echo ""
echo "✅ All classic template parts generated successfully!"
Enter fullscreen mode Exit fullscreen mode

For optimizing classic theme performance, see my article on Avada vs Elementor: Which Page Builder Wins.

Advanced: Multi-Theme Generator

Batch Process Multiple Themes

#!/bin/bash

# generate-all-themes.sh
# Generate template parts for all client themes

THEMES=(
    "client-a-theme"
    "client-b-theme"
    "client-c-theme"
    "agency-portfolio"
    "ecommerce-store"
)

for theme in "${THEMES[@]}"; do
    echo ""
    echo "=== Processing: $theme ==="
    ./generate-block-parts.sh "$theme"

    # Also generate functions.php additions
    ./add-template-support.sh "$theme"
done

echo ""
echo "🎉 Generated template parts for ${#THEMES[@]} themes!"
Enter fullscreen mode Exit fullscreen mode

Add Theme Support Automatically

#!/bin/bash

# add-template-support.sh
# Add required theme support for template parts

THEME_SLUG=$1
FUNCTIONS_FILE="/var/www/html/wp-content/themes/${THEME_SLUG}/functions.php"

if [ ! -f "$FUNCTIONS_FILE" ]; then
    echo "Error: functions.php not found for theme '${THEME_SLUG}'!"
    exit 1
fi

# Backup original functions.php
cp "$FUNCTIONS_FILE" "${FUNCTIONS_FILE}.backup"

# Append template part registration
cat >> "$FUNCTIONS_FILE" << 'EOF'

/**
 * Register navigation menus
 */
function theme_register_nav_menus() {
    register_nav_menus(array(
        'primary' => __('Primary Menu', 'theme-textdomain'),
        'footer'  => __('Footer Menu', 'theme-textdomain'),
    ));
}
add_action('after_setup_theme', 'theme_register_nav_menus');

/**
 * Register widget areas
 */
function theme_widgets_init() {
    register_sidebar(array(
        'name'          => __('Sidebar', 'theme-textdomain'),
        'id'            => 'sidebar-1',
        'before_widget' => '<section id="%1$s" class="widget %2$s">',
        'after_widget'  => '</section>',
        'before_title'  => '<h2 class="widget-title">',
        'after_title'   => '</h2>',
    ));

    register_sidebar(array(
        'name'          => __('Footer 1', 'theme-textdomain'),
        'id'            => 'footer-1',
        'before_widget' => '<section id="%1$s" class="widget %2$s">',
        'after_widget'  => '</section>',
        'before_title'  => '<h2 class="widget-title">',
        'after_title'   => '</h2>',
    ));

    register_sidebar(array(
        'name'          => __('Footer 2', 'theme-textdomain'),
        'id'            => 'footer-2',
        'before_widget' => '<section id="%1$s" class="widget %2$s">',
        'after_widget'  => '</section>',
        'before_title'  => '<h2 class="widget-title">',
        'after_title'   => '</h2>',
    ));

    register_sidebar(array(
        'name'          => __('Footer 3', 'theme-textdomain'),
        'id'            => 'footer-3',
        'before_widget' => '<section id="%1$s" class="widget %2$s">',
        'after_widget'  => '</section>',
        'before_title'  => '<h2 class="widget-title">',
        'after_title'   => '</h2>',
    ));
}
add_action('widgets_init', 'theme_widgets_init');
EOF

echo "✓ Added theme support to functions.php"
echo "✓ Backup created: ${FUNCTIONS_FILE}.backup"
Enter fullscreen mode Exit fullscreen mode

WP-CLI Integration

Create Navigation Menus Automatically

#!/bin/bash

# create-menus.sh
# Create and assign navigation menus via WP-CLI

THEME_SLUG=$1

echo "Creating navigation menus for theme: ${THEME_SLUG}"

# Create Primary Menu
PRIMARY_MENU_ID=$(wp menu create "Primary Menu" --porcelain)
echo "✓ Created Primary Menu (ID: ${PRIMARY_MENU_ID})"

# Add menu items
wp menu item add-post $PRIMARY_MENU_ID 1 --title="Home"
wp menu item add-post $PRIMARY_MENU_ID 2 --title="About"
wp menu item add-post $PRIMARY_MENU_ID 3 --title="Services"
wp menu item add-post $PRIMARY_MENU_ID 4 --title="Blog"
wp menu item add-post $PRIMARY_MENU_ID 5 --title="Contact"

echo "✓ Added 5 menu items to Primary Menu"

# Assign menu to location
wp menu location assign $PRIMARY_MENU_ID primary

echo "✓ Assigned Primary Menu to 'primary' location"

# Create Footer Menu
FOOTER_MENU_ID=$(wp menu create "Footer Menu" --porcelain)
echo "✓ Created Footer Menu (ID: ${FOOTER_MENU_ID})"

# Add footer menu items
wp menu item add-custom $FOOTER_MENU_ID "Privacy Policy" "/privacy-policy"
wp menu item add-custom $FOOTER_MENU_ID "Terms of Service" "/terms"
wp menu item add-custom $FOOTER_MENU_ID "Sitemap" "/sitemap"

echo "✓ Added 3 menu items to Footer Menu"

# Assign footer menu
wp menu location assign $FOOTER_MENU_ID footer

echo "✓ Assigned Footer Menu to 'footer' location"

echo ""
echo "✅ Navigation menus created and assigned!"
Enter fullscreen mode Exit fullscreen mode

Complete Workflow Script

#!/bin/bash

# complete-theme-setup.sh
# Complete theme setup: parts + support + menus

THEME_SLUG=$1

if [ -z "$THEME_SLUG" ]; then
    echo "Usage: ./complete-theme-setup.sh <theme-slug>"
    exit 1
fi

echo "========================================="
echo "Complete Theme Setup: ${THEME_SLUG}"
echo "========================================="
echo ""

# Step 1: Generate template parts
echo "Step 1: Generating template parts..."
./generate-block-parts.sh "$THEME_SLUG"
echo ""

# Step 2: Add theme support
echo "Step 2: Adding theme support..."
./add-template-support.sh "$THEME_SLUG"
echo ""

# Step 3: Create navigation menus
echo "Step 3: Creating navigation menus..."
./create-menus.sh "$THEME_SLUG"
echo ""

# Step 4: Activate theme
echo "Step 4: Activating theme..."
wp theme activate "$THEME_SLUG"
echo "✓ Theme activated"
echo ""

echo "========================================="
echo "✅ Theme setup complete!"
echo "========================================="
echo ""
echo "Next steps:"
echo "1. Visit Site Editor to customize header/footer"
echo "2. Add content to widget areas"
echo "3. Configure theme.json settings"
Enter fullscreen mode Exit fullscreen mode

For more on theme comparison and setup, check out Divi vs Avada: Why ThemeForest's #1 Seller Costs 5x More Long-Term.

CI/CD Integration

GitHub Actions Workflow

.github/workflows/theme-setup.yml:

name: Theme Setup

on:
  workflow_dispatch:
    inputs:
      theme_slug:
        description: 'Theme slug to set up'
        required: true

jobs:
  setup-theme:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup SSH
        uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}

      - name: Run theme setup
        run: |
          ssh user@server "cd /var/www/html && ./complete-theme-setup.sh ${{ github.event.inputs.theme_slug }}"

      - name: Notify Slack
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Theme ${{ github.event.inputs.theme_slug }} setup complete!'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Enter fullscreen mode Exit fullscreen mode

Real-World Scenario: Agency Workflow

Agency: 15 new client themes per month

Before automation:

  • Create header.php manually (15 × 30 min = 7.5 hours)
  • Create footer.php manually (15 × 30 min = 7.5 hours)
  • Create sidebar.php manually (15 × 20 min = 5 hours)
  • Create navigation menus (15 × 15 min = 3.75 hours)
  • 23.75 hours per month!!

With CLI automation:

# Setup all 15 client themes
for i in {1..15}; do
    ./complete-theme-setup.sh "client-theme-$i"
done

# Total time: 15 × 1 minute = 15 minutes!!
Enter fullscreen mode Exit fullscreen mode

Time savings: 23.75 hours → 15 minutes = 95% faster!!

ROI: Developer at $100/hr × 23.75 hours = $2,375 saved per month!!

Bottom Line

Stop manually creating template parts for every theme!!

CLI automation wins:

  • Bash scripts + WP-CLI
  • Generate all parts in seconds
  • Consistent structure across themes
  • 15 minutes vs 24 hours = massive savings!!

My agency results:

Before:

  • 4 hours per theme setup
  • Inconsistent template structure
  • Copy/paste errors

After:

  • 1 minute automated setup
  • Perfect consistency across 50+ themes
  • Zero copy/paste errors!!

Setup time: 2 hours to create scripts

ROI: First month saved 23+ hours = instant payoff!!

For agencies building multiple themes: Template part automation is ESSENTIAL!!

Generate 60 template files automatically vs creating them manually = 95% time savings!! 🚀

This article contains affiliate links!

Top comments (1)

Collapse
 
bhavin-allinonetools profile image
Bhavin Sheth

This is the kind of automation most WordPress developers don’t realize they need until they’ve wasted hours copy-pasting the same files.

Using WP-CLI + bash to standardize theme parts across clients is a huge time saver and also removes human errors. Really practical workflow — especially for agencies handling multiple themes every month.