spatie/dropbox-api
A minimal PHP client for Dropbox API v2, providing just the methods needed for the flysystem-dropbox adapter. Create folders, list contents, and generate temporary links with a simple authorization token.
Architecture fit. spatie/dropbox-api is a deliberately thin HTTP client over Dropbox API v2, not a framework integration. It has no service provider, no facade, no config file, and no Eloquent/storage coupling — it is a single Spatie\Dropbox\Client class that speaks Guzzle/PSR-7 and returns arrays and ResponseInterface objects. That is a feature for a TPM scoping work: the package owns transport and path normalization, your application owns credential lifecycle, retry policy, error mapping, and domain modeling. The two deliberate extension seams are worth noting in any design review: TokenProvider (a one-method interface, getToken(): string, that lets you plug in a refreshing OAuth2 adapter) and the raw rpcEndpointRequest() / contentEndpointRequest() escape hatches, which mean an unsupported Dropbox endpoint is a small internal helper rather than a fork or a blocked ticket.
Integration feasibility. Composer-only install, no published service provider to register, no migrations, no runtime configuration beyond a bearer token. A competent Laravel developer can get a read/write smoke test running in under an hour; the package's own README supplies the exact constructor permutations (access token, TokenProvider, [appKey, appSecret] app auth, or no-arg for public endpoints). The realistic integration cost sits almost entirely outside the package: acquiring and refreshing short-lived tokens, deciding whether to route through Laravel's filesystem (spatie/flysystem-dropbox on top of this client) or call the client directly, and building idempotency around upload/move conflicts.
Technical risk. The dominant risk is scope, not quality. The maintainers state plainly that the client "contains only the methods needed for our flysystem-dropbox adapter" — so the method surface is a subset of Dropbox v2, and any roadmap item touching sharing, team/admin endpoints, paper, or signatures should be validated against src/Client.php before commitment, not against Dropbox's docs. Secondary risks: (1) token lifecycle — Dropbox moved to short-lived access tokens, and the package intentionally does not implement refresh, so an AutoRefreshingDropBoxTokenService equivalent is your code and your outage surface; (2) no built-in retry, backoff, or rate-limit handling — 429s and transient 5xx will surface as exceptions unless wrapped; (3) error ergonomics are coarse, exceptions carry a Dropbox error code/string (e.g. to/conflict/file/...) that you must parse or pattern-match yourself; (4) transport coupling to Guzzle 7 and graham-campbell/guzzle-factory, which is benign in Laravel but means major-version bumps arrive as package releases, not your choice; (5) maintainer concentration — Spatie-stewarded with community PRs, which historically has meant long gaps between substantive feature work punctuated by targeted fixes (the 1.25.0 infinite-loop fix in uploadChunked() is exactly the kind of production-only defect a thin client accumulates). The "0 dependents" metadata is misleading and should not be used as an adoption signal — the package exists to serve flysystem-dropbox and is widely pulled in transitively.
Key questions.
rpcEndpointRequest() calls we would own?Stack fit. Clean fit for Laravel 10/11/12 on PHP 8.2+ (the package dropped older PHP in 1.22.0 and tests through 8.5). Guzzle is already a transitive Laravel dependency, so the added footprint is small. The idiomatic wiring is a singleton binding in a service provider: build a TokenProvider implementation from encrypted stored credentials, then $this->app->singleton(Client::class, fn () => new Client($tokenProvider)). Configuration belongs in config/services.php under a dropbox key with env-backed values, which keeps config:cache working. For anything user-facing or long-running (large uploads, bulk sync, exports), the client calls belong inside queued jobs with explicit timeouts and $tries, never in a request cycle.
Migration path. Three realistic entry points. (a) Greenfield: adopt the client directly behind a narrow internal interface — e.g. FileVault with store(), move(), temporaryLink() — so the package never leaks into domain code and can be swapped. (b) From Laravel's filesystem: if you are already on spatie/flysystem-dropbox or a community Dropbox driver, this package is already underneath; the migration is an extraction toward direct client calls for the operations where Flysystem's abstraction leaks (shared links, batch thumbnails, namespace-scoped business accounts). (c) From the legacy Dropbox PHP SDK or hand-rolled cURL: the path normalization, autorename, and chunked-upload helpers are the main draw; port call-site by call-site and keep both alive behind the internal interface during cutover. Avoid a big-bang rewrite — this is a transport swap, not a data migration, so it should be reversible per feature flag.
Compatibility. PSR-7 request/response throughout, Guzzle 7, and explicit support for business accounts (namespace ID settable per request) and app-level or unauthenticated usage. The subdomain:: endpoint prefix convention (e.g. content::files/get_thumbnail_batch) covers the content-vs-rpc split without extra plumbing. Version pinning deserves a decision: pin to a caret constraint on the current minor and let Dependabot open PRs, rather than floating, because minor releases have historically carried behavior changes (autorename semantics, move_v2, uploadChunked fixes).
Sequencing. (1) Spike: authenticate a real Dropbox app, exercise upload/list/temporary-link against a sandbox folder, and confirm which required endpoints are first-class vs raw. (2) Credential layer: TokenProvider implementation, encrypted token storage, refresh-on-401, revocation hook. (3) Internal abstraction: interface + adapter wrapping the client, with path convention and conflict policy defined once. (4) Async execution: queue jobs, timeouts, retry/backoff classification (retry 429/5xx, do not retry 409). (5) Observability and reconciliation: structured logging of Dropbox error codes, a drift check comparing local records to listFolder. (6) Hardening: load test at expected concurrency, document the fork/escalation path, then remove the feature flag.
Maintenance. MIT license, semver, a maintained CHANGELOG with per-release files, Dependabot automation, Pest-based CI across a wide PHP matrix, and a static-analysis job — this is a well-tended package by open-source standards. The maintenance burden it imposes on us is modest but real: we own the token refresh implementation, the exception-to-domain-error mapping, and the retry policy, none of which ship in the box. Budget a small recurring allocation for dependency bumps (Guzzle/PSR-7 majors, PHP minors) and for re-validating raw-endpoint call shapes against Dropbox's changelog, since Dropbox deprecations will not always produce a package release. The 307-star count is honest signal about the package's deliberately narrow ambition, not a red flag about quality, but it does mean fewer eyes on edge cases than a first-tier SDK — the recent uploadChunked() infinite-loop fix is a concrete example of what that looks like in practice.
Support. No commercial SLA; support is GitHub issues, a security disclosure mailbox, and Spatie's reputation. Practically, the fastest "support channel" is the 900-line Client.php — the README explicitly directs you there to discover methods, and any engineer onboarding should read it once rather than relying on the docs. Mitigate the absence of a vendor by keeping the internal adapter thin and fully unit-tested with mocked Guzzle handlers, so a package regression is caught by our suite rather than by a customer. Have a stated position on forking: MIT permits it, and for a package this small, vendoring a patched copy is a credible 1–2 day contingency.
Scaling. Dropbox is the bottleneck, not PHP. Plan for API rate limits and per-app throttling by batching (files/get_thumbnail_batch-style endpoints), reusing getTemporaryLink() results rather than re-fetching, and preferring chunked upload sessions for anything large or resumable. Memory is the practical constraint: upload() takes string or resource content, and the chunked helpers deliberately stream, so large files must not be read into memory in the request path. Run uploads on dedicated queue workers with tuned concurrency so a Dropbox slowdown degrades background throughput rather than saturating web workers. Horizontal scaling is trivial (the client is stateless); the only shared state is the token cache, which must tolerate concurrent refresh — use a lock.
Failure modes. Enumerate and instrument these before launch: expired or revoked access token (401) — must trigger refresh-then-retry-once, then a user-facing reconnect prompt; path conflicts (409, to/conflict/file/...) — decide between autorename=true, overwrite modes, or surfacing a conflict to the user, and apply consistently; rate limiting (429) — exponential backoff with jitter and a circuit breaker; partial chunked-session failure — session IDs expire, so resumption needs a bounded restart, not infinite retry; oversized single upload — detect by size and route to chunked proactively rather than after a 413; network timeouts mid-content-request — non-idempotent operations need a reconciliation read before retry, or you risk duplicate files; case-insensitive path collisions — Dropbox normalizes paths, so dedupe keys must not rely on case; and business-account namespace mix-ups, where a missing namespace ID silently targets the wrong root and looks like data loss. Each of these should have a named alert and a documented operator runbook step.
Ramp-up. Low for the happy path — a mid-level Laravel developer is productive in half a day given the README. Moderate for the parts that actually cause incidents: OAuth2 refresh, error-code taxonomy, chunked upload resumption, and namespace scoping. Realistic onboarding estimate: 1–2 days to first working integration in staging, 1–2 weeks to production-grade with retries, observability, and reconciliation, assuming one engineer familiar with Laravel queues and Guzzle. The absence of detailed docs beyond the README is the main friction — treat a short internal "how we use Dropbox" doc, covering our adapter, our conflict policy, and our runbook, as a required deliverable of the integration rather than an afterthought.
How can I help you explore Laravel packages today?