google-drive

Original🇺🇸 English
Translated

Read, export, upload, rename, move and delete Google Drive files explicitly selected or shared with the app via the Drive v3 REST API. Use when the user provides a Drive file ID/link or has selected files for the Google Drive connector.

15installs
Added on

NPX Install

npx skill4agent add acedatacloud/skills google-drive

Tags

Translated version includes tags in frontmatter
Drive Google Drive via
curl + jq
. The user's OAuth bearer token is in
$GOOGLE_DRIVE_TOKEN
; every call needs it as
Authorization: Bearer $GOOGLE_DRIVE_TOKEN
. The token carries
drive.file
plus identity scopes (
openid email profile
) and can only access files the user selected, opened, created, or shared with this app.
The Drive API returns standard JSON; failures surface as
{"error": {"code": 401|403|..., "message": "..."}}
— show that error verbatim to the user.
401
means the token expired and the user must re-install the connector.
403 insufficientPermissions
means the file was not shared with this app, or the action needs a broader Drive scope that is temporarily disabled during Google review.
Do not use this skill for broad Drive discovery: no "list my recent files", full-text search across Drive, shared-with-me scans, root-folder cleanup, or bulk moves based on a Drive-wide query. Ask the user to pick or paste the exact file/folder IDs first.
Before any destructive write (renaming, moving, trashing, or bulk-mutating files) show the exact target list and ask the user to confirm. Never trash by guessing an id — always echo back the file name + path you're about to touch.
Always start with
/about?fields=user
to confirm the connection works AND learn which Google account you're operating against.

Optional: Google Workspace CLI (
gws
) for uploads

gws
is Google's official CLI (not officially supported — community-maintained on the
googleworkspace
org). It dynamically builds its command surface from Google's Discovery Document, exits non-zero on API errors, supports
--page-all
auto-pagination, and ships a
+upload
helper that wraps the multipart upload protocol.
Use
gws
for uploads.
A Drive multipart upload requires a hand-formatted
multipart/related
body with a JSON metadata part and a binary file part separated by a boundary string — easy to get wrong from curl.
gws drive +upload
does it correctly. For everything else (get, export, rename, move, trash, delete) the curl recipes below are equivalent and shorter — stay on those.

Install

sh
npm install -g @googleworkspace/cli   # or: brew install googleworkspace-cli
# Pre-built binaries also at https://github.com/googleworkspace/cli/releases
gws --version

Auth

gws
reads its OAuth bearer token from the
GOOGLE_WORKSPACE_CLI_TOKEN
environment variable. The Drive token used in this skill is in
$GOOGLE_DRIVE_TOKEN
, so re-export it once at the top of every shell block that calls
gws
:
sh
export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_DRIVE_TOKEN"

Upload

sh
# Simple upload to My Drive (auto-detects MIME type, sets the file name
# from --name; falls back to the local filename if --name is omitted)
gws drive +upload ./report.pdf --name "Q1 Report"

# Upload into a specific folder, or with explicit metadata, via the
# generic Discovery method + --upload (multipart wire format handled
# for you)
gws drive files create \
  --json '{"name":"report.pdf","parents":["FOLDER_ID"],"description":"Q1"}' \
  --upload ./report.pdf
Both exit non-zero with a structured JSON error on stderr if Google rejects the request — surface that verbatim.

Recipes

Verify auth (always run first)

sh
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/about?fields=user(displayName,emailAddress,photoLink),storageQuota(usage,limit)" \
  | jq '{user, quota: .storageQuota}'

List children of a selected folder

Only use this after the user explicitly selected or provided the folder ID.
sh
FOLDER_ID='1A2B3CdEfGhIjKlMn'
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  --get "https://www.googleapis.com/drive/v3/files" \
  --data-urlencode "q='$FOLDER_ID' in parents and trashed = false" \
  --data-urlencode 'fields=files(id,name,mimeType,size,modifiedTime),nextPageToken' \
  | jq '.files'

Get metadata for a single file

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,mimeType,size,modifiedTime,parents,owners,webViewLink,description"

Download a binary file (PDF / image / zip / …)

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
OUT=/tmp/download.bin
curl -sS -L -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?alt=media" \
  -o "$OUT"
file "$OUT" && wc -c "$OUT"

Read a Google Doc as plain markdown / text

Google-native files (Docs, Sheets, Slides) don't have raw bytes — you have to ask Drive to export them to a concrete MIME type:
sh
DOC_ID='1A2B3CdEfGhIjKlMn'

# Markdown (best for chat-friendly summaries)
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$DOC_ID/export?mimeType=text/markdown" \
  > /tmp/doc.md
head -40 /tmp/doc.md

# Plain text fallback for older docs
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$DOC_ID/export?mimeType=text/plain" \
  > /tmp/doc.txt
Common export MIME types:
native MIMEexport to
application/vnd.google-apps.document
text/markdown
,
text/plain
,
text/html
,
application/pdf
,
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/vnd.google-apps.spreadsheet
text/csv
,
application/pdf
,
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.google-apps.presentation
application/pdf
,
text/plain
,
application/vnd.openxmlformats-officedocument.presentationml.presentation

Read a Google Sheet as CSV

sh
SHEET_ID='1A2B3CdEfGhIjKlMn'
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$SHEET_ID/export?mimeType=text/csv" \
  > /tmp/sheet.csv
head /tmp/sheet.csv
The Drive
export
endpoint returns the first sheet only. For multi-tab access the user needs to install a separate Google Sheets connector (currently out of catalog) — explain that and stop.

Get permissions / sharing on a file

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID/permissions?fields=permissions(id,type,role,emailAddress,domain,deleted)" \
  | jq '.permissions[] | {who: (.emailAddress // .domain // .type), role}'

Write recipes

These only work for files and folders available through
drive.file
. If Google returns
403 insufficientPermissions
, surface the error and ask the user to select/share the target file with the app. Always echo the target name + path back to the user before trashing or bulk-moving anything.

Rename a file

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
NEW_NAME='2026 Q2 OKR (final).gdoc'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data "{\"name\":$(jq -nr --arg n "$NEW_NAME" '$n')}" \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name"

Move a file to a different folder

Drive's folder model is parent-id based. Move = remove old parent, add new parent:
sh
FILE_ID='1A2B3CdEfGhIjKlMn'
NEW_PARENT='1XYZnewFolderId'

# Read existing parents (so we can pass them in removeParents)
OLD_PARENTS=$(curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=parents" \
  | jq -r '.parents | join(",")')

curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  --data '' \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?addParents=$NEW_PARENT&removeParents=$OLD_PARENTS&fields=id,name,parents"

Create a new folder

sh
PARENT_ID='1XYZparentFolderId'  # or 'root' for My Drive root
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data "{\"name\":\"Reports / 2026Q2\",\"mimeType\":\"application/vnd.google-apps.folder\",\"parents\":[\"$PARENT_ID\"]}" \
  "https://www.googleapis.com/drive/v3/files?fields=id,name,webViewLink" \
  | jq

Upload a file (multipart so metadata + bytes go in one request)

sh
LOCAL=/tmp/report.pdf
NAME='Q2 report.pdf'
PARENT_ID='1XYZparentFolderId'
MIME='application/pdf'

BOUNDARY='aceDataBoundary'
META=$(jq -nc --arg n "$NAME" --arg p "$PARENT_ID" '{name:$n, parents:[$p]}')
{
  printf -- '--%s\r\n' "$BOUNDARY"
  printf 'Content-Type: application/json; charset=UTF-8\r\n\r\n'
  printf '%s\r\n' "$META"
  printf -- '--%s\r\n' "$BOUNDARY"
  printf 'Content-Type: %s\r\n\r\n' "$MIME"
  cat "$LOCAL"
  printf '\r\n--%s--\r\n' "$BOUNDARY"
} > /tmp/_drive_upload.bin

curl -sS -X POST -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H "Content-Type: multipart/related; boundary=$BOUNDARY" \
  --data-binary @/tmp/_drive_upload.bin \
  "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,webViewLink" \
  | jq
For a media-only upload (no metadata) use
uploadType=media
; for files >5 MB use
uploadType=resumable
(covered in [Drive's docs] (https://developers.google.com/drive/api/guides/manage-uploads#resumable)).

Replace the contents of an existing file

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
LOCAL=/tmp/report-v2.pdf
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H 'Content-Type: application/pdf' \
  --data-binary @"$LOCAL" \
  "https://www.googleapis.com/upload/drive/v3/files/$FILE_ID?uploadType=media&fields=id,name,modifiedTime"
Metadata stays the same (id / parents / name) — only the bytes are replaced and Drive bumps
modifiedTime
.

Trash a file (or restore one)

sh
FILE_ID='1A2B3CdEfGhIjKlMn'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"trashed":true}' \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,trashed"

# Restore:
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{"trashed":false}' \
  "https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,trashed"
Prefer
trashed:true
over
DELETE
DELETE
is permanent and the user can't undo it. Only use
DELETE
when they explicitly say "permanently delete".

Common error codes

HTTPmeaningwhat to tell the user
401 UNAUTHENTICATED
token expired / revoked"Reconnect the Google Drive connector on the Connections page."
403 insufficientPermissions
target not shared with app / broader Drive scope disabled"This file isn't available to the app under the current
drive.file
permission. Select or share the exact file with the Google Drive connector, then try again."
403 userRateLimitExceeded
quotaretry once after 5–10s; if it persists, tell the user.
404 notFound
wrong file id OR file isn't visible to this appdouble-check the id; ask the user to select or share the file with the connector.
400 invalidQuery
malformed
q
print the
q
you sent + the error message back to the user.
Never log or echo
$GOOGLE_DRIVE_TOKEN
— treat it as a secret.