POST /image-pipeline/upload
POST /image-pipeline/upload
Accepts a multipart form upload, validates the file (MIME type, size, sourceType), uploads it to the R2 staging prefix, creates a MediaQueue row with PENDING status, and returns a jobId immediately. Processing happens asynchronously in the background worker.
@fastify/multipart must be registered before this plugin.
Request
Method: POST
Path: /image-pipeline/upload
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file | binary | Yes | Image file (JPEG, PNG, WebP, or GIF by default) |
sourceType | string | Yes | One of the configured source types — the five defaults (e.g. avatar, gallery) or custom types passed via the sourceTypes registration option |
sourceId | string | Yes | Identifier for the owning resource (e.g. user ID, post ID) |
Response
202 Accepted
{
"jobId": "clx1234abcd",
"message": "File uploaded. Processing started.",
"statusUrl": "/image-pipeline/status/clx1234abcd"
}
Throws
Errors are returned as status-coded JSON bodies ({ "error": "..." }) with the [xImagePipeline] prefix.
| Error | HTTP | Cause |
|---|---|---|
[xImagePipeline] No file provided | 400 | Request has no file field |
[xImagePipeline] sourceType and sourceId are required | 400 | Missing form fields |
[xImagePipeline] Unknown sourceType: {type}. Allowed types: … | 400 | sourceType is not a configured sourceTypes key (defaults or custom types passed at registration) |
[xImagePipeline] File type {mime} not allowed. Allowed types: … | 400 | MIME type not in allowedMimeTypes |
[xImagePipeline] File too large. Maximum size: {n}MB | 413 | File exceeds maxFileSize |
[xImagePipeline] Failed to upload file to storage | 500 | R2 upload error |
[xImagePipeline] Failed to create processing job | 500 | Database error creating the job (staging object is cleaned up) |
[xImagePipeline] Upload failed | 500 | Unexpected handler error |
Examples
cURL upload
curl -X POST http://localhost:3000/image-pipeline/upload \
-F "file=@/path/to/photo.jpg" \
-F "sourceType=avatar" \
-F "sourceId=user-abc123"
Fastify route that proxies a user avatar upload
fastify.post("/users/:id/avatar", async (request, reply) => {
const data = await request.file();
const form = new FormData();
form.append("file", data.file, { filename: data.filename, contentType: data.mimetype });
form.append("sourceType", "avatar");
form.append("sourceId", request.params.id);
const response = await fetch("http://localhost:3000/image-pipeline/upload", {
method: "POST",
body: form,
});
const { jobId, statusUrl } = await response.json();
// Store jobId and poll statusUrl until COMPLETE
return { jobId, statusUrl };
});
Custom source types
Custom sourceTypes passed at registration are honored by this route — uploads are validated against the effective configuration, not a hardcoded list. Providing sourceTypes replaces the five defaults entirely.
await fastify.register(xImagePipeline, {
r2: { /* ... */ },
db: prisma,
sourceTypes: {
product_photo: {
variants: ["sm", "md", "lg"],
formats: ["webp"],
quality: 90,
storeOriginal: true,
},
},
});
curl -F "file=@product.jpg" -F "sourceType=product_photo" -F "sourceId=sku-42" \
http://localhost:3000/image-pipeline/upload
See Also
- GET /image-pipeline/status/:jobId — Poll processing status after upload
- getStatus(jobId) — Programmatic status fetch via decorator
AI Context
package: "@xenterprises/fastify-ximagepipeline"
route: POST /image-pipeline/upload (multipart)
use-when: Upload an image file for async processing — returns jobId immediately; worker processes in background
fields: file (multipart), sourceType (string — any configured sourceTypes key, defaults or custom), sourceId (string)
returns: 202 { jobId, message, statusUrl }
S3 / R2 Utilities
Low-level S3-compatible storage helpers for uploading, downloading, deleting, listing, and signing objects — importable standalone from the pipeline plugin.
fastify-xpdf
Fastify 5 plugin for PDF generation and manipulation — HTML/Markdown/URL to PDF via Puppeteer, form filling, merging, page extraction, and metadata via pdf-lib, with optional S3 storage.
