roddy/firestore-eloquent
Bring Eloquent-style modeling to Google Cloud Firestore in Laravel. roddy/firestore-eloquent lets you define models and query Firestore collections with familiar Laravel syntax, making document data feel like native Eloquent records.
Architecture fit — conditional, and narrow. roddy/firestore-eloquent is not an Eloquent replacement; it is an Eloquent-shaped adapter over a document store. Firestore has no joins, no schema, no server-side aggregates worth relying on, and cursor-only pagination. Any domain that leans on relational idioms (reporting queries, whereHas, withCount, offset pagination, migrations as source of truth) will hit a leaky abstraction on day one. Where it does fit: append-heavy or read-by-key domains that need Firestore's native strengths — real-time listeners, offline/mobile SDK parity, per-document security rules, and horizontal write scaling without a DBA. Treat it as a bounded-context persistence adapter, not a default model base class. The value proposition is developer familiarity (Laravel devs ship faster against Firestore), and that value is real but must be budgeted against the abstraction-leak tax.
Integration feasibility — technically straightforward, procedurally unproven. Composer install, service provider auto-discovery, a base model class, and a service-account credential path (GOOGLE_APPLICATION_CREDENTIALS or a config-published JSON) are the expected integration surface, with google/cloud-firestore as the underlying transport. The emulator makes local and CI testing viable. The unknowns are all in the metadata: 16 stars, 0 dependents, a single maintainer, and a release date stamped 2026-10-28 — a future date that is either a metadata artifact or a clock-skew bug and must be verified before anything is pinned. Zero dependents means zero production validation in the wild; we would be an early adopter of an unhardened seam.
Technical risk — high, concentrated in the abstraction seam and vendor concentration.
| Risk | Severity | Note |
|---|---|---|
| Leaky Eloquent semantics | High | Eloquent idioms compile to expensive or unsupported Firestore operations without warning |
| Single-maintainer bus factor | High | 16 stars / 0 dependents → likely no external bug-finders; MIT permits fork, but we inherit ownership |
| Read-cost amplification | High | Eager-loading/relations become N+1 billable document reads |
| Missing composite indexes | Medium | Surface as runtime FAILED_PRECONDITION, not deploy-time failure |
| Laravel/PHP version drift | Medium | Constraint matrix unverified against Laravel 11/12 and PHP 8.3+ |
| Auth model inversion | Medium | Admin SDK bypasses Firestore security rules; authorization must be enforced entirely in Laravel |
Key questions (diligence checklist before any code is written).
composer.json, and is there a CI matrix that actually exercises them?with()?paginate() silently emulate offsets (a known anti-pattern against Firestore)?arrayUnion/increment, and Firestore transactions exposed, or does every save become a full-document overwrite?Stack fit. Reasonable for a Laravel monolith or modular monolith that already runs on Google Cloud and wants Firestore for a specific slice. Poor fit for a team standardized on MySQL/Postgres with BI tooling, SQL-based reporting, or an existing migration-driven schema discipline. Precondition: GCP project, Firestore Native mode, IAM-scoped service account, and a cost-allocation label strategy in place before the first query ships.
Migration path — strangler fig, never a rewrite. Do not port existing Eloquent models. Instead:
Compatibility — the constraint map is the whole conversation. Build this table with the team before estimation; every row is a potential mid-sprint surprise.
| Eloquent idiom | Firestore reality |
|---|---|
join / whereHas |
Unsupported — denormalize or multi-round-trip |
where('x','like','%y%') |
Unsupported — needs external search (Typesense/Algolia) |
orderBy + inequality on different fields |
Requires composite index; may be rejected |
paginate() (offset) |
Cursor-based only; UI must accept opaque tokens |
count() / sum() |
Per-document reads or maintained counters |
Auto-increment id |
Client-generated IDs; random IDs avoid hotspots |
| Migrations as schema truth | No schema; validation lives in the model/casts |
| Soft deletes | Application-level flag only |
Sequencing (one quarter, gated).
Maintenance. The dominant operational cost is not the package's code volume — it is that we will likely own it. A 16-star, single-maintainer, zero-dependent project with an anomalous future release date is a fork-in-waiting. Budget roughly 10–15% of one engineer's time for upstream tracking, security patching of the google/cloud-firestore transitive dependency, and Laravel major-version bumps. Cheap insurance; non-negotiable if the slice becomes load-bearing.
Support. No commercial backing. Google supports Firestore; nobody supports the adapter. Escalation path is GitHub issues, then our own engineers. This is acceptable for a bounded, non-revenue-critical slice and unacceptable for anything on the checkout or auth path. Document the fallback: revert the flag, route reads back to SQL, drain the dual-write queue.
Scaling. Firestore's horizontal scaling is genuine, but three limits bite teams coming from SQL:
with()-equivalent, a polling UI loop, or a dashboard that re-queries per request turns into a visible line item. Read-per-request must become a reviewed metric, the way query count is in SQL shops.Failure modes.
| Mode | Trigger | Blast radius | Mitigation |
|---|---|---|---|
| Missing composite index | New query shape at runtime | 500s on a specific filter combo | Index-as-code; integration tests for every query path |
| Read-cost runaway | N+1 relations, polling | Budget breach, latency | Per-journey read budget in CI; denormalize deliberately |
| Quota / rate exhaustion | Traffic spike, bulk backfill | Project-wide write failures | Backfill via rate-limited queue; quota alerts |
| Credential expiry / IAM drift | Rotated service account | Total outage of the slice | Workload identity, secret rotation tests |
| Partial batch failure | Batch write over limits | Silent data loss | Idempotency keys + reconciliation job |
| Type coercion drift | Missing casts, floats vs. ints | Subtle data corruption | Explicit cast coverage; schema tests |
| Emulator/prod divergence | Emulator laxity | Green tests, broken prod | Contract tests against a staging project |
Ramp-up. Superficially fast and deceptively fast. Laravel developers will be productive in a day because the API looks familiar — which is precisely the hazard. The abstraction invites Eloquent muscle memory (with(), paginate(), whereHas()) that produces slow, expensive, or failing Firestore calls with no error at authoring time. Plan an explicit half-day of Firestore-model training (denormalization, cursor pagination, indexes, pricing) before the first slice, and encode the constraint table above into the PR review checklist. The on-call runbook must cover the two failure signatures unique to this adapter: FAILED_PRECONDITION (index) and read-cost spikes (relations).
Bottom line. Approve as a time-boxed, single-slice experiment behind a repository interface and a feature flag, with the fork decision made in week three and cost/read instrumentation shipped before the second slice. Do not approve it as a default model base class, as a path off relational SQL for structured business data, or anywhere the loss of Firestore security rules in favor of in-app authorization is unacceptable. The package is a plausible velocity win; the risk being underwritten is not the code quality, it is the unvalidated 16-star dependency and the Eloquent-idiom tax that only shows up on the invoice.
How can I help you explore Laravel packages today?