Blog /
The week DesignSetGo started reading your custom post types
Short post. Shipped this week.
Until this release, dsgo.posts.list() defaulted to the standard post post type. Custom post types worked if you passed type explicitly, but only for CPTs registered with show_in_rest: true and only after a manifest opt-in for each type. It worked. It was also more friction than the rest of the bridge surface, and it was the thing I got the most questions about in the Anthropic Discord and on X over the last month.
This week the bridge picks up custom post types automatically, declares them in the install dialog, and turns them into first-class data the way post and page already were.
How to read a custom post type in a WordPress app
If you landed here from a search like read CPT in WordPress app or how do I build an app that reads my WordPress custom post types: in a DesignSetGo App, you call dsgo.posts.list({ type: 'your_cpt_slug' }) after declaring read: ['posts'] in dsgo-app.json. The bridge enumerates whatever CPTs the site has registered with show_in_rest: true (WooCommerce product, LearnDash sfwd-courses, IDX listing, ACF-registered types, theme CPTs) and surfaces them in the install dialog so the site owner can see what your app is reading. No second permission grant, no per-type manifest opt-in, no REST URL plumbing in your app code.
The rest of this post is the release-note version: what changed in the runtime to make that work, what the install dialog looks like now on a commerce-heavy site, and how to opt out if you want to pin your app to standard posts only.
What changed
dsgo.posts.list({ type: 'listing' }) now works without any manifest changes beyond the standard read: ['posts'] permission. The bridge enumerates the site’s registered post types at install time, surfaces them in the permission dialog (“This app wants to read: posts, pages, listings, courses”), and routes the bridge call through whichever WordPress REST endpoint serves that type.
Three concrete capabilities you have now that you did not before:
One. Read any CPT the site has registered with show_in_rest: true. WooCommerce products, EDD downloads, LearnDash courses, custom theme post types, IDX real-estate listings, ACF-registered “team_member” types.
const products = await dsgo.posts.list({ type: 'product' });
const courses = await dsgo.posts.list({ type: 'sfwd-courses' }); // LearnDash
const listings = await dsgo.posts.list({ type: 'listing' });
Two. Read custom taxonomies attached to those CPTs.
const ingredients = await dsgo.taxonomy.terms({ taxonomy: 'ingredient' });
const propertyTypes = await dsgo.taxonomy.terms({ taxonomy: 'property_type' });
If the taxonomy is registered with show_in_rest: true, the bridge sees it.
Three. Read CPTs as embeds in a post-context call.
const ctx = await dsgo.context.post();
if (ctx?.type === 'listing') {
// we're embedded inside a listing post
const price = ctx.meta.price;
}
The post-context surface used to only return data for standard posts and pages. Now it returns the type and meta for any CPT the parent is.
What you have to do to get this
Probably nothing.
If your app already has permissions.read: ["posts"] in the manifest and the site’s CPTs are REST-registered (most modern plugins do this; legacy ones may not), the new behavior is on by default after the runtime update.
The install dialog will be more verbose for sites with lots of CPTs. The plain-English summary changed from “This app wants to read: posts” to “This app wants to read: posts (including: products, courses, listings)” when the bridge can enumerate them. That is a UX change worth being aware of: the dialog is now longer on commerce-heavy and education-heavy sites. We considered hiding the enumeration behind a “show details” link and decided against it. The list is the consent.
If you want to restrict your app to just standard posts and not pick up new CPTs that the site adds later, you can pin the type list in your manifest:
"permissions": {
"read": [{ "posts": { "types": ["post", "page"] } }]
}
That is opt-out, not opt-in. The default is “read everything posts covers.”
What this unlocks
The two tutorials that depended on this behavior:
Reading your WordPress taxonomy from an app was waiting on this. It now ships next week as planned.
Build a course-catalog app that pulls from a custom post type was also waiting on this. It is now feasible without the workaround we were going to document.
The third thing this unlocks is the real-estate listings vertical, which now lets you build the listings browser directly against the IDX plugin’s listing CPT without the data-import shim we had written for the June 13 tutorial.
What I am proud of
Two small things.
The enumeration happens at install time and the result is cached in the manifest’s resolved permissions. The bridge does not call WordPress for the type list on every request. The runtime stays fast on commerce sites with 40 CPTs.
The install dialog rewrite was a Riff session that took 30 minutes. The harness’s pre-shipped Riff workflow generated three variants of the dialog copy and the bucket-renderer tests caught a regression on the first variant. The permission buckets post explains why this matters; the practical version is that even small copy changes to the install dialog go through the harness now, and the dialog has stayed honest as a result.
What I am not yet proud of
Two known issues. Both are tracked.
WooCommerce products are heavier than they need to be. A dsgo.posts.list({ type: 'product' }) returns the full product object including variants, attributes, images, and meta. For a list view you usually only want title + price + featured image. The fields parameter (?_fields=...) works as a workaround. A dsgo.commerce.products.list() with a default lean shape is the right answer and will land in the next release.
Per-CPT capability mapping is loose. The bridge returns posts the user can read; that is correct. But for CPTs with custom capabilities (view_listing, manage_courses), the capability mapping is whatever the CPT registered, which is sometimes nothing useful. If your CPT does not declare its own caps, dsgo.user.can('edit_listing', id) falls back to the standard edit_post capability. This is a WordPress idiom, not a bridge limitation, but it can surprise the first time you hit it.
The shorter version
CPTs are now first-class in the bridge. dsgo.posts.list({ type: 'whatever' }) works. The install dialog lists the types your app will read. No manifest changes for most apps.
If something does not work the way this post describes, tell me.
Further reading
- BRIDGE-API.md §posts: the updated method docs.
- WordPress REST API for custom post types: the canonical reference for
show_in_rest. - The bridge cookbook: updated with two new CPT patterns.