This guide walks you through setting up Google Drive integration using a Service Account for direct file uploads from Next.js. This is much simpler than OAuth because you don't need to generate refresh tokens manually!This guide will help you set up direct Google Drive uploads from your Next.js application using the Google Drive API (no Apps Script required).
✅ No manual token generation - just download one JSON file The application now uploads files directly to Google Drive using:
✅ Never expires - no need to refresh tokens - Google Drive API v3 via the googleapis npm package
✅ Simpler setup - fewer steps - OAuth 2.0 with a refresh token for server-side authentication
✅ Perfect for server-to-server - ideal for Next.js API routes - Next.js API Route (/api/drive-upload) that handles the upload
- Google Drive API enabled
-
Go to Google Cloud Console- OAuth 2.0 credentials (Client ID and Client Secret)
-
Create a new project or select an existing one
-
Note your project name## Step-by-Step Setup
-
In the Cloud Console, go to APIs & Services → Library1. Go to Google Cloud Console
-
Search for "Google Drive API"2. Click Select a project → New Project
-
Click Enable3. Enter a project name (e.g., "MXShare Upload")
-
Click Create
-
Go to IAM & Admin → Service Accounts
-
Click Create Service Account1. In your project, go to APIs & Services → Library
-
Fill in:2. Search for "Google Drive API"
-
Service account name:
mxshare-drive-uploader(or any name)3. Click on it and press Enable -
Service account description:
Service account for uploading files to Google Drive
-
-
Click Create and Continue### 3. Configure OAuth Consent Screen (IMPORTANT!)
-
Skip "Grant this service account access to project" (click Continue)
-
Skip "Grant users access to this service account" (click Done)1. Go to APIs & Services → OAuth consent screen
-
Choose User Type:
4. Create and Download Service Account Key - External (if you want to use this with any Google account)
- Or Internal (if using Google Workspace and only for your organization)
-
Find your newly created service account in the list3. Fill in the required fields:
-
Click on it to open details - App name:
MXShare(or your app name) -
Go to the Keys tab - User support email: Your email
-
Click Add Key → Create new key - Developer contact: Your email
-
Choose JSON format4. Click Save and Continue
-
Click Create5. Add Scopes:
-
A JSON file will download automatically - keep it safe! - Click Add or Remove Scopes
- Search for and add:
https://www.googleapis.com/auth/drive.file
- Search for and add:
The downloaded file looks like this: - This allows the app to create and modify files it creates
{6. **Add Test Users** (CRITICAL for development):
"type": "service_account", - Click **Add Users**
"project_id": "your-project", - Add your Google account email (the one you'll use for uploads)
"private_key_id": "...", - Click **Add** then **Save and Continue**
"private_key": "-----BEGIN PRIVATE KEY-----\n...",7. Click **Back to Dashboard**
"client_email": "mxshare-drive-uploader@your-project.iam.gserviceaccount.com",
"client_id": "...",> ⚠️ **Important**: While your app is in "Testing" mode, only test users you added can authorize it. This is why you're getting the "access denied" error!
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",### 4. Create OAuth 2.0 Credentials
...
}1. Go to **APIs & Services** → **Credentials**
```2. Click **Create Credentials** → **OAuth client ID**
3. Configure:
### 5. Add Service Account Key to .env - Application type: **Desktop app** (not Web application!)
- Name: "MXShare Desktop Client"
1. Open the downloaded JSON file4. Click **Create**
2. Copy the **ENTIRE contents** (all of it, exactly as is)5. **Save the Client ID and Client Secret** that appear
3. Open your `.env` file
4. Paste it as one line for `GOOGLE_SERVICE_ACCOUNT_KEY`:### 5. Generate a Refresh Token
```envThe application includes a helper script to generate your refresh token.
GOOGLE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"your-project",...}
```1. **Add your credentials to `.env`**:
```bash
**Important:** The entire JSON should be on one line. If you need to format it, you can also escape it: GOOGLE_CLIENT_ID=your-client-id-here
```env GOOGLE_CLIENT_SECRET=your-client-secret-here
GOOGLE_SERVICE_ACCOUNT_KEY='{"type":"service_account","project_id":"your-project",...}' ```
- Run the refresh token generator:
node scripts/generate-refresh-token.js
This is the crucial step that many people miss! ```
-
Go to Google Drive3. Follow the prompts:
-
Create a folder for uploads (e.g., "MXShare Uploads") or use an existing one - Copy the URL that appears
-
Right-click the folder → Share - Open it in your browser
-
In the "Add people" field, paste the service account email from the JSON file - Sign in with the Google account you added as a test user (important!)
-
It looks like:
mxshare-drive-uploader@your-project.iam.gserviceaccount.com- Grant permissions to access Google Drive -
You can find it in the JSON as
"client_email"- Google will display an authorization code on the screen
-
-
Give it Editor access - Copy the authorization code
-
Click Share (you might see a warning that it's not a regular Gmail - that's OK!) - Paste it into the terminal and press Enter
-
Copy the folder ID from the URL:
https://drive.google.com/drive/folders/FOLDER_ID_HERE -
Add it to
.env:4. The script will output your refresh token. Copy it immediately!GOOGLE_DRIVE_FOLDER_ID=your-folder-id-here5. **Add the refresh token to `.env`**: ``` ```bash GOOGLE_REFRESH_TOKEN=your-refresh-token-here
# Google Drive Service Account> - Make sure you added your email as a test user in the OAuth consent screen
GOOGLE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"your-project",...}> - Make sure you're using a **Desktop app** OAuth client (not Web application)
> - Try signing out of Google and signing in again with the test user account
# Folder where uploads will go (must share this folder with the service account email!)
GOOGLE_DRIVE_FOLDER_ID=1YlUouqhP8nRXmUKuUcNZ2L0ymK_b8a0x### 6. (Optional) Create a Dedicated Drive Folder
# ... other environment variables ...To organize uploads in a specific folder:
- Go to Google Drive
-
Open the folder and copy the ID from the URL:
-
Start your development server: ```
npm run dev ^^^^^^^^^^^^^^^^^^^^ ``` This is the Folder ID -
Navigate to
http://localhost:3000/upload4. Add it to.env: -
Click "Upload to Google Drive" GOOGLE_DRIVE_FOLDER_ID=your-folder-id-here
-
Select a test file
If you skip this step, files will be uploaded to the root of "My Drive".
-
Verify:
-
✅ Upload completes successfully### 7. Verify Your
.envFile -
✅ "Google Drive Link" field is populated
-
✅ File appears in your Google Drive folderYour
.envshould now contain these Google Drive variables:
-
"GOOGLE_SERVICE_ACCOUNT_KEY is not configured"GOOGLE_CLIENT_ID=123456789-abc123.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-abc123def456
Solution: Make sure you copied the entire JSON file contents to your .env file.GOOGLE_REFRESH_TOKEN=1//0abc123def456...
GOOGLE_DRIVE_FOLDER_ID=1A2B3C4D5E6F7G8H9I0J # Optional
Solution: The JSON might be malformed. Make sure:NEXTAUTH_SECRET=...
-
You copied the complete JSON (including opening
{and closing})MONGODB_URI=... -
No line breaks in the middle of the JSON stringALLOWED_DOMAIN=...
-
Or wrap it in single quotes:
GOOGLE_SERVICE_ACCOUNT_KEY='{ ... }'URL=http://localhost:3000
OPENROUTER_API_KEY=...
Solution: You forgot to share the folder with the service account!## Testing the Upload
-
Open the folder in Google Drive
-
Click Share1. Start your development server:
-
Add the service account email (from
client_emailin the JSON) ```bash -
Give it Editor access npm run dev
- Navigate to
http://localhost:3000/upload
Solution: Check the folder ID is correct, or remove GOOGLE_DRIVE_FOLDER_ID temporarily to upload to the service account's root folder.
- Click "Upload to Google Drive"
- Select a file from your computer
Solution:
-
Make sure the Google Drive API is enabled in your project5. Wait for the upload to complete (you'll see "✓ Uploaded to Drive")
-
Share the destination folder with the service account email
-
Give the service account at least "Editor" permissions6. The Google Drive Link field should auto-populate
-
Never commit
.envto version control - it contains your private key!## How It Works -
Add
.envto your.gitignorefile -
The service account key gives full access to any folder shared with it### Upload Flow
-
Store the downloaded JSON file securely (you can delete it after adding to
.env) -
For production, use environment variables on your hosting platform (Vercel, Railway, etc.)1. Client (
app/upload/page.tsx):- User clicks "Upload to Google Drive"
- Selected file is sent as raw body to
/api/drive-upload
When deploying to production (Vercel, Railway, etc.): - Headers include filename, size, and MIME type
-
Go to your hosting platform's environment variables settings2. Server (
app/api/drive-upload/route.ts): -
Add
GOOGLE_SERVICE_ACCOUNT_KEYwith the full JSON contents - Reads the file from request body -
Add
GOOGLE_DRIVE_FOLDER_IDwith your folder ID - Creates OAuth2 client with refresh token -
Make sure the production folder is also shared with the service account! - Uploads file to Google Drive using
googleapis- Sets file permissions to "anyone with link can view"
| Feature | Service Account (Current) | OAuth Refresh Token (Old) |3. Client (continued):
|---------|--------------------------|---------------------------| - Receives Drive URL from API
| Setup complexity | ⭐ Simple | ⭐⭐⭐ Complex | - Auto-fills the "Google Drive Link" field
| Token expiration | ✅ Never | ❌ Can expire | - Shows success notification
| Manual steps | 1 (download JSON) | 3+ (authorize, copy code, etc.) |
| Maintenance | ✅ None | ❌ Regenerate tokens periodically |### File Permissions
| Best for | Server apps | User apps |
By default, uploaded files are set to "anyone with the link can view". This means:
-
✅ Only people with the direct link can access them
-
Google Service Accounts Documentation- ✅ No sign-in required to view
-
Service Account Keys Best PracticesIf you want to change this behavior, edit
app/api/drive-upload/route.tsand modify or remove the permission setting.
Solution: Make sure GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN are set in .env
Solution: Your refresh token may have expired. Regenerate it:
node scripts/generate-refresh-token.jsSolution:
- Make sure you granted the correct scope when generating the token
- The scope should be
https://www.googleapis.com/auth/drive.file - If you changed scopes, regenerate the refresh token
Solution:
- Verify
GOOGLE_DRIVE_FOLDER_IDis correct - Make sure the Google account that created the refresh token has access to that folder
- If the folder is in a Shared Drive, additional setup may be required
Possible causes:
- Server memory limits (current implementation buffers entire file)
- Request timeout limits
- Network interruptions
Solutions:
- For files >50MB, consider implementing resumable uploads
- Increase Next.js server timeout if needed
- Add client-side file size validation
- Never commit
.envto git - It contains sensitive credentials - Refresh tokens are long-lived - Treat them like passwords
- Client/Server separation - OAuth tokens never leave the server
- File validation - Consider adding server-side file type/size checks
- Rate limiting - Consider adding rate limits to the upload endpoint
If you previously used Google Apps Script for uploads:
- ❌ No more popup window
- ❌ No more
GAS_UPLOAD_URLin.env - ❌ No more
postMessagecommunication - ✅ Direct upload from Next.js
- ✅ Native file picker
- ✅ Better error handling
- ✅ No external dependencies
- Remove
GAS_UPLOAD_URLfrom.env(already done) - Your old Apps Script code can be deleted or kept as backup
- Existing file links in the database continue to work
If you need additional Google Drive permissions, modify the scope in scripts/generate-refresh-token.js:
const SCOPES = [
'https://www.googleapis.com/auth/drive.file', // Upload/manage files created by app
// 'https://www.googleapis.com/auth/drive', // Full drive access (use carefully!)
];Then regenerate your refresh token.
To upload to a Shared Drive (formerly Team Drive):
- Ensure your OAuth scope includes Shared Drive access
- Modify the upload route to include
supportsAllDrives: true - Use the Shared Drive folder ID in
GOOGLE_DRIVE_FOLDER_ID
To make files private by default, remove or comment out this section in app/api/drive-upload/route.ts:
// Remove this block to keep files private
await drive.permissions.create({
fileId,
requestBody: {
role: 'reader',
type: 'anyone',
},
});For issues with:
- Google Cloud Console: Google Cloud Support
- OAuth/Tokens: Check Google OAuth Documentation
- Drive API: Google Drive API Reference
- This Implementation: Create an issue in the repository