Shivamyadav's avatar

How to setup google drive api to upload files from laravel?

Currently I am using the service account and setup the testing route with the credentials and getting this error:

Route::get('/drive-test', function () {

    $service = new \App\Services\GoogleDriveService();

    $fileId = $service->upload(
        storage_path('1770402265.shivam-resume (1).pdf'),
        'test.pdf'
    );

    return $service->getFileUrl($fileId);
});
Google \ Service \ Exception (403)
{ "error": { "code": 403, "message": "Service Accounts do not have storage quota. Leverage shared drives (https://developers.google.com/workspace/drive/api/guides/about-shareddrives), or use OAuth delegation (http://support.google.com/a/answer/7281227) instead.", "errors": [ { "message": "Service Accounts do not have storage quota. Leverage shared drives (https://developers.google.com/workspace/drive/api/guides/about-shareddrives), or use OAuth delegation (http://support.google.com/a/answer/7281227) instead.", "domain": "usageLimits", "reason": "storageQuotaExceeded" } ] } }
0 likes
9 replies
Glukinho's avatar

Laravel by default doesn't have ability to use Google Drive as a storage. It means you're trying to utilize some external package. What is it? Did you read it's documentation?

Can you show \App\Services\GoogleDriveService class contents?

Shivamyadav's avatar
Glukinho's avatar

Did you write it yourself or got this code somewhere?

ian_h's avatar

Also, I'd highly recommend not using env() in the code, rather Config::get() as the former will cause you issues when you cache the config in production.

1 like
martinbean's avatar

@shivamyadav As @ian_h says, you should not be using the env helper in your code. The Laravel docs also tell you not to do this.

Instead, you should be binding that class in the service container where the configuration values are passed to your constructor:

public function register(): void
{
    $this->app->singleton(GoogleDriveService::class, function () {
        return new GoogleDriveService(
            clientId: $this->app['config']['services.google.drive.client_id'],
            clientSecret: $this->app['config']['services.google.drive.client_secret'],
            redirectUri: $this->app['config']['services.google.drive.redirect'],
        );
    });
}

You can then just type-hint the service class, and it will be resolved (and configured) by the service container:

class SomeController extends Controller
{
    protected GoogleDriveService $googleDrive;

    public function __construct(GoogleDriveService $googleDrive)
    {
        $this->googleDrive = $googleDrive;
    }
}

As for the error, it’s telling you what the problem is: service accounts don’t have a storage quote, so you can’t use service accounts for storage-related operations like you’re trying to do. You need to use an OAuth approach so you’re authenticating as a user, who will have a storage quota. You can use Socialite to create (and refresh) Google access tokens.

1 like
Shivamyadav's avatar

Thanks for the info. I did not know about the env direct helper issue in the code.

I have already setup the oauth and using and it works fine now.

1 like
Jsanwo64's avatar
  1. Create a Shared Drive

In Google Drive (Workspace), create a Shared drive (e.g., “App Uploads”).

  1. Add the service account as a member

Add [email protected] to that Shared Drive with at least Content manager.

  1. Put your target folder inside that Shared Drive

Create (or pick) a folder inside the shared drive and copy its folder ID.

  1. Use the service account JSON and upload into that shared drive folder

Key points:

Authenticate with the service account JSON (not client_id/secret/refresh_token).

Set supportsAllDrives => true.

Parent folder must be in the shared drive.

use Google\Client;
use Google\Service\Drive;
use Google\Service\Drive\DriveFile;

$client = new Client();
$client->setAuthConfig(storage_path('app/google/service-account.json'));
$client->addScope(Drive::DRIVE);

$drive = new Drive($client);

$folderIdInSharedDrive = config('drive.parent_folder_id'); // must be a folder inside the Shared Drive

$fileMetadata = new DriveFile([
    'name' => 'test.pdf',
    'parents' => [$folderIdInSharedDrive],
]);

$content = file_get_contents(storage_path('1770402265.shivam-resume (1).pdf'));

$file = $drive->files->create($fileMetadata, [
    'data' => $content,
    'mimeType' => 'application/pdf',
    'uploadType' => 'multipart',
    'supportsAllDrives' => true,
]);

return $file->id;

If you also search/create subfolders, include shared-drive flags there too (you already have supportsAllDrives => true). When listing folders in shared drives, you often also need:

includeItemsFromAllDrives => true

corpora => 'drive'

driveId => '{SHARED_DRIVE_ID}'

Shared drives are explicitly the recommended path for service accounts JSYK.

so your code should be looking like this

and your config

<?php

return [
    'shared_drive_id'  => env('GOOGLE_SHARED_DRIVE_ID'),   // ID of the Shared Drive itself
    'parent_folder_id' => env('GOOGLE_DRIVE_FOLDER_ID'),   // ID of a folder inside that Shared Drive

    'position_folders' => [
        'developer'  => 'Developer Resumes',
        'designer'   => 'Designer Resumes',
        'manager'    => 'Manager Resumes',
        // add more mappings as needed
    ],
];

Please or to participate in this conversation.