D1: limitedBoundParameters workaround misses drafts queries (parent IN (...)), breaking bulk publish above ~100 docs
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 67/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- sqlite, typescript
Research direction
Start in packages/drizzle/src/queries/parseParams.ts, then trace the id-to-parent rewrite in packages/payload/src/versions/drafts/appendVersionToQueryKey.ts. Review the affected paths in packages/payload/src/collections/operations/update.ts and packages/next/src/views/List/enrichDocsWithVersionStatus.ts, and reproduce the D1 query with about 100 IDs. Done means the parent-based IN query uses the existing workaround without the bound-parameter failure or silent status omission.
Written by the indexing model from the issue text.
Description
Describe the Bug
On D1, the limitedBoundParameters workaround that inlines id IN (...) as raw SQL is gated on the query path being literally 'id'. Every drafts-related query rewrites id to parent before it reaches that check, so the workaround silently does not apply and the query falls back to inArray() with one bound parameter per ID — hitting D1's hard limit of 100 bound parameters per query.
The guard is here:
https://github.com/payloadcms/payload/blob/main/packages/drizzle/src/queries/parseParams.ts — in the in / not_in branch:
if (
adapter.limitedBoundParameters &&
(operator === 'in' || operator === 'not_in') &&
relationOrPath === 'id' && // <-- only 'id'
Array.isArray(queryValue)
) {
// ... inlines the values as raw SQL, avoiding bound parameters
}
But appendVersionToQueryKey (packages/payload/src/versions/drafts/appendVersionToQueryKey.ts) maps id to parent:
if (key !== 'id') {
return { ...res, [`version.${key}`]: val }
}
return { ...res, parent: val }
So by the time parseParams sees it, relationOrPath is 'parent', not 'id', and the workaround is skipped.
Affected call sites
1. Bulk publish / unpublish from the list view (hard failure)
updateOperation takes the queryDrafts path when the collection has drafts enabled, after running the incoming where through appendVersionToQueryKey:
// packages/payload/src/collections/operations/update.ts
if (hasDraftsEnabled(collectionConfig) && (shouldSaveDraft || isTrashAttempt)) {
const versionsWhere = appendVersionToQueryKey(fullWhere)
const query = await payload.db.queryDrafts({ ..., where: versionsWhere })
The admin's PublishMany sends where: { and: [{ _status: { not_equals: 'published' } }, { id: { in: selectedIDs } }] }, which becomes parent IN (...) with N bound parameters. Selecting ~100 documents (the list view's max page size is 100 — admin.pagination.limits defaults to [5, 10, 25, 50, 100]) exceeds the limit and the whole request fails with too many SQL variables before a single document is published.
2. List view "changed" status (silent failure)
enrichDocsWithVersionStatus (packages/next/src/views/List/enrichDocsWithVersionStatus.ts) queries:
where: {
and: [
{ parent: { in: draftDocIds } },
{ 'version._status': { equals: 'published' } },
],
}
Same parent path, same missed workaround. With 100 drafts on one page that is 100 + 1 = 101 bound parameters. This one is wrapped in a try/catch that only logs, so it fails silently — the user just sees documents that never get the "changed" badge, with no visible error.
Steps to Reproduce
- Deploy a Payload app on Cloudflare Workers with
@payloadcms/db-d1-sqlite. - Add a collection with
versions: { drafts: true }. - Create ~120 draft documents in it.
- In the admin list view, set per-page to 100, select all 100 on the page, and click Publish.
Expected: all 100 documents are published.
Actual: the request fails with too many SQL variables and nothing is published.
Selecting a smaller number (e.g. 50) works, which is what makes this look like a flaky/size-dependent bug from the user's side.
Proposed fix
Widen the guard to cover the drafts path, e.g.:
(relationOrPath === 'id' || relationOrPath === 'parent')
or, more robustly, key the decision off the resolved column being an integer PK/FK rather than off the path string.
Note that the parent values are the same document IDs that already pass the isValidStringID validation in the existing branch, so the same inlining logic applies unchanged.
Environment
- Payload Version:
3.85.2 - Database Adapter:
@payloadcms/db-d1-sqlite3.85.2 - Deployment: Cloudflare Workers (Paid) + D1
- Node Version:
24.x
Related but different
#14766 is also a too many SQL variables report on D1, but that one is about wide UPDATE statements on collections with many columns (batching applies to INSERT but not UPDATE). This issue is about the IN (...) query path and the id → parent rewrite, which is a separate root cause.
Workaround
Splitting the bulk publish into chunks of ~20 IDs client-side avoids both limits. Worth noting that D1's other limit — 1000 queries per Worker invocation — is hit independently at roughly 50–60 documents, because updateOperation runs all matched documents in one request via Promise.all and each publish costs 15–20 queries. So chunking is needed regardless of the fix above; the fix here removes the hard 100-document wall and the silent list-view failure.
- Dominant language
- TypeScript
- Stars
- 44.8k
- Forks
- 4.2k
- Avg merge
- 2d 21h
- Merged PRs (30d)
- 53
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from payloadcms/payload
-
area: ui Bug status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
payloadcms/payload#18215 · 1 comment ·
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
payloadcms/payload#18163 ·
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
payloadcms/payload#18161 ·
-
area: core Bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
payloadcms/payload#18072 ·
-
Bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
payloadcms/payload#18067 · 1 comment ·
All issues in payloadcms/payload
Similar issues
-
clawsweeper:fix-shape-clear clawsweeper:queueable-fix clawsweeper:source-repro impact:ux-friction issue-rating: 🦞 diamond lobster no-stale P3
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 76/100
-
code-quality refactoring
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
github/gh-aw-firewall#8816 ·
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
vercel/react-tweet#225 ·