AppEngine has a unified file pipeline that backs the CMS, the storefront product images, document uploads in CRM, AI document processing, and avatar handling. One set of endpoints, one access-control model, multiple providers behind the scenes.
Storage providers
The repository module ships three file providers:
- Local filesystem —
repository.provider.file.ts. Default in dev; files live under the configured upload path. - S3 —
repository.provider.file.s3.ts. The production default; handles signed URLs, multipart uploads, and CDN edge integration. - AWS replica —
repository.provider.aws.ts. For multi-region replication setups.
The active provider is decided per-org by config — the same code paths upload, fetch, and delete regardless of where bytes actually land. Find out which is in use:
/repository/file/driverJWTUploading
The primary upload endpoint takes a multipart form with a file field.
/repository/file/uploadJWTcurl -X POST https://appengine.appmint.io/repository/file/upload \
-H "orgid: my-org" \
-H "Authorization: Bearer $JWT" \
-F "file=@./photo.jpg" \
-F "path=products/sku-12345/" \
-F "isPublic=true"
The response includes the storage path, the public URL (when isPublic: true), the content type, and any image metadata (width, height, generated thumbnails). For S3, AppEngine generates xs/sm/md derivatives automatically for image uploads.
For browser-direct uploads that skip the API entirely, ask for a presigned URL:
/repository/file/upload-urlJWTcurl -X POST https://appengine.appmint.io/repository/file/upload-url \
-H "orgid: my-org" -H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{ "path": "uploads/large-video.mp4", "contentType": "video/mp4" }'
The browser then PUTs directly to S3. Use this for anything over a few MB — saves your AppEngine instance the bandwidth.
Customer uploads
Customer principals (storefront buyers, support requesters) get their own upload route, scoped to their own folder.
/profile/customer/file/uploadJWTSame multipart shape, but the destination path is forced under the customer's namespace, so a buyer can't overwrite another buyer's files.
File operations
Beyond upload, the repository exposes the operations you'd expect from a remote filesystem:
/repository/fileJWT/repository/file/bufferJWT/repository/file/streamJWT/repository/file/urlJWT/repository/file/signurlJWT/repository/file/statJWT/repository/file/existsJWT/repository/file/copyJWT/repository/file/moveJWT/repository/file/deleteJWT/repository/file/appendJWT/repository/file/prependJWT/repository/file/signurl is the most useful one for private content — it returns a time-limited signed URL the browser can fetch directly. Default expiry is 15 minutes; pass expires (seconds) to override.
How files attach to records
Files don't live in their own first-class collection. They're referenced by path or URL inside whichever record needs them — a product's images, a contact's avatar, a page's coverImage. The shape on the record is consistent:
{
"data": {
"title": "Sneakers",
"images": [
{
"path": "products/sku-12345/photo.jpg",
"url": "https://cdn.appmint.io/.../photo.jpg",
"contentType": "image/jpeg",
"isPublic": true,
"meta": {
"width": 2048,
"height": 1536,
"size": 412345,
"xs": { "path": "...", "url": "..." },
"sm": { "path": "...", "url": "..." },
"md": { "path": "...", "url": "..." }
}
}
]
}
}
When the upload endpoint returns, you copy the response straight into the parent record's relevant field and update the record. There's no separate "attach" call.
Asset endpoints
A separate set of endpoints — find-asset, update-asset, delete-asset, search-asset — operate on records whose primary purpose is to hold a file (images, videos, PDFs in a media library). They wrap the same file plumbing with a media-library query layer.
/repository/find-asset/{datatype}JWT/repository/update-asset/{datatype}/{id}JWT/repository/delete-asset/{datatype}JWT/repository/search-asset/{datatype}JWTThese are the endpoints the admin media browser calls.
Public vs private files
Files are private by default. Two ways to make one public:
- Set
isPublic: trueon upload — the file lands in a publicly-readable bucket and the response carries a stable URL. - Use signed URLs (
/repository/file/signurl) for time-limited shares without changing visibility.
Once a file is public, it stays public until you delete it or move it. Don't put PII or auth tokens in public buckets — there's no way to "unpublish" the URL after a CDN cache.
The product/avatar pattern uses isPublic: true so the CDN can serve images at scale. Customer-uploaded documents (KYC, support attachments) stay private and need signed URLs.
Favicons and synthetic assets
A small helper exists for generating site favicons from a source image:
/repository/file/create_faviconJWTIt crops/resizes a source image into the standard favicon set and writes the bundle to the site folder. Used by the site provisioning flow.