How Instatic Handles Media Storage and Image Variants: Architecture and Implementation

Instatic treats media as a first-class resource using a pluggable storage adapter system with a two-phase upload contract and an automatic image-variant pipeline that generates responsive WebP ladders.

The CoreBunch/Instatic repository implements a fully modular media layer that separates storage concerns from presentation logic. By abstracting uploads through signed plans and delegating variant generation to either local workers or external CDNs, the system keeps plugins lightweight while ensuring bytes never cross the QuickJS sandbox boundary.

Storage Adapter Architecture

Instatic organizes media access through storage adapters, each implementing a strict two-phase upload contract. Adapters register via the plugin SDK at api.cms.media.registerStorageAdapter and declare supported roles (original, variant, avatar, font, plugin-pack). The host elects one adapter per role, persisting the choice in the active_media_storage_adapter database row managed by src/server/repositories/media.ts.

Two-Phase Upload Contract

Every adapter implements beginWrite → finalizeWrite:

  1. beginWrite: Returns a MediaStorageUploadPlan containing signed URLs, HTTP method, and expiration.
  2. Streaming: The host executes the plan in src/server/handlers/cms/mediaUploadExecutor.ts, streaming bytes via fetch or local filesystem operations without entering the plugin sandbox.
  3. finalizeWrite: Confirms persistence and validates content hashes.

Registry and Resolution

The singleton registry in src/core/plugins/mediaStorageRegistry.ts maintains an in-memory map of all adapters. At boot, the system wires the built-in local-disk adapter via mediaStorageRegistry.configureLocalDisk, storing files under the uploads/ directory. When resolving reads or writes, the registry queries the elected adapter ID from the database and returns the matching instance.

Serving Modes

Adapters declare a servingMode that determines how browsers retrieve assets:

  • public-url: Direct browser access (used by the local-disk adapter).
  • signed-redirect: The host issues a short-lived signed URL and redirects via /_instatic/media/<adapterId>/<storagePath>.
  • proxy: The host streams bytes through a FastCGI-style proxy.

These contracts are defined in src/core/plugin-sdk/types/media.ts.

The Upload Pipeline

When a client initiates an upload, the request flows through a strictly typed dispatch chain:


POST /admin/api/cms/media/upload
   └─► server/handlers/cms/mediaUpload.ts (validation)
        └─► mediaUploadDispatch.ts (role-based routing)
             └─► mediaUploadExecutor.ts (byte streaming)
                  └─► executeUploadPlan()

The executor walks the MediaStorageUploadPlan steps, handling file:// sentinels for local writes or performing PUT/POST requests for remote buckets. This design ensures that bytes never cross the QuickJS sandbox, keeping plugins secure and lightweight.

Image Variant Generation and Ladder

After the original file persists, the image-variant worker in src/server/handlers/cms/imageVariantWorker.ts processes the asset using sharp. It generates a ladder of WebP variants at preset widths ranging from 64 px up to the image’s intrinsic width.

Variant Storage

Generated variants are stored in the media_assets table under the variants_json column (following the repository’s *_json naming convention for JSON columns). Each entry contains the variant’s width, path, and metadata.

Rendering Helpers

The admin UI and publisher rely on utilities in src/modules/base/utils/mediaAttrs.ts to construct responsive markup:

// Build a srcset attribute from the variant ladder
export function buildMediaSrcset(media: RenderResolvedMedia): string | null;

// Select the smallest variant ≥ target width
export function pickMediaVariantUrl(
  media: RenderResolvedMedia | null,
  targetWidth: number | null,
): string | null;

These functions sort the variants array and return HTML-safe URLs via safeUrl, enabling browsers to select optimal resolutions.

Variant Delegates for External CDNs

For sites using external image CDNs, Instatic supports variant delegates (Tier 3 plugins). A delegate replaces the local ladder generation with URL templates pointing to services like Cloudflare Images or Imgix.

Registration occurs via api.cms.media.registerVariantDelegate:

api.cms.media.registerVariantDelegate({
  id: 'myplugin.cdn',
  variantUrlTemplate:
    'https://cdn.example.com/img/{width}/{format}{path}',
  widths: [640, 1280, 1920],
  formats: ['webp', 'jpeg'],
});

When elected, the host skips local variant generation and stores only the delegate template. At render time, src/core/publisher/mediaPresentation.ts builds URLs on-the-fly by interpolating the template. The delegate registry lives in src/core/plugins/mediaVariantDelegateRegistry.ts.

Database Schema and Persistence

Media assets are organized across dedicated tables:

  • media_assets: Stores original file metadata, storage adapter IDs, and variants_json.
  • media_folders: Hierarchical organization of media.
  • media_asset_folders: Junction table linking assets to folders.

The schema supports strict separation between storage backend configuration and asset metadata, allowing adapter swaps without migrating file data.

Security and Runtime Flow

The complete lifecycle enforces a strict security model:

  1. Validation: server/handlers/cms/mediaUpload.ts validates MIME types and content hashes before dispatch.
  2. Signed Plans: Adapters return time-limited signed URLs (default 5-minute expiration) via beginWrite.
  3. Sandbox Isolation: Plugin code only manipulates upload plans; byte streaming executes in the host process via mediaUploadExecutor.ts.
  4. Proxy Safety: When using servingMode: 'proxy', the host validates all paths before streaming to prevent directory traversal.

Code Examples

Register a Custom Storage Adapter

// Plugin-side registration for S3-compatible storage
api.cms.media.registerStorageAdapter({
  id: 'myplugin.s3',
  label: 'Amazon S3',
  roles: ['original', 'variant'],
  servingMode: 'signed-redirect',
  beginWrite: async (input) => {
    const signedUrl = await getSignedPutUrl(input.suggestedStoragePath);
    return {
      storagePath: input.suggestedStoragePath,
      steps: [{ method: 'PUT', url: signedUrl, headers: {} }],
      expiresAt: Date.now() + 5 * 60 * 1000,
    };
  },
  finalizeWrite: async ({ storagePath, uploadReceipts }) => {
    // Verify etag and update database
  },
});

Register a Variant Delegate

// Redirect variant generation to Cloudflare Images
api.cms.media.registerVariantDelegate({
  id: 'myplugin.cloudflare',
  variantUrlTemplate:
    'https://example.com/cdn-cgi/image/width={width},format={format},quality=80{path}',
  widths: [640, 1280, 1920, 3840],
  formats: ['webp', 'jpeg'],
});

Consume Variants in a React Component

import { buildMediaSrcset, pickMediaVariantUrl } from '@modules/base/utils/mediaAttrs';

function ResponsiveImage({ media }: { media: RenderResolvedMedia }) {
  const srcSet = buildMediaSrcset(media);
  const src = pickMediaVariantUrl(media, 800); // Target 800px width
  
  return (
    <img 
      src={src} 
      srcSet={srcSet} 
      alt={media.altText}
      loading="lazy"
    />
  );
}

Execute Upload Plan on the Host

import { executeUploadPlan } from '@core/plugins/mediaUploadExecutor';

async function streamUpload(
  plan: MediaStorageUploadPlan, 
  fileBuffer: Uint8Array,
  adapter: MediaStorageAdapter
) {
  // Execute the signed plan steps
  const receipts = await executeUploadPlan(plan, fileBuffer);
  
  // Confirm completion with the adapter
  await adapter.finalizeWrite({
    storagePath: plan.storagePath,
    uploadReceipts: receipts,
  });
}

Summary

  • Pluggable Architecture: Storage adapters implement a beginWrite/finalizeWrite contract, allowing seamless integration with local disks or cloud buckets without modifying core logic.
  • Secure Isolation: Byte streaming occurs in the host process (mediaUploadExecutor.ts), while plugins only handle signed URLs, maintaining strict QuickJS sandbox boundaries.
  • Automatic Variants: The image-variant worker generates WebP ladders stored in variants_json, with helpers in mediaAttrs.ts enabling responsive srcset generation.
  • CDN Delegation: Tier 3 plugins can replace local processing via variant delegates, offloading transformation to external CDNs using URL templates.
  • Type Safety: All contracts are defined in src/core/plugin-sdk/types/media.ts, ensuring consistent behavior across storage backends and rendering contexts.

Frequently Asked Questions

How does Instatic secure media uploads against unauthorized access?

Instatic validates MIME types and content hashes in server/handlers/cms/mediaUpload.ts before dispatching to adapters. Storage adapters return time-limited signed URLs (typically expiring after 5 minutes) through the beginWrite method, ensuring that upload endpoints cannot be replayed. Additionally, the servingMode configuration restricts direct file access, with signed-redirect and proxy modes enforcing host-side authorization before bytes reach the browser.

Can I use Amazon S3 or Google Cloud Storage instead of local disk?

Yes. You can register a custom storage adapter via api.cms.media.registerStorageAdapter that implements the two-phase upload contract. The adapter specifies roles: ['original', 'variant'] and a servingMode of either signed-redirect (for presigned S3 URLs) or proxy (for streaming through your server). The host stores the elected adapter ID in active_media_storage_adapter and routes all read/write operations through your implementation without code changes to the core.

What image formats and sizes does Instatic generate by default?

By default, the image-variant worker in server/handlers/cms/imageVariantWorker.ts generates WebP variants at preset widths starting from 64 px up to the image’s intrinsic width. The exact width steps are configurable, but the system prioritizes WebP for compression efficiency. If you register a variant delegate, Instatic skips local generation entirely and relies on the delegate’s specified formats (commonly webp and jpeg) and widths array.

How do I customize the responsive image sizes for my theme?

In the publisher or admin UI, use the pickMediaVariantUrl helper from src/modules/base/utils/mediaAttrs.ts to select variants by target width. For global customization, register a variant delegate with api.cms.media.registerVariantDelegate defining your own widths array (e.g., [640, 1280, 1920]). The delegate’s variantUrlTemplate can point to any image processing service, allowing you to define custom size ladders without modifying the database schema.

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 →