PHP SDK for Dropbox API v2 with Laravel 8-12 support. Provides a type-safe interface for working with Dropbox storage, file sharing, and collaboration from PHP 8.1+ applications. Can be used standalone or as a Laravel package with auto-registered service provider and facade.
The package covers all major Dropbox API v2 endpoints: files, sharing, users, file requests, and Paper. It handles OAuth 2.0 flows, chunked uploads for large files, and batch operations. Error handling is built around a dedicated exception class with access to Dropbox error details.
Laravel users get a service provider, facade, and config publishing out of the box. Everyone else can use DropboxClient directly — no framework required.
🌐 Language: English | Русский
- Features
- Supported Endpoints
- What's Inside
- Requirements
- Installation
- Quick Start
- Detailed Usage Examples
- OAuth 2.0 Authorization
- Laravel Usage
- Error Handling
- Advanced Examples
- Package Structure
- Testing
- Contributing
- Changelog
- License
- ✅ Dropbox API v2 — all major endpoints covered
- 🚀 Laravel 8–12 — service provider, facade, config publishing
- 🎯 PHP 8.1+ — typed properties, enums, named arguments
- 📦 Standalone — works without any framework
- 🔐 OAuth 2.0 — authorization URL, token exchange, refresh
- 📝 Documentation — usage examples and API reference
- 🧪 Tests — PHPUnit test suite included
- 🎨 Clean API —
$client->files->upload(...)style - ⚡ Chunked Upload — large files uploaded in configurable chunks
- 🔄 Batch Operations — copy, move, delete up to 1000 files per call
- Files - Upload, download, move, copy, delete, search, and manage files/folders
- Sharing - Create shared links, manage folder/file sharing, collaborate with team members
- Users - Get account information, space usage, and user features
- File Requests - Create and manage file request forms
- Paper - Create, edit, and manage Dropbox Paper documents
- Check - API connectivity and health checks
- PHP 8.1 or higher
- Guzzle HTTP client 7.0+
- Laravel 8.0+ (optional, for Laravel integration)
Install the package via Composer:
composer require tigusigalpa/dropbox-phpThe package will automatically register its service provider and facade.
Publish the configuration file:
php artisan vendor:publish --tag=dropbox-configAdd your Dropbox credentials to your .env file:
DROPBOX_ACCESS_TOKEN=your_access_token_here
DROPBOX_APP_KEY=your_app_key
DROPBOX_APP_SECRET=your_app_secret
DROPBOX_REDIRECT_URI=https://your-app.com/callback- Create a Dropbox app at Dropbox App Console
- Choose your app type and permissions
- Generate an access token from the app settings page
For production applications, implement the OAuth 2.0 flow (see OAuth 2.0 Authorization section).
use Tigusigalpa\Dropbox\DropboxClient;
$client = new DropboxClient('your_access_token');
// Get current user account info
$account = $client->users->getCurrentAccount();
echo "Hello, " . $account['name']['display_name'];
// Upload a file
$result = $client->files->upload(
'/Documents/hello.txt',
'Hello, Dropbox!',
'add'
);
// List folder contents
$contents = $client->files->listFolder('/Documents');
foreach ($contents['entries'] as $entry) {
echo $entry['name'] . "\n";
}
// Download a file
$file = $client->files->download('/Documents/hello.txt');
file_put_contents('local-hello.txt', $file['content']);
// Create a shared link
$link = $client->sharing->createSharedLinkWithSettings('/Documents/hello.txt');
echo "Share this link: " . $link['url'];use Tigusigalpa\Dropbox\Facades\Dropbox;
// Using Facade
$account = Dropbox::users()->getCurrentAccount();
// Using Dependency Injection
use Tigusigalpa\Dropbox\DropboxClient;
class FileController extends Controller
{
public function upload(Request $request, DropboxClient $dropbox)
{
$content = file_get_contents($request->file('document')->path());
$result = $dropbox->files->upload(
'/uploads/' . $request->file('document')->getClientOriginalName(),
$content
);
return response()->json($result);
}
}// Simple upload
$client->files->upload('/path/to/file.txt', 'File content');
// Upload with options
$client->files->upload(
'/path/to/file.txt',
$content,
'overwrite', // mode: 'add', 'overwrite', or 'update'
true, // autorename if conflict
false, // mute notifications
false // strict conflict checking
);
// Upload from local file
$content = file_get_contents('/local/path/file.pdf');
$client->files->upload('/Dropbox/file.pdf', $content);
// Upload with metadata
$result = $client->files->upload(
'/Documents/report.pdf',
file_get_contents('local-report.pdf'),
'add',
false,
false,
false
);
echo "Uploaded file: {$result['name']}, size: {$result['size']} bytes";// Start upload session
$session = $client->files->uploadSessionStart($firstChunk, false);
$sessionId = $session['session_id'];
// Append chunks
$offset = strlen($firstChunk);
$client->files->uploadSessionAppend($sessionId, $offset, $secondChunk, false);
// Finish upload
$offset += strlen($secondChunk);
$result = $client->files->uploadSessionFinish(
$sessionId,
$offset,
$lastChunk,
['path' => '/large-file.zip', 'mode' => 'add']
);
// Complete example for uploading a large file
$filePath = '/path/to/large-video.mp4';
$chunkSize = 4 * 1024 * 1024; // 4MB chunks
$file = fopen($filePath, 'rb');
// First chunk
$firstChunk = fread($file, $chunkSize);
$session = $client->files->uploadSessionStart($firstChunk, false);
$offset = strlen($firstChunk);
// Remaining chunks
while (!feof($file)) {
$chunk = fread($file, $chunkSize);
$isLast = feof($file);
if ($isLast) {
// Last chunk - finish session
$client->files->uploadSessionFinish(
$session['session_id'],
$offset,
$chunk,
['path' => '/Videos/large-video.mp4', 'mode' => 'add']
);
} else {
// Intermediate chunk
$client->files->uploadSessionAppend(
$session['session_id'],
$offset,
$chunk,
false
);
$offset += strlen($chunk);
}
}
fclose($file);// Download file
$file = $client->files->download('/Documents/report.pdf');
file_put_contents('local-report.pdf', $file['content']);
echo "Downloaded file size: " . strlen($file['content']) . " bytes";
// Download specific revision
$file = $client->files->download('/Documents/report.pdf', 'rev123abc');
// Download folder as ZIP
$zip = $client->files->downloadZip('/Documents/Project');
file_put_contents('project.zip', $zip['content']);
// Get temporary download link (valid for 4 hours)
$link = $client->files->getTemporaryLink('/Documents/report.pdf');
echo "Temporary link: " . $link['link'];
// Export file (e.g., Google Docs to PDF)
$exported = $client->files->export('/Documents/google-doc.gdoc', 'pdf');
file_put_contents('exported.pdf', $exported['content']);// Create folder
$folder = $client->files->createFolder('/Projects/NewProject');
echo "Created folder: " . $folder['metadata']['path_display'];
// Create multiple folders at once
$result = $client->files->createFolderBatch([
'/Projects/Project1',
'/Projects/Project2',
'/Projects/Project3'
], false, false);
// Move file/folder
$moved = $client->files->move('/old/path.txt', '/new/path.txt');
echo "Moved to: " . $moved['metadata']['path_display'];
// Copy file/folder
$copied = $client->files->copy('/source.txt', '/destination.txt');
// Delete file/folder (to trash)
$client->files->delete('/path/to/delete.txt');
// Permanently delete (bypass trash)
$client->files->permanentlyDelete('/path/to/file.txt');
// Restore file to previous revision
$restored = $client->files->restore('/path/to/file.txt', 'rev123abc');
// Get file revision history
$revisions = $client->files->listRevisions('/Documents/important.docx', 'path', 100);
foreach ($revisions['entries'] as $rev) {
echo "Revision: {$rev['rev']}, date: {$rev['client_modified']}\n";
}// List folder contents
$result = $client->files->listFolder('/Documents');
foreach ($result['entries'] as $entry) {
if ($entry['.tag'] === 'folder') {
echo "Folder: " . $entry['name'] . "\n";
} else {
echo "File: " . $entry['name'] . " (" . $entry['size'] . " bytes)\n";
echo " Modified: " . $entry['client_modified'] . "\n";
}
}
// List recursively
$result = $client->files->listFolder('', true);
// Continue listing with cursor (pagination)
if ($result['has_more']) {
$more = $client->files->listFolderContinue($result['cursor']);
}
// Complete listing of large folder with pagination
$allEntries = [];
$result = $client->files->listFolder('/BigFolder');
$allEntries = array_merge($allEntries, $result['entries']);
while ($result['has_more']) {
$result = $client->files->listFolderContinue($result['cursor']);
$allEntries = array_merge($allEntries, $result['entries']);
}
echo "Total items: " . count($allEntries);
// Search files
$results = $client->files->search(
'invoice', // query
'/Documents', // path
100, // max results
'relevance', // order by
'active', // file status
null, // filename only
['pdf', 'docx'], // file extensions
['documents'] // file categories
);
foreach ($results['matches'] as $match) {
$file = $match['metadata']['metadata'];
echo "Found: {$file['name']} in {$file['path_display']}\n";
}
// Continue search with cursor
if ($results['has_more']) {
$moreResults = $client->files->searchContinue($results['cursor']);
}
// Get file metadata
$metadata = $client->files->getMetadata('/Documents/file.txt', true);
echo "Modified: " . $metadata['client_modified'];
echo "Size: " . $metadata['size'] . " bytes";
echo "ID: " . $metadata['id'];
// Get metadata with media info
$metadata = $client->files->getMetadata('/Photos/vacation.jpg', true);
if (isset($metadata['media_info'])) {
echo "Dimensions: {$metadata['media_info']['metadata']['dimensions']['width']}x";
echo "{$metadata['media_info']['metadata']['dimensions']['height']}";
}// Get thumbnail
$thumb = $client->files->getThumbnail(
'/Photos/vacation.jpg',
'jpeg', // format: 'jpeg' or 'png'
'w256h256', // size: w32h32, w64h64, w128h128, w256h256, w480h320, w640h480, w960h640, w1024h768, w2048h1536
'strict' // mode: 'strict', 'bestfit', 'fitone_bestfit'
);
file_put_contents('thumb.jpg', $thumb['content']);
// Get preview
$preview = $client->files->getPreview('/Documents/presentation.pptx');
file_put_contents('preview.pdf', $preview['content']);
// Get thumbnails in batch
$thumbs = $client->files->getThumbnailBatch([
[
'path' => '/Photos/img1.jpg',
'format' => 'jpeg',
'size' => 'w128h128',
'mode' => 'strict'
],
[
'path' => '/Photos/img2.jpg',
'format' => 'jpeg',
'size' => 'w128h128',
'mode' => 'strict'
],
]);
foreach ($thumbs['entries'] as $entry) {
if ($entry['.tag'] === 'success') {
$thumbnail = $entry['thumbnail'];
file_put_contents('thumb_' . basename($entry['metadata']['name']), $thumbnail);
}
}// Create shared link
$link = $client->sharing->createSharedLinkWithSettings('/Documents/report.pdf', [
'requested_visibility' => ['.tag' => 'public'],
'audience' => ['.tag' => 'public'],
'access' => ['.tag' => 'viewer'],
]);
echo "Share URL: " . $link['url'];
// Create link with password and expiration
$link = $client->sharing->createSharedLinkWithSettings('/Documents/secret.pdf', [
'link_password' => 'mypassword123',
'expires' => '2024-12-31T23:59:59Z',
]);
// List all shared links
$links = $client->sharing->listSharedLinks();
// Get shared link metadata
$metadata = $client->sharing->getSharedLinkMetadata('https://www.dropbox.com/...');
// Modify shared link settings
$updated = $client->sharing->modifySharedLinkSettings(
'https://www.dropbox.com/...',
['requested_visibility' => ['.tag' => 'password']]
);
// Revoke shared link
$client->sharing->revokeSharedLink('https://www.dropbox.com/...');// Share a folder
$shared = $client->sharing->shareFolder('/Projects/TeamProject', null, false);
$folderId = $shared['shared_folder_id'];
// Add members to shared folder
$client->sharing->addFolderMember($folderId, [
[
'member' => ['.tag' => 'email', 'email' => '[email protected]'],
'access_level' => ['.tag' => 'editor'],
],
], false, 'Please review this project');
// List folder members
$members = $client->sharing->listFolderMembers($folderId);
// Update member permissions
$client->sharing->updateFolderMember(
$folderId,
['.tag' => 'email', 'email' => '[email protected]'],
'viewer'
);
// Remove folder member
$client->sharing->removeFolderMember(
$folderId,
['.tag' => 'email', 'email' => '[email protected]'],
true // leave a copy
);
// List shared folders
$folders = $client->sharing->listFolders(100);
// Mount shared folder
$client->sharing->mountFolder($folderId);
// Unmount shared folder
$client->sharing->unmountFolder($folderId);
// Unshare folder
$client->sharing->unshareFolder($folderId, false);// Add file members
$client->sharing->addFileMember(
'/Documents/contract.pdf',
[
[
'member' => ['.tag' => 'email', 'email' => '[email protected]'],
'access_level' => ['.tag' => 'viewer'],
],
],
'Please review and sign',
false,
'viewer'
);
// List file members
$members = $client->sharing->listFileMembers('/Documents/contract.pdf');
// Remove file member
$client->sharing->removeFileMember(
'/Documents/contract.pdf',
['.tag' => 'email', 'email' => '[email protected]']
);Convert Dropbox shared links to direct links for embedding images in HTML, Markdown, or other content. This is perfect for hosting images for blogs, portfolios, or documentation.
// Convert existing shared link to direct link
$sharedLink = 'https://www.dropbox.com/s/abcd1234/image.jpg?dl=0';
// Method 1: Using userusercontent (recommended - cleaner URLs)
$directLink = $client->sharing->convertToDirectLink($sharedLink);
// Returns: https://dl.dropboxusercontent.com/s/abcd1234/image.jpg
// Method 2: Using raw parameter
$directLink = $client->sharing->convertToDirectLink($sharedLink, 'raw');
// Returns: https://www.dropbox.com/s/abcd1234/image.jpg?raw=1
// Create a new shared link and convert it immediately
$result = $client->sharing->createDirectLink('/Photos/vacation.jpg');
echo "Direct URL: " . $result['direct_url'];
// Use in HTML
echo '<img src="' . $result['direct_url'] . '" alt="Vacation">';
// Use in Markdown
echo '';
// Create password-protected direct link with expiration
$result = $client->sharing->createDirectLink(
'/Photos/private.jpg',
[
'link_password' => 'mypassword123',
'expires' => '2024-12-31T23:59:59Z',
]
);
// Batch convert multiple links
$links = [
'https://www.dropbox.com/s/abc123/img1.jpg?dl=0',
'https://www.dropbox.com/s/def456/img2.png?dl=0',
];
foreach ($links as $link) {
$directLink = $client->sharing->convertToDirectLink($link);
echo $directLink . "\n";
}
// Create image gallery
$photos = $client->files->listFolder('/Photos');
$gallery = [];
foreach ($photos['entries'] as $photo) {
if ($photo['.tag'] === 'file' && preg_match('/\.(jpg|jpeg|png|gif)$/i', $photo['name'])) {
$linkData = $client->sharing->createDirectLink($photo['path_display']);
$gallery[] = [
'name' => $photo['name'],
'url' => $linkData['direct_url'],
];
}
}
// Display gallery
foreach ($gallery as $image) {
echo '<img src="' . $image['url'] . '" alt="' . $image['name'] . '">' . "\n";
}Laravel Example:
use Tigusigalpa\Dropbox\Facades\Dropbox;
// Convert link
$directLink = Dropbox::sharing()->convertToDirectLink($sharedLink);
// Upload and get direct link
$file = $request->file('image');
$content = file_get_contents($file->getRealPath());
Dropbox::files()->upload('/Images/' . $file->getClientOriginalName(), $content);
$result = Dropbox::sharing()->createDirectLink('/Images/' . $file->getClientOriginalName());
return response()->json(['url' => $result['direct_url']]);Benefits:
- ✅ No need for external image hosting services
- ✅ Direct embedding in HTML, Markdown, forums, etc.
- ✅ Reliable CDN delivery via Dropbox infrastructure
- ✅ Support for password protection and expiration dates
- ✅ Two conversion methods for different use cases
// Get current account info
$account = $client->users->getCurrentAccount();
echo "Account ID: " . $account['account_id'];
echo "Name: " . $account['name']['display_name'];
echo "Email: " . $account['email'];
echo "Country: " . $account['country'];
// Get another user's account info
$user = $client->users->getAccount('dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc');
// Get multiple users' info
$users = $client->users->getAccountBatch([
'dbid:AAH4f99T0taONIb-OurWxbNQ6ywGRopQngc',
'dbid:AAH1234567890abcdefghijklmnopqrst',
]);
// Get space usage
$space = $client->users->getSpaceUsage();
echo "Used: " . $space['used'] . " bytes\n";
echo "Allocated: " . $space['allocation']['allocated'] . " bytes\n";
$percentage = ($space['used'] / $space['allocation']['allocated']) * 100;
echo "Usage: " . number_format($percentage, 2) . "%\n";
// Check feature availability
$features = $client->users->getFeaturesValues([
'paper_as_files',
'file_locking',
]);// Create file request
$request = $client->fileRequests->create(
'Upload your documents',
'/File Requests/Documents',
'2024-12-31T23:59:59Z', // deadline
true, // open
'Please upload all required documents for the application'
);
echo "File request URL: " . $request['url'];
// Get file request
$request = $client->fileRequests->get('oaCAVmEyrqYnkZX9955Y');
// List all file requests
$requests = $client->fileRequests->list(1000);
// Update file request
$updated = $client->fileRequests->update('oaCAVmEyrqYnkZX9955Y', [
'title' => 'Updated Title',
'open' => false,
]);
// Delete file requests
$client->fileRequests->delete(['oaCAVmEyrqYnkZX9955Y']);
// Delete all closed file requests
$client->fileRequests->deleteAllClosed();
// Count file requests
$count = $client->fileRequests->count();
echo "Total file requests: " . $count['file_request_count'];// Create Paper document
$doc = $client->paper->docsCreate(
'<h1>Meeting Notes</h1><p>Discussion points...</p>',
'html'
);
$docId = $doc['doc_id'];
// Download Paper document
$content = $client->paper->docsDownload($docId, 'markdown');
file_put_contents('notes.md', $content['content']);
// Update Paper document
$client->paper->docsUpdate(
$docId,
'<h1>Updated Notes</h1><p>New content...</p>',
'html',
'append',
1
);
// Get Paper document metadata
$metadata = $client->paper->docsGetMetadata($docId);
// List Paper documents
$docs = $client->paper->docsList('docs_accessed', 'modified', 'descending', 100);
// Share Paper document
$client->paper->docsUsersAdd($docId, [
[
'member' => ['.tag' => 'email', 'email' => '[email protected]'],
'permission_level' => ['.tag' => 'edit'],
],
]);
// List users with access
$users = $client->paper->docsUsersList($docId, 100);
// Remove users
$client->paper->docsUsersRemove($docId, [
['.tag' => 'email', 'email' => '[email protected]'],
]);
// Delete Paper document
$client->paper->docsPermanentlyDelete($docId);// Copy multiple files
$job = $client->files->copyBatch([
['from_path' => '/file1.txt', 'to_path' => '/backup/file1.txt'],
['from_path' => '/file2.txt', 'to_path' => '/backup/file2.txt'],
]);
// Check batch job status
$status = $client->files->copyBatchCheck($job['async_job_id']);
// Move multiple files
$job = $client->files->moveBatch([
['from_path' => '/old/file1.txt', 'to_path' => '/new/file1.txt'],
['from_path' => '/old/file2.txt', 'to_path' => '/new/file2.txt'],
]);
// Delete multiple files
$job = $client->files->deleteBatch(['/file1.txt', '/file2.txt', '/file3.txt']);// Save file from URL
$job = $client->files->saveUrl('/Downloads/image.jpg', 'https://example.com/image.jpg');
// Check save URL job status
$status = $client->files->saveUrlCheckJobStatus($job['async_job_id']);
if ($status['.tag'] === 'complete') {
echo "File saved successfully!";
}use Tigusigalpa\Dropbox\DropboxClient;
// Generate authorization URL
$authUrl = DropboxClient::getAuthorizationUrl(
'your_app_key',
'https://your-app.com/callback',
'random_state_string', // CSRF protection
['files.content.write', 'files.content.read'] // optional scopes
);
// Redirect user to authorization URL
header('Location: ' . $authUrl);// In your callback route
$code = $_GET['code'];
$state = $_GET['state'];
// Verify state parameter (CSRF protection)
if ($state !== $_SESSION['oauth_state']) {
die('Invalid state parameter');
}
// Exchange code for access token
$tokenData = DropboxClient::getAccessToken(
$code,
'your_app_key',
'your_app_secret',
'https://your-app.com/callback'
);
// Store tokens securely
$accessToken = $tokenData['access_token'];
$refreshToken = $tokenData['refresh_token'] ?? null;
// Create client with new token
$client = new DropboxClient($accessToken);// routes/web.php
Route::get('/dropbox/auth', [DropboxController::class, 'redirectToDropbox']);
Route::get('/dropbox/callback', [DropboxController::class, 'handleCallback']);
// app/Http/Controllers/DropboxController.php
use Tigusigalpa\Dropbox\DropboxClient;
class DropboxController extends Controller
{
public function redirectToDropbox()
{
$state = Str::random(40);
session(['dropbox_state' => $state]);
$url = DropboxClient::getAuthorizationUrl(
config('dropbox.app_key'),
config('dropbox.redirect_uri'),
$state
);
return redirect($url);
}
public function handleCallback(Request $request)
{
if ($request->state !== session('dropbox_state')) {
abort(403, 'Invalid state');
}
$tokenData = DropboxClient::getAccessToken(
$request->code,
config('dropbox.app_key'),
config('dropbox.app_secret'),
config('dropbox.redirect_uri')
);
// Store tokens for the user
auth()->user()->update([
'dropbox_access_token' => encrypt($tokenData['access_token']),
'dropbox_refresh_token' => encrypt($tokenData['refresh_token'] ?? null),
]);
return redirect('/dashboard')->with('success', 'Dropbox connected!');
}
}// Refresh access token when expired
$newTokenData = DropboxClient::refreshAccessToken(
$refreshToken,
'your_app_key',
'your_app_secret'
);
$newAccessToken = $newTokenData['access_token'];
// Update client token
$client->setAccessToken($newAccessToken);use Tigusigalpa\Dropbox\Exceptions\DropboxException;
try {
$result = $client->files->upload('/test.txt', 'content');
} catch (DropboxException $e) {
echo "Error: " . $e->getMessage() . "\n";
echo "Status Code: " . $e->getCode() . "\n";
// Get detailed error information
$response = $e->getResponse();
if ($response) {
echo "Error Summary: " . $e->getErrorSummary() . "\n";
echo "Error Tag: " . $e->getErrorTag() . "\n";
print_r($response);
}
}use GuzzleHttp\Client as GuzzleClient;
use Tigusigalpa\Dropbox\DropboxClient;
// Create custom Guzzle client
$guzzle = new GuzzleClient([
'timeout' => 60,
'verify' => true,
'proxy' => 'http://proxy.example.com:8080',
]);
// Note: Currently the package creates its own Guzzle instance
// For custom configuration, you may need to extend the DropboxClient class// List all files in a large folder
$cursor = null;
$allFiles = [];
do {
if ($cursor === null) {
$result = $client->files->listFolder('/LargeFolder');
} else {
$result = $client->files->listFolderContinue($cursor);
}
$allFiles = array_merge($allFiles, $result['entries']);
$cursor = $result['cursor'];
} while ($result['has_more']);
echo "Total files: " . count($allFiles);// Get initial cursor
$cursor = $client->files->listFolderGetLatestCursor('/MonitoredFolder', true);
$cursorValue = $cursor['cursor'];
// Later, check for changes
$changes = $client->files->listFolderLongpoll($cursorValue, 30);
if ($changes['changes']) {
// Get the actual changes
$result = $client->files->listFolderContinue($cursorValue);
foreach ($result['entries'] as $entry) {
echo "Changed: " . $entry['name'] . "\n";
}
}- Chunked uploads — large files are split into chunks (default 4MB, configurable) to avoid memory and timeout issues
- Batch endpoints — copy, move, delete up to 1000 files in a single API call
- Persistent connections — Guzzle keeps connections alive between requests
- Stream-based downloads — files are not loaded entirely into memory
// Cache folder listings to reduce API calls
$cacheKey = 'dropbox_folder_' . md5($path);
$contents = Cache::remember($cacheKey, 3600, function() use ($client, $path) {
return $client->files->listFolder($path);
});
// Cache shared links
$linkCache = Cache::remember('dropbox_link_' . $fileId, 86400, function() use ($client, $path) {
return $client->sharing->createSharedLinkWithSettings($path);
});Dropbox API has rate limits. When you hit them, the SDK throws an exception with code 429:
try {
$result = $client->files->upload($path, $content);
} catch (DropboxException $e) {
if ($e->getCode() === 429) {
// Rate limited - wait and retry
$retryAfter = $e->getResponse()['retry_after'] ?? 60;
sleep($retryAfter);
$result = $client->files->upload($path, $content);
}
}Run the test suite:
composer testRun tests with coverage:
composer test:coverageFor complete API documentation, visit:
// Backup local files to Dropbox
$backupFolder = '/Backups/' . date('Y-m-d');
$client->files->createFolder($backupFolder);
$files = glob('/var/www/app/storage/backups/*.sql');
foreach ($files as $file) {
$content = file_get_contents($file);
$client->files->upload(
$backupFolder . '/' . basename($file),
$content
);
}// Sync local folder with Dropbox
$localPath = '/local/documents';
$remotePath = '/Documents';
$localFiles = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($localPath)
);
foreach ($localFiles as $file) {
if ($file->isFile()) {
$relativePath = str_replace($localPath, '', $file->getPathname());
$content = file_get_contents($file->getPathname());
$client->files->upload(
$remotePath . $relativePath,
$content,
'overwrite'
);
}
}// Create image gallery with thumbnails
$photos = $client->files->listFolder('/Photos');
foreach ($photos['entries'] as $photo) {
if ($photo['.tag'] === 'file') {
// Get thumbnail
$thumb = $client->files->getThumbnail(
$photo['path_display'],
'jpeg',
'w256h256'
);
// Save thumbnail
file_put_contents(
'thumbs/' . $photo['name'],
$thumb['content']
);
// Create shared link for full image
$link = $client->sharing->createSharedLinkWithSettings(
$photo['path_display']
);
echo '<img src="thumbs/' . $photo['name'] . '" data-full="' . $link['url'] . '">';
}
}dropbox-php/
├── config/
│ └── dropbox.php # Laravel configuration
├── examples/
│ ├── basic-usage.php # Standalone PHP usage examples
│ ├── laravel-usage.php # Laravel integration examples
│ └── oauth-flow.php # OAuth 2.0 flow implementation
├── src/
│ ├── Endpoints/ # API endpoint implementations
│ │ ├── Check.php # API health checks
│ │ ├── FileRequests.php # File request operations
│ │ ├── Files.php # File/folder operations (40+ methods)
│ │ ├── Paper.php # Dropbox Paper operations
│ │ ├── Sharing.php # Sharing and collaboration (30+ methods)
│ │ └── Users.php # User account operations
│ ├── Exceptions/
│ │ └── DropboxException.php # Custom exception class
│ ├── Facades/
│ │ └── Dropbox.php # Laravel facade
│ ├── DropboxClient.php # Main client class
│ └── DropboxServiceProvider.php # Laravel service provider
└── tests/ # PHPUnit tests
Standalone PHP:
$client = new DropboxClient($accessToken);
$result = $client->files->upload('/path/file.txt', $content);Laravel Facade:
use Tigusigalpa\Dropbox\Facades\Dropbox;
$result = Dropbox::files()->upload('/path/file.txt', $content);Laravel Dependency Injection:
public function upload(DropboxClient $dropbox) {
$result = $dropbox->files->upload('/path/file.txt', $content);
}The package ships with:
- Unit tests for core functionality
- Integration test examples
- GitHub Actions CI/CD workflow
- PHPUnit configuration
Run tests:
composer testRun tests with coverage:
composer test:coverage-
Fork the repository
-
Clone your fork:
git clone https://github.com/YOUR_USERNAME/dropbox-php.git cd dropbox-php -
Install dependencies:
composer install
-
Create a
.envfile with your Dropbox credentials for testing:DROPBOX_ACCESS_TOKEN=your_test_token
Contributions are welcome. Please follow these guidelines:
- Follow PSR-12 coding standards
- Write clear, descriptive commit messages
- Add PHPDoc blocks for all public methods
- Use type hints for parameters and return types
- Keep methods focused and single-purpose
-
Create a new branch for your feature:
git checkout -b feature/your-feature-name
-
Make your changes and commit:
git commit -m "Add feature: description" -
Push to your fork:
git push origin feature/your-feature-name
-
Create a Pull Request on GitHub
-
Ensure all tests pass and code follows standards
When adding new Dropbox API endpoints:
- Create or update the appropriate endpoint class in
src/Endpoints/ - Add comprehensive PHPDoc comments
- Include links to official Dropbox API documentation
- Add usage examples to README.md
- Write tests for the new functionality
- Use the GitHub issue tracker
- Include PHP version, Laravel version (if applicable)
- Provide code examples that reproduce the issue
- Include error messages and stack traces
Problem: "Invalid access token" error
Solutions:
- Verify your access token is correct and not expired
- For OAuth tokens, implement refresh token logic
- Check that your app has the required permissions/scopes
- Ensure the token hasn't been revoked in Dropbox App Console
// Check token validity
try {
$account = $client->users->getCurrentAccount();
echo "Token is valid for: " . $account['email'];
} catch (DropboxException $e) {
if ($e->getCode() === 401) {
// Token invalid - need to refresh or re-authenticate
$newToken = DropboxClient::refreshAccessToken($refreshToken, $appKey, $appSecret);
}
}Problem: Upload fails for large files or times out
Solutions:
- Use chunked upload for files larger than 150MB
- Increase PHP
max_execution_timeandmemory_limit - Implement retry logic for network failures
- Check file path format (must start with /)
// Robust upload with retry
$maxRetries = 3;
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$result = $client->files->upload($path, $content);
break;
} catch (DropboxException $e) {
$attempt++;
if ($attempt >= $maxRetries) throw $e;
sleep(2 ** $attempt); // Exponential backoff
}
}Problem: "Path not found" or "Malformed path" errors
Solutions:
- Ensure paths start with
/(e.g.,/Documents/file.txt) - Use proper encoding for special characters
- Check that parent folders exist before creating files
- Verify case sensitivity (Dropbox paths are case-insensitive but case-preserving)
// Ensure parent folder exists
$filePath = '/Documents/Reports/2024/report.pdf';
$parentPath = dirname($filePath);
try {
$client->files->getMetadata($parentPath);
} catch (DropboxException $e) {
// Parent doesn't exist - create it
$client->files->createFolder($parentPath);
}
$client->files->upload($filePath, $content);Problem: Service provider not loading or facade not working
Solutions:
- Clear Laravel cache:
php artisan cache:clear - Clear config cache:
php artisan config:clear - Republish config:
php artisan vendor:publish --tag=dropbox-config --force - Verify
.envvariables are set correctly - Check that package is in
composer.jsonrequire section
# Complete Laravel reset
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
composer dump-autoloadEnable detailed error logging:
try {
$result = $client->files->upload($path, $content);
} catch (DropboxException $e) {
// Log detailed error information
Log::error('Dropbox API Error', [
'message' => $e->getMessage(),
'code' => $e->getCode(),
'error_summary' => $e->getErrorSummary(),
'error_tag' => $e->getErrorTag(),
'response' => $e->getResponse(),
'trace' => $e->getTraceAsString()
]);
}Q: Is this library production-ready?
A: Yes. It has error handling, test coverage, and follows PSR-12. Used in production by several projects.
Q: What's the difference between this and the official Dropbox SDK?
A: This package targets PHP 8.1+, ships with Laravel integration (service provider, facade, config), and provides a simpler method-based API. The official SDK is more low-level.
Q: Can I use this without Laravel?
A: Yes, works as a standalone PHP library. Laravel integration is optional.
Q: Does it support Dropbox Business/Team accounts?
A: Yes, both personal and business accounts are supported. Use team-scoped access tokens for team operations.
Q: What's the maximum file size I can upload?
A: Up to 350GB with chunked upload. Files under 150MB can use the simple upload() method. Larger files go through upload sessions.
Q: How do I handle rate limiting?
A: The SDK throws DropboxException with code 429. Implement exponential backoff as shown in the troubleshooting section.
Q: Can I upload files from URLs directly to Dropbox?
A: Yes, saveUrl() tells Dropbox to fetch the file from a URL on its side.
Q: How do I get a permanent link to a file?
A: createSharedLinkWithSettings() gives you a permanent link. For short-lived links (4 hours), use getTemporaryLink().
Q: Does it support Dropbox Paper?
A: Yes, the Paper endpoint covers creating, editing, sharing, and deleting Paper documents.
Q: How do I handle file conflicts?
A: Pass the mode parameter: 'add' (fail if exists), 'overwrite' (replace), or 'update' (update a specific revision).
Q: How should I store access tokens?
A: Don't store tokens in plain text. Use encrypt() in Laravel, environment variables, or a secrets manager. In production, use OAuth with refresh tokens.
Q: Is it safe to use in multi-tenant applications?
A: Yes. Create a separate DropboxClient per user with their own token. Don't share tokens between users.
Q: How do I revoke access?
A: Revoke tokens via the Dropbox App Console or build a revocation flow in your app settings.
- Never hardcode access tokens - Use environment variables or secure configuration management
- Implement OAuth 2.0 flow - For production applications, use proper OAuth instead of generated tokens
- Use refresh tokens - Implement token refresh logic to maintain long-term access
- Validate user input - Sanitize file paths and names before passing to API
- Implement rate limiting - Add application-level rate limiting to prevent API abuse
- Log security events - Track authentication failures and suspicious activities
- Use HTTPS only - Ensure all callbacks and webhooks use HTTPS
- Scope permissions appropriately - Request only the OAuth scopes your application needs
- Use type hints - Leverage PHP 8.1+ type system for better code quality
- Handle exceptions properly - Always wrap API calls in try-catch blocks
- Implement retry logic - Handle transient failures with exponential backoff
- Cache API responses - Reduce API calls by caching folder listings and metadata
- Use batch operations - Process multiple files efficiently with batch endpoints
- Test with real data - Create a test Dropbox account for development
- Monitor API usage - Track API call volumes to stay within rate limits
- Version your code - Use semantic versioning and maintain changelog
- Use chunked uploads - For files over 150MB, always use upload sessions
- Implement pagination - Handle large folder listings with cursor-based pagination
- Stream large downloads - Don't load entire files into memory
- Optimize search queries - Use specific paths and filters to reduce result sets
- Leverage cursors - Use
listFolderContinue()for efficient pagination - Batch thumbnail requests - Get multiple thumbnails in one API call
- Use temporary links - For public file access, temporary links are faster than downloads
- Implement queue workers - Process large file operations asynchronously in Laravel
// Use queued jobs for large operations
class UploadToDropboxJob implements ShouldQueue
{
public function handle(DropboxClient $dropbox)
{
$dropbox->files->upload($this->path, $this->content);
}
}
// Use events for file operations
event(new FileUploadedToDropbox($filePath, $metadata));
// Implement middleware for Dropbox webhooks
Route::post('/dropbox/webhook', [DropboxWebhookController::class, 'handle'])
->middleware('verify.dropbox.signature');| Feature | This SDK | Official Dropbox SDK |
|---|---|---|
| PHP Version | 8.1+ | 7.4+ |
| Laravel Integration | Built-in (provider, facade) | Manual |
| API v2 Coverage | All major endpoints | All endpoints |
| Documentation | Examples + API reference | API reference |
| Type Safety | Full type hints | Partial |
| Error Handling | Dedicated exception class | Basic exceptions |
| Chunked Upload | Built-in helpers | Manual implementation |
| Batch Operations | Supported | Supported |
| OAuth 2.0 Helpers | Included | Manual |
Method names map closely to the Dropbox HTTP API documentation:
// Old library (example)
$dropbox->uploadFile('/path', $content);
// This SDK
$client->files->upload('/path', $content);
// Old library
$dropbox->getMetadata('/path');
// This SDK
$client->files->getMetadata('/path');Method names follow the Dropbox API naming, so the official HTTP docs serve as a reference.
Store product images, invoices, and customer documents:
// Upload product images with organized structure
$productId = 12345;
$imagePath = "/products/{$productId}/images/main.jpg";
$client->files->upload($imagePath, $imageContent);
// Generate shareable link for product image
$link = $client->sharing->createSharedLinkWithSettings($imagePath);
$product->image_url = $link['url'];Manage corporate documents with version control:
// Upload document with metadata
$result = $client->files->upload(
"/documents/contracts/{$contractId}.pdf",
$pdfContent,
'add'
);
// Track revisions
$revisions = $client->files->listRevisions($result['path_display']);
// Share with specific users
$client->sharing->addFileMember($result['path_display'], [
['member' => ['.tag' => 'email', 'email' => '[email protected]']]
]);Create scheduled backups of critical data:
// Laravel scheduled task
protected function schedule(Schedule $schedule)
{
$schedule->call(function (DropboxClient $dropbox) {
$backupPath = '/backups/' . date('Y-m-d-H-i-s');
$dropbox->files->createFolder($backupPath);
// Backup database
$dbBackup = Storage::get('backups/database.sql');
$dropbox->files->upload("{$backupPath}/database.sql", $dbBackup);
// Backup files
$filesBackup = Storage::get('backups/files.tar.gz');
$dropbox->files->upload("{$backupPath}/files.tar.gz", $filesBackup);
})->daily();
}Build a photo gallery with thumbnails:
// Upload photos and generate thumbnails
foreach ($photos as $photo) {
$path = "/gallery/{$albumId}/{$photo->name}";
$client->files->upload($path, $photo->content);
// Get thumbnail
$thumb = $client->files->getThumbnail($path, 'jpeg', 'w256h256');
Storage::put("thumbnails/{$photo->id}.jpg", $thumb['content']);
// Create public link
$link = $client->sharing->createSharedLinkWithSettings($path);
$photo->public_url = $link['url'];
}Enable team collaboration with shared folders:
// Create project workspace
$projectFolder = "/projects/{$projectName}";
$client->files->createFolder($projectFolder);
// Share with team
$shared = $client->sharing->shareFolder($projectFolder);
// Add team members
foreach ($teamMembers as $member) {
$client->sharing->addFolderMember($shared['shared_folder_id'], [[
'member' => ['.tag' => 'email', 'email' => $member->email],
'access_level' => ['.tag' => $member->role === 'admin' ? 'editor' : 'viewer']
]]);
}Added:
- Initial release
- Full Dropbox API v2 support
- Files endpoint with complete file/folder operations
- Sharing endpoint for collaboration features
- Users endpoint for account management
- File Requests endpoint
- Paper endpoint for Dropbox Paper documents
- Check endpoint for API health checks
- Laravel 8-12 integration with service provider and facade
- OAuth 2.0 flow helpers
- Comprehensive documentation and examples
- PHPUnit test suite
- GitHub Actions CI/CD workflow
Features:
- Upload/download files with chunked upload support
- File and folder management (copy, move, delete, search)
- Shared links and folder sharing
- Batch operations support
- Thumbnail generation
- File preview and export
- Space usage tracking
- Error handling with detailed exceptions
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
MIT License (MIT). Please see LICENSE file for more information.
