docs / Data & D1
Data & D1
Migrations run at build time against an in-memory SQLite; the request-time layer is a Workers-native query builder and ORM subset over D1.
Schema, without a database
tools/schema-compile.php boots your app, runs the migrations against an in-memory SQLite, and dumps the resulting DDL through pragma. Your database/ directory is not touched, no MySQL or Postgres server is involved, and the output isdist/schema.sql + dist/schema.json — the same DDL for a MySQL, Postgres or SQLite source app.
On a fresh checkout with no database file, the build seeds a throwaway SQLite under.cache/ so Laravel can boot for prerendering. It is empty on purpose: it is not your database and not the cloud D1.
The query builder
| surface | supported |
|---|---|
| filters | where*, where(fn), orWhere*, whereIn, whereNull, whereBetween, whereDate/Year/Month/Day/Time |
| shapes | join, leftJoin, group, having, distinct, orderBy, limit, offset |
| aggregates | count, sum, avg, min, max, exists |
| writes | insert, insertGetId, upsert, update, updateOrInsert, increment, delete, truncate |
| batching | chunk, pluck, value, first, get |
Parameter binding is D1-safe: booleans become 1/0, dates ISO strings,undefined becomes NULL. The builder is exercised against a real in-memory SQLite integration suite, not only against mocks.
// compiled from PHP, at build time; executed against D1 at request time
const posts = await Post.where('published', 1)
.orderBy('created_at', 'desc')
.limit(20)
.get(db);Eloquent
A schema-driven subset of the ORM compiles: find/findOrFail,all, where*, orderBy*, limit withfirst/get/count/pluck/value; relations hasMany/hasOne/belongsTo (eager and lazy, and as properties); writes create/updateOrCreate/firstOrCreate/fill/save/delete; fillable, accessors and mutators; casts json, decimal:N, hashed;refresh/increment; soft deletes.
Anything outside that — a custom query scope that calls a PHP function the edge cannot run, for example — fails loud with the failing construct named.
Cache, storage, uploads
| need | at the edge |
|---|---|
| Cache | the database store on D1 (get/put/add/remember/increment/decrement/forget/flush), plus KV cache-aside. Cache::tags() fails loud: a non-taggable store throws in Laravel too. |
| Storage | Storage::put/get/exists/delete on the default disk → the D1 blob store (lw_blobs, 1.5 MB/object, no card), or R2 when a bucket is bound. Anamed disk fails loud — it would need its own bucket. |
| Uploads | the multipart body is parsed at the edge, $request->file() answers a file object, store()/storeAs() write to the default disk, thefile rule and the size rules in kilobytes work, and amimes/mimetypes rule compiles for the formats the edge can read from the file header. $file->getMimeType() — the finfo answer — fails loud, and so does a mimes rule naming a format the header read cannot confirm. |
| Pagination | paginate(10) returns a real LengthAwarePaginator;->links() renders Laravel's pagination::tailwind view byte-identically. |
Two numbers to keep in mind
D1 Free allows 100k writes a day and 5M reads. The toolchain is shaped around that: cookie sessions write zero rows, the throttle is applied to the routes that need it rather than blanket, and eager loading exists to keep query counts down. Theaudit records which constructs were measured and which were only documented.
Source: README.md §Compiles today, §Zero-touch; COMPILED.md; MAP.md §5.D — repository ba1e9a5