The CLI supports multipart uploads for APIs that accept file content, such as Drive, Gmail, and Apps Script.
Basic Upload Syntax
Use the --upload flag to specify a local file to upload:
gws drive files create --json '{"name": "report.pdf"}' --upload ./report.pdf
The CLI automatically:
- Reads the file content
- Builds a
multipart/related request
- Uses the
uploadType=multipart endpoint
- Includes both metadata (JSON) and file content
How It Works
The multipart upload implementation (in src/executor.rs:621-659) creates a multipart/related body:
--boundary_12345
Content-Type: application/json; charset=UTF-8
{"name": "report.pdf", "mimeType": "application/pdf"}
--boundary_12345
Content-Type: application/pdf
<binary file content>
--boundary_12345--
Supported Services
Upload support varies by API. Common use cases:
Drive Files
Create a new file:
gws drive files create --json '{"name": "budget.xlsx", "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}' \
--upload ./budget.xlsx
Update existing file content:
gws drive files update --params '{"fileId": "1A2B3C"}' \
--json '{"mimeType": "application/pdf"}' \
--upload ./updated-report.pdf
Gmail Messages
Send a message with an attachment:
gws gmail users messages send --params '{"userId": "me"}' \
--json '{"raw": "...base64-encoded-email..."}' \
--upload ./message.eml
For Gmail, you typically construct the full MIME message locally and upload it. The CLI helper gws gmail +send provides a simpler interface for attachments.
Apps Script Projects
Deploy a script project:
gws script projects create --json '{"title": "My Script"}' --upload ./script.json
MIME Type Detection
The CLI extracts the MIME type from the mimeType field in your JSON metadata:
gws drive files create --json '{"name": "image.png", "mimeType": "image/png"}' --upload ./photo.png
If no mimeType is provided, it defaults to application/octet-stream.
Upload Endpoint Selection
The CLI reads the Discovery Document’s mediaUpload section to determine the upload endpoint:
"mediaUpload": {
"protocols": {
"simple": {
"path": "/upload/drive/v3/files"
}
}
}
The upload path is automatically substituted at runtime (see src/executor.rs:516-533).
Examples
Upload a CSV file to Drive
gws drive files create \
--json '{"name": "data.csv", "parents": ["1XYZ"], "mimeType": "text/csv"}' \
--upload ./data.csv
Upload and convert to Google Sheets
gws drive files create \
--json '{"name": "Q1 Sales", "mimeType": "application/vnd.google-apps.spreadsheet"}' \
--upload ./sales.xlsx
Replace file content
# Get the file ID first
FILE_ID=$(gws drive files list --params '{"q": "name='report.pdf'"}' | jq -r '.files[0].id')
# Update the content
gws drive files update --params '{"fileId": "'$FILE_ID'"}' --upload ./report-v2.pdf
Error Handling
If the file cannot be read:
{
"error": "Failed to read upload file './missing.pdf': No such file or directory (os error 2)"
}
If the API doesn’t support uploads for the method:
{
"error": "Method supports media upload but no upload path found in Discovery Document"
}
Dry Run
Preview the upload request structure without sending it:
gws drive files create --json '{"name": "test.pdf"}' --upload ./test.pdf --dry-run
{
"dry_run": true,
"url": "https://www.googleapis.com/upload/drive/v3/files",
"method": "POST",
"query_params": {
"uploadType": "multipart"
},
"body": {
"name": "test.pdf"
},
"is_multipart_upload": true
}
Best Practices
Always specify the correct mimeType in your JSON metadata to ensure the file is uploaded with the right content type.
Large file uploads (>5 MB) may fail with multipart mode. Use resumable uploads for files larger than 5 MB (currently not supported; use the Drive API directly for resumable uploads).
The --upload flag is only valid for methods that declare supportsMediaUpload: true in the Discovery Document.