Plugin routes should support raw responses (custom content-type, headers) #1003
devondragon
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Problem
Plugin route handlers registered via
PluginRouteHandlercannot return non-JSON content. Every return value is wrapped in the standard API envelope, which blocks plugins from serving files at conventional paths with specific content-types.Ground truth (from reading
emdash@0.1.0source)PluginRouteHandler.invoke()wraps every return value as{ success, data, status: 200 }.src/astro/routes/api/plugins/[pluginId]/[...path].tsunconditionally callsapiSuccess(result.data)— the JSON envelope is non-negotiable.PluginHooksexists to intercept the response.Promise<unknown>but the HTTP response is alwaysapplication/jsonwith the envelope.Motivating use cases
While building
@devondragon/emdash-plugin-seo-aeo-geo-toolkit, we hit this immediately. Real-world use cases that need raw responses:sitemap.xml— must be served asapplication/xmlat a well-known path for search engines.llms.txt—text/plainat site root, the emerging convention for AI crawler guidance.robots.txt, RSS/Atom feeds, iCal (.ics) exports, OpenSearch descriptors, WebFinger/JRD, any plugin wanting to publish a file at a conventional path.None of these can live behind a JSON envelope — consumers (Googlebot, RSS readers, calendar apps) expect the raw bytes with the right
Content-Type.Proposed API shapes (not prescriptive — just options to consider)
Option A: return a
Responsedirectly. If the handler returns an object that isinstanceof Response, the route file returns it as-is and skipsapiSuccess(). Minimal, Web-standards-native, zero new types.Option B: structured raw-response shape. Handler returns
{ body, contentType, status?, headers? }, and the envelope is skipped when that shape is detected.Either works; Option A is probably less surface area and aligns with how Astro/modern runtimes think about responses.
Current workaround
Our plugin exports pure builder functions; consuming sites add ~5-line Astro route files that import the builder and return
new Response(xml, { headers: { 'content-type': 'application/xml' } }). It works, so this isn't a blocker — just boilerplate that every consumer has to copy for what should be plug-and-play.Why file this
With a first-class raw-response path, a whole category of SEO/discovery/syndication plugins becomes drop-in installable. Happy to contribute a PR if the maintainers have a preferred direction.
Thanks for the great plugin system so far.
All reactions