ℹ Disclaimer: Content may contain affiliate links, WPThink.com may earn a commission from qualifying purchases.
Using Custom Post Types in Astro Beyond Blog Posts
A custom post type usually fails in Astro at the exact moment it gets mistaken for a second blog.
A familiar pattern: the content model works perfectly in WordPress, then Astro fetches posts and everything looks fine—until projects or events vanish from the API, route to the wrong slug, or lose critical meta fields. A custom post type is not just post with a new label. It has its own REST exposure rules, rest_base, rewrite settings, archive behavior, and field registration.
That changes the whole Astro pipeline. Collection queries, archive pages, and [slug].astro routes cannot assume blog defaults. If show_in_rest is disabled, has_archive is false, or ACF/meta fields are not registered for REST, Astro has nothing reliable to build from.
- show_in_rest controls whether Astro can fetch the type at all.
- rewrite, rest_base, and has_archive can all point to different URL assumptions.
Audit the post type before Astro
Before Astro templates enter the picture, the custom post type should be treated as an API contract inside WordPress. A type that looks fine in wp-admin can still fail headlessly if show_in_rest, public, or publicly_queryable are misaligned. That same discipline is essential when structuring content for headless WordPress: the backend defines what the frontend may safely assume.
Check four points first:
- Exposure flags:
show_in_restmust be enabled, ideally with a deliberate REST base. - Archive behavior: turn on
has_archiveonly if Astro will actually render listing routes. - Taxonomies: register categories, tags, or custom taxonomies explicitly so filters and related content appear in the payload.
- Editorial states: decide whether drafts, scheduled posts, private entries, and revisions require preview handling.
If those rules are unclear, Astro code ends up guessing instead of consuming a stable content model.
A post type can be editable in WordPress while still being unusable to Astro. Headless reliability comes from the registration arguments and publishing workflow, not the dashboard UI.
Pick the API by content shape
Match the API to the content model
For flat records—title, slug, excerpt, and a few meta fields—REST is usually simpler. Astro can fetch archive-style endpoints in getStaticPaths() and request detail records only when needed. That keeps failure points obvious, especially after choosing between WPGraphQL and REST for Astro.
When the model becomes relational, request count starts deciding the winner.
- Choose REST when collections are shallow, pagination maps neatly to endpoint URLs, and field coverage rarely changes.
- Choose WPGraphQL when entries nest authors, taxonomies, related content, media, or repeater-like structures that would otherwise trigger many follow-up requests.
- Prefer WPGraphQL if strong TypeScript typing matters; generated types make centralized build-time queries safer.
- Prefer REST if pagination must closely mirror WordPress archive behavior.
In Astro, sprawling build-time fetch orchestration is usually the signal that GraphQL will simplify the pipeline.
Normalize every post type into one internal shape
-
Treat upstream content as hostile to templates
After pulling content beyond standard blog posts in Astro, raw REST or GraphQL responses should stop at a mapper layer. WordPress can return missing fields, different media structures, draft-state gaps, and inconsistent date formats across post types.
-
Emit one stable object per entry
A normalized record keeps templates dumb:
slug,url,title,excerpt,publishedAt,updatedAt,featuredImage,status, andfields. Even when a source field is absent, the key still exists withnull, a fallback value, or an empty array. -
Resolve formatting once, not in every component
Date parsing, timezone decisions, permalink assembly, image alt fallback, and boolean coercion belong in the boundary. Components should receive already-safe values, not branches for every post type.
-
Hide source-specific quirks behind adapters
Books may expose
isbn, events may exposestartDate, case studies may expose client metadata. Those remain custom insidefields, while shared rendering code keeps relying on the same top-level contract. -
Gain safer builds and easier growth
Adding a new content type becomes an adapter problem instead of a template rewrite. The result is fewer conditional checks, cleaner TypeScript types, and predictable collection pages.
Model single pages from URLs, not slugs
A single entry page is a URL contract. Treating it as slug -> page is only safe when WordPress and Astro share the same routing rules.
If the WordPress permalink is the source of truth, use its full URI. That matters for nested paths like /resources/guides/headless-seo/, hierarchical post types, translated structures, and installs where the post type base differs from the REST route. In those cases, uri or link is more reliable than a bare slug.
Compose Astro paths manually only when Astro intentionally owns the public URL. Common examples include:
- collapsing several post types under one section
- removing a WordPress base such as
/case-study/ - enforcing locale prefixes or custom taxonomic segments
When Astro rewrites the path, it should also rewrite the canonical URL. A page rendered at /work/acme/ with a canonical pointing to /case-study/acme/ splits signals, confuses indexation, and can create duplicate-content competition. Path generation, internal links, sitemaps, and canonicals must all describe the same address.
A slug identifies content. A URI identifies the public location. For custom post types, those are often not interchangeable.
Design archives for how people browse
An archive for a custom post type is rarely a reverse-chronological feed. In Astro, treat each listing page as a browse interface shaped by the content itself.
- Events usually sort by upcoming start date, not publish date.
- Case studies often work better grouped by industry, service, or outcome.
- Documentation benefits from version, product, or topic filters.
- Team pages usually need role, location, or department ordering.
Pagination should follow reading intent. A docs index may prefer dense search and filtering over page numbers; an events archive may split upcoming and past items before paginating either set.
Titles and empty states also need custom logic. “Latest Posts” is wrong for most post types. Better patterns include “Upcoming events,” “Customer stories,” or “No engineers in Berlin yet.” Those small changes make archives feel native to the content model instead of inherited from the blog.
Turn metadata into page structure
Custom post types become valuable on the frontend when taxonomy terms, related entries, and custom fields shape navigation and page modules. A “project” is not just a title and body; it may need industry tags, linked case studies, client logos, location data, and a status field that changes archive behavior.
Fetch that structure in batches, not inside component loops. Repeated follow-up requests for terms, authors, or linked posts create classic N+1 overhead and slow builds. Prefer one GraphQL query or a preloaded REST expansion layer that resolves common relationships before rendering.
Denormalize with intent
A small amount of duplication often pays off in Astro:
- Store a resolved term label list alongside raw IDs.
- Flatten related cards into
{ title, uri, image, summary }. - Precompute booleans like
isFeatured,hasDownload, orisSoldOut.
This keeps components simple and predictable while preserving raw source data for edge cases.
When field groups repeat across types, render them through shared patterns: stats grids, CTA blocks, team lists, spec tables. Reusable presenters matter more than post type names when the schema repeats.
Plan for post-launch change
Static builds stay simple when content changes are predictable and rebuild latency is acceptable. They become brittle when editors need draft previews, instant slug fixes, or taxonomy updates reflected everywhere.
Treat edits by blast radius
Use full static generation when updates are infrequent and each change touches few routes. Shift to SSR or on-demand revalidation when one edit can invalidate:
- the single entry page
- paginated archives
- term archives and filter counts
- related-content blocks, breadcrumbs, and sitemaps
Slug changes also need redirects, canonical updates, and cache purges. Archive toggles or new taxonomies can create whole route families, so route generation should read registration settings instead of hardcoded assumptions. For previews, SSR is usually cleaner: drafts and scheduled entries should not wait for a production rebuild.
Static fits low-content churn. On-demand rebuilds fit bounded invalidation. SSR fits previews, permission-aware content, and archives that must be correct on every request.
Repeatable model for the next content type
-
Define the contract
Registration flags, statuses, taxonomies, and custom fields describe the guarantees Astro can rely on. If that contract is vague, every downstream layer becomes defensive.
-
Pick the transport
REST suits flatter payloads and simpler caches. GraphQL earns its place when relationships, nested fields, and typed querying start to dominate build complexity.
-
Normalize into one entry shape
Raw WordPress responses should disappear behind a mapper. Templates, cards, feeds, and search then consume the same predictable object regardless of source type.
-
Model URLs and browsing separately
Single pages follow the real URI structure; archive pages follow user intent. Those are related concerns, but they should not be forced into blog-style assumptions.
-
Contain change at the type boundary
Preview rules, invalidation scope, redirects, and schema evolution belong to the content type’s adapter. That keeps future additions from disturbing stable route families.
- Shared pipeline, type-specific rules
- Canonical URLs and archive UX are domain decisions
- Adapters absorb post-launch change
Treat each post type as a small content product with its own contract, transport, normalized model, routing logic, archive behavior, and change policy. Once those decisions exist, adding another type stops feeling like a rebuild and starts looking like a controlled extension.
That is why the approach scales cleanly across events, portfolios, case studies, directories, and documentation: the stack stays consistent while each model keeps its own rules.