A Go module that provides a file system-like interface for Google Drive operations.
drivefs provides a convenient and intuitive Go API for managing files and directories in Google Drive. It wraps the google.golang.org/api/drive/v3 package and offers familiar filesystem operations such as creating, reading, writing, copying, renaming, moving, and deleting files and directories. The package fully supports both My Drive and Shared Drives.
go get github.com/Jumpaku/go-drivefsRequirements:
- Go 1.24.10 or later
- An authenticated Google Drive API
*drive.Service
package main
import (
"fmt"
"log"
"github.com/Jumpaku/go-drivefs"
"google.golang.org/api/drive/v3"
)
func main() {
// Assuming you have an authenticated drive.Service
// See Authentication section for setup details
var service *drive.Service // Your authenticated service
// Create DriveFS instance
driveFS := drivefs.New(service)
// Get the root folder (My Drive)
rootID := drivefs.FileID("root")
// Create a directory
dirInfo, err := driveFS.MkdirAll(rootID, "/my-project/data")
if err != nil {
log.Fatal(err)
}
// Create and write a file
fileInfo, err := driveFS.Create(dirInfo.ID, "notes.txt")
if err != nil {
log.Fatal(err)
}
err = driveFS.WriteFile(fileInfo.ID, []byte("Hello from drivefs!"))
if err != nil {
log.Fatal(err)
}
// Read the file back
data, err := driveFS.ReadFile(fileInfo.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data)) // Output: Hello from drivefs!
}package main
import (
"context"
"fmt"
"log"
"github.com/Jumpaku/go-drivefs"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
)
func main() {
ctx := context.Background()
// Create a drive.Service (authentication setup required)
// For authentication examples, see:
// https://developers.google.com/drive/api/v3/quickstart/go
service, err := drive.NewService(ctx /* add your auth options here */)
if err != nil {
log.Fatal(err)
}
// Create a new DriveFS instance
driveFS := drivefs.New(service)
// Get the root folder ID
// Use "root" for the user's My Drive root, or provide a specific folder ID
// for a Shared Drive or subdirectory
rootID := drivefs.FileID("root")
// Create a directory structure
// MkdirAll creates all directories along the path if they don't exist
dirInfo, err := driveFS.MkdirAll(rootID, "/path/to/directory")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created directory: %s (ID: %s)\n", dirInfo.Name, dirInfo.ID)
// Create a file in the directory
fileInfo, err := driveFS.Create(dirInfo.ID, "example.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created file: %s (ID: %s)\n", fileInfo.Name, fileInfo.ID)
// Write content to the file
err = driveFS.WriteFile(fileInfo.ID, []byte("Hello, Google Drive!"))
if err != nil {
log.Fatal(err)
}
// Read the file content
data, err := driveFS.ReadFile(fileInfo.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println("File content:", string(data))
// Get file metadata
info, err := driveFS.Info(fileInfo.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("File: %s, Size: %d bytes, Modified: %v\n", info.Name, info.Size, info.ModTime)
// List directory contents
entries, err := driveFS.ReadDir(dirInfo.ID)
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
fmt.Printf("- %s (folder: %v, ID: %s)\n", entry.Name, entry.IsFolder(), entry.ID)
}
// Find files by path
// Returns all matching files (multiple if duplicates exist at any level)
resolvedInfos, err := driveFS.FindByPath(rootID, "/path/to/directory/example.txt")
if err != nil {
log.Fatal(err)
}
for _, info := range resolvedInfos {
fmt.Printf("Resolved: %s (ID: %s)\n", info.Name, info.ID)
}
// Get the full path from a file ID
// Note: This returns a path without a root prefix
path, err := driveFS.ResolvePath(fileInfo.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Path: %s\n", path)
// Copy a file to a different directory
newParentInfo, err := driveFS.MkdirAll(rootID, "/new/location")
if err != nil {
log.Fatal(err)
}
copiedFileInfo, err := driveFS.Copy(fileInfo.ID, newParentInfo.ID, "example_copy.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Copied file: %s (ID: %s)\n", copiedFileInfo.Name, copiedFileInfo.ID)
// Rename a file
renamedFileInfo, err := driveFS.Rename(fileInfo.ID, "renamed_example.txt")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Renamed file: %s\n", renamedFileInfo.Name)
// Move a file to a different directory
err = driveFS.Move(fileInfo.ID, newParentInfo.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println("File moved successfully")
// Walk the file tree
// Traverses all files and directories recursively
err = driveFS.Walk(rootID, func(path drivefs.Path, info drivefs.FileInfo) error {
fmt.Printf("%s: %s (ID: %s)\n", path, info.Name, info.ID)
return nil
})
if err != nil {
log.Fatal(err)
}
// Manage permissions
// List current permissions
permissions, err := driveFS.PermList(fileInfo.ID)
if err != nil {
log.Fatal(err)
}
for _, perm := range permissions {
fmt.Printf("Permission ID: %s, Role: %s\n", perm.ID(), perm.Role())
}
// Grant read access to a user
_, err = driveFS.PermSet(fileInfo.ID, drivefs.UserPermission("user@example.com", drivefs.RoleReader))
if err != nil {
log.Fatal(err)
}
// Grant write access to a group
_, err = driveFS.PermSet(fileInfo.ID, drivefs.GroupPermission("group@example.com", drivefs.RoleWriter))
if err != nil {
log.Fatal(err)
}
// Grant read access to anyone with the link
_, err = driveFS.PermSet(fileInfo.ID, drivefs.AnyonePermission(drivefs.RoleReader, false))
if err != nil {
log.Fatal(err)
}
// Remove a user's permission
_, err = driveFS.PermDel(fileInfo.ID, drivefs.User("user@example.com"))
if err != nil {
log.Fatal(err)
}
// Remove a file (move to trash)
err = driveFS.Remove(fileInfo.ID, true)
if err != nil {
log.Fatal(err)
}
// Permanently delete a file (skip trash)
err = driveFS.Remove(copiedFileInfo.ID, false)
if err != nil {
log.Fatal(err)
}
// Remove a directory and all its contents
err = driveFS.RemoveAll(dirInfo.ID, true)
if err != nil {
log.Fatal(err)
}
}The main type for interacting with Google Drive.
func New(service *drive.Service) *DriveFSCreates a new DriveFS instance with the given Google Drive service.
- The
serviceparameter should be an authenticated*drive.Servicefrom the Google Drive API - This constructor does not take a root ID; root IDs are passed to individual methods as needed
- Returns a new DriveFS instance ready to perform operations
func (s *DriveFS) MkdirAll(rootID FileID, path Path) (FileInfo, error)Creates all directories along the given path if they do not already exist.
rootID: The starting point folder ID (use"root"for My Drive root, or a specific folder ID)path: Must be absolute (start with/)- Returns the FileInfo of the final directory in the path
- If multiple directories with the same name exist at any level, returns
ErrAlreadyExists
func (s *DriveFS) Mkdir(parentID FileID, name string) (FileInfo, error)Creates a single directory with the given name under the specified parent.
- Creates a new directory even if one with the same name already exists (Google Drive allows duplicates)
func (s *DriveFS) Create(parentID FileID, name string) (FileInfo, error)Creates a new empty file in the specified parent directory.
- Creates a new file even if one with the same name already exists (Google Drive allows duplicates)
func (s *DriveFS) ReadFile(fileID FileID) ([]byte, error)Reads and returns the entire contents of a file.
- Returns
ErrNotReadablefor Google Apps files (Docs, Sheets, Slides, etc.) - For Google Apps files, use the Drive API export functionality instead
func (s *DriveFS) WriteFile(fileID FileID, data []byte) errorWrites data to an existing file, completely replacing its contents.
func (s *DriveFS) Shortcut(parentID FileID, name string, targetID FileID) (FileInfo, error)Creates a shortcut (link) to another file or directory.
parentID: The directory where the shortcut will be createdname: The name of the shortcuttargetID: The ID of the file or directory that the shortcut points to- Returns the FileInfo of the created shortcut
func (s *DriveFS) Info(fileID FileID) (FileInfo, error)Returns the FileInfo (metadata) for the file or directory with the given ID.
func (s *DriveFS) ReadDir(fileID FileID) ([]FileInfo, error)Lists all files and directories directly within the specified directory.
- Does not include trashed items
- Returns only immediate children (not recursive)
func (s *DriveFS) FindByPath(rootID FileID, path Path) ([]FileInfo, error)Resolves an absolute path (relative to the specified root) and returns all matching FileInfo objects.
rootID: The starting point folder ID (use"root"for My Drive root, or a specific folder ID)path: Must be absolute (start with/)- Returns multiple results if there are duplicate files/folders with the same name at any level
- Returns an empty slice if the path does not exist
func (s *DriveFS) ResolvePath(fileID FileID) (Path, error)Returns the absolute path from the root to the file or directory with the given ID.
- Returns
ErrMultiParentsNotSupportedif the file has multiple parents - The path is built by traversing up to the topmost parent (the file with no parents) and returns an absolute path starting with
/
func (s *DriveFS) Query(query string) ([]FileInfo, error)Executes a Google Drive API query and returns all matching files and directories.
query: A Google Drive API query string (see Search for files)- Returns a slice of FileInfo objects for all matching items
- Useful for advanced searches that go beyond simple path-based lookups
func (s *DriveFS) Copy(fileID, newParentID FileID, newName string) (FileInfo, error)Creates a copy of the file in the specified new parent directory with the provided new name.
- Creates a new file with a new ID
- Original file remains unchanged
func (s *DriveFS) Rename(fileID FileID, newName string) (FileInfo, error)Renames the file or directory to the specified new name.
- Does not change the file's parent or location
func (s *DriveFS) Move(fileID, newParentID FileID) errorMoves a file or directory to a new parent directory.
- Removes all existing parents and sets the new parent
- Does not change the file's name
func (s *DriveFS) Remove(fileID FileID, moveToTrash bool) errorRemoves a file or directory.
- If
moveToTrashistrue, the item is moved to Google Drive trash - If
moveToTrashisfalse, the item is permanently deleted - Returns
ErrNotRemovableif attempting to remove a non-empty directory - For non-empty directories, use
RemoveAllinstead
func (s *DriveFS) RemoveAll(fileID FileID, moveToTrash bool) errorRemoves a file or directory and all its contents recursively.
- If
moveToTrashistrue, items are moved to Google Drive trash - If
moveToTrashisfalse, items are permanently deleted - Safe to use on both files and directories
func (s *DriveFS) Walk(rootID FileID, f func(Path, FileInfo) error) errorWalks the file tree rooted at the specified file or directory, calling the provided function for each item.
rootID: The starting point folder ID to begin walking from- Includes the root item itself (passed as "/" to the callback)
- The function receives both the path (starting from "/" for the root item, then its children as "/childname", etc.) and FileInfo for each item
- If the callback function returns an error, walking stops and that error is returned
func (s *DriveFS) PermList(fileID FileID) ([]Permission, error)Lists all permissions for the file or directory with the given ID.
- Returns a slice of Permission objects representing all users, groups, domains, or anyone who has access
- Each Permission contains information about the grantee, role, and whether file discovery is allowed
func (s *DriveFS) PermSet(fileID FileID, permission Permission) ([]Permission, error)Sets or updates a permission for the file or directory with the given ID.
- If a permission for the specified grantee already exists, it will be updated
- If no permission exists for the grantee, a new one will be created
- Returns the updated list of all permissions for the file
- Use helper functions like
UserPermission(),GroupPermission(),DomainPermission(), orAnyonePermission()to create Permission objects
func (s *DriveFS) PermDel(fileID FileID, grantee Grantee) ([]Permission, error)Deletes all permissions matching the specified grantee for the file or directory.
- Removes permissions for the specified user, group, domain, or anyone access
- Returns the updated list of remaining permissions for the file
- Use helper functions like
User(),Group(),Domain(), orAnyone()to create Grantee objects
type FileID stringA unique identifier for a file or directory in Google Drive. These IDs are assigned by Google Drive and remain stable across renames and moves.
type Path stringAn absolute path string representing a location in the Drive filesystem.
- Must start with
/(absolute path required) - Relative path components (
.and..) are not allowed - Path separators are forward slashes (
/) - Example:
"/folder/subfolder/file.txt"
type FileInfo struct {
Name string // File or directory name
ID FileID // Unique Google Drive ID
Size int64 // File size in bytes (0 for directories)
Mime string // MIME type (e.g., "text/plain", "application/vnd.google-apps.folder")
ModTime time.Time // Last modification time
ShortcutTarget FileID // Target file ID (for shortcuts only, empty otherwise)
WebViewLink string // URL to view the file in the Google Drive web interface
}Contains metadata about a file or directory.
Methods:
func (i FileInfo) IsFolder() boolReturns true if the item is a folder/directory.
func (i FileInfo) IsShortcut() boolReturns true if the item is a shortcut to another file or directory.
The target file ID can be found in the ShortcutTarget field.
func (i FileInfo) IsAppFile() boolReturns true if the item is a Google Apps file (e.g., Google Docs, Sheets, Slides).
Google Apps files cannot be read with ReadFile() and must be exported using the Drive API's export functionality.
type Permission interface {
ID() PermissionID
Grantee() Grantee
Role() Role
AllowFileDiscovery() bool
}Represents a permission granted to a user, group, domain, or anyone for a file or directory.
Helper Functions to Create Permissions:
func UserPermission(email string, role Role) PermissionCreates a permission for a specific user identified by email address.
func GroupPermission(email string, role Role) PermissionCreates a permission for a Google Group identified by email address.
func DomainPermission(domain string, role Role, allowFileDiscovery bool) PermissionCreates a permission for an entire domain (e.g., "example.com").
func AnyonePermission(role Role, allowFileDiscovery bool) PermissionCreates a permission that grants access to anyone with the link.
type Grantee interface {
// Sealed interface - cannot be implemented outside the package
}Represents the recipient of a permission (user, group, domain, or anyone).
Helper Functions to Create Grantees:
func User(email string) GranteeCreates a grantee representing a specific user.
func Group(email string) GranteeCreates a grantee representing a Google Group.
func Domain(domain string) GranteeCreates a grantee representing an entire domain.
func Anyone() GranteeCreates a grantee representing anyone with the link.
Concrete Grantee Types:
GranteeUser- Represents a user with anEmailfieldGranteeGroup- Represents a group with anEmailfieldGranteeDomain- Represents a domain with aDomainfieldGranteeAnyone- Represents public access (anyone with the link)
type Role stringRepresents the level of access granted by a permission.
Available Roles:
const (
RoleOwner Role = "owner" // Full ownership with ability to delete
RoleOrganizer Role = "organizer" // Can organize files in Shared Drives
RoleFileOrganizer Role = "fileOrganizer" // Can organize files
RoleWriter Role = "writer" // Can edit files and add comments
RoleCommenter Role = "commenter" // Can view and add comments
RoleReader Role = "reader" // Can only view files
)type PermissionID stringA unique identifier for a permission. These IDs are assigned by Google Drive and are used to update or delete specific permissions.
The package defines the following error constants that can be checked using errors.Is():
var (
ErrInvalidPath error // Invalid path format
ErrDriveError error // Google Drive API error
ErrIOError error // I/O operation error
ErrNotFound error // File or directory not found
ErrAlreadyExists error // File or directory already exists
ErrMultiParentsNotSupported error // File has multiple parents
ErrNotReadable error // File cannot be read (e.g., Google Apps files)
ErrNotRemovable error // Directory not empty or cannot be removed
)Error Descriptions:
ErrInvalidPath- Returned when a path is invalid (e.g., not absolute, contains.or..components, or is empty)ErrDriveError- Returned when a Google Drive API call fails (wraps the underlying API error)ErrIOError- Returned when an I/O operation fails (e.g., reading response body)ErrNotFound- Returned when a requested file or directory is not foundErrAlreadyExists- Returned whenMkdirAllencounters multiple directories with the same name at any level in the pathErrMultiParentsNotSupported- Returned byResolvePathwhen attempting to resolve the path of a file that has multiple parents (Google Drive allows files to have multiple parents, but this library doesn't support path resolution for such files)ErrNotReadable- Returned byReadFilewhen attempting to read a Google Apps file (Docs, Sheets, Slides, etc.), which cannot be downloaded as raw bytesErrNotRemovable- Returned byRemovewhen attempting to remove a non-empty directory (useRemoveAllinstead)
Error Handling Example:
import (
"errors"
"github.com/Jumpaku/go-drivefs"
)
data, err := driveFS.ReadFile(fileID)
if err != nil {
if errors.Is(err, drivefs.ErrNotReadable) {
// Handle Google Apps files differently
fmt.Println("This is a Google Apps file, use export instead")
} else if errors.Is(err, drivefs.ErrNotFound) {
fmt.Println("File not found")
} else {
log.Fatal(err)
}
}- ✅ File and Directory Operations: Create, read, write, copy, rename, move, and delete files and directories
- ✅ Permission Management: List, set, and delete permissions for users, groups, domains, and public access
- ✅ Shortcut Support: Create shortcuts (links) to files and directories
- ✅ Path-Based Operations: Use familiar path strings like
/folder/subfolder/file.txt - ✅ Path Resolution: Convert between file IDs and absolute paths
- ✅ Tree Walking: Recursively traverse directory structures with the
Walkfunction - ✅ Shared Drive Support: Full support for both My Drive and Shared Drives
- ✅ Comprehensive Error Handling: Well-defined error constants that can be checked with
errors.Is() - ✅ Google Apps File Detection: Identify Google Docs, Sheets, Slides, and other Apps files
- ✅ Trash Support: Choose between moving items to trash or permanently deleting them
This package requires an authenticated *drive.Service instance from the Google Drive API.
-
Create a Google Cloud project
- Visit the Google Cloud Console
- Create a new project or select an existing one
-
Enable the Google Drive API
- In your project, navigate to "APIs & Services" > "Library"
- Search for "Google Drive API" and enable it
-
Create credentials
- Go to "APIs & Services" > "Credentials"
- Create OAuth 2.0 credentials (for user access) or a Service Account (for server-to-server access)
- Download the credentials JSON file
-
Authenticate and create a
drive.Service
See the Google Drive API Go Quickstart for detailed authentication instructions.
- Full read-write access:
drive.DriveScope(https://www.googleapis.com/auth/drive) - Read-only access:
drive.DriveReadonlyScope(https://www.googleapis.com/auth/drive.readonly)
Example Authentication (OAuth 2.0):
import (
"context"
"log"
"os"
"golang.org/x/oauth2/google"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
)
// Load credentials from a JSON file
b, err := os.ReadFile("credentials.json")
if err != nil {
log.Fatalf("Unable to read credentials file: %v", err)
}
config, err := google.ConfigFromJSON(b, drive.DriveScope)
if err != nil {
log.Fatalf("Unable to parse credentials: %v", err)
}
// Get an OAuth 2.0 token (implement token retrieval as needed)
token, err := getToken(config)
if err != nil {
log.Fatalf("Unable to retrieve token: %v", err)
}
client := config.Client(context.Background(), token)
service, err := drive.NewService(context.Background(), option.WithHTTPClient(client))
if err != nil {
log.Fatalf("Unable to create Drive service: %v", err)
}
// Now use the service with drivefs
driveFS := drivefs.New(service)
// Use "root" as the root ID for My Drive operations
rootID := drivefs.FileID("root")
dirInfo, err := driveFS.MkdirAll(rootID, "/my-files")Unlike traditional filesystem libraries, DriveFS does not bind to a single root directory at construction time. Instead:
- The constructor
New(service)only takes a Google Drive service - Methods that need a starting point (
MkdirAll,FindByPath,Walk) accept arootIDparameter - Use
drivefs.FileID("root")to reference the user's My Drive root - Use any folder ID to start operations from that folder (useful for Shared Drives or subdirectories)
This design allows you to work with multiple roots (e.g., My Drive and Shared Drives) using a single DriveFS instance.
- Absolute Paths Only: All path strings must be absolute and start with
/ - No Relative Components: Paths cannot contain
.(current directory) or..(parent directory) components - Forward Slashes: Use
/as the path separator (Unix-style) - Relative to Root: Paths in
MkdirAllandFindByPathare interpreted relative to the providedrootID
Google Drive's file system differs from traditional filesystems in that it allows multiple files or folders with the same name in the same parent directory.
Create()andMkdir()will create new items even if items with the same name already exist- To avoid duplicates, check existing items with
ReadDir()before creating FindByPath()returns all matching items when duplicates existMkdirAll()returns an error if it encounters multiple directories with the same name at any level
Google Apps files (Docs, Sheets, Slides, Forms, etc.) are special:
- They cannot be read with
ReadFile()- this will returnErrNotReadable - They have MIME types starting with
application/vnd.google-apps. - Use
FileInfo.IsAppFile()to detect them - To access their content, use the Google Drive API's export functionality
Google Drive allows files to have multiple parent directories:
- This library primarily supports single-parent files
ResolvePath()will returnErrMultiParentsNotSupportedfor files with multiple parentsMove()removes all existing parents and sets a single new parent- When resolving paths, files with multiple parents cannot have an unambiguous path
- Trashed items are automatically excluded from
ReadDir()and path resolution operations - Use the
moveToTrashparameter inRemove()andRemoveAll():moveToTrash=true: Items can be restored from Google Drive trashmoveToTrash=false: Items are permanently deleted
- Full support for Shared Drives (formerly Team Drives)
- All API calls use
SupportsAllDrives(true)andIncludeItemsFromAllDrives(true) - To work within a Shared Drive, pass the Shared Drive root folder ID to methods like
MkdirAll,FindByPath, orWalk
BSD 2-Clause License. See LICENSE for details.