Photo storage and delivery for photo-sharing apps
Dreep stores every original your users upload and serves each size from the URL. Phones upload directly through presigned URLs your backend issues, photos sit under users/{userId}/, private albums live in signed folders opened by short-lived links, and iPhone HEIC is delivered as WebP or AVIF. Plan limits are fixed and there is no automatic overage billing.
From camera roll to feed
Your backend decides who may upload and how much; Dreep stores, converts and delivers. The bytes never pass through your servers.
User shares 8 photos
HEIC from iPhone, JPEG from Android
Backend checks quota
Presigns into albums/{userId}/{albumId}
Phone PUTs bytes
Single-use URL, 10 minutes
Confirm
Pending until confirmed
Feed and lightbox
format=avif, signed if private
Folder layout: public profiles, private albums
Access is set per folder and inherited by every subfolder, so pick two roots and let uploads create the rest on the fly.
| Path | Access | Holds | Served as |
|---|---|---|---|
| users/{userId}/avatar | Public (root users/) | Profile photo | Plain URL, e.g. width=128&height=128&radius=max&format=webp |
| users/{userId}/posts | Public (inherited) | Photos shared to the public feed | Plain URLs with responsive sizes |
| albums/{userId}/{albumId} | Signed (root albums/) | Private and friends-only albums | Signed URLs your backend issues per viewer, e.g. 10 minutes |
Private albums and plan-limit handling
Create the Signed root once, presign each upload into the owner's album, and hand viewers signed URLs that carry the size they need.
import { Dreep, DreepLimitError } from "dreep";
const dreep = new Dreep({
apiKey: process.env.DREEP_API_KEY,
signingSecret: process.env.DREEP_SIGNING_SECRET,
});
// Once, at setup: albums/ and every folder under it are Signed.
await dreep.createFolder({ path: "albums", accessControlType: "signed", defaultExpirySeconds: 600 });
// POST /api/albums/:albumId/photos/presign (user already authenticated)
export const presignAlbumPhoto = async ({ userId, albumId, filename, contentType, sizeBytes }) => {
try {
const upload = await dreep.presignUpload({
filename,
contentType, // "image/heic" straight from the phone is fine
sizeBytes,
folder: "albums/" + userId + "/" + albumId,
});
return { id: upload.id, uploadUrl: upload.uploadUrl };
} catch (error) {
if (error instanceof DreepLimitError) {
// Project plan limit reached: error.featureKey, error.used, error.limit
return { error: "storage_full" };
}
throw error;
}
};
// Only after checking the viewer may open this album.
export const albumPhotoUrls = ({ asset }) => ({
thumb: dreep.signedUrl(asset, {
expiresIn: 600,
transform: { width: 384, height: 384, fit: "cover", gravity: "attention", format: "avif", quality: 65 },
}),
full: dreep.signedUrl(asset, {
expiresIn: 600,
transform: { width: 1920, fit: "inside", format: "avif", quality: 75 },
}),
});// Once, at setup: albums/ and every folder under it are Signed.
_, err := client.CreateFolder(ctx, dreep.CreateFolderOptions{
Path: "albums",
AccessControlType: dreep.AccessSigned,
DefaultExpirySeconds: 600,
})
// Per photo in a private album, after checking the viewer's access.
// dreep-go signs the original only, so pass nil. Sign resized thumbnails
// with the JavaScript SDK until the Go SDK signs transform parameters.
photo, err := client.SignedURL(asset.ID, 10*time.Minute, nil)
// Plan limits come back as 402s you can branch on.
if key, ok := dreep.FeatureLimit(err); ok {
log.Printf("plan limit reached on %s", key)
}# Once: a Signed root for private albums
curl -X POST https://api.dreep.cloud/api/v1/folders \
-H "Authorization: Bearer $DREEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path":"albums","accessControlType":"signed","defaultExpirySeconds":600}'
# Per photo: presign into the user's album (HEIC accepted as is)
curl -X POST https://api.dreep.cloud/api/v1/upload/presign \
-H "Authorization: Bearer $DREEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"IMG_4120.HEIC","contentType":"image/heic","sizeBytes":3145728,
"folder":"albums/u_193/summer-2026"}'
# A public post, delivered as AVIF regardless of the HEIC original
curl -o post.avif "https://cdn.dreep.cloud/api/v1/fetch/$ASSET_ID?width=1080&fit=inside&format=avif&quality=75"HEIC from phones, WebP and AVIF out
iPhones save photos as HEIC by default, and many browsers cannot display it. Accept the file as uploaded, keep it as the original, and request format=avif or format=webp on every delivery URL. Viewers never see the HEIC file and you never run a conversion job.
For a grid, width=384&height=384&fit=cover&gravity=attention&format=avif&quality=65 gives square thumbnails cropped around the most salient part of the photo. For the lightbox, width=1920&fit=inside keeps the full frame. Add dpr=2 or dpr=3 for high-density screens instead of guessing pixel sizes. Each size is generated on first request, then cached.
Transform on upload, on demand or on the device
Three places to resize a photo, each with a cost. Most photo apps combine a light device-side downscale with on-demand delivery sizes.
| Approach | How | Gains | Costs | Use it for |
|---|---|---|---|---|
| On upload | transform on upload or confirm, e.g. width=2560&format=webp | Smaller stored file; one transformation per photo | The original is not kept, so you cannot regenerate a larger size later | Capping huge camera files when you never need full resolution |
| On demand | URL parameters or presets at delivery | Original kept; add new sizes any time; cached after first request | Each new variant counts towards monthly transformations; cached variants use storage | Feeds, grids, lightboxes, share previews |
| On the device | Resize in the app before upload | Smaller uploads on mobile data; no Dreep quota used | Quality varies by device, costs battery, HEIC handling differs by OS; you still need delivery sizes | Trimming 48 MP camera output before it leaves the phone |
Predictable quotas, and what happens at the limit
Limits are per project and reset monthly for transformations. Photo counts below assume an average stored original of 2.5 MB and ignore cached variants.
| Plan | Price | Storage | Roughly | Image transformations / month |
|---|---|---|---|---|
| Free | $0 | 1 GB | 400 photos | 100 |
| Starter | $2.99/month | 10 GB | 4,000 photos | 10,000 |
| Pro | $5.99/month | 50 GB | 20,000 photos | 50,000 |
| Add-ons | +100 GB $6.99/month; +100k transformations $4.99 once | Stackable | +40,000 photos per 100 GB | One-time packs |
When a limit is reached, the request fails with LIMIT_EXCEEDED (HTTP 402); the JavaScript SDK throws DreepLimitError with featureKey, used and limit, and the Go SDK exposes the same through dreep.FeatureLimit. Nothing is billed automatically. You buy an add-on or upgrade, and requests succeed again.
Dreep enforces limits for the whole project, not per end user. Keep per-user quotas in your own database, using sizeBytes from each confirmed asset, and poll GET /usage so you can alert at 80% rather than discover the limit from a failed upload.
What each feature does for a photo app
Presigned uploads
Photos go from the phone to Dreep without touching your servers, so upload spikes after a holiday weekend do not need more backend capacity.
Inherited folder access
A new album folder created by an upload is Signed because albums/ is Signed. No per-album setup, no accidental public album.
Format on delivery
HEIC, JPEG and PNG originals all leave as AVIF or WebP, chosen by a URL parameter rather than a conversion pipeline.
Fixed limits
A viral week cannot produce an open-ended bill. It produces a LIMIT_EXCEEDED error you can catch and act on.
Who this is for
- Photo-sharing, family album and community apps with user uploads from phones.
- Apps that need private or friends-only albums served through expiring links.
- Teams that want a fixed monthly cost and explicit errors instead of overage charges.
- Apps whose users upload HEIC and view on any browser.
When Dreep is not the right fit
- You need automatic nudity or abuse detection. Dreep does not moderate content; add a moderation step.
- You need face recognition or people tagging. Dreep only offers attention-based cropping.
- You store many terabytes on managed storage. Plan tiers here are sized in gigabytes; look at Bring Your Own Bucket or a storage-first provider.
Frequently asked questions
Can users upload HEIC photos straight from an iPhone?
Yes. HEIC and HEIF are accepted inputs. Store the file as uploaded and request format=avif or format=webp when you display it.
Should private albums use Private or Signed folders?
Signed. Private folders can only be viewed by project members in the Dreep dashboard, so your users could never open them. Signed folders are fetched through time-limited signed URLs your backend generates.
How long should a signed album link last?
As short as your UI allows, often 5 to 15 minutes for in-app viewing. Anyone holding a signed URL can open it until it expires, so treat it as a bearer token. Signing is local to the SDK, so issuing fresh links costs no API call.
How do I limit how much each user can upload?
In your backend, before you presign. Dreep enforces plan limits per project, not per end user. Record sizeBytes from each confirmed upload and refuse to presign when a user is over their allowance.
Do thumbnails count against my plan?
Yes. Generating a new size counts towards monthly image transformations, and cached variants take up storage. Fewer, consistent sizes keep both numbers down, which is what presets are for.
How do I delete everything when a user closes their account?
Walk the user's folders with listAllMedia (or ListAllMedia in Go) and delete each asset. Deleting removes the original and every cached transform, and credits the space back.
Related
Secure file uploads
Presigned uploads and what your backend must still authorise.
Image CDN API
Width, format, quality, gravity and dpr parameters in detail.
Mobile apps
Swift and Kotlin uploads with a backend-issued URL.
Bring your own bucket
Keep originals and variants in your own S3-compatible bucket.
Pricing
Plan limits and add-on packs in one table.
Signed URLs in the docs
How exp and sig are computed and checked.
Build a private album flow on the free plan
Create a Signed albums folder, upload a HEIC photo from your phone and open it through a signed AVIF link. 1 GB and 100 transformations are free.