LESSON 3 ⏱️ 8 min read

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.ts

Post 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.