# WordPress File Headers Have Overstayed Their Welcome

In 2014, I [commented on WordPress Trac ticket #24152](https://core.trac.wordpress.org/ticket/24152#comment:16) suggesting it was time to revisit the non-standard file headers specification. Scott [reopened the ticket](https://core.trac.wordpress.org/ticket/24152#comment:17) with a detailed case for replacing them with JSON.

That was eleven years ago. The ticket is still open, and we still use the same file headers in WordPress plugins and themes.

## What are file headers?

Every WordPress theme must include a `style.css` file with metadata in CSS comments:

```
/*
Theme Name: My Theme
Theme URI: https://example.com
Description: A theme description
Author: Developer Name
Author URI: https://example.com
Version: 1.0.0
Requires at least: 6.0
Requires PHP: 8.2
License: GPL-2.0-or-later
Text Domain: my-theme
Tags: blog, custom-colors
*/

```

Plugins do the same thing in their main PHP file:

```
/*
Plugin Name: My Plugin
Plugin URI: https://example.com
Description: A plugin description
Author: Developer Name
Author URI: https://example.com
Version: 1.0.0
Requires at least: 6.0
Requires PHP: 8.2
License: GPL-2.0-or-later
Text Domain: my-plugin
*/

```

WordPress parses these with `get_file_data()`, a function that reads the first 8 KB of a file and runs regular expressions against it. This is the foundation that the entire plugin and theme system is built on.

It's a custom, non-standard specification that exists nowhere else in the software world.

## Why this is a problem

### It's slow

As Scott [benchmarked in 2014](https://core.trac.wordpress.org/ticket/24152#comment:17), parsing file headers with regular expressions is nearly 10x slower than `json_decode`:

| Method | 10,000 iterations |
|---|---|
| `get_file_data` | 1,980ms |
| `json_decode` | 218ms |
| `parse_ini_file` | 482ms |

This matters when WordPress is scanning dozens of plugins and themes on every admin page load.

It gets worse. WordPress doesn't know which PHP file contains a plugin's header. When `get_plugins()` is called, it opens every `.php` file in each plugin directory, reads the first 8 KB, and runs regex against it until it finds a valid header. The results are cached, but the cache gets invalidated on plugin changes and doesn't persist without an object cache. With a `plugin.json`, WordPress would know exactly where to look.

### It's error prone

The regex-based parsing has had bugs with whitespace handling. Here are all the variations that have caused problems, from [\#19854](https://core.trac.wordpress.org/ticket/19854) and [\#15193](https://core.trac.wordpress.org/ticket/15193):

```
 Original Theme Name
    Original Theme Name
        Theme Name
      Theme Name
    Theme Name
  Theme Name
 *   Theme Name
 * Theme Name
 *Theme Name
 Theme Name
/* Theme Name
/*    Theme Name
/*Theme Name
* Theme Name
﻿/*Theme Name
        Theme Name
    Theme Name
Theme Name

```

JSON doesn't have this problem. A key is a key.

### It's illogical

Why is a theme's name, version, and license in the comments of a stylesheet? Why is a plugin's metadata in comments in a PHP file?

Theme metadata has nothing to do with CSS. Plugin metadata has nothing to do with PHP execution. Metadata belongs in a metadata file. This is an arbitrary convention that WordPress invented. No other platform does this.

And the metadata isn't even in one place. For plugins, it's split between the PHP file header and `readme.txt`. Two different files, two different formats, for the same package. Themes have the same problem with `style.css` and `readme.txt`. A single JSON file replaces all of it.

### It's non-standard

The [File Header specification](https://developer.wordpress.org/plugins/plugin-basics/header-requirements/) is a list of 13 different things for plugins and 14 for themes. There's no formal spec, no schema, no validation tools. It's parsed by reading raw file bytes and running regex against comment blocks.

JSON has a formal specification, built-in parsers in every language, schema validation, and tooling in every editor.

## The proposal

### For themes: use `theme.json`

WordPress already requires `theme.json` for block themes. It's already a JSON file that WordPress already parses. But theme metadata (name, version, author) is still stuck in `style.css` comments.

Think about that for a second. WordPress parses JSON for theme colors, fonts, spacing, layout, templates, and patterns. But for the theme's own name and version? CSS comments.

Add a `metadata` property to `theme.json`:

```
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "metadata": {
    "name": "My Theme",
    "uri": "https://example.com",
    "description": "A theme description",
    "author": "Developer Name",
    "authorUri": "https://example.com",
    "version": "1.0.0",
    "license": "GPL-2.0-or-later",
    "textDomain": "my-theme",
    "tags": ["blog", "custom-colors"],
    "requires": {
      "wordpress": "6.0",
      "php": "8.2"
    }
  },
  "settings": {
  }
}

```

The file already exists. The parsing infrastructure already exists. Many classic themes are already hybrid themes with a `theme.json` file. They'd get this for free.

### For plugins: introduce `plugin.json`

Plugins don't have an equivalent to `theme.json`, so they need a new file:

```
{
  "$schema": "https://schemas.wp.org/trunk/plugin.json",
  "name": "My Plugin",
  "uri": "https://example.com",
  "description": "A plugin description",
  "author": "Developer Name",
  "authorUri": "https://example.com",
  "version": "1.0.0",
  "license": "GPL-2.0-or-later",
  "textDomain": "my-plugin",
  "requires": {
    "wordpress": "6.0",
    "php": "8.2",
    "plugins": ["woocommerce", "jetpack"]
  }
}

```

Notice the `requires.plugins` field. WordPress currently handles plugin dependencies with a comma-separated string in a comment header (`Requires Plugins: woocommerce, jetpack`). A JSON array is the obvious way to represent a list of things.

### Why not `composer.json` or `package.json`?

When this was discussed in 2014, TJNowell [suggested using `composer.json`](https://core.trac.wordpress.org/ticket/24152#comment:18) which is a reasonable idea. Composer is the standard dependency manager for PHP, and `composer.json` already has provisions for defining WordPress themes and plugins.

But WordPress doesn't support Composer. There's no native Composer integration in core, and no indication that's changing. We've been [using Composer with WordPress](https://roots.io/bedrock/) since 2013 through Bedrock, but that's a project-level tool. It doesn't solve the problem for individual themes and plugins that need to identify themselves to WordPress.

`package.json` has the same problem in the other direction. Not every theme or plugin uses Node. Requiring a Node package manifest for a PHP project that might not have any JavaScript build step doesn't make sense.

The solution needs to be WordPress-native. `theme.json` already is. `plugin.json` would follow the same pattern.

## Backwards compatibility

This is fully backwards compatible:

1. If `metadata` exists in `theme.json`, use it. Otherwise, fall back to `style.css` headers.
2. If `plugin.json` exists, use it. Otherwise, fall back to PHP file headers.

`wp_get_theme()` and `get_plugin_data()` handle the abstraction. No existing theme or plugin breaks. Developers migrate on their own timeline.

The file headers don't even need to be formally deprecated right away. Give developers a better option and let adoption happen naturally, the same way `theme.json` itself was adopted for block theme settings.

## Thirteen years and counting

WordPress Trac [\#24152](https://core.trac.wordpress.org/ticket/24152) was opened in 2013. It was closed as "wontfix" because file headers are simple and JSON introduces unnecessary complexity. We [disagreed then](https://core.trac.wordpress.org/ticket/24152#comment:16). Scott [made the case in detail](https://core.trac.wordpress.org/ticket/24152#comment:17). Others have continued to argue for it in the years since.

Then WordPress introduced `theme.json`. A JSON configuration file that defines theme settings and styles. The file is *right there*. Just put the metadata in it.

Every argument against this has been answered. Performance is better. The format is standardized. The file already exists for themes. Backwards compatibility is trivial.

WordPress already chose JSON. It just forgot to finish the job. [I've opened a PR to finish it](https://github.com/WordPress/wordpress-develop/pull/11200).

[See my demo repo](https://github.com/retlehs/json-metadata-theme-and-plugin) for an example of a minimal plugin and theme using `plugin.json` and `theme.json`.