Mapping WordPress Values – astrojs-wp-integration
Mapping WordPress Values
Section titled “Mapping WordPress Values”WordPress often stores technical values that are not ideal for UI output. ACF choice fields are a common example: the REST response may contain in_progress, while the UI should show In progress.
Use one reusable callback for both read and write paths:
mapEntrynormalizes loader entries before Astro receives content data.mapResponsenormalizes successful create/update action responses before Astro receives action data.
Both callbacks receive the WordPress object first and a context object with at least resource, so the same function can support loaders and actions.
The mapper reads field definitions from the WordPress REST API at runtime. If your WordPress instance exposes choice labels in the REST schema (for example via a custom endpoint or a plugin that extends the schema), fetch them once and cache the result per resource:
type AcfChoice = { label: string; value: string;};
async function loadChoiceLabels(baseUrl: string) { const response = await fetch( `${baseUrl}/wp-json/my-plugin/v1/field-choices`, ); const data = (await response.json()) as Record<string, AcfChoice[]>; const choiceLabels = new Map<string, Map<string, string>>();
for (const [fieldName, choices] of Object.entries(data)) { if (!Array.isArray(choices)) continue;
choiceLabels.set( fieldName, new Map(choices.map((choice) => [choice.value, choice.label])), ); }
return choiceLabels;}Use that lookup inside a reusable mapper:
type EntryWithAcf = { acf?: Record<string, unknown> | null;};
export function createAcfChoiceLabelMapper(baseUrl: string) { const lookups = new Map<string, Promise<Map<string, Map<string, string>>>>();
return async function mapAcfChoiceLabels<TEntry extends EntryWithAcf>( entry: TEntry, _context: { resource: string }, ): Promise<TEntry> { if (!entry.acf) return entry;
if (!lookups.has('choices')) { lookups.set('choices', loadChoiceLabels(baseUrl)); }
const choiceLabels = await lookups.get('choices')!;
return { ...entry, acf: Object.fromEntries( Object.entries(entry.acf).map(([fieldName, value]) => [ fieldName, choiceLabels.get(fieldName)?.get(String(value)) ?? value, ]), ), }; };}Use the mapper in live or static loaders:
import { defineLiveCollection } from 'astro:content';import { WordPressClient, postSchema, wordPressPostLoader,} from 'wp-astrojs-integration';import { createAcfChoiceLabelMapper } from './lib/acf-choice-label-mapper';
const baseUrl = import.meta.env.PUBLIC_WORDPRESS_BASE_URL;const wp = new WordPressClient({ baseUrl });const mapAcfChoiceLabels = createAcfChoiceLabelMapper(baseUrl);
const posts = defineLiveCollection({ loader: wordPressPostLoader(wp, { mapEntry: mapAcfChoiceLabels, }), schema: postSchema,});
export const collections = { posts };Use the same mapper in create/update actions:
import { WordPressClient, createUpdatePostAction, updatePostInputSchema,} from 'wp-astrojs-integration';import { z } from 'astro/zod';import { createAcfChoiceLabelMapper } from '../lib/acf-choice-label-mapper';
const baseUrl = import.meta.env.WP_URL;const wp = new WordPressClient({ baseUrl, auth: { username: import.meta.env.WP_USERNAME, password: import.meta.env.WP_APP_PASSWORD, },});const mapAcfChoiceLabels = createAcfChoiceLabelMapper(baseUrl);
export const server = { updatePost: createUpdatePostAction(wp, { schema: updatePostInputSchema.extend({ acf: z.object({ acf_project_status: z.string().optional(), }).optional(), }), mapResponse: mapAcfChoiceLabels, }),};This keeps package behavior generic. Your app owns the mapping rules, and the same mapper can handle ACF choices, plugin fields, custom meta, or any other normalized value your UI needs.