Guide

Learn how to connect to any WordPress site, explore its data, and build templates with live preview.

Connecting to a site

Enter any WordPress site URL into the input field on the home page — for example techcrunch.com or wordpress.org/news. HeadlessPlayground will auto-detect the REST API endpoint and connect.

No API keys or authentication needed. Any WordPress site with the REST API enabled (which is most of them) will work out of the box.

tip
You don't need to include https:// or /wp-json — just the domain is enough. The tool figures out the rest.

The code editor

Once connected, you'll see a full-screen IDE. The code editor is on the left, the live preview is on the right. Code uses an Astro-like syntax with two parts:

playground.astro
---
// Frontmatter: fetch data here (async JavaScript)
const posts = await fetchPosts({ per_page: 5 });
---

<!-- Template: HTML with {expressions} -->
<div class="grid gap-4">
  {posts.map(post => (
    <h3 class="text-white text-lg">{post.title.rendered}</h3>
  ))}
</div>

Frontmatter (between the --- fences) runs as async JavaScript. Declare variables here — they're automatically available in the template below.

Template (below the fences) is HTML with {expression} interpolation. Tailwind CSS is available — all utility classes work in the preview.

Press Run (or Cmd+Enter / Ctrl+Enter) to execute your code and see the result.

API functions

These functions are available in the frontmatter. They fetch from the connected WordPress site automatically — you don't need to pass the site URL.

FunctionDescription
fetchPosts(params?)Get blog posts
fetchPages(params?)Get pages
fetchCategories()Get all categories
fetchTags()Get all tags
fetchMedia(params?)Get media items (images, files)
fetchSiteInfo()Get site name, description, URL

Functions that accept params take an object with WordPress REST API query parameters:

// Get 3 posts matching "design"
const posts = await fetchPosts({ per_page: 3, search: "design" });

// Get posts in category ID 5
const catPosts = await fetchPosts({ categories: 5 });

// Get 20 media items
const media = await fetchMedia({ per_page: 20 });

Helper functions

Available in both frontmatter and templates:

FunctionWhat it does
stripHtml(html)Removes all HTML tags, returns plain text
formatDate(dateString)Formats a date nicely — e.g. "March 7, 2026"
console.log(...)Prints output to the Console panel in the preview
tip
WordPress returns titles and excerpts as HTML (with &amp; entities, <p> tags, etc). Use stripHtml() to get clean text for display.

Data Explorer

Click the Data button in the toolbar to open the Data Explorer panel on the left. It shows actual data from the connected site, organized into tabs: Posts, Pages, Categories, Tags, and Media.

Hover over any item to see a tooltip listing all available data paths — things like post.title.rendered, post.date, post.slug, etc.

Click a data path in the tooltip to insert it directly into your code at the current cursor position. This is the fastest way to reference data fields without having to remember the exact path.

Template syntax

The template section supports several patterns:

Loops — use .map() with HTML inside parentheses:

{posts.map(post => (
  <div class="p-4 border border-white/10 rounded-xl">
    <h3>{post.title.rendered}</h3>
  </div>
))}

Conditionals — use && for simple show/hide:

{post._embedded?.["wp:featuredmedia"]?.[0]?.source_url && (
  <img src={post._embedded["wp:featuredmedia"][0].source_url}
       class="w-full h-44 object-cover rounded-lg" />
)}

Ternaries — use for if/else rendering:

{posts.length === 0 ? (
  <p class="text-zinc-500">No posts found.</p>
) : (
  <div class="grid gap-4">
    {posts.map(post => (
      <h3>{post.title.rendered}</h3>
    ))}
  </div>
)}

Attribute binding — use {expressions} inside attributes:

<img src={post._embedded["wp:featuredmedia"][0].source_url}
     alt={stripHtml(post.title.rendered)} />

Examples

Click the Examples button in the toolbar to load ready-made templates. Here are some patterns to try:

Blog post cards with images:

playground.astro
---
const posts = await fetchPosts({ per_page: 5 });
---

<div class="grid gap-4">
  {posts.map(post => (
    <article class="bg-white/5 border border-white/10 rounded-xl p-5">
      {post._embedded?.["wp:featuredmedia"]?.[0]?.source_url && (
        <img src={post._embedded["wp:featuredmedia"][0].source_url}
             class="w-full h-44 object-cover rounded-lg mb-3" />
      )}
      <h3 class="text-white font-semibold text-lg mb-1">{post.title.rendered}</h3>
      <p class="text-zinc-500 text-sm">{formatDate(post.date)}</p>
      <p class="text-zinc-400 text-sm mt-2">
        {stripHtml(post.excerpt.rendered).slice(0, 120)}...
      </p>
    </article>
  ))}
</div>

Site dashboard with multiple API calls:

playground.astro
---
const [site, categories, pages] = await Promise.all([
  fetchSiteInfo(),
  fetchCategories(),
  fetchPages({ per_page: 100 })
]);
---

<div class="text-center py-6">
  <h2 class="text-3xl font-bold text-white mb-1">{site.name}</h2>
  <p class="text-zinc-500 mb-8">{site.description}</p>
  <div class="flex gap-4 justify-center">
    <div class="bg-white/5 border border-white/10 rounded-xl px-8 py-5">
      <div class="text-3xl font-bold text-emerald-400">{categories.length}</div>
      <div class="text-zinc-500 text-sm mt-1">Categories</div>
    </div>
    <div class="bg-white/5 border border-white/10 rounded-xl px-8 py-5">
      <div class="text-3xl font-bold text-emerald-400">{pages.length}</div>
      <div class="text-zinc-500 text-sm mt-1">Pages</div>
    </div>
  </div>
</div>

Image gallery:

playground.astro
---
const media = await fetchMedia({ per_page: 12 });
const images = media.filter(m => m.media_type === "image");
---

<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
  {images.map(item => (
    <div class="aspect-square rounded-xl overflow-hidden border border-white/10">
      <img src={item.source_url} alt={item.alt_text || ""}
           class="w-full h-full object-cover hover:scale-110 transition-transform duration-500" />
    </div>
  ))}
</div>

Tips & tricks

tip
Use Promise.all() when you need data from multiple endpoints. It fetches everything in parallel instead of one at a time, making your code noticeably faster.
tip
Use console.log(posts[0]) in the frontmatter to inspect the shape of the data. The output shows up in the Console panel above the preview — handy for discovering available fields.
tip
WordPress posts include embedded data (author info, featured images) via the _embedded field. Use optional chaining (?.) to safely access nested fields like post._embedded?.["wp:featuredmedia"]?.[0]?.source_url.
tip
The preview uses Tailwind CSS via CDN — all utility classes work. Dark backgrounds look best since the preview has a dark background. Use classes like text-white, bg-white/5, border-white/10 for a clean look.
tip
You can filter and transform data in the frontmatter just like regular JavaScript. For example: posts.filter(p => p.categories.includes(5)) or posts.slice(0, 3).
Back to HeadlessPlayground
HeadlessPlayground