Implementing Media Uploads for Prompts with Required Media Types in prompts.chat

To implement media uploads for prompts with required media types in prompts.chat, use the set_media_requirements AI tool to declare upload constraints, persist them via the /api/prompts endpoint, and handle file validation through the /api/upload route with configurable storage plugins.

The f/prompts.chat repository enables prompt creators to mandate user-supplied media through a structured three-layer architecture. By implementing media uploads for prompts with required media types, you can enforce that consumers provide specific images, videos, or documents before executing a prompt. This implementation spans AI tool definitions, database persistence, and pluggable storage backends.

Declaring Media Requirements in the Prompt Builder

The system exposes a set_media_requirements tool to the AI prompt builder, allowing dynamic declaration of media constraints during prompt creation.

The set_media_requirements Tool Definition

In src/lib/ai/prompt-builder-tools.ts (lines 85-102), the tool schema defines three parameters that control upload behavior:

  • requiresMediaUpload (boolean): Whether the prompt requires a file upload.
  • mediaType ("IMAGE" | "VIDEO" | "DOCUMENT"): The allowed media category.
  • mediaCount (number): How many files the user must provide, clamped between 1 and 10.

Updating Builder State

When the AI invokes this tool, the handler in src/lib/ai/prompt-builder-tools.ts (lines 516-529) mutates the builder state to store these requirements. The state persists these values until the prompt is saved to the database.

// Tool execution handler updates state
{
  name: "set_media_requirements",
  arguments: {
    requiresMediaUpload: true,
    mediaType: "IMAGE",
    mediaCount: 3
  }
}

Persisting Media Constraints to the Database

The /api/prompts route validates and stores media requirements alongside the prompt record. In src/app/api/prompts/route.ts (lines 23-55), a Zod schema validates the incoming payload including requiresMediaUpload, requiredMediaType, and requiredMediaCount.

The persistence logic (lines 180-185) stores these fields in the database via Prisma, ensuring that media constraints are permanently associated with the prompt.

// POST /api/prompts request body
{
  title: "Visual Analysis",
  description: "Analyze user-uploaded images",
  requiresMediaUpload: true,
  requiredMediaType: "IMAGE",
  requiredMediaCount: 2
}

Exposing Requirements in the Public Feed

When the public JSON feed is generated at /prompts.json, the system includes the media requirement flags so that client applications can render appropriate upload widgets. In src/app/prompts.json/route.ts (line 85), the requiresMediaUpload field is explicitly included in the feed response.

Handling File Uploads with Validation and Compression

The /api/upload endpoint serves as the core of the media-upload pipeline, handling authentication, validation, compression, and storage delegation.

Authentication and Storage Plugin Selection

The handler in src/app/api/upload/route.ts (lines 1-121) first validates the user session via NextAuth, then selects a storage plugin based on the ENABLED_STORAGE environment variable (defaulting to url).

The storage plugin is retrieved from the registry at src/lib/plugins/registry.ts and must implement isConfigured() to verify deployment readiness.

File Type and Size Validation

The endpoint enforces strict validation before processing:

  • Allowed types: ALLOWED_IMAGE_TYPES and ALLOWED_VIDEO_TYPES arrays whitelist specific MIME types.
  • Size limits: 4 MiB maximum for both images and videos (matching Vercel serverless limits).
// Validation logic from src/app/api/upload/route.ts
if (!ALLOWED_IMAGE_TYPES.includes(file.type) && !ALLOWED_VIDEO_TYPES.includes(file.type)) {
  // Reject unsupported types with 400 error
}

if (file.size > maxSize) {
  // Reject oversized payloads
}

Image Compression with Sharp

For image uploads, the system uses sharp to compress files to JPEG format at 90% quality before storage:

import sharp from 'sharp';

async function compressToJpg(buffer: Buffer): Promise<Buffer> {
  return await sharp(buffer)
    .jpeg({ quality: 90, mozjpeg: true })
    .toBuffer();
}

The endpoint returns a JSON response containing the public URL and file size:

{ "url": "https://cdn.example.com/prompt-media-12345.jpg", "size": 312345 }

Configuring Storage Plugins (S3, DigitalOcean Spaces, or URL-Only)

The architecture supports three storage backends selected via environment configuration.

URL-Only Mode

The default url plugin (src/lib/plugins/storage/url.ts, lines 15-20) acts as a fallback that rejects uploads with a helpful message, forcing users to provide external URLs instead of direct uploads.

AWS S3 Implementation

The S3 plugin in src/lib/plugins/storage/s3.ts (lines 45-70) creates signed PUT requests and streams the buffer to S3. Configuration requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET, and AWS_REGION environment variables.

DigitalOcean Spaces

For DigitalOcean Spaces, src/lib/plugins/storage/do-spaces.ts (lines 65-90) implements the same interface but targets the DO Spaces endpoint. It requires DO_SPACES_KEY, DO_SPACES_SECRET, DO_SPACES_BUCKET, and DO_SPACES_ENDPOINT.

Both cloud plugins validate configuration via isConfigured() before upload attempts.

End-to-End Implementation Example

To create a prompt that requires three images and handle the upload flow:

  1. Define the prompt via the API:
await fetch("/api/prompts", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    title: "Photo Comparison",
    requiresMediaUpload: true,
    requiredMediaType: "IMAGE",
    requiredMediaCount: 3,
    // additional fields...
  })
});
  1. Upload files from the client:
const form = new FormData();
form.append('file', fileInput.files[0]);

fetch('/api/upload', {
  method: 'POST',
  body: form,
  credentials: 'include'
})
.then(r => r.json())
.then(({ url }) => {
  // Store URL for prompt execution
  console.log('Media available at:', url);
});

Summary

  • Declare requirements using the set_media_requirements AI tool in src/lib/ai/prompt-builder-tools.ts to set requiresMediaUpload, mediaType, and mediaCount.
  • Persist constraints through the /api/prompts endpoint which validates fields via Zod in src/app/api/prompts/route.ts.
  • Expose flags in the public feed via src/app/prompts.json/route.ts so client UIs know to render upload widgets.
  • Validate uploads at /api/upload with type checking, 4 MiB size limits, and sharp-based image compression.
  • Configure storage using S3, DigitalOcean Spaces, or URL-only mode via environment variables and the plugin registry.

Frequently Asked Questions

How do I configure S3 for media uploads in prompts.chat?

Set the ENABLED_STORAGE environment variable to s3 and provide AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_BUCKET, and AWS_REGION. The system will automatically use the S3 plugin in src/lib/plugins/storage/s3.ts to generate signed URLs and upload buffers.

What file types and sizes are supported for uploads?

The upload endpoint in src/app/api/upload/route.ts accepts only MIME types listed in ALLOWED_IMAGE_TYPES and ALLOWED_VIDEO_TYPES arrays. All files must be under 4 MiB due to Vercel serverless function limits. Images are automatically compressed to JPEG at 90% quality using sharp.

How does the frontend know when to show an upload widget?

The prompt record includes requiresMediaUpload, requiredMediaType, and requiredMediaCount fields. These are exposed in the public JSON feed (src/app/prompts.json/route.ts) and returned by the /api/prompts endpoint. The client UI checks these flags to conditionally render the upload component and enforce the required file count.

Can I use DigitalOcean Spaces instead of AWS S3?

Yes. Set ENABLED_STORAGE to do-spaces and configure DO_SPACES_KEY, DO_SPACES_SECRET, DO_SPACES_BUCKET, and DO_SPACES_ENDPOINT. The implementation in src/lib/plugins/storage/do-spaces.ts follows the same interface as the S3 plugin, allowing seamless substitution of the storage backend.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →