hyperledger/fabric · warning

form does not contains part key

Error message

form does not contains part key: %s

What it means

After successfully reading the multipart form, the handler requires a file part under the reserved key FormDataConfigBlockKey ("config-block"). If no such file part exists, the join request lacks the required config block and the handler returns 400 with this formatted error naming the expected key.

Solutions

  1. Attach the config block as a FILE part named exactly config-block: curl -F 'config-block=@path/to/genesis.block'
  2. Ensure it is uploaded as a file part (binary), not a form value field
  3. Check the client library's form field name matches FormDataConfigBlockKey ("config-block")
  4. Read the %s in the server error message to confirm the expected key name

Example fix

// before
curl -X POST -F 'block=@genesis.block' http://orderer/participation/v2/channels?channelID=mychannel
// after
curl -X POST -F 'config-block=@genesis.block' http://orderer/participation/v2/channels?channelID=mychannel
Defensive patterns

Strategy: validation

Validate before calling

const FormDataConfigBlockKey = "config-block"
// verify before sending:
if _, err := os.Stat(configBlockPath); err != nil {
	return fmt.Errorf("must attach a file part named %q", FormDataConfigBlockKey)
}

Try / catch

if strings.Contains(err.Error(), "form does not contains part key") {
	return fmt.Errorf("attach the block as a file part named exactly %q (curl -F 'config-block=@file')", "config-block")
}

Prevention

When it happens

Trigger: serveJoin posting a multipart form without a file part named config-block — e.g. attaching the block under a different field name, sending it as a plain value field, or omitting the file entirely.

Common situations: Scripts using -F 'block=@...' instead of -F 'config-block=@...'; clients uploading the block as a text field rather than a file part; typos in the form key; sending only JSON metadata with no attachment.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/21aedb3c458e404b. Report an issue: GitHub.

Appendix: source

Thrown at orderer/common/channelparticipation/restapi.go:437

	h.logger.Debugf("Successfully update config channel: %s", info.URL)
	h.sendResponseCreated(resp, info.URL, info)
}

// Expect a multipart/form-data with a single part, of type file, with key FormDataConfigBlockKey.
func (h *HTTPHandler) multipartFormDataBodyToBlock(params map[string]string, req *http.Request, resp http.ResponseWriter) *cb.Block {
	boundary := params["boundary"]
	reader := multipart.NewReader(
		http.MaxBytesReader(resp, req.Body, int64(h.config.MaxRequestBodySize)),
		boundary,
	)
	form, err := reader.ReadForm(2 * int64(h.config.MaxRequestBodySize))
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrap(err, "cannot read form from request body"))
		return nil
	}

	if _, exist := form.File[FormDataConfigBlockKey]; !exist {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Errorf("form does not contains part key: %s", FormDataConfigBlockKey))
		return nil
	}

	if len(form.File) != 1 || len(form.Value) != 0 {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.New("form contains too many parts"))
		return nil
	}

	fileHeader := form.File[FormDataConfigBlockKey][0]
	file, err := fileHeader.Open()
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot open file part %s from request body", FormDataConfigBlockKey))
		return nil
	}

	blockBytes, err := io.ReadAll(file)
	if err != nil {
		h.sendResponseJsonError(resp, http.StatusBadRequest, errors.Wrapf(err, "cannot read file part %s from request body", FormDataConfigBlockKey))

View on GitHub (pinned to 2736b63f8f)