docusealco/docuseal · error · Submitters::ParamsError

file param is missing

Error message

file param is missing

What it means

Submitters.create_attachment! (lib/submitters.rb:127) raises ParamsError 'file param is missing' when the uploaded file argument is blank before it ever touches storage. It is the guard for submitter attachment upload endpoints (e.g. adding documents/images to a submitter): the multipart 'file' field must be present and non-empty.

Source

Thrown at lib/submitters.rb:127

    if AccountConfig.exists?(account_id: submitter.account_id,
                             key: AccountConfig::COMBINE_PDF_RESULT_KEY,
                             value: true) &&
       submitter.submission.completed_at? &&
       submitter.submission.template_fields.none? { |f| f['type'] == 'verification' }
      return [submitter.submission.combined_document_attachment || Submissions::EnsureCombinedGenerated.call(submitter)]
    end

    original_documents = submitter.submission.schema_documents.preload(:blob)
    is_more_than_two_images = original_documents.many?(&:image?)

    submitter.documents.preload(:blob).reject do |attachment|
      is_more_than_two_images &&
        original_documents.find { |a| a.uuid == (attachment.metadata['original_uuid'] || attachment.uuid) }&.image?
    end
  end

  def create_attachment!(submitter, file, metadata: {})
    raise ParamsError, 'file param is missing' if file.blank?

    extension = File.extname(file.original_filename).delete_prefix('.').downcase

    if DANGEROUS_EXTENSIONS.include?(extension)
      raise MaliciousFileExtension, "File type '.#{extension}' is not allowed."
    end

    blob = ActiveStorage::Blob.create_and_upload!(io: file.tap(&:rewind).open,
                                                  filename: file.original_filename,
                                                  content_type: file.content_type,
                                                  metadata:)

    ActiveStorage::Attachment.create!(blob:, name: 'attachments', record: submitter)
  end

  def normalize_preferences(account, user, params)
    preferences = {}

View on GitHub (pinned to 004a22c1c8)

Solutions

  1. Send the request as multipart/form-data with the exact file field name the endpoint expects (Rack yields an ActionDispatch::Http::UploadedFile).
  2. Client-side, assert a file is selected before submitting and disable the upload button otherwise.
  3. If using curl: curl -F 'file=@doc.pdf' (not -d).
  4. Rescue Submitters::ParamsError at the controller boundary and map it to a 400 with this message.

Example fix

# before (JSON body — file param never materializes)
fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) })

# after
const form = new FormData()
form.append('file', fileInput.files[0])
fetch(url, { method: 'POST', body: form })
Defensive patterns

Strategy: validation

Validate before calling

# Validate the upload before calling create_attachment!
def upload_file?(param)
  param.is_a?(ActionDispatch::Http::UploadedFile) && param.present? &&
    param.original_filename.present?
end

Try / catch

begin
  Submitters.create_attachment!(submitter, params[:file])
rescue Submitters::ParamsError => e
  render json: { error: e.message }, status: :bad_request
end

Prevention

When it happens

Trigger: POST to the submitter attachments endpoint as JSON instead of multipart/form-data so params[:file] is a string/absent; multipart body missing the file field or using a different field name; an empty file part (0-byte with blank Rack object); calling create_attachment! internally with nil.

Common situations: Frontends sending fetch with Content-Type: application/json instead of FormData; curl posts without -F; reverse proxies or body-size limits stripping large parts; field named 'document' or 'files' instead of the expected param.

Related errors


AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21). Data as JSON: /api/errors/984410e130fde9a3. Report an issue: GitHub.