siyuan-note/siyuan · error

multipart form is missing

Error message

multipart form is missing

What it means

DecodeMultipart requires a non-nil *multipart.Form; a nil form means the request carried no parseable multipart content. The decoder returns this error rather than dereferencing nil. It usually indicates the request body was empty or not actually multipart/form-data despite hitting a multipart endpoint.

Solutions

  1. Ensure the client sends a real multipart/form-data body with at least one field or file.
  2. Set the correct Content-Type with boundary (let fetch/axios/curl -F build it automatically instead of setting the header manually).
  3. Verify server-side middleware actually parsed the request into *multipart.Form before calling DecodeMultipart.

Example fix

// before
fetch(url, {method: "POST", headers: {"Content-Type": "multipart/form-data"}, body: formData}) // boundary lost
// after
fetch(url, {method: "POST", body: formData}) // browser sets Content-Type + boundary
Defensive patterns

Strategy: validation

Validate before calling

function checkMultipartBody(formData) {
  if (!(formData instanceof FormData) || [...formData.entries()].length === 0) {
    throw new Error("multipart body is empty");
  }
}

Type guard

const hasEntries = (f) => f != null && typeof f.entries === "function" && [...f.entries()].length > 0;

Try / catch

try { await api.post(url, formData); } catch (e) { if (String(e).includes("multipart form is missing")) { throw new Error("request body was empty or not multipart/form-data: " + e.message); } throw e; }

Prevention

When it happens

Trigger: Calling a multipart endpoint with an empty body, with Content-Type not set to multipart/form-data (so the Gin parser produced no form), or programmatically invoking DecodeMultipart(nil).

Common situations: Client forgot to append any fields/files to FormData; missing enctype on an HTML form; a proxy stripped the multipart body; boundary parameter missing from Content-Type.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/e8973a8725de94bc. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/multipart.go:43

		if field.Type != reflect.TypeFor[string]() && field.Type != reflect.TypeFor[*string]() && field.Type != reflect.TypeFor[*multipart.FileHeader]() && field.Type != reflect.TypeFor[[]*multipart.FileHeader]() {
			return fmt.Errorf("unsupported multipart field: %s", field.Name)
		}
		for _, option := range strings.Split(field.Tag.Get("api"), ",") {
			if option != "" && option != "optional" && option != "nonnullable" {
				return fmt.Errorf("unsupported multipart option: %s", option)
			}
		}
	}
	return nil
}

// DecodeMultipart 保留表单重复字段取首值的行为,文件内容由业务入口按需读取。
func (e Endpoint[Request, Data]) DecodeMultipart(form *multipart.Form) (request Request, err error) {
	if e.definition.Body != MultipartBody && e.definition.Body != FormBody {
		return request, fmt.Errorf("endpoint does not accept multipart data")
	}
	if form == nil {
		return request, fmt.Errorf("multipart form is missing")
	}
	value := reflect.ValueOf(&request).Elem()
	if value.Type() == reflect.TypeFor[ExtensionCopyRequest]() {
		decoded, decodeErr := decodeExtensionCopyForm(form)
		if decodeErr != nil {
			return request, decodeErr
		}
		value.Set(reflect.ValueOf(decoded))
		return
	}
	if value.Type() == reflect.TypeFor[MultipartFields]() {
		value.Set(reflect.ValueOf(MultipartFields{Value: form.Value, File: form.File}))
		return
	}
	if value.Kind() != reflect.Struct {
		return request, fmt.Errorf("multipart request must be a struct")
	}
	if err = validateMultipartRequest(value.Type()); err != nil {

View on GitHub (pinned to 9f775e8a12)