Basically, if you specify a files parameter (a dictionary), then requests will send a multipart/form-data POST instead of a application/x-www-form-urlencoded POST. You are not limited to using actual files in that dictionary, however:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

and httpbin.org lets you know what headers you posted with; in response.json() we have:

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

And just to be explicit: you should not set the Content-Type header when you use the files parameter, leave this to requests because it needs to specify a (unique) boundary value in the header that matches the value used in the request body.

Better still, you can further control the filename, content type and additional headers for each part by using a tuple instead of a single string or bytes object. The tuple is expected to contain between 2 and 4 elements; the filename, the content, optionally a content type, and an optional dictionary of further headers.

I'd use the tuple form with None as the filename, so that the filename="..." parameter is dropped from the request for those parts:

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files can also be a list of two-value tuples, if you need ordering and/or multiple fields with the same name:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

If you specify both files and data, then it depends on the value of data what will be used to create the POST body. If data is a string, only it willl be used; otherwise both data and files are used, with the elements in data listed first.

There is also the excellent requests-toolbelt project, which includes advanced Multipart support. It takes field definitions in the same format as the files parameter, but unlike requests, it defaults to not setting a filename parameter. In addition, it can stream the request from open file objects, where requests will first construct the request body in memory:

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Fields follow the same conventions; use a tuple with between 2 and 4 elements to add a filename, part mime-type or extra headers. Unlike the files parameter, no attempt is made to find a default filename value if you don't use a tuple.

Answer from Martijn Pieters on Stack Overflow
🌐
Medium
techkamar.medium.com › sending-form-data-post-using-python-requests-library-55850c7a93d5
Sending FORM data POST using Python Requests library - tech kamar - Medium
June 26, 2024 - http://localhost:8080/login is the end point I want to hit with username and password as form data. ... import requests url = "http://localhost:8080/login" form_data = {"username":"johndoe", "password": "user@1234"} resp = requests.post(url, data=form_data)
Top answer
1 of 16
397

Basically, if you specify a files parameter (a dictionary), then requests will send a multipart/form-data POST instead of a application/x-www-form-urlencoded POST. You are not limited to using actual files in that dictionary, however:

>>> import requests
>>> response = requests.post('http://httpbin.org/post', files=dict(foo='bar'))
>>> response.status_code
200

and httpbin.org lets you know what headers you posted with; in response.json() we have:

>>> from pprint import pprint
>>> pprint(response.json()['headers'])
{'Accept': '*/*',
 'Accept-Encoding': 'gzip, deflate',
 'Connection': 'close',
 'Content-Length': '141',
 'Content-Type': 'multipart/form-data; '
                 'boundary=c7cbfdd911b4e720f1dd8f479c50bc7f',
 'Host': 'httpbin.org',
 'User-Agent': 'python-requests/2.21.0'}

And just to be explicit: you should not set the Content-Type header when you use the files parameter, leave this to requests because it needs to specify a (unique) boundary value in the header that matches the value used in the request body.

Better still, you can further control the filename, content type and additional headers for each part by using a tuple instead of a single string or bytes object. The tuple is expected to contain between 2 and 4 elements; the filename, the content, optionally a content type, and an optional dictionary of further headers.

I'd use the tuple form with None as the filename, so that the filename="..." parameter is dropped from the request for those parts:

>>> files = {'foo': 'bar'}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--bb3f05a247b43eede27a124ef8b968c5
Content-Disposition: form-data; name="foo"; filename="foo"

bar
--bb3f05a247b43eede27a124ef8b968c5--
>>> files = {'foo': (None, 'bar')}
>>> print(requests.Request('POST', 'http://httpbin.org/post', files=files).prepare().body.decode('utf8'))
--d5ca8c90a869c5ae31f70fa3ddb23c76
Content-Disposition: form-data; name="foo"

bar
--d5ca8c90a869c5ae31f70fa3ddb23c76--

files can also be a list of two-value tuples, if you need ordering and/or multiple fields with the same name:

requests.post(
    'http://requestb.in/xucj9exu',
    files=(
        ('foo', (None, 'bar')),
        ('foo', (None, 'baz')),
        ('spam', (None, 'eggs')),
    )
)

If you specify both files and data, then it depends on the value of data what will be used to create the POST body. If data is a string, only it willl be used; otherwise both data and files are used, with the elements in data listed first.

There is also the excellent requests-toolbelt project, which includes advanced Multipart support. It takes field definitions in the same format as the files parameter, but unlike requests, it defaults to not setting a filename parameter. In addition, it can stream the request from open file objects, where requests will first construct the request body in memory:

from requests_toolbelt.multipart.encoder import MultipartEncoder

mp_encoder = MultipartEncoder(
    fields={
        'foo': 'bar',
        # plain file object, no filename or mime type produces a
        # Content-Disposition header with just the part name
        'spam': ('spam.txt', open('spam.txt', 'rb'), 'text/plain'),
    }
)
r = requests.post(
    'http://httpbin.org/post',
    data=mp_encoder,  # The MultipartEncoder is posted as data, don't use files=...!
    # The MultipartEncoder provides the content-type header with the boundary:
    headers={'Content-Type': mp_encoder.content_type}
)

Fields follow the same conventions; use a tuple with between 2 and 4 elements to add a filename, part mime-type or extra headers. Unlike the files parameter, no attempt is made to find a default filename value if you don't use a tuple.

2 of 16
147

Requests has changed since some of the previous answers were written. Have a look at this Issue on Github for more details and this comment for an example.

In short, the files parameter takes a dictionary with the key being the name of the form field and the value being either a string or a 2, 3 or 4-length tuple, as described in the section POST a Multipart-Encoded File in the Requests quickstart:

>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

In the above, the tuple is composed as follows:

(filename, data, content_type, headers)

If the value is just a string, the filename will be the same as the key, as in the following:

>>> files = {'obvius_session_id': '72c2b6f406cdabd578c5fd7598557c52'}

Content-Disposition: form-data; name="obvius_session_id"; filename="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52

If the value is a tuple and the first entry is None the filename property will not be included:

>>> files = {'obvius_session_id': (None, '72c2b6f406cdabd578c5fd7598557c52')}

Content-Disposition: form-data; name="obvius_session_id"
Content-Type: application/octet-stream

72c2b6f406cdabd578c5fd7598557c52
Discussions

Python Requests - multipart form data
Looks like the API expects a request structured like this. More on reddit.com
🌐 r/learnpython
6
1
February 14, 2022
Help with multipart-formdata - Python Requests code
Hi, When I create a form-data POST request with two images, the Postman Request works. However when I select the code for Python - Requests the generated code does not work at all. It’s looking for the boundary markers… Is there a way to see the full code that Postman generates that succeeds ? More on community.postman.com
🌐 community.postman.com
0
0
September 24, 2020
Python Requests library - post method with form data help
What headers are sent when you do it manually? You may need to send a header with "accept: application/json, text/plain, /" or something similar in order for it to accept your "files" payload. You probably do have to send the entire form-data content unfortunately More on reddit.com
🌐 r/webscraping
5
1
December 17, 2021
request.FILES is empty. File didn't upload
I run into this problem about once a month and it's always a missing enctype . And yes, that link is my answer on my question. I'm glad I posted them because they're the posts I keep finding when I run into this problem :) Can't deny it makes me feel like an idiot when I'm answering my own questions a decade after I initially had the problem. More on reddit.com
🌐 r/django
21
0
March 19, 2019
🌐
ScrapeOps
scrapeops.io › home › python web scraping playbook › python requests how to submit forms
How To Submit Forms With Python Requests | ScrapeOps
July 8, 2024 - ... Whether you're automating form ... Let's start with requests.post(), this is your go-to function for submitting forms. It allows you to send form data to a server using an HTTP POST request....
🌐
Requests
requests.readthedocs.io › en › latest › user › quickstart
Quickstart — Requests 2.33.0.dev1 documentation
To do this, simply pass a dictionary to the data argument. Your dictionary of data will automatically be form-encoded when the request is made:
🌐
Reddit
reddit.com › r/learnpython › python requests - multipart form data
r/learnpython on Reddit: Python Requests - multipart form data
February 14, 2022 -

I am new to python requests and python in general - I have a somewhat intermediate java background.

I am trying to figure out why this post request throws a 400 error when trying to upload a file.

Any hints?

def upload_a_document(self, library_id, folder_id, filepath):
        url = "https://xxx" \
              "/{library_id}!{folder_id}/documents".format(
            server=self.server,
            customer_id=self.customer_id,
            library_id=library_id,
            folder_id=folder_id)
        params = \
            {
                "profile":
                    {
                        "doc_profile": {
                            "access": "full_access",
                            "comment": f"{filepath}",
                            "database": f"{library_id}",
                            "default_security": "public",
                            "name": f"{os.path.basename(filepath)}",
                            "size": os.path.getsize(filepath),
                            "type": "WORDX",
                            "type_description": "WORD 2010",
                            "wstype": "document"
                        },
                        "warnings_for_required_and_disabled_fields": True
                    },
                "file": {open(f"{filepath}", "rb").read()}
            }
        payload = ""

        # encoded formdata for multipart
        encoded_formdata = encode_multipart_formdata(params)

        headers = {'X-Auth-Token': self.x_auth_token, 'Content-Type': encoded_formdata[1]}
        response = requests.request("POST", url, data=payload, headers=headers, params=params)
        myJSON = response.json()
        print(json.dumps(myJSON, indent=4))

Getting this error -

requests.exceptions.JSONDecodeError: [Errno Expecting value] <html><body><h1>400 Bad request</h1>

Your browser sent an invalid request.

🌐
Quora
quora.com › How-do-you-pass-form-data-in-Python-requests
How to pass form data in Python requests - Quora
Answer: It depends whether the form expects data in a POST or a GET request - most likely a POST. the way you format the data is different between the two. So you need to know: 1. What are all the field names on the form, including any hidden fields that you need to include - (beware some of t...
Find elsewhere
🌐
FastAPI
fastapi.tiangolo.com › tutorial › request-forms
Form Data - FastAPI
But when the form includes files, it is encoded as multipart/form-data. You'll read about handling files in the next chapter. If you want to read more about these encodings and form fields, head to the MDN web docs for POST. ... You can declare multiple Form parameters in a path operation, but you can't also declare Body fields that you expect to receive as JSON, as the request will have the body encoded using application/x-www-form-urlencoded instead of application/json.
🌐
Postman
community.postman.com › help hub
Help with multipart-formdata - Python Requests code - Help Hub - Postman Community
September 24, 2020 - Hi, When I create a form-data POST request with two images, the Postman Request works. However when I select the code for Python - Requests the generated code does not work at all. It’s looking for the boundary marker…
🌐
GitHub
gist.github.com › srafay › 19e0a13fe7e402f0a79715b1ed3f6560
Python Request for form-data · GitHub
Python Request for form-data. GitHub Gist: instantly share code, notes, and snippets.
🌐
Reddit
reddit.com › r/webscraping › python requests library - post method with form data help
r/webscraping on Reddit: Python Requests library - post method with form data help
December 17, 2021 -

Hello

So I've been working on this project and currently am using the requests library to pull html data from internal webpages. Because they're proprietary I can't share links, but essentially I have to open with a requests Session object, get the first page which is a search bar. In the search bar I need to pass 2 strings, a command and an account number then hit enter.

Looking at the post method that happens after 'enter' it seems the command and account numbers are passed as strings in the form-data of the headers tab. I copied the key value pair that shows the 2 strings as values and created a dictionary to pass them into the post as an argument. looks like:

with requests.Session() as s:
    get_holdings = s.get(url, verify=True, auth=auth)

files = {
        'field': 'command%account#'
    }
    get_holdings2 = s.post(posturl, verify=True, auth=auth, data=files)

    soup = BeautifulSoup(get_holdings2.content, 'html5lib')

However, the post method does not seem to be passing the form-data to get the information back from the page and I'm just getting the original "get_holdings" page. I may be using the subsequent post incorrectly or not navigating right. I also thought I may need to pass the ENTIRE form-data contents into requests (which is pretty big) . If any one has some advice on navigating through webpages using requests that would be super helpful, a lot of the data I need comes from webpages that are very interactive, lots of clicking through links to get to what you're looking for. This is a pretty new library for me.

🌐
W3Schools
w3schools.com › python › ref_requests_post.asp
Python Requests post Method
Python Variables Variable Names Assign Multiple Values Output Variables Global Variables Variable Exercises Code Challenge Python Data Types ... Python Strings Slicing Strings Modify Strings Concatenate Strings Format Strings Escape Characters String Methods String Exercises Code Challenge Python Booleans
🌐
w3resource
w3resource.com › python-exercises › urllib3 › python-urllib3-exercise-19.php
Python File Upload: Simulate POST request with multipart/Form-Data
import urllib3 # Create a PoolManager http = urllib3.PoolManager() # Specify the file and URL file_path = 'test.txt' upload_url = 'https://example.com/upload' # Prepare the multipart/form-data payload fields = { 'field1': 'value1', # Additional form fields if needed 'field2': 'value2', 'file': ('filename.txt', open(file_path, 'rb').read(), 'text/plain') } # Set the Content-Type header to multipart/form-data headers = {'Content-Type': 'multipart/form-data'} # Make the POST request response = http.request('POST', upload_url, fields=fields, headers=headers) # Receive and print the response print(response.data.decode('utf-8'))
🌐
ProxiesAPI
proxiesapi.com › articles › sending-form-data-with-python-requests
Sending Form Data with Python Requests | ProxiesAPI
The Python Requests module provides an elegant and simple HTTP library for your Python projects. With Requests, you can submit forms, upload files, and send JSON data seamlessly.
🌐
ScrapingDog
scrapingdog.com › blog › send-post-python-requests
How To Send A Post Requests in Python?
October 2, 2024 - The Python requests library is commonly used to send POST requests to web servers. Data can be sent in JSON format using the json= parameter or as form data using data=.
🌐
Apify
blog.apify.com › python-post-request
How to post JSON data with Python Requests
December 7, 2025 - When you submit a form on a website, ... a database or update an existing one. Python offers a powerful and easy-to-use requests library for making HTTP requests, including sending JSON data....
🌐
Franklingu
franklingu.github.io › programming › 2017 › 10 › 30 › post-multipart-form-data-using-requests
Post multipart form data using Python requests | Junchao's blog
October 30, 2017 - In [1]: data_req = Request('POST', 'https://franklingu.github.io/', data={'name ...: ': 'normal'}).prepare() In [2]: print(data_req.body) name=normal In [3]: normal_multipart_req = Request('POST', 'https://franklingu.github.io/', ...: files={'name': open('test.txt', 'r'), 'name2': 'content'}).pre ...: pare() In [4]: print(normal_multipart_req.body.decode('utf-8')) --cdf39af4e1bf449384b62fef701eda7b Content-Disposition: form-data; name="name"; filename="name" test --cdf39af4e1bf449384b62fef701eda7b Content-Disposition: form-data; name="name2"; filename="name2" content --cdf39af4e1bf449384b62fef
🌐
Scrapfly
scrapfly.io › blog › posts › how-to-python-requests-post
Guide to Python requests POST method - Scrapfly Blog
November 5, 2024 - It allows for customizable and straightforward data sending by specifying the URL, headers, and the data itself. The most common data types include JSON, form data, or raw body data, all handled easily with python requests POST method.
🌐
ProxiesAPI
proxiesapi.com › articles › sending-multipart-form-data-with-python-requests
Sending Multipart Form Data with Python Requests | ProxiesAPI
To send a multipart form request with Requests, you need to create a ... import requests url = 'https://api.example.com/upload' data = { 'description': 'My File', 'extra': 'Some extra data' } files = { 'file_upload': open('report.pdf', 'rb') } r = requests.post(url, data=data, files=files)
🌐
WebScraping.AI
webscraping.ai › faq › requests › how-do-i-upload-files-using-multipart-form-data-with-requests
How do I upload files using multipart/form-data with Requests? | WebScraping.AI
Learn how to upload files using multipart/form-data with Python Requests library. Complete guide with code examples and best practices.
🌐
Stack Abuse
stackabuse.com › bytes › how-to-send-multipart-form-data-with-requests-in-python
How to Send "multipart/form-data" with Requests in Python
January 29, 2025 - The main advantage of multipart/form-data is its ability to bundle multiple parts, each with potentially different data types, into a single HTTP request. This is very efficient and can save a lot of bandwidth. When it comes to HTTP requests in Python, the Requests library is a go-to tool.