siyuan-note/siyuan · error

endpoint does not accept multipart data

Error message

endpoint does not accept multipart data

What it means

DecodeMultipart is the generic multipart/form-data decoder for contract-bound endpoints. It first checks that the endpoint's declared body kind is MultipartBody or FormBody; if the endpoint was defined with another body type (e.g. JSON), multipart data is rejected. This prevents silently feeding form data to endpoints that expect a different content type.

Solutions

  1. Send the request as JSON (Content-Type: application/json) matching the endpoint's declared body type.
  2. Use a different endpoint that is declared with MultipartBody or FormBody for file/form uploads.
  3. If you own the endpoint, update its contract definition to MultipartBody/FormBody (and run the API contract generation/check steps).

Example fix

// before
fetchPost("/api/endpoint", formData) // endpoint expects JSON
// after
fetchPost("/api/endpoint", {field: value}) // or use the declared multipart endpoint
Defensive patterns

Strategy: validation

Validate before calling

function checkBodyKind(endpointDef, request) {
  const isForm = request instanceof FormData;
  const multipartOk = endpointDef.body === "multipart" || endpointDef.body === "form";
  if (isForm && !multipartOk) throw new Error("endpoint " + endpointDef.name + " does not accept FormData");
}

Type guard

const isFormData = (b) => typeof FormData !== "undefined" && b instanceof FormData;

Try / catch

try { await api.post(url, body); } catch (e) { if (String(e).includes("does not accept multipart")) { await api.post(url, await formDataToJson(body)); } else { throw e; } }

Prevention

When it happens

Trigger: POSTing multipart/form-data to an endpoint whose contract definition declares a non-multipart, non-form body (e.g. JSONBody).

Common situations: A client sends FormData with fetch/axios to an API that expects a JSON body; the endpoint contract was changed or bound incorrectly; a plugin calls a JSON-only route with form encoding.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/multipart.go:40

		if !field.IsExported() || field.Anonymous || field.Tag.Get("json") == "" {
			return fmt.Errorf("multipart fields must be named and exported: %s", field.Name)
		}
		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 {

View on GitHub (pinned to 9f775e8a12)