TypeScript Types for WordPress Content
Why TypeScript for WordPress Data?
WordPress REST API returns complex JSON objects. Without types, you're guessing at the data structure and prone to runtime errors.
With TypeScript, you get:
- Autocomplete for post properties
- Compile-time error checking
- Self-documenting code
- Refactoring safety
Core WordPress Types
The wp-headless-client package includes built-in types, but let's understand what they represent.
Create a types file:
touch src/lib/types.tsPost Type
// src/lib/types.ts
// Base rendered content (title, content, excerpt)
interface RenderedContent {
rendered: string;
protected?: boolean;
}
// WordPress Post
export interface WPPost {
id: number;
date: string;
date_gmt: string;
modified: string;
modified_gmt: string;
slug: string;
status: 'publish' | 'draft' | 'pending' | 'private';
type: string;
link: string;
title: RenderedContent;
content: RenderedContent;
excerpt: RenderedContent;
author: number;
featured_media: number;
categories: number[];
tags: number[];
}
// WordPress Page
export interface WPPage {
id: number;
slug: string;
status: string;
title: RenderedContent;
content: RenderedContent;
excerpt: RenderedContent;
parent: number;
menu_order: number;
featured_media: number;
}
// Category
export interface WPCategory {
id: number;
count: number;
description: string;
link: string;
name: string;
slug: string;
parent: number;
}
// Tag
export interface WPTag {
id: number;
count: number;
description: string;
link: string;
name: string;
slug: string;
}
// Media/Image
export interface WPMedia {
id: number;
date: string;
slug: string;
type: string;
link: string;
title: RenderedContent;
alt_text: string;
caption: RenderedContent;
media_type: 'image' | 'file';
mime_type: string;
source_url: string;
media_details: {
width: number;
height: number;
sizes: {
[key: string]: {
file: string;
width: number;
height: number;
source_url: string;
};
};
};
}
// Author/User
export interface WPUser {
id: number;
name: string;
slug: string;
description: string;
avatar_urls: {
[key: string]: string;
};
}Extended Post Type with Embedded Data
When using _embed parameter, WordPress returns related data inline:
// Extended post with embedded author and media
export interface WPPostWithEmbed extends WPPost {
_embedded?: {
author?: WPUser[];
'wp:featuredmedia'?: WPMedia[];
'wp:term'?: [WPCategory[], WPTag[]];
};
}Utility Types for API Responses
// Pagination info from headers
export interface WPPagination {
total: number;
totalPages: number;
currentPage: number;
perPage: number;
}
// API Response with data and pagination
export interface WPApiResponse<T> {
data: T[];
pagination: WPPagination;
}Type Guards
Add helper functions to safely narrow types:
// Type guard for checking if post has featured image
export function hasFeatureImage(
post: WPPostWithEmbed
): post is WPPostWithEmbed & {
_embedded: { 'wp:featuredmedia': WPMedia[] };
} {
return (
post._embedded?.['wp:featuredmedia'] !== undefined &&
post._embedded['wp:featuredmedia'].length > 0
);
}
// Get featured image URL safely
export function getFeaturedImageUrl(
post: WPPostWithEmbed,
size: string = 'full'
): string | null {
if (!hasFeatureImage(post)) return null;
const media = post._embedded['wp:featuredmedia'][0];
const sizedUrl = media.media_details?.sizes?.[size]?.source_url;
return sizedUrl || media.source_url;
}
// Get author name safely
export function getAuthorName(post: WPPostWithEmbed): string {
return post._embedded?.author?.[0]?.name ?? 'Unknown Author';
}
// Get author avatar safely
export function getAuthorAvatar(
post: WPPostWithEmbed,
size: string = '96'
): string | null {
return post._embedded?.author?.[0]?.avatar_urls?.[size] ?? null;
}Using Types in Components
Now you can use these types throughout your app:
// src/components/PostCard.tsx
import { WPPostWithEmbed, getFeaturedImageUrl, getAuthorName } from '@/lib/types';
interface PostCardProps {
post: WPPostWithEmbed;
}
export function PostCard({ post }: PostCardProps) {
const imageUrl = getFeaturedImageUrl(post, 'medium_large');
const author = getAuthorName(post);
return (
<article className="border rounded-lg overflow-hidden">
{imageUrl && (
<img
src={imageUrl}
alt={post.title.rendered}
className="w-full h-48 object-cover"
/>
)}
<div className="p-4">
<h2 className="text-xl font-bold">{post.title.rendered}</h2>
<p className="text-gray-600 text-sm">By {author}</p>
<div
className="mt-2 text-gray-700"
dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }}
/>
</div>
</article>
);
}Summary
In this lesson, we:
- ✅ Created TypeScript interfaces for WordPress data
- ✅ Added utility types for API responses
- ✅ Built type guards for safe property access
- ✅ Used types in a React component
Next up: Building a data fetching layer with proper error handling and caching.