Working with Images

Mushu provides on-demand image transforms via URL. Only the original is stored; variants are generated and cached at the edge on first request.

Named Variants

Every uploaded image is available in these predefined variants:

VariantSizeUse Case
original Original size Full resolution downloads
thumbnail 150x150 (cover) Grid views, lists
small 320px width Mobile previews
medium 640px width Standard display
large 1280px width Hero images, galleries

Custom Sizes

For sizes not covered by named variants, use the custom endpoint with URL parameters:

https://images.mushucorp.com/t/custom/KEY?w=WIDTH&h=HEIGHT&fit=FIT&gravity=GRAVITY&q=QUALITY

Parameters

ParamValuesDefaultDescription
w 1-2000 required Width in pixels
h 1-2000 optional Height in pixels (maintains aspect ratio if omitted)
fit cover, contain, scale-down, crop cover How to fit image to dimensions
gravity auto, face, center auto Focus point when cropping
q 1-100 85 Output quality

Avatar Example

For profile photos with face detection:

<!-- 128x128 avatar with face detection -->
<img
  src="https://images.mushucorp.com/t/custom/org_xxx/photo.jpg?w=128&h=128&gravity=face"
  width="128"
  height="128"
  alt="Profile"
/>

<!-- Common avatar sizes -->
<img src=".../t/custom/KEY?w=32&h=32&gravity=face" />   <!-- tiny -->
<img src=".../t/custom/KEY?w=64&h=64&gravity=face" />   <!-- small -->
<img src=".../t/custom/KEY?w=128&h=128&gravity=face" /> <!-- medium -->
<img src=".../t/custom/KEY?w=200&h=200&gravity=face" /> <!-- standard -->
<img src=".../t/custom/KEY?w=400&h=400&gravity=face" /> <!-- high-DPI -->

Width-Only Resize

Omit h to maintain aspect ratio:

<!-- Resize to 500px wide, height calculated automatically -->
<img src="https://images.mushucorp.com/t/custom/org_xxx/photo.jpg?w=500" />

Getting Image URLs

CLI

mushu media url MEDIA_ID --variant thumbnail

API

GET /media/{media_id}/images
Authorization: Bearer YOUR_TOKEN

Response:

{
  "media_id": "media_abc123",
  "original": "https://images.mushucorp.com/t/original/org_xxx/media_abc123/photo.jpg",
  "thumbnail": "https://images.mushucorp.com/t/thumbnail/org_xxx/media_abc123/photo.jpg",
  "small": "https://images.mushucorp.com/t/small/org_xxx/media_abc123/photo.jpg",
  "medium": "https://images.mushucorp.com/t/medium/org_xxx/media_abc123/photo.jpg",
  "large": "https://images.mushucorp.com/t/large/org_xxx/media_abc123/photo.jpg"
}

URL Format

Image URLs follow this pattern:

https://images.mushucorp.com/t/VARIANT/KEY

Where:

  • VARIANT - One of: original, thumbnail, small, medium, large, or custom (with params)
  • KEY - The storage key from the media item

Using in HTML

<!-- Responsive image with srcset -->
<img
  src="https://images.mushucorp.com/t/medium/org_xxx/photo.jpg"
  srcset="
    https://images.mushucorp.com/t/small/org_xxx/photo.jpg 320w,
    https://images.mushucorp.com/t/medium/org_xxx/photo.jpg 640w,
    https://images.mushucorp.com/t/large/org_xxx/photo.jpg 1280w
  "
  sizes="(max-width: 320px) 320px, (max-width: 640px) 640px, 1280px"
  alt="My photo"
/>

<!-- Thumbnail for lists -->
<img
  src="https://images.mushucorp.com/t/thumbnail/org_xxx/photo.jpg"
  width="100"
  height="100"
  alt="Thumbnail"
/>

Using in iOS

import SDWebImage

// Load thumbnail in a collection view cell
imageView.sd_setImage(with: URL(string: media.thumbnailUrl))

// Load full size with placeholder
imageView.sd_setImage(
    with: URL(string: media.largeUrl),
    placeholderImage: UIImage(named: "placeholder")
)

Using in React

function MediaImage({ media, variant = 'medium' }) {
  const baseUrl = 'https://images.mushucorp.com/t';

  return (
    <picture>
      <source
        media="(min-width: 1024px)"
        srcSet={`${baseUrl}/large/${media.key}`}
      />
      <source
        media="(min-width: 640px)"
        srcSet={`${baseUrl}/medium/${media.key}`}
      />
      <img
        src={`${baseUrl}/${variant}/${media.key}`}
        alt={media.filename}
        loading="lazy"
      />
    </picture>
  );
}

Image Processing Details

  • Format - Output is WebP for better compression (JPEG fallback)
  • Quality - 85% quality for optimal size/quality balance
  • Caching - Images are cached at Cloudflare's edge globally
  • On-Demand - Variants are generated on first request, then cached

Listing Images

CLI

mushu media list --org ORG_ID --type image

API

GET /media/org/{org_id}?media_type=image
Authorization: Bearer YOUR_TOKEN

Deleting Images

CLI

mushu media delete MEDIA_ID

API

DELETE /media/{media_id}
Authorization: Bearer YOUR_TOKEN

Deletion removes the original file from R2. Cached variants will expire based on CDN TTL settings (typically within 24 hours).

FAQ

Can I request custom sizes?

Yes! Use the /t/custom/KEY endpoint with URL parameters. See Custom Sizes above.

What happens if I request a variant for a video?

The image endpoints only work for media with media_type: "image". For video thumbnails, use the video endpoint.

Are there rate limits?

Image transforms are rate-limited to prevent abuse. Normal usage (thousands of requests per day) won't hit limits.

Can I hotlink images?

Yes, image URLs are public and can be used anywhere. Consider the security implications for sensitive content.