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