laravel-sharepoint-filesystem maintained by sahablibya
Laravel SharePoint/OneDrive Filesystem Driver
A Laravel filesystem driver for SharePoint and OneDrive using Microsoft Graph API with client credentials authentication. No manual OAuth flows required - perfect for server-side applications and automated backups.
✨ Features
- ✅ Zero-configuration OAuth - Automatic token management with client credentials flow
- ✅ SharePoint Document Libraries - Direct access to your SharePoint sites
- ✅ OneDrive for Business - Full OneDrive integration
- ✅ Automatic Token Refresh - Handles token expiry seamlessly with smart caching
- ✅ Laravel 10, 11, 12 - Compatible with modern Laravel versions
- ✅ Flysystem v3 - Built on the latest Flysystem architecture
- ✅ Large File Support - Optimized for files up to 250MB
- ✅ Safe Copy & Move - Monitors Microsoft Graph copy jobs before completing moves
- ✅ Path-Safe Operations - Handles spaces, special characters, and Unicode file names
- ✅ Production Ready - Battle-tested in real-world applications
- ✅ Spatie Backup Compatible - Perfect for automated backups to SharePoint
📋 Requirements
- PHP 8.1 or higher
- Laravel 10.x, 11.x, or 12.x
- Microsoft Azure app registration with appropriate permissions
📦 Installation
Install via Composer:
composer require sahablibya/laravel-sharepoint-filesystem
The service provider will be automatically registered via Laravel's package discovery.
⚙️ Configuration
Step 1: Azure App Registration
- Go to Azure Portal
- Navigate to Azure Active Directory → App registrations
- Click New registration
- Enter a name (e.g., "Laravel SharePoint Integration")
- Click Register
- Note your Application (client) ID and Directory (tenant) ID
Step 2: Create Client Secret
- In your app registration, go to Certificates & secrets
- Click New client secret
- Add a description and set expiration
- Click Add
- ⚠️ Copy the secret value immediately (you won't see it again!)
Step 3: Grant API Permissions
- Go to API permissions
- Click Add a permission → Microsoft Graph → Application permissions
- Add these permissions:
Files.ReadWrite.All- Read and write files in all site collectionsSites.ReadWrite.All- Read and write items in all site collections
- Click Grant admin consent (requires admin privileges)
- Wait 2-5 minutes for permissions to propagate
Step 4: Get SharePoint Drive ID (Optional)
To use a specific SharePoint document library, you need the drive ID:
Option A: Using Microsoft Graph Explorer
- Go to Graph Explorer
- Sign in with your account
- Find your site:
GET https://graph.microsoft.com/v1.0/sites?search=YourSiteName - Get drives for that site:
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drives - Copy the
idof your desired document library
Option B: Using PowerShell
Connect-PnPOnline -Url "https://yourtenant.sharepoint.com/sites/yoursite"
Get-PnPList | Where-Object {$_.BaseTemplate -eq 101}
Step 5: Environment Configuration
Add these variables to your .env file:
GRAPH_CLIENT_ID=your-application-client-id
GRAPH_CLIENT_SECRET=your-client-secret-value
GRAPH_TENANT_ID=your-tenant-id
# Optional: Specify a SharePoint document library
SHAREPOINT_DRIVE_ID=your-drive-id
# Optional: Prefix path within the drive
SHAREPOINT_PREFIX=backups
# Optional: Tune Microsoft Graph async copy monitoring
SHAREPOINT_COPY_MONITOR_TIMEOUT=300
SHAREPOINT_COPY_MONITOR_INTERVAL_MS=1000
Step 6: Register Filesystem Disk
Add the SharePoint disk to your config/filesystems.php:
'disks' => [
// ... other disks
'sharepoint' => [
'driver' => 'sharepoint',
'client_id' => env('GRAPH_CLIENT_ID'),
'client_secret' => env('GRAPH_CLIENT_SECRET'),
'tenant_id' => env('GRAPH_TENANT_ID', 'common'),
'drive_id' => env('SHAREPOINT_DRIVE_ID'), // Optional
'prefix' => env('SHAREPOINT_PREFIX', ''), // Optional
'copy_monitor_timeout' => env('SHAREPOINT_COPY_MONITOR_TIMEOUT', 300),
'copy_monitor_interval_ms' => env('SHAREPOINT_COPY_MONITOR_INTERVAL_MS', 1000),
'throw' => false,
],
],
🚀 Usage
Basic Operations
use Illuminate\Support\Facades\Storage;
// Write a file
Storage::disk('sharepoint')->put('documents/report.pdf', $contents);
// Write from a stream (memory efficient for large files)
$stream = fopen('/path/to/large-file.zip', 'r');
Storage::disk('sharepoint')->writeStream('backups/large-file.zip', $stream);
// Read a file
$contents = Storage::disk('sharepoint')->get('documents/report.pdf');
// Read as stream
$stream = Storage::disk('sharepoint')->readStream('documents/report.pdf');
// Check if file exists
if (Storage::disk('sharepoint')->exists('documents/report.pdf')) {
// File exists
}
// Delete a file
Storage::disk('sharepoint')->delete('documents/report.pdf');
// Delete multiple files
Storage::disk('sharepoint')->delete(['file1.pdf', 'file2.pdf']);
// Copy a file
Storage::disk('sharepoint')->copy('old.pdf', 'new.pdf');
// Move a file
Storage::disk('sharepoint')->move('old-location.pdf', 'new-location.pdf');
Directory Operations
// Create a directory
Storage::disk('sharepoint')->makeDirectory('documents/2024');
// List files in a directory
$files = Storage::disk('sharepoint')->files('documents');
// List all files recursively
$files = Storage::disk('sharepoint')->allFiles('documents');
// List directories
$directories = Storage::disk('sharepoint')->directories('documents');
// List all directories recursively
$directories = Storage::disk('sharepoint')->allDirectories('documents');
// Delete a directory
Storage::disk('sharepoint')->deleteDirectory('old-documents');
File Metadata
// Get file size
$size = Storage::disk('sharepoint')->size('documents/report.pdf');
// Get last modified time
$timestamp = Storage::disk('sharepoint')->lastModified('documents/report.pdf');
// Get MIME type
$mimeType = Storage::disk('sharepoint')->mimeType('documents/report.pdf');
URLs & Downloads
// Store an uploaded file
$path = $request->file('document')->store('uploads', 'sharepoint');
// Download a file
return Storage::disk('sharepoint')->download('documents/report.pdf');
// Download with custom name
return Storage::disk('sharepoint')->download('documents/report.pdf', 'custom-name.pdf');
🔄 Using with Spatie Laravel Backup
Perfect integration with Spatie Laravel Backup:
// config/backup.php
'destination' => [
'disks' => [
'local',
'sharepoint', // Add SharePoint as backup destination
],
],
Run backups:
# Full backup (database + files)
php artisan backup:run
# Database only
php artisan backup:run --only-db
# List backups
php artisan backup:list
# Clean old backups
php artisan backup:clean
🔧 Advanced Configuration
Multiple SharePoint Sites
'disks' => [
'sharepoint-hr' => [
'driver' => 'sharepoint',
'client_id' => env('GRAPH_CLIENT_ID'),
'client_secret' => env('GRAPH_CLIENT_SECRET'),
'tenant_id' => env('GRAPH_TENANT_ID'),
'drive_id' => 'hr-drive-id',
'prefix' => 'employee-files',
],
'sharepoint-finance' => [
'driver' => 'sharepoint',
'client_id' => env('GRAPH_CLIENT_ID'),
'client_secret' => env('GRAPH_CLIENT_SECRET'),
'tenant_id' => env('GRAPH_TENANT_ID'),
'drive_id' => 'finance-drive-id',
'prefix' => 'reports',
],
],
Using OneDrive
The package also supports OneDrive for Business. Use the onedrive driver:
'onedrive' => [
'driver' => 'onedrive',
'client_id' => env('GRAPH_CLIENT_ID'),
'client_secret' => env('GRAPH_CLIENT_SECRET'),
'tenant_id' => env('GRAPH_TENANT_ID'),
'prefix' => env('ONEDRIVE_PREFIX', ''),
],
Token Caching
Access tokens are automatically cached for 58 minutes (tokens typically expire in 60 minutes). The cache key is unique per disk configuration, allowing multiple SharePoint/OneDrive connections with independent token management.
Copy Monitoring
Microsoft Graph copy operations run asynchronously. This package waits for Graph's copy monitor to report completion before copy() returns. Because move() uses copy followed by delete, the source file is only deleted after the copy is confirmed complete.
You can tune the monitor wait behavior per disk:
'copy_monitor_timeout' => 300, // seconds
'copy_monitor_interval_ms' => 1000, // milliseconds
🐛 Troubleshooting
Permission Errors
Error: "Access denied" or "403 Forbidden"
Solutions:
- Verify
Files.ReadWrite.AllandSites.ReadWrite.Allpermissions are added - Ensure admin consent is granted (look for green checkmarks in Azure Portal)
- Wait 2-5 minutes after granting consent for changes to propagate
- Clear Laravel cache:
php artisan cache:clear
Authentication Errors
Error: "Failed to obtain access token" or "invalid_client"
Solutions:
- Verify
GRAPH_CLIENT_IDmatches your app registration's Application ID - Verify
GRAPH_CLIENT_SECRETis correct (they expire!) - Check
GRAPH_TENANT_IDmatches your Directory (tenant) ID - Ensure no extra spaces in your
.envfile
Drive Not Found
Error: "itemNotFound" or "Resource not found"
Solutions:
- Verify
SHAREPOINT_DRIVE_IDis correct - Try removing
drive_idto use the default OneDrive - Ensure the app has access to the specified drive
- Check the drive exists and hasn't been deleted
Timeout Issues
Error: Timeouts when uploading large files
Solutions:
- The package automatically sets a 5-minute timeout for file operations
- For files > 250MB, consider using Microsoft's resumable upload API
- Check your PHP
max_execution_timeandmemory_limitsettings
Clear Token Cache
If you're experiencing authentication issues:
php artisan cache:clear
Or clear specific SharePoint tokens:
php artisan cache:forget sharepoint_access_token_*
🧪 Testing Connection
Test your SharePoint connection:
use Illuminate\Support\Facades\Storage;
Route::get('/test-sharepoint', function () {
try {
// Create a test file
$testContent = 'Test file created at ' . now();
Storage::disk('sharepoint')->put('test.txt', $testContent);
// Verify it exists
if (!Storage::disk('sharepoint')->exists('test.txt')) {
return 'File creation failed!';
}
// Read it back
$content = Storage::disk('sharepoint')->get('test.txt');
// Clean up
Storage::disk('sharepoint')->delete('test.txt');
return 'SharePoint connection successful! Content: ' . $content;
} catch (\Exception $e) {
return 'Connection failed: ' . $e->getMessage();
}
});
🔐 Security Best Practices
- Never commit credentials - Keep
.envin.gitignore - Use environment-specific apps - Separate Azure apps for dev/staging/production
- Rotate secrets regularly - Set expiration dates on client secrets in Azure
- Monitor access logs - Review app activity in Azure Portal regularly
- Principle of least privilege - Only grant necessary permissions
- Secure your
.env- Restrict file permissions:chmod 600 .env
📚 API Reference
Supported Flysystem Operations
| Method | Supported | Notes |
|---|---|---|
write() |
✅ | Write file contents |
writeStream() |
✅ | Write from stream (memory efficient) |
read() |
✅ | Read file contents |
readStream() |
✅ | Read as stream |
delete() |
✅ | Delete file |
deleteDirectory() |
✅ | Delete directory and contents |
createDirectory() |
✅ | Create directory |
fileExists() |
✅ | Check if file exists |
directoryExists() |
✅ | Check if directory exists |
listContents() |
✅ | List directory contents with Graph pagination |
move() |
✅ | Move/rename file after monitored copy completion |
copy() |
✅ | Copy file with Graph monitor polling |
lastModified() |
✅ | Get last modified timestamp |
fileSize() |
✅ | Get file size |
mimeType() |
✅ | Get MIME type |
visibility() |
❌ | Not supported by SharePoint/OneDrive |
setVisibility() |
❌ | Not supported by SharePoint/OneDrive |
🤝 Contributing
Contributions are welcome! Please see CONTRIBUTING.md for details.
Development Setup
# Clone the repository
git clone https://github.com/sahablibya/laravel-sharepoint-filesystem.git
cd laravel-sharepoint-filesystem
# Install dependencies
composer install
# Run tests
composer test
📝 Changelog
Please see CHANGELOG.md for recent changes.
📄 License
This package is open-sourced software licensed under the MIT license.
💡 Credits
- Developed by SahabLibya Development Team
- Built on Flysystem by Frank de Jonge
- Powered by Microsoft Graph API
🙏 Acknowledgments
Special thanks to:
- The Laravel community for inspiration and best practices
- Microsoft for the comprehensive Graph API
- All contributors who help improve this package
📞 Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: dev@sahablibya.ly
Made with ❤️ by SahabLibya Development Team