Roadmap
What Pushduck has shipped, what's actively being worked on, and what's next. Structured by intent, not by quarter.
How to read this roadmap
Pushduck's roadmap is structured by intent, not by quarter. Dates rot; intent doesn't.
- Shipped — in the current version, works today
- In progress — actively being touched right now
- Next — committed, near-term, not yet started
- Later — aspirational, meaningful work, no commitment to dates
- Under consideration — not committed, driven by demand
- Not doing — explicit out-of-scope, belongs in your code or in a recipe
See Philosophy for why the scope is what it is. See Limitations for the honest list of what the current version doesn't do.
Shipped
Core
- Web-standard
Request/Responsehandler — works on any runtime that speaks WinterCG. Next.js, Hono, Elysia, Bun, Deno, SvelteKit, Nuxt, Remix, Astro, Cloudflare Workers, Vercel edge, Netlify functions, etc., work without a dedicated adapter. - Typesafe router with
InferClientRouter<typeof router>— server and client share types in a TS monorepo. - Middleware chain with typed metadata flowing through.
- Lifecycle hooks:
onStart,onProgress,onSuccess,onError,onUploadComplete. - Schema chain:
s3.image(),s3.file(),.accept(),.maxFileSize(),.maxFiles(),.middleware(),.paths(),.onUploadComplete(). - Per-file and aggregate progress tracking — progress %, upload speed, ETA.
- Client-side file metadata passthrough — attach app-specific metadata on
uploadFiles(files, metadata)and receive it in server middleware and lifecycle hooks.
Providers
- AWS S3
- Cloudflare R2
- DigitalOcean Spaces
- MinIO (self-hosted, dev, testing)
- Any other S3-compatible provider via the generic AWS config with a custom endpoint (Backblaze B2, Wasabi, Scaleway, Linode, Storj, Tebi, etc.)
Framework adapters
- Next.js (App Router + Pages Router)
- Express (via
toExpressHandler()) - Fastify (via
toFastifyHandler()) - Any framework that speaks Web-standard
Request/Responseworks out of the box without a shim.
Clients
pushduck/client— React hook (useUploadRoute) with property-based client (createUploadClient).pushduck/react-native— React Native / Expo with direct picker-asset support. Pass assets fromexpo-image-picker,expo-document-picker, orreact-native-image-pickerdirectly; no field mapping.
In progress
- Documentation and scope alignment — cleaning up the docs site, adding the Limitations page, aligning the roadmap with what's actually shipped and planned.
- v1.0 API stabilization — locking down the public API surface for semver stability ahead of v1.0.
Next
These items are committed. They will ship, and they're the focus after the current in-progress work lands.
Security and correctness
Before anything else. These are small, high-value fixes surfaced by code review.
- Verify
Content-Lengthis signed in presigned PUT URLs. If not, enforce server-side size limits via header signing so client-declared sizes can't be spoofed. - Error-to-status-code mapping in the universal handler. Thrown middleware errors should return
401/403/400with the right shape, not always500. - Body-size limit on
/presign— closes a DoS vector where a hostile client POSTs a huge JSON metadata blob. - Replace
console.errorwith the structured logger in the universal handler. - Integration tests against MinIO in CI — end-to-end presign → PUT → complete against a real S3-compatible server in Docker. Catches signing regressions unit tests miss.
Recipes library
A first-party collection of copy-paste snippets for common integrations. Lives at docs/content/docs/recipes/ and renders as its own section in the sidebar.
Planned first set:
recipes/nextjs-starter— minimal App Router setup with typed route + client hookrecipes/clerk-middleware— auth middleware using Clerkrecipes/authjs-middleware— auth middleware using Auth.jsrecipes/betterauth-middleware— auth middleware using BetterAuthrecipes/sharp-thumbnails— thumbnail generation inonUploadCompleterecipes/exif-stripping— strip GPS/metadata from photos before storingrecipes/clamav-scan— virus scanning via ClamAVrecipes/rate-limiting— per-user upload quota in middlewarerecipes/db-record— write an upload row to Postgres / SQLite / Drizzlerecipes/retry-on-failure— wrapuploadFileswith exponential backoff
Recipes are standalone, framework-agnostic, and work without any CLI.
ShadCN-style CLI
pushduck add <recipe> — a thin command that downloads a recipe from the repo and writes it into the user's codebase. The CLI does almost nothing; the recipes do the work.
npx pushduck add nextjs-starter
npx pushduck add clerk-middleware
npx pushduck add sharp-thumbnailsDesign principles for the CLI:
- No framework detection magic — the user picks the recipe, the CLI copies the file
- No installing extra packages — each recipe is self-contained TypeScript that drops into the codebase
- No rewriting existing files — the CLI only creates new files and prompts the user to wire them up
- Tiny: ~100 lines, no detection logic, no AST manipulation
This is the ShadCN CLI pattern applied to upload recipes. The previous CLI attempt was fragile because it tried to auto-detect the user's framework and edit their config files. This version doesn't do any of that.
Framework-agnostic core
Extract a pure-JS upload state store (~100-200 lines, no React, no dependencies) from the current React hook. Once extracted, each framework binding becomes a thin (~50 line) wrapper subscribing to the same store.
This is a prerequisite for Vue and Svelte support.
Vue support
pushduck/vue — Composition API composable (useUploadRoute) with the same typesafe router and progress semantics as the React hook. Targets Vue 3 Composition API and pairs naturally with Nuxt.
Svelte support
pushduck/svelte — Svelte store wrapping the same core. Works with SvelteKit out of the box; compatible with Svelte 4 stores and Svelte 5 runes.
Later
Meaningful work, aspirational timeline, no hard commitment.
Multipart uploads
Support for files larger than 5 GB (S3's single-PUT limit). Uses the S3 multipart upload API — client-side chunking, parallel part uploads, server-side session tracking, commit/abort logic.
This is a substantial feature. It roughly doubles the library's surface area — part orchestration, session state, client-side resume tokens, partial-state cleanup when a browser tab dies. It's a major-version effort, not a weekend add.
Resumable uploads
Once multipart lands, resumable uploads come naturally as a follow-up. The client tracks which parts have committed and can resume from the last committed part after a network failure. May adopt the tus protocol or build on top of S3 multipart directly.
Together with multipart, this is the biggest gap the library has compared to heavier alternatives.
Solid support
pushduck/solid — signal-based primitives. Drops out of the framework-agnostic core once that's extracted, so the work is ~a day once the core is ready.
Vanilla JS / Web Components
Raw subscribe-based API without any framework runtime. Targets embedding in any page without React/Vue/Svelte — smallest surface area once the core is extracted.
GCS (native)
Google Cloud Storage via GCS's native REST API, not the S3 interop layer. The S3 interop already works for basic operations, but a native adapter unlocks GCS-specific features (object holds, retention policies, resumable session URIs) that interop doesn't expose.
Vercel Blob
Vercel's storage service — new, growing in the Vercel ecosystem, fits the edge-first narrative. Thin adapter over the Vercel Blob API.
Service-worker background uploads
Survive tab close and mobile backgrounding. Separate service worker with IndexedDB-backed queue. Significant work and a different mental model from the main upload flow; shipped only if enough users hit the "my upload died when I switched apps" problem.
Path-based action routing
Replace ?action=presign / ?action=complete query-param routing with /api/upload/{route}/presign path segments. Better for CDN caching, observability, and CORS. Breaking change — held for v1.0 or v2.0.
Under consideration
Not committed. Prioritized based on user demand.
- Azure Blob Storage — different API surface from S3 (SAS tokens, different signing). Large enterprise market. Ships if demand is real.
- Supabase Storage — S3 under the hood with a REST wrapper. May already work via the MinIO/S3 generic config depending on endpoint — needs verification. If not, a thin Supabase adapter.
- Filesystem / "dev-local" provider — "upload straight to a folder on my server" breaks the client → presigned URL → storage architecture entirely. The current answer is: run MinIO in Docker — it's an S3-compatible server that stores to disk, works with Pushduck today, and takes 30 seconds to start (
docker run minio/minio server /data). A native filesystem provider would ship only if the MinIO path genuinely doesn't fit real use cases. - Angular support — different paradigm, non-trivial effort. Not currently planned unless demand materializes.
- Backblaze B2 native — B2's S3-compatible endpoint already works today. Only add value if the S3 endpoint lacks something users need.
- iCloud / cross-provider mirroring — out of scope for the library, but could be a recipe if users build it.
Not doing
These belong in your code (or in a recipe), not in the library. The same scope discipline that keeps Pushduck a thin library also keeps it from turning into a platform.
- File processing — thumbnail generation, EXIF stripping, format conversion, resizing. Use Sharp, FFmpeg, Jimp, or ImageMagick in your
onUploadCompletehandler. Recipes provided. - Virus / malware scanning — use ClamAV, VirusTotal, or a hosted scanner in
onUploadComplete. Recipe provided. - Authentication integrations —
middlewarehook accepts any async function. Recipes provided for Clerk, Auth.js, BetterAuth, Lucia. - Admin dashboard — Pushduck is a library, not a platform.
- Hosted service / managed storage — you bring the bucket.
- Team management, billing, multi-tenant quotas — application concerns.
- GraphQL integration — out of scope. Upload mechanics are REST; use GraphQL above the upload layer for metadata.
- Webhooks — write them in your
onUploadCompletehandler. Recipe provided. - Built-in analytics — surface lifecycle events; you wire Plausible, PostHog, Segment, or whatever.
- Built-in upload UI components — headless state, yes. Styled components, no. Recipes provide examples you can copy and restyle.
What's changed since the previous roadmap
- React Native support shipped (v0.5.0) —
pushduck/react-nativewith direct picker-asset support fromexpo-image-picker,expo-document-picker, andreact-native-image-picker. - Schema unification (v0.6.0) —
accept()replaces the older separatetypes,formats, andextensionsmethods into a single call. - Chain order flexibility —
maxFileSize,maxFiles,acceptcan be chained in any order on schemas. - ACL header signing fix — presigned URLs now sign only the ACL header as required, not the full header set.
- Middleware request type —
middleware({ req: Request })is properly typed with the Web-standardRequest, notany. - Nullish coalescing for provider config defaults — prevents
0and empty strings from being silently overridden.
How to influence the roadmap
The "Under consideration" and "Later" sections change based on what users actually need. If your use case is blocked by something in those lists:
- Propose it in Ideas — or upvote an existing post if someone already asked. Upvotes are how I decide what to promote from "Later" to "Next".
- Ask in Q&A if you need help with something that already exists but isn't working for you.
- Open an issue for bugs or for feature work that's already been committed to.
Priority goes to concrete use cases, not speculative requests. "I'm shipping a video upload feature and need multipart for files larger than 5 GB" is more actionable than "it would be nice to have multipart someday."
Principles
As features land, these don't get compromised:
- Small bundle — every KB added to the client bundle is justified
- Focused scope — do one thing (S3 uploads) exceptionally well; hooks for everything else
- Type safety — every new feature has full TypeScript inference
- Backward compatibility — no gratuitous breaking changes; when they're necessary, they come with a migration guide
- Universal by default — features work on Workers, Bun, Deno, and Node without special cases
- Unopinionated — you control auth, processing, persistence, and UX; Pushduck owns the transport and signing