Typesafe Integration – astrojs-wp-integration
Overview
Section titled “Overview”The easiest way to get a very typesafe setup is:
- Generate Zod schemas and TypeScript declarations from your real WordPress site with the
fluent-wp-clientCLI. - Use those schemas in Astro collections and action factories.
- Optionally enable the Astro catalog integration so discovery metadata is fetched during builds and reused by catalog-aware helpers.
This package is the Astro integration layer. fluent-wp-client is the source of truth for resource access, discovery, embeds, and schema generation.
For build-time catalog discovery, use wp-astrojs-integration/integration in astro.config.mjs. See docs/catalog.mdx for the full setup.
When the catalog integration is enabled, virtual:wp-astrojs/collections and virtual:wp-astrojs/schemas can apply catalog-derived Zod schemas to loaders and action factories automatically.
Recommended Approach
Section titled “Recommended Approach”Use CLI-generated schemas for most projects.
This is the best fit when:
- you have one main WordPress backend
- your content model is mostly known ahead of time
- you want strong editor types and stable builds
Use runtime discovery only when schemas are not known at build time, such as multi-tenant or per-site dynamic setups.
Quick Start
Section titled “Quick Start”-
Generate schemas from your WordPress site.
Generate Zod schemas and types npx fluent-wp-client schemas \--url https://example.com \--zod-out src/generated/wp-schemas.ts \--types-out src/generated/wp-types.d.ts -
Create a shared WordPress client.
src/lib/wp.ts import { WordPressClient } from 'fluent-wp-client';export const wp = new WordPressClient({baseUrl: import.meta.env.PUBLIC_WORDPRESS_BASE_URL,}); -
Use generated schemas in Astro content collections.
src/content.config.ts import { defineCollection, defineLiveCollection } from 'astro:content';import {wordPressPostStaticLoader,wordPressContentLoader,} from 'wp-astrojs-integration';import { wp } from './lib/wp';import {postsItemSchema,booksItemSchema,} from './generated/wp-schemas';export const collections = {posts: defineCollection({loader: wordPressPostStaticLoader(wp),schema: postsItemSchema,}),books: defineLiveCollection({loader: wordPressContentLoader(wp, { resource: 'books' }),schema: booksItemSchema,}),}; -
Use generated schemas in actions.
src/actions/index.ts import { createCreatePostAction } from 'wp-astrojs-integration';import { wp } from '../lib/wp';import {postsCreateSchema,postsItemSchema,booksCreateSchema,booksItemSchema,} from '../generated/wp-schemas';export const server = {createPost: createCreatePostAction(wp, {schema: postsCreateSchema,responseSchema: postsItemSchema,}),createBook: createCreatePostAction(wp, {resource: 'books',schema: booksCreateSchema,responseSchema: booksItemSchema,}),};
What This Gives You
Section titled “What This Gives You”With that setup you get:
- typed collection entries in Astro
- generated TypeScript declarations for the WordPress instance
- typed action inputs
- optional typed action response validation via
responseSchema - much better support for custom post types and custom fields
The catalog integration uses the same rc.5 CLI output model internally. It writes Zod runtime modules and TypeScript declarations to Astro’s cache with --zod-out and --types-out, and does not emit JSON Schema artifacts because Astro consumes Zod validators.
Collections
Section titled “Collections”Use the generated *ItemSchema for collection entry validation.
import { defineCollection, defineLiveCollection } from 'astro:content';import { wordPressPostStaticLoader, wordPressPageLoader, wordPressContentLoader,} from 'wp-astrojs-integration';import { wp } from './lib/wp';import { postsItemSchema, pagesItemSchema, booksItemSchema,} from './generated/wp-schemas';
export const collections = { posts: defineCollection({ loader: wordPressPostStaticLoader(wp), schema: postsItemSchema, }), pages: defineLiveCollection({ loader: wordPressPageLoader(wp), schema: pagesItemSchema, }), books: defineLiveCollection({ loader: wordPressContentLoader(wp, { resource: 'books' }), schema: booksItemSchema, }),};Actions
Section titled “Actions”Use generated *CreateSchema or *UpdateSchema values as the Astro action input schema.
Use generated *ItemSchema as responseSchema when you want the action result validated in Astro too.
import { createCreatePostAction, createUpdatePostAction,} from 'wp-astrojs-integration';import { wp } from '../lib/wp';import { postsCreateSchema, postsUpdateSchema, postsItemSchema,} from '../generated/wp-schemas';
export const server = { createPost: createCreatePostAction(wp, { schema: postsCreateSchema, responseSchema: postsItemSchema, }), updatePost: createUpdatePostAction(wp, { schema: postsUpdateSchema, responseSchema: postsItemSchema, }),};Custom Post Types And Taxonomies
Section titled “Custom Post Types And Taxonomies”This pattern becomes especially useful for custom resources.
import { createCreatePostAction, wordPressContentLoader, wordPressTermLoader,} from 'wp-astrojs-integration';import { wp } from './lib/wp';import { booksCreateSchema, booksItemSchema, genresItemSchema,} from './generated/wp-schemas';
const booksLoader = wordPressContentLoader(wp, { resource: 'books' });const genresLoader = wordPressTermLoader(wp, { resource: 'genres' });
const createBook = createCreatePostAction(wp, { resource: 'books', schema: booksCreateSchema, responseSchema: booksItemSchema,});
void booksLoader;void genresLoader;void createBook;This is the main benefit of the CLI workflow: your CPT and taxonomy schemas come from the real WordPress site instead of hand-written approximations.
Embeds And Advanced Reads
Section titled “Embeds And Advanced Reads”Loaders return the base resource payload by default. They do not include _embedded relations automatically.
If you need embeds, either enable the loader embed option for post, page, and custom content live loaders or use the standalone client directly.
import { defineLiveCollection } from 'astro:content';import { wordPressPostLoader } from 'wp-astrojs-integration';import { wp } from './lib/wp';
const posts = defineLiveCollection({ loader: wordPressPostLoader(wp, { embed: ['author', 'wp:featuredmedia', 'wp:term'], }),});
void posts;import { WordPressClient, getEmbeddedAuthor, getEmbeddedFeaturedMedia, getEmbeddedTerms,} from 'fluent-wp-client';
const wp = new WordPressClient({ baseUrl: 'https://example.com',});
const post = await wp.content('posts').item('hello-world', { embed: ['author', 'wp:featuredmedia', 'wp:term'],});
const author = getEmbeddedAuthor(post);const featuredMedia = getEmbeddedFeaturedMedia(post);const categories = getEmbeddedTerms(post, 'category');Runtime Discovery
Section titled “Runtime Discovery”Use discovery when the schema is not known ahead of time.
This is useful when:
- each site has different custom post types
- plugins register unknown fields
- you are building multi-site or SaaS tooling
import { WordPressClient } from 'fluent-wp-client';import { zodFromJsonSchema, zodSchemasFromDescription } from 'fluent-wp-client/zod';
const wp = new WordPressClient({ baseUrl: 'https://example.com', auth: { username: 'admin', password: 'app-password' },});
const catalog = await wp.explore();
const bookSchemas = zodSchemasFromDescription(catalog.content.books);const validatedBookInput = bookSchemas.create!.parse({ title: { raw: 'My new book' }, status: 'draft',});
await wp.content('books').create(validatedBookInput);
const description = await wp.content('posts').describe();const postCreateSchema = zodFromJsonSchema(description.schemas.create!);void postCreateSchema;Cache Discovery Catalogs
Section titled “Cache Discovery Catalogs”If you use discovery, cache the catalog and restore it later.
const catalog = await wp.explore();
await cache.set('wp:catalog', JSON.stringify(catalog));
const restored = JSON.parse(await cache.get('wp:catalog'));wp.useCatalog(restored);That avoids repeated discovery requests and makes builds or server startup more predictable.
Which Approach Should I Pick?
Section titled “Which Approach Should I Pick?”| Approach | Best for | Recommendation |
|---|---|---|
| CLI-generated schemas | Normal Astro sites with one known WordPress backend | Start here |
| Runtime discovery | Multi-tenant or per-site dynamic WordPress setups | Use only when needed |
| Hybrid | Mostly known schemas plus runtime validation for drift | Good advanced option |
Practical Recommendation
Section titled “Practical Recommendation”For most implementors:
- generate schemas with the CLI
- commit the generated file
- use generated item schemas in collections
- use generated create/update schemas in actions
- use
WordPressClientdirectly only for embeds, discovery, or uncommon workflows
Helpful Script
Section titled “Helpful Script”{ "scripts": { "wp:types": "fluent-wp-client schemas --url $PUBLIC_WORDPRESS_BASE_URL --zod-out src/generated/wp-schemas.ts --types-out src/generated/wp-types.d.ts" }}Run that script whenever your WordPress resources or fields change.