Skip to content
Nick Diego

Stop struggling with cumbersome theme.json files

This week, during WooCommerce Office Hours, I chatted with a community member about the challenges of managing large, cumbersome theme.json files—something I can definitely relate to. I'm currently working on a new WordPress block theme, and editing a 900+ line file has been a real struggle.

There’s no official Core solution, but I knew a compiler/bundler could make things much easier. This would allow me to break theme.json into smaller, manageable files. However, learning to build one felt too time-consuming and unnecessary given how few themes I create each year—until now. With AI tools like Cursor, time and expertise are no longer barriers.

While still chatting with the community member, I prompted Cursor for a solution, and within minutes, I had a working Node.js script. Since I’m already using Sass in the theme for custom block styles, integrating this script was quick and straightforward.

I was so impressed with how easy it was that I recorded this short video:

For those curious, here’s the script Cursor generated. However, I wouldn't recommend copy and pasting this code. Use AI to generate a custom solution tailored to your own workflow and theme structure.

const fs = require("fs");
const path = require("path");

// Base theme.json structure
const baseConfig = require("../src/json/base.json");

// Function to read and parse JSON file
function readJsonFile(filePath) {
  try {
    return JSON.parse(fs.readFileSync(filePath, "utf8"));
  } catch (error) {
    console.error(`Error reading ${filePath}:`, error);
    return {};
  }
}

// Enhanced deep merge function to handle arrays
function deepMerge(target, source) {
  for (const key in source) {
    if (source[key] instanceof Object) {
      if (Array.isArray(source[key])) {
        // If it's an array, replace it entirely
        target[key] = source[key];
      } else if (key in target) {
        // If it's an object and exists in target, merge recursively
        target[key] = deepMerge(target[key], source[key]);
      } else {
        // If it's an object but doesn't exist in target, copy it
        target[key] = source[key];
      }
    } else {
      // For primitive values, simply replace
      target[key] = source[key];
    }
  }
  return target;
}

// Function to read all JSON files in a directory
function readJsonDirectory(dirPath) {
  let result = {};

  try {
    const files = fs.readdirSync(dirPath);

    files.forEach((file) => {
      if (file.endsWith(".json")) {
        const filePath = path.join(dirPath, file);
        console.log(`Processing: ${filePath}`);
        const jsonData = readJsonFile(filePath);
        result = deepMerge(result, jsonData);
      }
    });
  } catch (error) {
    console.error(`Error reading directory ${dirPath}:`, error);
  }

  return result;
}

// Build final theme.json
function buildThemeJson() {
  try {
    // Read settings
    console.log("Processing settings...");
    const settings = readJsonDirectory(
      path.join(__dirname, "../src/json/settings"),
    );
    baseConfig.settings = settings;

    // Read styles
    console.log("Processing styles...");
    const styles = readJsonDirectory(
      path.join(__dirname, "../src/json/styles"),
    );
    baseConfig.styles = styles;

    // Write final theme.json with tab indentation
    const outputPath = path.join(__dirname, "../theme.json");
    fs.writeFileSync(outputPath, JSON.stringify(baseConfig, null, "\t"));
    console.log(`Successfully created theme.json at ${outputPath}`);
  } catch (error) {
    console.error("Error building theme.json:", error);
    process.exit(1);
  }
}

buildThemeJson();

The AI also added a dedicated script to my theme's package.json file, which looks like this:

{
  ...
  "scripts": {
    ...
    "build:theme-json": "node scripts/build-theme-json.js"
  },
  ...
}

So, whenever I want to generate the complete theme.json file, I just run:

npm run build:theme-json

Every day, I discover new ways AI can streamline my workflow and save me time. If you haven’t tried these tools yet, I highly recommend starting with Cursor. I recently recorded a short primer for WordPress.com that should help you get started.