Skip to content

File Upload Pipeline

Ant Design Upload wrapped for form usage with size validation and optional image compression.

What Problem This Solves

Antd Upload is powerful but tricky to use inside forms. This pattern normalizes it for single/multi-file selection with size limits.

Prerequisites

  • antd

Files

upload.ts

ts
import { type FormItemProps } from "antd";
import { type StoreValue } from "antd/es/form/interface";

export const singleFile: FormItemProps["getValueFromEvent"] = (e) => {
  let file: StoreValue;
  if (Array.isArray(e)) file = e;
  if (e?.fileList?.length) file = [e.fileList[e.fileList.length - 1]];
  return file;
};

export const multiFile: FormItemProps["getValueFromEvent"] = (e) => {
  let file: StoreValue;
  if (Array.isArray(e)) file = e;
  if (e?.fileList?.length) file = e.fileList;
  return file;
};

upload-input.tsx

tsx
import { UploadOutlined } from "@ant-design/icons";
import { Button, message, Upload, type UploadProps } from "antd";
import { useMemo } from "react";

import { InputItem, type InputItemProps } from "./input-item";
import { multiFile, singleFile } from "./upload";

type UploadInputProps = {
  inputWrapper?: Omit<InputItemProps, "multiple">;
  uploadProps?: Omit<UploadProps, "multiple">;
  triggerLabel?: React.ReactNode;
  maxFileSize?: number; // in MB
  multiple?: UploadProps["multiple"];
};

const SINGLE_MEGABYTE = 1048576;

const beforeUpload = (file: File, maxFileSize?: number) => {
  const maxSize = maxFileSize ?? 5;
  if (file.size > maxSize * SINGLE_MEGABYTE) {
    message.error(`File size must not exceed ${maxSize} MB`);
    return Upload.LIST_IGNORE;
  }
  return false; // Prevent auto-upload, handle manually
};

export function UploadInput({
  inputWrapper,
  uploadProps,
  triggerLabel = "Upload Document",
  maxFileSize = 15,
  multiple = false,
}: UploadInputProps) {
  const filePickerTrigger = useMemo(
    () => (
      <Button type="primary" block icon={<UploadOutlined />}>
        {triggerLabel}
      </Button>
    ),
    [triggerLabel],
  );

  return (
    <InputItem
      {...inputWrapper}
      valuePropName="fileList"
      getValueFromEvent={multiple ? multiFile : singleFile}
    >
      <Upload
        beforeUpload={(file) => beforeUpload(file, maxFileSize)}
        listType="picture"
        maxCount={multiple ? undefined : 1}
        multiple={multiple}
        {...uploadProps}
      >
        {filePickerTrigger}
      </Upload>
    </InputItem>
  );
}

Usage

tsx
import { Form } from "antd";
import { UploadInput } from "./upload-input";

export function DocumentForm() {
  return (
    <Form>
      <UploadInput inputWrapper={{ name: "document", label: "Document" }} maxFileSize={10} />
      <UploadInput
        inputWrapper={{ name: "attachments", label: "Attachments" }}
        multiple
        maxFileSize={5}
      />
    </Form>
  );
}

Key Points

  • beforeUpload returns false to prevent automatic upload — you handle submission manually via your API client
  • singleFile extracts only the last selected file (for single-file inputs)
  • multiFile extracts the entire fileList (for multi-file inputs)
  • Upload.LIST_IGNORE prevents oversized files from appearing in the list