Frontmatter

How to generate WordPress-compatible PHP templates from an Astro project

A practical guide to converting Astro components into WordPress-compatible PHP templates — partials, page templates, and variable wiring — without a full rewrite.

WordPress powers 40% of the web. Astro is where modern frontends are built.

Getting them to work together is not complicated — but it requires a clear mental model of what maps to what.

The mental model

An Astro component is a PHP partial waiting to happen.

---
export interface Props {
  headline: string;
  sub?: string;
}
const { headline, sub } = Astro.props;
---
<section class="hero">
  <h1>{headline}</h1>
  {sub && <p>{sub}</p>}
</section>

Becomes:

<?php // partials/hero.php — generated by FM Solo ?>
<section class="hero">
  <h1><?= esc_html($fm['props']['headline']) ?></h1>
  <?php if (!empty($fm['props']['sub'])): ?>
    <p><?= esc_html($fm['props']['sub']) ?></p>
  <?php endif; ?>
</section>

Same structure. Same class names. Same conditional logic. Different syntax.

The WordPress theme folder structure

A standard WordPress theme maps cleanly to the Solo output:

wp-content/themes/your-theme/
├─ index.php           ← from output/pages/
├─ page.php            ← from output/pages/
├─ header.php          ← from output/layouts/
├─ footer.php          ← from output/layouts/
├─ partials/
│  ├─ hero.php         ← from output/partials/
│  └─ section.php      ← from output/partials/
└─ functions.php       ← you write this

Astro layouts map to WordPress header/footer templates. The Astro pages become page templates. The Astro components become partials.

Wiring variables in WordPress

The generated INTEGRATION.md lists every variable each partial expects. For a hero partial with headline and sub, the WordPress developer wires them like this:

With ACF (Advanced Custom Fields):

<?php // page-home.php
get_header();

$fm = [
  'props' => [
    'headline' => get_field('hero_headline'),
    'sub'      => get_field('hero_sub'),
  ]
];

include get_template_directory() . '/partials/hero.php';

get_footer();

With post meta:

$fm = [
  'props' => [
    'headline' => get_post_meta(get_the_ID(), 'hero_headline', true),
    'sub'      => get_post_meta(get_the_ID(), 'hero_sub', true),
  ]
];

With hardcoded content (for static sections):

$fm = [
  'props' => [
    'headline' => 'Welcome to our site',
    'sub'      => null,
  ]
];

The partial doesn’t care where the data comes from. It receives an array and renders it.

What to use instead of esc_html

Solo generates htmlspecialchars() by default — standard PHP escaping. If you’re integrating into WordPress, you can replace it with esc_html() and esc_url(). The templates are plain PHP files, fully editable.

The full conversion workflow

  1. Build the site in Astro with typed Props interfaces on every component
  2. Run frontmatter solo:build --adapter php
  3. Copy output/partials/ and output/layouts/ into the WordPress theme
  4. Use INTEGRATION.md to wire variables in each page template
  5. Write functions.php — this is the only file Solo doesn’t generate

Frontmatter Solo handles steps 1 through 4 automatically. Step 5 is intentionally left to the WordPress developer — it’s the one file that is genuinely project-specific.

Frontmatter Solo does not generate a complete WordPress theme. It generates templates and structure only.

Theme configuration (functions.php, hooks, CMS wiring) remains the responsibility of the backend developer.

Why not use a headless WordPress setup

Headless WordPress (REST API or WPGraphQL) requires maintaining two separate deployments — the WordPress backend and the Astro frontend. That’s fine for large teams, but it adds operational complexity that most WordPress projects don’t need.

The Solo approach is simpler: the Astro project is the design source, Solo generates the WordPress theme, and everything runs on a single WordPress installation. No second server. No API coupling. No CORS headaches.

The tradeoff is that you can’t use Astro’s routing or islands in production — the WordPress theme is purely server-rendered PHP. For most WordPress sites, that’s exactly what you want.


Scope

Frontmatter Solo:

  • generates templates (Twig or PHP)
  • defines a variable contract (manifest + INTEGRATION.md)

It does not:

  • integrate with your backend
  • fetch data
  • configure a CMS
  • provide runtime behavior

Integration is explicit and owned by the host application.