Skip to content

fix(plugins): parse query params as input for GET/HEAD/DELETE routes#2163

Open
dchaudhari7177 wants to merge 4 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/plugin-route-query-input
Open

fix(plugins): parse query params as input for GET/HEAD/DELETE routes#2163
dchaudhari7177 wants to merge 4 commits into
emdash-cms:mainfrom
dchaudhari7177:fix/plugin-route-query-input

Conversation

@dchaudhari7177

Copy link
Copy Markdown
Contributor

What does this PR do?

A plugin route with an input schema always failed validation on GET, HEAD, and DELETE. Route input was read exclusively via request.json(), and those methods carry no body — so request.json() resolves to undefined and the Zod check rejects every request with a 400.

New parseRouteInput(request) helper switches on method:

  • POST/PUT/PATCH → parse the JSON body (unchanged)
  • GET/HEAD/DELETE → parse the URL query string into an object; a repeated key (?tag=a&tag=b) becomes an array, a single key stays a scalar; no params yields {} so all-optional/defaulted schemas still validate

Applied at both plugin dispatch sites in emdash-runtime.ts (trusted/configured plugins and the sandboxed path), replacing the duplicated try { request.json() } blocks.

The exact repro from the issue now works:

routes: {
  search: {
    input: z.object({ limit: z.coerce.number().min(1).default(50), q: z.string().optional() }),
    handler: async (ctx) => ({ received: ctx.input }),
  },
}
// GET /_emdash/api/plugins/my-plugin/search?limit=20&q=hello
// -> { received: { limit: 20, q: "hello" } }

Closes #2146

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes for my change (packages/core — the one remaining error, cacheControl on ManifestRouteEntry in bundle-utils.ts, is pre-existing and reproduces on a clean main)
  • pnpm lint passes for the changed files (overall pnpm lint fails on a pre-existing error in packages/registry-verification)
  • pnpm test passes (targeted: packages/core tests/unit/plugins/routes.test.ts, 45/45)
  • pnpm format has been run
  • I have added/updated tests for my changes
  • User-visible strings wrapped for translation (n/a)
  • I have added a changeset (emdash patch)
  • New features link to an approved Discussion (n/a — bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (Claude Code)

Tests

New parseRouteInput suite in routes.test.ts: GET/HEAD/DELETE query parsing, repeated-key-to-array, empty-query {}, JSON body for POST/PUT/PATCH, undefined for a body method with no body, and an end-to-end GET through PluginRouteHandler.invoke with a z.coerce schema returning { limit: 20, q: "hello" }.

Plugin route input was always read via request.json(), so a GET, HEAD,
or DELETE route with an input schema always failed validation — those
methods carry no body, so request.json() resolves to undefined and the
Zod check rejects it (emdash-cms#2146).

A new parseRouteInput helper switches on method: POST/PUT/PATCH parse
the JSON body as before; GET/HEAD/DELETE parse the URL query string into
an object (repeated keys become arrays). Applied to both the trusted and
sandboxed plugin route dispatch paths.

Fixes emdash-cms#2146
Copilot AI review requested due to automatic review settings July 21, 2026 05:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9a19297

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond area/core size/M labels Jul 21, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 21, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2163

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2163

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2163

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2163

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2163

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2163

emdash

npm i https://pkg.pr.new/emdash@2163

create-emdash

npm i https://pkg.pr.new/create-emdash@2163

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2163

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2163

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2163

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2163

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2163

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2163

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2163

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2163

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2163

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2163

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2163

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2163

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2163

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2163

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2163

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2163

commit: 9a19297

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Can you update the docs too, because this is new behaviour

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Jul 21, 2026
oxlint --deny-warnings flagged values[0]! as an unnecessary type
assertion (noUncheckedIndexedAccess is off, so values[0] is already
string). Remove it; behaviour is identical.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Jul 21, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for docs

The route dispatcher now parses bodyless-method input from the query
string. Document the parsing semantics (repeated keys become arrays,
single keys stay scalars, values are always strings so use z.coerce)
and fix the method list to GET/HEAD/DELETE.
@dchaudhari7177

Copy link
Copy Markdown
Contributor Author

Added docs for the new behaviour in api-routes.mdx:

  • Updated the input-validation line to say the query string is parsed for GET/HEAD/DELETE (was "GET/DELETE").
  • New Query-string input subsection documenting the parsing semantics: repeated keys become arrays (?tag=a&tag=b{ tag: ["a", "b"] }), a single key stays a scalar, and every value is a string so non-string fields should use z.coerce. Included a GET list example.

Let me know if you'd like it anywhere else.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin API routes fail input validation on GET/HEAD/DELETE

3 participants