Skip to main content
Wildo.ai Coming soon

Files and storage

Let people frame an image before sending it

An image field’s aspect ratio can activate cropping and resizing inside the standard upload control.

A chosen square crop becomes the uploaded portrait framing.

Let people frame an image before sending it

An image can be valid and still be the wrong shape for its place in the product. Wildo can turn an image field’s declared aspect ratio into a crop step, so people choose the framing before upload.

The control crops and resizes in the browser, then sends the resulting file through the ordinary validation and upload path. The field keeps one set of requirements for both steps.

Example — Choose the face in a profile photo

A wide photograph is selected for a square avatar. The person pans and zooms to frame the face, confirms the crop and uploads the result. A small source image is not enlarged just to reach the maximum allowed dimensions.

For engineers

Declare the intended image shape

The avatar in UserProfileSchema, shared by the administrative and self-profile resources, declares the square ratio and dimension limits. The account resource (USERS) has no avatar field. This excerpt from users.shared.schemas.ts omits the surrounding privacy wrapper.

z_file({
  nature: FileNature.IMAGE,
  allowedMimeTypes: 'Images',
  maxSize: 2 * 1024 * 1024,
  imageConstraints: {
    maxWidth: 1024,
    maxHeight: 1024,
    aspectRatio: { width: 1, height: 1 },
    generateThumbnails: true,
  },
}).optional().dataCategories(PersonalDataCategory.IDENTITY),

Understand when the crop step opens

The standard field routes a selection through cropping only for the supported single-image case. This excerpt from form-field-file.tsx retains the normal upload fallback; explanatory comments are omitted.

const handleFiles = React.useCallback((files: File[]) => {
  if (files.length === 0) return

  if (isMultiple) {
    handleMultipleFilesUpload(files)
  } else {
    const file = files[0]
    if (aspectRatioConstraint && isCroppableRasterImageFile(file) && canExportWithinAllowedMimeTypes(allowedMimeTypes)) {
      const mimeError = validateFileMimeType(file)
      if (mimeError) {
        setInternalError(mimeError)
        return
      }
      setInternalError(undefined)
      setPendingCropFile(file)
      return
    }
    handleSingleFileUpload(file)
  }
}, [isMultiple, handleMultipleFilesUpload, handleSingleFileUpload, aspectRatioConstraint, validateFileMimeType, allowedMimeTypes])

Treat export as preparation, not acceptance

The cropper applies image orientation, constrains movement to cover the crop area and resizes without enlarging the selected source. It tries allowed PNG, WebP and JPEG outputs, varying lossy quality where useful. If no candidate fits the byte ceiling, it returns the smallest allowed result and the usual size validator can refuse it.

Multiple-file selections use their normal upload path. SVG, undecodable images and fields with no supported canvas export type bypass cropping. Keep server type and size validation enabled for every path. The default server analyzer independently enforces declared image dimensions and aspect ratio from uploaded bytes. If required geometry cannot be measured, the upload is refused. A crop dialog prepares an image; it does not establish server-side acceptance.

Replace the crop interaction without replacing file handling

The form renders the injectable ImageCrop wrapper. Its LOW_LEVEL_IMAGE_CROP slot receives the selected file and the field’s constraints, then returns a File to the existing upload flow. Your application can change the interaction while keeping upload, readiness and parent-record attachment in the form.

For an application-wide replacement, register your component after registerDefaultPresets() and before the registry completeness check or application mount. This illustrative registration replaces the default slot; ApplicationImageCrop is your implementation of ImageCropProps from @wildo-ai/saas-frontend-lib.

import { CorePresetNames, FrontendComponentType } from '@wildo-ai/presets-components-models';
import { ComponentRegistryService } from '@wildo-ai/saas-frontend-lib';
import { ApplicationImageCrop } from './application-image-crop';

ComponentRegistryService.register(
  FrontendComponentType.LOW_LEVEL_IMAGE_CROP,
  CorePresetNames.DEFAULT,
  ApplicationImageCrop,
  { isConfigurable: true },
);

The registry stores one component for that slot and preset. Calling the default registrations again afterwards would overwrite your replacement. Wonder Todos currently uses the default cropper; the registration above shows the extension point, not a customization already installed there.

ContractResponsibility of a replacement
file, aspectRatio, maskShapePresent the selected image at the required ratio; a circular mask is visual, not a circular exported file
maxOutputWidth, maxOutputHeight, maxOutputSizeBytes, allowedMimeTypesProduce an export within the supplied field constraints
onConfirm(croppedFile)Return the resulting browser File to the form’s upload flow
onCancel()Dismiss the crop and discard the selection
onUncroppable(originalFile)Return an undecodable source to the normal upload path, where server validation still applies

The slot does not own storage or permission checks. The backend validates received bytes even when a caller bypasses the browser crop interaction entirely.

Building a B2B product or an internal tool?

Wildo is not self-service yet. Tell us what you have in mind and we will say plainly whether it fits, and what happens next.