Google Drive Skill
Use when the user wants to interact with a public Google Drive — listing, reading, creating, updating, or deleting files and folders. Handles both read-only...
Like a lobster shell, security has layers — review code before you run it.
License
SKILL.md
Google Drive Skill
Resolve SKILL_SCRIPTS
IMPORTANT: All commands below use SKILL_SCRIPTS as shorthand for the absolute path to the scripts/ directory of this skill. Resolve it before running any script:
SKILL_SCRIPTS="$(
_ws_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
_ws_path="$_ws_root/.github/skills/google-drive-skill/scripts"
_global_path="$HOME/.openclaw/workspace/skills/google-drive-skill/scripts"
_global_path2="$HOME/.openclaw/skills/google-drive-skill/scripts"
if [ -d "$_ws_path" ]; then
echo "$_ws_path"
elif [ -d "$_global_path" ]; then
echo "$_global_path"
elif [ -d "$_global_path2" ]; then
echo "$_global_path2"
else
find "$HOME" /opt -type d -name google-drive-skill -path '*/skills/*' 2>/dev/null \
| head -1 | sed 's|$|/scripts|'
fi
)"
echo "SKILL_SCRIPTS=$SKILL_SCRIPTS"
Overview
This skill provides patterns and ready-to-run scripts for CRUD operations on Google Drive using the Drive API v3.
| Script | Operation | Auth Required |
|---|---|---|
list_files.py | List files/folders in a folder | API key (public) or service account |
download_file.py | Download a file to disk | API key (public) or service account |
create_folder.py | Create a new folder | Service account |
upload_file.py | Upload a local file | Service account |
update_file.py | Rename, move, or replace content | Service account |
delete_file.py | Trash or permanently delete | Service account |
set_permissions.py | Make public / share / revoke | Service account |
Supporting files:
scripts/drive_client.py— shared auth factory (imported by all scripts)assets/service_account_template.json— template for your service account JSONreferences/mime_types.md— MIME type quick referencereferences/error_codes.md— HTTP error codes + retry patterns
Quick Start
1. Install dependencies
pip install google-api-python-client google-auth google-auth-httplib2
2. Set credentials
# Read-only (public files):
export GOOGLE_API_KEY="your_api_key_here"
# Read + write (service account):
export GOOGLE_SERVICE_ACCOUNT_JSON="/path/to/service_account.json"
See assets/service_account_template.json for the expected JSON structure.
3. Resolve SKILL_SCRIPTS (see top of this file), then run scripts
Script Commands
List files in a folder
# Table output (default):
python "$SKILL_SCRIPTS/list_files.py" --folder-id FOLDER_ID
# JSON output:
python "$SKILL_SCRIPTS/list_files.py" --folder-id FOLDER_ID --json
Download a file
# Save to specific path:
python "$SKILL_SCRIPTS/download_file.py" --file-id FILE_ID --dest ./downloads/report.pdf
# Save to directory (auto filename from Drive):
python "$SKILL_SCRIPTS/download_file.py" --file-id FILE_ID --dest ./downloads/
Create a folder
# In Drive root:
python "$SKILL_SCRIPTS/create_folder.py" --name "My New Folder"
# Inside a specific parent folder:
python "$SKILL_SCRIPTS/create_folder.py" --name "Subfolder" --parent-id PARENT_FOLDER_ID
Upload a file
# Basic upload (MIME type auto-detected):
python "$SKILL_SCRIPTS/upload_file.py" --src ./report.pdf --parent-id FOLDER_ID
# Custom name on Drive:
python "$SKILL_SCRIPTS/upload_file.py" --src ./report.pdf --name "Q1-Report.pdf" --parent-id FOLDER_ID
# Upload and make publicly readable immediately:
python "$SKILL_SCRIPTS/upload_file.py" --src ./photo.png --parent-id FOLDER_ID --make-public
Update a file
# Rename:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --name "New Name.pdf"
# Move to a different folder:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --move-to NEW_PARENT_ID --old-parent OLD_PARENT_ID
# Replace file content:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --src ./updated_report.pdf
# Rename + replace content:
python "$SKILL_SCRIPTS/update_file.py" --file-id FILE_ID --name "v2.pdf" --src ./v2.pdf
Delete or trash a file
# Move to trash (safe, recoverable):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID
# Permanently delete (prompts for confirmation):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID --permanent
# Permanently delete (non-interactive):
python "$SKILL_SCRIPTS/delete_file.py" --file-id FILE_ID --permanent --yes
Manage permissions
# Make file publicly readable (anyone with link):
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --public
# Share with a user as writer:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --email user@example.com --role writer
# Share with a user as reader:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --email user@example.com --role reader
# List current permissions (returns JSON):
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --list
# Remove a specific permission:
python "$SKILL_SCRIPTS/set_permissions.py" --file-id FILE_ID --remove PERMISSION_ID
Auth Setup
Option A — API Key (read-only public files)
Use when the file/folder is shared as "Anyone with the link" and you only need to read.
from googleapiclient.discovery import build
API_KEY = os.environ["GOOGLE_API_KEY"]
drive = build("drive", "v3", developerKey=API_KEY)
Option B — Service Account (read + write on shared/public drives)
Use when you need to create, modify, or delete files.
from google.oauth2 import service_account
from googleapiclient.discovery import build
import os
SCOPES = ["https://www.googleapis.com/auth/drive"]
creds = service_account.Credentials.from_service_account_file(
os.environ["GOOGLE_SERVICE_ACCOUNT_JSON"], scopes=SCOPES
)
drive = build("drive", "v3", credentials=creds)
Copy
assets/service_account_template.jsonto see the expected structure.
Store the real file outside the repo and never commit it.
Addservice_account.jsonto.gitignore.
File & Folder ID
Every Drive resource has a unique fileId. Extract it from the share URL:
https://drive.google.com/drive/folders/FOLDER_ID_HERE
https://drive.google.com/file/d/FILE_ID_HERE/view
CRUD Operations
List Files in a Public Folder
def list_files(drive, folder_id: str) -> list[dict]:
results = []
page_token = None
while True:
resp = drive.files().list(
q=f"'{folder_id}' in parents and trashed=false",
fields="nextPageToken, files(id, name, mimeType, size, modifiedTime)",
pageToken=page_token,
supportsAllDrives=True,
includeItemsFromAllDrives=True,
).execute()
results.extend(resp.get("files", []))
page_token = resp.get("nextPageToken")
if not page_token:
break
return results
Download a Public File
import io
from googleapiclient.http import MediaIoBaseDownload
def download_file(drive, file_id: str, dest_path: str) -> None:
request = drive.files().get_media(fileId=file_id, supportsAllDrives=True)
with open(dest_path, "wb") as fh:
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
_, done = downloader.next_chunk()
Create a Folder
def create_folder(drive, name: str, parent_id: str | None = None) -> str:
metadata = {
"name": name,
"mimeType": "application/vnd.google-apps.folder",
}
if parent_id:
metadata["parents"] = [parent_id]
folder = drive.files().create(
body=metadata,
fields="id",
supportsAllDrives=True,
).execute()
return folder["id"]
Upload / Create a File
from googleapiclient.http import MediaFileUpload
def upload_file(
drive,
local_path: str,
name: str,
parent_id: str | None = None,
mime_type: str = "application/octet-stream",
) -> str:
metadata = {"name": name}
if parent_id:
metadata["parents"] = [parent_id]
media = MediaFileUpload(local_path, mimetype=mime_type, resumable=True)
file = drive.files().create(
body=metadata,
media_body=media,
fields="id",
supportsAllDrives=True,
).execute()
return file["id"]
Upload from In-Memory Bytes
from googleapiclient.http import MediaIoBaseUpload
def upload_bytes(
drive,
data: bytes,
name: str,
parent_id: str | None = None,
mime_type: str = "application/octet-stream",
) -> str:
metadata = {"name": name}
if parent_id:
metadata["parents"] = [parent_id]
media = MediaIoBaseUpload(io.BytesIO(data), mimetype=mime_type, resumable=True)
file = drive.files().create(
body=metadata,
media_body=media,
fields="id",
supportsAllDrives=True,
).execute()
return file["id"]
Update File Metadata (rename, move)
def rename_file(drive, file_id: str, new_name: str) -> None:
drive.files().update(
fileId=file_id,
body={"name": new_name},
supportsAllDrives=True,
).execute()
def move_file(drive, file_id: str, new_parent_id: str, old_parent_id: str) -> None:
drive.files().update(
fileId=file_id,
addParents=new_parent_id,
removeParents=old_parent_id,
fields="id, parents",
supportsAllDrives=True,
).execute()
Update File Content
def update_file_content(
drive,
file_id: str,
local_path: str,
mime_type: str = "application/octet-stream",
) -> None:
media = MediaFileUpload(local_path, mimetype=mime_type, resumable=True)
drive.files().update(
fileId=file_id,
media_body=media,
supportsAllDrives=True,
).execute()
Delete a File or Folder
def delete_file(drive, file_id: str) -> None:
drive.files().delete(fileId=file_id, supportsAllDrives=True).execute()
Deleting a folder also deletes all its children permanently. Prefer trashing:
drive.files().update(fileId=file_id, body={"trashed": True}).execute()
Set Permissions (Make Public)
To make a file publicly readable (anyone with the link):
def make_public(drive, file_id: str) -> None:
drive.permissions().create(
fileId=file_id,
body={"role": "reader", "type": "anyone"},
supportsAllDrives=True,
).execute()
To grant write access to a specific user:
def share_with_user(drive, file_id: str, email: str, role: str = "writer") -> None:
# role: "reader", "commenter", "writer", "fileOrganizer", "organizer", "owner"
drive.permissions().create(
fileId=file_id,
body={"role": role, "type": "user", "emailAddress": email},
sendNotificationEmail=False,
supportsAllDrives=True,
).execute()
Common MIME Types
| Content | MIME Type |
|---|---|
| Folder | application/vnd.google-apps.folder |
| Google Doc | application/vnd.google-apps.document |
| Google Sheet | application/vnd.google-apps.spreadsheet |
application/pdf | |
| Plain text | text/plain |
| JSON | application/json |
| PNG | image/png |
| ZIP | application/zip |
Error Handling
from googleapiclient.errors import HttpError
def safe_delete(drive, file_id: str) -> bool:
try:
drive.files().delete(fileId=file_id, supportsAllDrives=True).execute()
return True
except HttpError as e:
if e.resp.status == 404:
return False # already gone
raise
Common HTTP status codes:
400— bad request (check field names, MIME types)403— permission denied (check auth scope or file sharing settings)404— file not found (checkfileId,supportsAllDrives)429— rate limited (back off and retry)
Required Python Packages
google-api-python-client>=2.100.0
google-auth>=2.23.0
google-auth-httplib2>=0.1.1
Install:
pip install google-api-python-client google-auth google-auth-httplib2
Security Notes
- Never hardcode API keys or service account credentials in source code. Use environment variables or secret managers.
- Restrict API key to the Drive API scope in the Google Cloud Console.
- Service account credentials (
service_account.json) must be in.gitignore. - When sharing files, prefer time-limited access tokens over permanent public links where possible.
"type": "anyone"with"role": "writer"on a production folder is dangerous — audit permissions regularly.
Reference
Local references (this skill):
- references/mime_types.md — full MIME type table
- references/error_codes.md — HTTP errors, Drive reasons, retry pattern
- assets/service_account_template.json — service account JSON structure
Official docs:
Files
13 totalComments
Loading comments…
