TypeScript Generation for Headless WordPress
Field Forge auto-generates TypeScript type definitions (.d.ts files) for every field group on your WordPress site. Every field type is mapped to its TypeScript equivalent. Repeater fields become Array<SubFields>. Flexible content becomes discriminated unions. The result: a fully-typed custom fields API for your Next.js, Astro, Nuxt, SvelteKit, or any JavaScript frontend.
The headless WordPress type-safety problem
Headless WordPress means WordPress serves as the CMS and a separate JavaScript frontend (Next.js, Astro, Nuxt, etc.) renders the pages. The frontend fetches content via REST API or WPGraphQL and displays it. For simple content — title, excerpt, featured image — TypeScript types are built into most SDKs.
Custom fields are the problem. Custom fields are plugin-defined, not core WordPress. TypeScript doesn’t know that your “Hero Section” field group has a title: string, subtitle: string, background_image: WPImage, and cta_button: { text: string; url: string; }. Frontend code that accesses data.hero_title has no type safety — typos, missing fields, and field-rename refactors all become runtime errors.
The solutions that existed before Field Forge:
- Write types by handtedious and drifts out of sync when you edit field groups in WordPress
- Use a generic
anydefeats the purpose of TypeScript - Use Zod or similar runtime validationadds boilerplate to every API call
- Use code generation from GraphQL schemaworks if you use WPGraphQL + GraphQL Code Generator, but complex to set up
Field Forge provides a simpler path: auto-generate TypeScript definitions directly from your field groups.
How it works
Step 1: Build a field group in Field Forge
You design a field group in the visual builder — let’s say “Hero Section” with fields for title, subtitle, background image, and CTA button.
Step 2: Press “Generate TypeScript”
On the Import / Export screen, the Headless Types section (Pro) has a Generate TypeScript button. It shows the type definitions for your field groups, with Copy and Download buttons for the result.
Step 3: Use the types in your frontend
Commit the downloaded file to your frontend repo (or copy and paste it into an existing types file). TypeScript now knows the exact shape of every field group’s data.
import { HeroSectionFields } from './types/fieldforge.d.ts';
function Hero({ data }: { data: HeroSectionFields }) {
return (
<section>
<h1>{data.title}</h1>
<p>{data.subtitle}</p>
<img src={data.background_image.url} alt={data.background_image.alt} />
<a href={data.cta_button.url}>{data.cta_button.text}</a>
</section>
);
}
TypeScript catches typos, missing fields, and wrong types at compile time. IDE autocomplete works. Refactoring is safe.
Field type mapping
Every Field Forge field type has a corresponding TypeScript type:
| Field Forge field | TypeScript type |
|---|---|
| Text | string |
| Textarea | string |
| Number | number |
| Range | number |
string |
|
| URL | string |
| Password | string (sensitive) |
| Image | WPImage (built-in interface) |
| File | WPFile |
| WYSIWYG | string (HTML) |
| oEmbed | string (embed HTML) |
| Gallery | WPImage[] |
| Select (single) | 'option_a' \| 'option_b' \| 'option_c' (literal union of choices) |
| Select (multi) | Array<'option_a' \| 'option_b'> |
| Checkbox | string[] |
| Radio | 'option_a' \| 'option_b' |
| True/False | boolean |
| Button Group | 'option_a' \| 'option_b' |
| Relationship | WPPost[] |
| Post Object | WPPost |
| Page Link | string (URL) |
| Taxonomy | WPTerm[] or WPTerm (depending on settings) |
| User | WPUser or WPUser[] |
| Date Picker | string (ISO format) |
| Time Picker | string |
| Color Picker | string (hex or rgba) |
| Repeater | Array<RepeaterSubFields> (nested type) |
| Group | nested type |
| Flexible Content | Array<LayoutA \| LayoutB \| LayoutC> (discriminated union) |
| Clone | reference to cloned group |
Built-in types
Field Forge’s generated .d.ts includes helper types you’ll use throughout your frontend:
interface WPImage {
id: number;
url: string;
alt: string;
width: number;
height: number;
sizes: {
thumbnail: string;
medium: string;
large: string;
full: string;
};
}
interface WPFile {
id: number;
url: string;
filename: string;
mime_type: string;
filesize: number;
}
interface WPPost {
id: number;
title: string;
slug: string;
content: string;
excerpt: string;
date: string;
modified: string;
featured_image: WPImage | null;
categories: WPTerm[];
tags: WPTerm[];
}
interface WPUser {
id: number;
name: string;
email: string;
avatar: string;
}
interface WPTerm {
id: number;
name: string;
slug: string;
description: string;
}
Example: Hero field group
A field group called “Hero Section” with title, subtitle, background image, and CTA button generates:
export interface HeroSectionFields {
title: string;
subtitle: string;
background_image: WPImage;
cta_button: {
text: string;
url: string;
open_in_new_tab: boolean;
};
}
Usage in a Next.js component:
import type { HeroSectionFields } from '@/types/fieldforge';
interface Props {
hero: HeroSectionFields;
}
export function Hero({ hero }: Props) {
return (
<section className="hero">
<img
src={hero.background_image.sizes.large}
alt={hero.background_image.alt}
width={hero.background_image.width}
height={hero.background_image.height}
/>
<h1>{hero.title}</h1>
<p>{hero.subtitle}</p>
<a
href={hero.cta_button.url}
target={hero.cta_button.open_in_new_tab ? '_blank' : undefined}
>
{hero.cta_button.text}
</a>
</section>
);
}
Every property is typed. TypeScript catches hero.titel (typo), hero.background_image.URL (wrong case), and hero.cta_button.open_new_tab (wrong field name) at compile time.
Repeater types
Repeater fields become Array<SubFields>. Nested repeaters work recursively:
export interface TeamPageFields {
team_members: Array<{
name: string;
photo: WPImage;
bio: string;
skills: Array<{
skill_name: string;
proficiency: number;
}>;
}>;
}
TypeScript understands the nesting. Iterating over team_members and then skills is fully typed.
Flexible content as discriminated unions
Flexible content fields generate discriminated unions — TypeScript’s native feature for handling multi-type arrays:
type HeroLayout = {
acf_fc_layout: 'hero';
title: string;
subtitle: string;
background_image: WPImage;
};
type FeaturesLayout = {
acf_fc_layout: 'features';
section_title: string;
features: Array<{
icon: string;
title: string;
description: string;
}>;
};
type CTALayout = {
acf_fc_layout: 'cta';
headline: string;
button: { text: string; url: string };
};
export interface LandingPageFields {
page_sections: Array<HeroLayout | FeaturesLayout | CTALayout>;
}
In your frontend component, use a type guard to render the right layout:
function PageSections({ sections }: { sections: LandingPageFields['page_sections'] }) {
return (
<>
{sections.map((section, i) => {
if (section.acf_fc_layout === 'hero') {
return <Hero key={i} data={section} />;
}
if (section.acf_fc_layout === 'features') {
return <Features key={i} data={section} />;
}
if (section.acf_fc_layout === 'cta') {
return <CTA key={i} data={section} />;
}
})}
</>
);
}
Inside each if block, TypeScript narrows section to the correct type. No casting needed.
Regeneration workflow
When you edit field groups in Field Forge’s admin, the types change. To keep your frontend in sync:
- Edit the field group in WordPress
- Click “Download TypeScript” in the Field Forge admin
- Replace the
.d.tsfile in your frontend repo - Commit the change
Or automate it via Field Forge’s REST API:
# Fetch the latest TypeScript definitions via curl
curl https://wp.example.com/wp-json/fieldforge/v1/typescript > types/fieldforge.d.ts
Add this to your frontend build pipeline and types are always up to date.
Works with any frontend framework
TypeScript types are framework-agnostic. Field Forge’s generated types work with:
- Next.jsReact-based, excellent TypeScript integration
- Astroislands architecture, great for content sites
- NuxtVue-based, Nuxt Content integration
- SvelteKitSvelte-based, built-in TypeScript support
- RemixReact-based, server-first
- Vanilla TypeScriptany frontend that uses TypeScript
Ready for type-safe headless WordPress?
TypeScript generation is included in every paid plan. No other WordPress custom fields plugin has this feature.