Perform resumable uploads Stay organized with collections Save and categorize content based on your preferences.
This page describes how to make a resumable upload request in theCloud Storage JSON and XML APIs. This protocol lets you resume an uploadoperation after a communication failure interrupts the flow of data.
For information on using resumable uploads in the Google Cloud CLI and clientlibraries, seeHow tools and APIs use resumable uploads.
Required roles
To get the permissions that you need to perform a resumable upload,ask your administrator to grant you one of the following roles:
For uploads that include anObject Retention Lock, ask youradministrator to grant you the Storage Object Admin(
roles/storage.objectAdmin) IAM role for the bucket.For all other cases, ask your administrator to grant you the Storage ObjectUser (
roles/storage.objectUser) IAM role for the bucket.
These predefined roles contain the permissions required to upload an object to abucket for their respective cases. To see the exact permissions that arerequired, expand theRequired permissions section:
Required permissions
storage.objects.createstorage.objects.delete- This permission is only required for uploads that overwrite an existingobject.
storage.objects.setRetention- This permission is only required for uploads that include an ObjectRetention Lock.
You can also get these permissions with otherpredefined roles orcustom roles.
For information about granting roles on buckets, seeSet and manage IAM policies on buckets.
Initiate a resumable upload session
To initiate a resumable upload session:
JSON API
Have gcloud CLIinstalled and initialized, which lets you generate an access token for the
Authorizationheader.Optionally, create a JSON file that contains themetadata youwant to set on the object that you're uploading. For example, thefollowing JSON file sets the
contentTypemetadata of the object youwant to upload toimage/png:{ "contentType": "image/png"}Use
cURLto call theJSON API with aPOSTObject request:curl -i -X POST --data-binary @METADATA_LOCATION \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ -H "Content-Length:INITIAL_REQUEST_LENGTH" \ "https://storage.googleapis.com/upload/storage/v1/b/BUCKET_NAME/o?uploadType=resumable&name=OBJECT_NAME"
Where:
METADATA_LOCATIONis the local path to theJSON file containing the optional metadata you specified in theprevious step. If you are not including a metadata file, excludethis, along with--data-binary @and theContent-Typeheader.INITIAL_REQUEST_LENGTHis the number ofbytes in the body of this initial request, for example79.BUCKET_NAMEis the name of the bucket towhich you are uploading your object. For example,my-bucket.OBJECT_NAMEis the URL-encoded name you wantto give your object. For example,pets/dog.png, URL-encoded aspets%2Fdog.png. This is not required if you included anameinthe object metadata file in Step 2.
If you have enabledCross-Origin Resource Sharing, you should alsoinclude an
Originheader in both this and subsequent upload requests.Optional headers that you can add to the request include
X-Upload-Content-TypeandX-Upload-Content-Length.If successful, the response includes a
200status code and lookssimilar to the following:HTTP/2 200content-type: text/plain; charset=utf-8x-guploader-uploadid: ABgVH8_jqDHM_KOvNAJCx73r9v5fINktk9U-pXana1szCM5803tlJ7CKsRbDxkyYCrfEiNqzcZ6B7DfoDtc-bdzpH-SpVTAMEO9EQV34qG0-0yklocation: https://storage.googleapis.com/upload/storage/v1/b/my-bucket/o?uploadType=resumable&name=cat-pic.jpeg&upload_id=ABgVH8_jqDHM_KOvNAJCx73r9v5fINktk9U-pXana1szCM5803tlJ7CKsRbDxkyYCrfEiNqzcZ6B7DfoDtc-bdzpH-SpVTAMEO9EQV34qG0-0ykdate: Mon, 07 Jul 2025 14:57:21 GMTvary: Originvary: X-Origincache-control: no-cache, no-store, max-age=0, must-revalidateexpires: Mon, 01 Jan 1990 00:00:00 GMTpragma: no-cachecontent-length: 0server: UploadServeralt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Save the resumablesession URI given in the
locationheader ofthe response to yourPOSTObject request.This URI is used in subsequent requests to upload the object data.
Caution: Be careful when sharing the resumable session URI, because itcan be used by anyone to upload data to the target bucket without anyfurther authentication.
XML API
Have gcloud CLIinstalled and initialized, which lets you generate an access token for the
Authorizationheader.Use
cURLto call theXML API with aPOSTObject request that has an empty body:curl -i -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Length: 0" \ -H "Content-Type:OBJECT_CONTENT_TYPE" \ -H "x-goog-resumable: start" \ "https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"
Where:
OBJECT_CONTENT_TYPEis thecontent type of the object. For example,image/png. If you do not specify a content type, the Cloud Storage system sets theContent-Typemetadata for the object to beapplication/octet-stream.BUCKET_NAMEis the name of the bucket to which you are uploading your object. For example,my-bucket.OBJECT_NAMEis the URL-encoded name you want to give your object. For example,pets/dog.png, URL-encoded aspets%2Fdog.png.
If you have enabledCross-Origin Resource Sharing, you should alsoinclude an
Originheader in both this and subsequent upload requests.If successful, the response includes a
201status message.Save the resumablesession URI given in the
locationheaderof the response to yourPOSTObject request.This URI is used in subsequent requests to upload the object data.
Caution: Be careful when sharing the resumable session URI, because itcan be used by anyone to upload data to the target bucket without anyfurther authentication.
Upload the data
Once you have initiated a resumable upload, there are two ways to upload theobject's data:
- In a single chunk: This approach is usually best, since it requiresfewer requests and thus has better performance.
- In multiple chunks: Use this approach if you need to reduce the amount ofdata transferred in any single request, such as when there is a fixed timelimit for individual requests, or if you don't know the total size of theupload at the time the upload begins.
Single chunk upload
To upload the data in a single chunk:
JSON API
Use
cURLto call theJSON API with aPUTObject request:curl -i -X PUT --data-binary @OBJECT_LOCATION \ -H "Content-Length:OBJECT_SIZE" \ "SESSION_URI"
Where:
OBJECT_LOCATIONis the local path to yourobject. For example,Desktop/dog.png.OBJECT_SIZEis the number of bytes in yourobject. For example,20000000.SESSION_URIis the value returned in thelocationheader when youinitiated the resumable upload.
Optionally, you can include headers prefixed with
X-Goog-Meta-to add custom metadata for the object as part of this request.
XML API
Use
cURLto call theXML API with aPUTObject request:curl -i -X PUT --data-binary @OBJECT_LOCATION \ -H "Content-Length:OBJECT_SIZE" \ "SESSION_URI"
Where:
OBJECT_LOCATIONis the local path to yourobject. For example,Desktop/dog.png.OBJECT_SIZEis the number of bytes in yourobject. For example,20000000.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
If the upload completes in its entirety, you receive a200 OK or201 Createdresponse, along with any metadata associated with the resource.
If the upload request is interrupted, or if you receive a5xxresponse, follow the procedure inResuming an interrupted upload.
Multiple chunk upload
To upload the data in multiple chunks:
JSON API
Create a chunk of data from the overall data you want to upload.
The chunk size should be a multiple of 256 KiB (256 x 1024 bytes),unless it's the last chunk that completes the upload. Larger chunk sizestypically make uploads faster, but note that there's a tradeoff betweenspeed and memory usage. It's recommended that you use at least 8 MiB forthe chunk size.
Use
cURLto call theJSON API with aPUTObject request:curl -i -X PUT --data-binary @CHUNK_LOCATION \ -H "Content-Length:CHUNK_SIZE" \ -H "Content-Range: bytesCHUNK_FIRST_BYTE-CHUNK_LAST_BYTE/TOTAL_OBJECT_SIZE" \ "SESSION_URI"
Where:
CHUNK_LOCATIONis the local path to the chunkthat you're currently uploading.CHUNK_SIZEis the number of bytes you'reuploading in the current request. For example,8388608.CHUNK_FIRST_BYTEis the starting byte in theoverall object that the chunk you're uploading contains.CHUNK_LAST_BYTEis the ending byte in theoverall object that the chunk you're uploading contains.TOTAL_OBJECT_SIZEis the total size of theobject you're uploading. If you don't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
An example
Content-RangeisContent-Range: bytes 0-8388607/20000000.SeeContent-Rangefor more information about this header.If the request succeeds, the server responds with
308 Resume Incomplete. The response contains aRangeheader.Repeat the above steps for each remaining chunk of data that you want toupload, using the upper value contained in the
Rangeheader of eachresponse to determine where to start each successive chunk; you shouldnot assume that the server received all bytes sent in any given request.Optionally, in the final request of the resumable upload you can includeheaders prefixed with
X-Goog-Meta-to add custom metadata forthe object.
XML API
Create a chunk of data from the overall data you want to upload.
The chunk size should be a multiple of 256 KiB (256 x 1024 bytes),unless it's the last chunk that completes the upload. Larger chunk sizestypically make uploads faster, but note that there's a tradeoff betweenspeed and memory usage. It's recommended that you use at least 8 MiB forthe chunk size.
Use
cURLto call theXML API with aPUTObject request:curl -i -X PUT --data-binary @CHUNK_LOCATION \ -H "Content-Length:CHUNK_SIZE" \ -H "Content-Range: bytesCHUNK_FIRST_BYTE-CHUNK_LAST_BYTE/TOTAL_OBJECT_SIZE" \ "SESSION_URI"
Where:
CHUNK_LOCATIONis the local path to the chunkthat you're currently uploading.CHUNK_SIZEis the number of bytes you'reuploading in the current request. For example,8388608.CHUNK_FIRST_BYTEis the starting byte in theoverall object that the chunk you're uploading contains.CHUNK_LAST_BYTEis the ending byte in theoverall object that the chunk you're uploading contains.TOTAL_OBJECT_SIZEis the total size of theobject you're uploading. If you don't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
An example
Content-RangeisContent-Range: bytes 0-8388607/20000000.SeeContent-Rangefor more information about this header.If the request succeeds, the server responds with
308 Resume Incomplete. The response contains aRangeheader.Repeat the above steps for each remaining chunk of data that you want toupload, using the upper value contained in the
Rangeheader of eachresponse to determine where to start each successive chunk; you shouldnot assume that the server received all bytes sent in any given request.
Once the upload completes in its entirety, you receive a200 OK or201 Created response, along with any metadata associated with the resource.
If any of the chunk uploads are interrupted, or if you receive a5xxresponse, you should either resend the interrupted chunk in its entirety orresume the interrupted upload from where it left off.
Check the status of a resumable upload
If your resumable upload is interrupted, or you're not sure the uploadcompleted, you can check the status of the upload:
JSON API
Use
cURLto call theJSON API with aPUTObject request:curl -i -X PUT \ -H "Content-Length: 0" \ -H "Content-Range: bytes */OBJECT_SIZE" \ "SESSION_URI"
Where:
OBJECT_SIZEis the total number of bytes inyour object. If you don't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
XML API
Use
cURLto call theXML API with aPUTObject request:curl -i -X PUT \ -H "Content-Length: 0" \ -H "Content-Range: bytes */OBJECT_SIZE" \ "SESSION_URI"
Where:
OBJECT_SIZEis the total number of bytes inyour object. If you don't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
A200 OK or201 Created response indicates that the upload wascompleted, and no further action is necessary.
A308 Resume Incomplete response indicates that you need to continueuploading the data.
- If Cloud Storage has not yet persisted any bytes, the
308responsedoes not have aRangeheader. In this case, you should start yourupload from the beginning. - Otherwise, the
308response has aRangeheader, which specifieswhich bytes Cloud Storage has persisted so far. Use this value whenresuming an interrupted upload.
Resume an interrupted upload
Important: Cloud Storageignores any bytes you send at an offsetthat Cloud Storagehas already persisted.If an upload request is terminated before receiving a response, or if youreceive a503 or500 response, then you need to resumethe interrupted upload from where it left off. To resume an interrupted upload:
JSON API
Check the status of your resumable upload.
Save the upper value of the
Rangeheader contained in the responseto your status check. If the response does not have aRangeheader,Cloud Storage has not yet persisted any bytes, and you shouldupload from the beginning.Ensure that that object data you're about to upload begins at the bytefollowing the upper value in the
Rangeheader.If the interrupted request contained headers prefixed with
X-Goog-Meta-, include those headers in the request to resumeyour upload.Use
cURLto call theJSON API with aPUTObject request that picks up at the byte following the value in theRangeheader:curl -i -X PUT --data-binary @PARTIAL_OBJECT_LOCATION \ -H "Content-Length:UPLOAD_SIZE_REMAINING" \ -H "Content-Range: bytesNEXT_BYTE-LAST_BYTE/TOTAL_OBJECT_SIZE" \ "SESSION_URI"
Where:
PARTIAL_OBJECT_LOCATIONis the local path tothe remaining portion of data that you want to upload.UPLOAD_SIZE_REMAININGis the number of bytesyou're uploading in the current request. For example, uploading therest of an object with a total size of 20000000 that was interruptedafter bytes 0-42 uploaded would have anUPLOAD_SIZE_REMAININGof19999957.NEXT_BYTEis the next integer after the valueyou saved in step 2. For example, if42is the upper value instep 2, the value forNEXT_BYTEis43.LAST_BYTEis the ending byte contained in thisPUTrequest. For example, to finish uploading an object whose totalsize is20000000, the value forLAST_BYTEis19999999.TOTAL_OBJECT_SIZEis the total size of theobject you're uploading. For example,20000000. If you don't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
XML API
Check the status of your resumable upload.
Save the upper value of the
Rangeheader contained in theresponse to your status check. If the response does not have aRangeheader, Cloud Storage has not yet persisted any bytes, and youshouldupload from the beginning.Ensure that that object data you're about to upload begins at the bytefollowing the upper value in the
Rangeheader.Use
cURLto call theXML API with aPUTObjectrequest that picks up at the byte following the value in theRangeheader:curl -i -X PUT --data-binary @PARTIAL_OBJECT_LOCATION \ -H "Content-Length:UPLOAD_SIZE_REMAINING" \ -H "Content-Range: bytesNEXT_BYTE-LAST_BYTE/TOTAL_OBJECT_SIZE" \ "SESSION_URI"
Where:
PARTIAL_OBJECT_LOCATIONis the local path tothe remaining portion of data that you want to upload.UPLOAD_SIZE_REMAININGis the number of bytesyou're uploading in the current request. For example, uploading therest of an object with a total size of 20000000 that was interruptedafter bytes 0-42 uploaded would have anUPLOAD_SIZE_REMAININGof19999957.NEXT_BYTEis the next integer after the valueyou saved in step 2. For example, if42is the upper value instep 2, the value forNEXT_BYTEis43.LAST_BYTEis the ending byte contained in thisPUTrequest. For example, to finish uploading an object whose totalsize is20000000, the value forLAST_BYTEis19999999.TOTAL_OBJECT_SIZEis the total size ofthe object you're uploading. For example,20000000. If youdon't know the full size of your object, use*for this value.SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
You can resume uploads as many times as necessary while the session URI isactive; the session URI expires after a week. When the data is successfullyuploaded, Cloud Storage responds with a200 OK or201 createdstatus code.
Cancel an upload
To cancel an incomplete resumable upload and prevent any further action on it:
JSON API
Use
cURLto call theJSON API with aDELETErequest:curl -i -X DELETE -H "Content-Length: 0" \ "SESSION_URI"
Where:
SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
If successful, the response contains a499 status code. Future attemptsto query or resume the upload result in a4xx response.
XML API
Use
cURLto call theXML API with aDELETErequest:curl -i -X DELETE -H "Content-Length: 0" \ "SESSION_URI"
Where:
SESSION_URIis the value returned in theLocationheader when youinitiated the resumable upload.
If successful, the response contains a204 status code, and futureattempts to query or resume the upload also result in a204 response.
Failure Handling
Under rare circumstances, a request to resume an interrupted upload might failwith a non-retriable '4xx' error because permissions on the bucket have changed,or because the integrity check on the final uploaded object detected a mismatch.If this occurs, retry the upload byinitiating a new resumable uploadsession.
What's next
- Learn more aboutresumable uploads.
- Read an overview for theJSON API andXML API.
- Stream uploads of unknown size.
- Batch multiple requests to the Cloud Storage JSON API.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-19 UTC.