Skip to content

Changelog

Flare ships as three packages that move together — @flaredev/core, @flaredev/cli and create-flare-framework. They always share a version.

Terminal window
npx flare --version # what this app is on
pnpm add -D @flaredev/cli@latest # upgrade it
0.14.17 November 2026

Your tables will gain columns when you upgrade. That is the fix, and it is worth saying first so it is not a surprise.

A resource with a cover image had no Cover column. Four field kinds — file, long text, json and markdown — were born list: false, so the framework decided they looked untidy in a table and left them out. That is a judgement about taste, and taste belongs to whoever wrote the descriptor; what it actually achieved was sending people to look for a bug in their own code, because a field they had declared and filled in was nowhere on the screen.

Every field is a column now. Hide the ones you would rather not see:

meta: field.json({ list: false }), // kept, not shown
blurb: field.text({ list: false }),

Readers can also hide columns for themselves from the table’s column menu, which is per person and changes nothing for anyone else.

Nothing had to be built for this. FileCell has drawn a thumbnail for an image and a filename for anything else since it was written, and formatValue truncates JSON to one line with “this goes in a table cell” in the comment above it. The renderers were ready; the default was hiding them.

What stays off by default is the part about correctness rather than looks: long text and markdown are still not searchable (a LIKE over paragraphs is not a search), and json and file are still not sortable.

0.14.07 November 2026

Upgrading an app runs a migration. Both stacks gain a role_permission table, and the Next.js stack gains Role to go with the one Cloudflare already had. npx flare migrate writes and applies it.

Roles and permissions, from the dashboard. /dashboard/roles: create a role, delete one, and tick what each may do per resource and action. Assign it from Users, and the grant decides what that person reaches — in the admin and over the API, which read the same policy so they cannot drift.

Where permissions live was the question worth getting right. Flare’s policies are files, and that is why they are reviewable in a pull request, travel with the repository, and are the same on every machine before anything deploys. A database is none of those. So the two compose: the policy file is the floor and the database only ever widens it. Nothing ticked in a running dashboard can take away access your code grants, and nothing can lock an app out. A tick that came from a file is shown locked, with the filename in its tooltip.

The corner worth knowing: a resource with no policy file is open to every signed-in user, so grants on it do nothing. Inventing a policy out of the grants would be worse than useless — an app would start refusing people the moment somebody opened that screen. The row says it is open and gives the command that restricts it. Roles already held by accounts appear whether or not they were registered, because flare user:role someone auditor is a complete way to make one.

Bulk add. Every resource list gets it beside New: a full-height sheet of rows and columns you can type into, tab across, Enter down, and paste a whole block into — tab-separated rows off a spreadsheet fill from the cell you are in. Cells are checked against the same validators the form and the API use, so a batch with a bad row is refused whole rather than written halfway, and rows are written through the same path the CSV import takes, so hooks, policies and ownership all apply. File columns are included, behind a one-line Attach control rather than a dropzone per row.

Every password box has the eye. It was on the sign-up and reset fields and missing from sign-in and “your password” — so the app offered to reveal your password in one place and not the next.

Devices said 0000:0000:0000:0000:0000:0000:0000:0000. That is the IPv6 loopback written in full: correct, unreadable, and identical on every row. It says “this computer” now.

Every “choose one” is searchable. Enum fields in generated forms, and the role fields in the Users screen, are comboboxes you can type into — a six-option enum is a list you scan, a sixty-option one is a list you search, and the descriptor does not say which you have.

flare gen resource <Name> --add 'phone:phone?, notes:text?' appends columns. --fields replaces the block, so adding one column used to mean restating every field you had, which is how a field gets dropped by accident.

New descriptors carry the settings people reach for on day two — icon, group, titleField, defaultSort, perPage, softDelete, rateLimit — written commented out with aligned notes and a link. A key you can see is a key you will use.

Smaller things. The error page tells a stale tab from a broken app: a tab running old code after an update, or after a different app started on the same port, is offered a reload, because React’s “Try again” re-renders the same stale modules and can only fail. The command builder pins the version its commands belong to and passes --yes, so a copied command runs to completion instead of asking the questions the form already asked. Four places still told you to run prisma directly — gen resource, rm resource, gen apikeys and doctor — and now say flare.

0.13.07 November 2026

flare migrate on Next.js now develops — change your CI if it relied on the old meaning. It used to run prisma migrate deploy, which applies what is committed and never invents a migration — so after flare gen resource it said “No pending migrations to apply” and the only way forward was a raw prisma command. Locally it now runs prisma migrate dev and then prisma generate (Prisma 7 removed generator runs from migrate dev, and a client without the new model fails the very next command). --name skips Prisma’s prompt. In CI and production, use flare migrate --deploy — that is the old behaviour, unchanged: committed migrations only, nothing invented.

You never type a prisma command. flare db:seed seeds the development admin (admin@example.com / Password, role admin; safe to run twice), and every surface that teaches the flow — the boxes after create, the dashboard’s empty state, the quickstart, installation — now says the same flare commands, identically, on both stacks. The app’s db:migrate and db:seed scripts are thin aliases of the same commands, so they cannot drift.

Users are managed from the dashboard. /dashboard/users on both stacks: everyone who can sign in, their role, verified, two-factor, joined. New user from a dialog; role changes — including a typed, brand-new role — and ban/unban from a row menu. All of it goes through Better Auth’s admin API, the same path as flare user:add and flare user:role, and your own row has no menu, because demoting or banning yourself is a lockout. A role is a string your policies give meaning; the page says so and points at flare gen policy. The account menu is three entries now: Account settings, Manage roles (admins), Log out.

The first sign-in no longer faces an empty page. The overview gets a Users stat card and a Users row in “Your data”, and its empty state is the three commands that make a first resource — the same three the terminal printed. The account you seed is told to you in a “Sign in with” box after create.

Observability on Next.js shows what the app can see — users, records, signed-in sessions, changes this week, with a link to Vercel for edge traffic — instead of three dashes marked “not connected” and instructions mentioning wrangler on an app that deploys to Vercel. The Costs page is Cloudflare-only now: it prices Workers, D1 and R2, which mean nothing on Vercel and Neon.

The auth screens live in app/(auth)/. Seven folders — sign-in, sign-up, forgot-password, reset-password, verify-email, two-factor, invite — were scattered across app/. They are one route group now, so the folder exists and no URL changes. Verified on both routers.

A command builder and two tutorials. The builder turns your choices — stack, storage, theme, sign-in — into the one create command to copy. The tutorials build a contact app end to end, including a rename that keeps its data and a policy tried against a second account, on Next.js and Cloudflare. Every step was run against a scaffolded app before it was written down.

Smaller things. next dev no longer announces “We detected TypeScript… reconfigured your tsconfig.json” on first start — the template ships what Next 16 mandates. The Cloudflare dev server is pinned to port 3000, where every box and document already said it was. Sidebar group labels are capitals with a divider between sections. The app’s display name is capitalised — danlite is Danlite on the sidebar, the sign-in screen and in emails — while machine names (the Workers script, the bucket prefix) stay lowercase. Dashboard counts cache for a minute rather than five, so a fresh seed:resource shows on refresh. flare user:add --role no longer refuses when the role table exists but is empty — a fresh Cloudflare app — which was blocking the seed that makes the first admin. base.prisma explains in comments why its generator block is Prisma 7’s shape, for everyone who stops and wonders.

0.12.26 November 2026

flare gen migration --from-schema could not apply a rename on Next.js. It ran prisma migrate dev --skip-generate, and Prisma 7 removed that flag — the CLI answers unknown or unexpected option and refuses the whole command, so the rename migration was written to disk and never applied. The flag is gone; v7 does not run generators from migrate dev anyway, which is all it was there to prevent.

While fixing it: --from-schema described itself as (Cloudflare only). It is not. On Next.js it is the flag that turns a rename in a descriptor into an ALTER TABLE … RENAME COLUMN, and without it Prisma emits a drop and an add and the data goes with them. The help and the reference both say so now.

Prisma 7.10.1, up from 7.10.0, across prisma, @prisma/client and @prisma/adapter-pg — the newest stable 7.x.

Pinned exactly, and worth knowing why: prisma@latest is an 8.0 release candidate, and Prisma’s update notifier prints npm i --save-dev prisma@latest inside the output of prisma migrate. Following the advice an app’s own commands give lands it on a prerelease Flare does not generate for. npx flare doctor now checks this — it fails an app that has drifted off Prisma 7, fails one whose three Prisma packages disagree on a major (a client the installed CLI did not generate breaks on the first query, not at build), and warns on mixed 7.x patches.

The generated Prisma client is git-ignored. lib/generated/ was not in a new app’s .gitignore, so prisma generate output — hundreds of files rewritten on every install and every build — was committed, and went stale the moment somebody changed the schema without regenerating. This is Prisma’s own advice for the v7 generator, and it is only safe now that 0.12.1 made postinstall regenerate the client.

0.12.16 November 2026

A new Next.js app would not start. pnpm dev failed on Can't resolve './generated/prisma/client' before serving a page, and so did flare user:add, which imports lib/db.

Prisma 7 generates its client into lib/generated/prisma, which is git-ignored and therefore absent from every fresh checkout — including the one create had just written. prisma generate ran in build and nowhere else. New apps now run it from postinstall, so the first dev works and so does a teammate’s first clone. Existing apps: add it yourself, or run pnpm db:generate once.

pnpm db:seed makes an account to sign in with. A new app had tables and no users, and the dashboard was a wall. The seed writes admin@example.com / Password with the admin role, through the same flare user:add that makes any other account. It is safe to run again — the new --skip-existing flag makes a second run a no-op rather than an error, which is what a seed has to survive when somebody resets their database.

The box printed after create now names the database steps as well, because the app genuinely does not work without them: docker compose up -d, db:migrate, db:seed, then dev.

UploadThing: one variable, and the SDK is installed for you.

UPLOADTHING_APP_ID is gone. A v7 token is base64 JSON — { apiKey, appId, regions } — so the app id was always inside the token Flare already had, and asking for it separately asked for a value that could not disagree without being wrong. When it did disagree the failure was quiet: uploads succeeded and every read 404’d from a subdomain belonging to another app. The driver reads it from the token now. Existing apps can delete the variable; passing appId explicitly still works.

Choosing UploadThing at create also puts uploadthing in the app’s dependencies rather than printing “Install its SDK” after the install has already finished — a step people skip, whose error arrives at the first upload.

A new app’s first install could stop and ask. msgpackr-extract reached pnpm’s undecided-build list, which is ERR_PNPM_IGNORED_BUILDS and a failed install, not a warning. It is refused, like sharp and @prisma/engines: it is an optional native accelerator that msgpackr falls back from on its own.

Smaller things. The script that builds the shipped lockfiles no longer runs the scaffolded app’s own postinstall: resolution writes no node_modules, so prisma generate ran against a Prisma that was never installed and took the release build down with it.

0.12.06 November 2026

One error envelope — change this if you read the API from your own code. (#29)

There were four error shapes. { error } from most guards, { error, field } from a unique conflict, { error, issues } with a path in each issue from validation, and { error, issues } with a param instead from a bad query string — plus a plain-text refusal from the security layer. A client had to handle whichever one the guard that refused it happened to use.

Every error is now the same:

{
"error": "Number is already taken.",
"code": "unique_violation",
"issues": [{ "path": "number", "message": "Number is already taken.", "code": "unique" }]
}

error is the sentence to show a person and its wording can change between releases. code is for your code: nineteen values, a closed set, stable. issues is present whenever the answer is about fields.

What to change. If you read field, read issues[0].path. If you read issues[].param, read issues[].path. Nothing Flare generates needs changing — the forms and the import screen moved with it — so this only affects code you wrote against the API yourself. Full list of codes in one error shape.

Two things fell out of it: a store with an ownership policy and nobody signed in answered 403 and now answers 401, because there is no identity to refuse; and the security layer answers a browser navigation with a sentence and an API request with the envelope, rather than plain text to both.

flare seed:resource works on the Next.js stack. (#24)

The one-liner that fills a table from its descriptor was Cloudflare-only, which stopped being a footnote when Next.js became the default: the quickest way to get something to build against was missing from the stack most people now start on.

Terminal window
npx flare seed:resource Ticket 5k

Cloudflare still writes SQL straight at D1. Postgres goes through the app’s own Prisma client, because hand-writing the literal syntax for a text[], a jsonb column and a Postgres enum is three chances to be subtly wrong about escaping, and the client already knows all three. About 2,000 rows a second against local Postgres, against ~14,000 on D1.

--remote has no meaning here — Postgres has one DATABASE_URL and it points wherever you point it — and the command says so rather than implying a second database.

A store now says whether it is confined to one person’s rows. (#23)

createResourceStore built without currentUser quietly stopped applying an own policy and returned every row. That is right for a seed and a hole in a server action, and both were written the same way — so the dangerous reading was the one you got by forgetting an argument.

createResourceStore({ resource, rows, policy, currentUser }); // confined
createResourceStore({ resource, rows, policy, system: true }); // on purpose
createResourceStore({ resource, rows, policy }); // throws

Passing both is allowed and means what it says: a job that wants the user for an audit hook and still needs every row. Nothing Flare generates changes — every route and the dashboard already passed currentUser, which is why this went unnoticed. The call sites it catches are hand-written ones, and the first one it caught was Flare’s own CI script.

A renamed field keeps its data. (#56, #75)

fullName: field.string({ rename: { from: "name" } })
Terminal window
npx flare sync-types
npx flare gen migration rename_name --from-schema
# create migrations/0004_rename_people_name_to_full_name.sql (people.name → full_name, data kept)

Renaming a field used to be a drop and an add, which emptied the column. It applies once — the migration history decides whether there is anything to do, so the declaration is safe to leave in place and safe to delete.

And the bug underneath it, which was worse. drizzle-kit asks whether a column was renamed or dropped, needs a terminal to ask in, and when it has none it prints the question as an error and exits 0. Flare read only the exit code, so that came back as “no table changes” — and because the unresolved rename stays in the diff, every later gen resource produced no migration either, silently. A project could stop generating migrations altogether and give no sign of it. The run’s output is now read alongside its exit code, and a run that wrote nothing while printing an error is a failure whatever it exited with.

belongsTo("User") is a relation like any other. (#73)

Terminal window
npx flare gen resource Report --fields 'title:string, reporterId:belongsTo(User)?'

“Who reported it” is in nearly every app and is usually the first relation somebody reaches for. It failed, and the error told you to generate a User resource first — which would have made a second, competing user table, so the advice was a trap. You get the real foreign key, the index and a searchable picker on both stacks.

onDelete defaults differently here: set null when the field is optional, because somebody closing their account should not be held open by a lost-property report from two years ago. A required one is restrict.

The picker needed an endpoint over the accounts table, which needed a decision about who may see whom. /api/users is not a directory: an admin gets the searchable list, everybody else gets their own account, signed out gets 401. See a relation to the signed-in account.

Rate limits per resource and action. (#36)

rateLimit: { create: "10/min", "*": "120/min" }

Checked in the generated route, straight after the policy, counted per signed-in account (or per IP when there is nobody signed in). A refused call is 429 with retry-after and the rate_limited code. Worth setting on the actions that cost something — a create that sends an email, an export that reads the whole table — rather than on everything, which is what the edge limiter is for.

Counted in a Durable Object on Cloudflare and in Redis on Next.js, not in Cloudflare’s rate-limiting binding: its window may only be 10 or 60 seconds and its limit is fixed in the Worker’s configuration, so "500/hour" would work on one stack and not the other. A declared limit with nothing configured to count it logs once and allows, and flare doctor reports it. See rate limits.

flare create --signup=open|invite|closed. (#16)

Sign-up was open with no way to change it. The default stays open — a new resource is admin-only and the first account becomes the admin, so a stranger who signs up reaches nothing — but invite and closed are now one flag, or one line in lib/auth-config.ts afterwards.

Enforced where the account is created, not on each sign-up route: there are four ways to end up with a new account and gating them one at a time is how one gets missed. The first account is always allowed, whatever the mode, or an app could never be set up.

Invitations live in Better Auth’s existing verification table, so switching an app to invite needs no migration. Links are single-use and only the hash is stored, which is why the link is shown once to copy. closed needs a way in, so there is one:

Terminal window
npx flare user:add ada@example.com --role admin

See who may sign up.

flare doctor. (#54)

Terminal window
npx flare doctor

Eleven checks, for when something does not work and you do not know why: Node’s version, the package manager, the app’s @flaredev/core against the CLI, missing environment keys, bindings, whether the descriptors load, generated files that are stale or hand-edited, resources with no policy, a declared rename with no migration, a rate limit nothing is counting, and migrations on disk against the ones the database has run.

It changes nothing, so it is safe on a broken app — the only time anybody runs it. A check that cannot run says so rather than passing, and the exit code is 1 only for a failure, never a warning.

Enum options can have punctuation in them. (#74)

Terminal window
npx flare gen resource Patient --fields 'bloodGroup:enum(A+,A-,B+,B-,O+,O-,AB+,AB-)?'

Blood groups had to be spelled aPos/aNeg and mapped back by hand everywhere they were shown. An option may now use any printable character except a space, a comma, round brackets, a backtick or $.

This turned up something already broken: a Prisma enum member has to be an identifier, so A+ becomes A_2b with the real value in @map — and Prisma’s client speaks member names, not column values. The seed writer translated; the runtime store did not. So on the Next.js stack every hyphenated option was already broken: in-progress has always been the member in_progress, which the admin showed and the validators then rejected. The store now translates in both directions.

flare deploy names the resources that have no policy. (#17)

A resource with no policy file is readable and writable by any signed-in user. gen resource writes an admin-only policy, so this only happens to a resource generated before 0.11.0 or one whose policy was deleted — but nothing said so at the moment it mattered. A warning, not a refusal: an open resource is a real choice. --allow-open says it was meant.

Uploads: three more places to put files, and images shrunk before they are sent.

STORAGE_DRIVER takes uploadthing, cloudinary and disk alongside the default R2 bucket — disk writes to a directory, the way Laravel’s local driver does, which is what a VPS or a single container wants.

Images are now optimised in the browser before upload, by default: a 5.51 MB photo went to 257 KB in a real app, twenty-two times smaller, with the original never leaving the device. Set NEXT_PUBLIC_OPTIMISE_UPLOADS=false to send the original bytes instead. It is read at build time, so changing it is a rebuild rather than a restart.

New apps are scaffolded on vinext 1.4.3, and there is now a written policy for depending on it. (#18)

vinext reached 1.0 in October; apps were still being created against a beta from before the stable line existed. Six packages move as one set — vinext, @vinext/cloudflare, react, react-dom, react-server-dom-webpack and @vitejs/plugin-rsc — because bumping one alone gives you an app that will not install.

Depending on vinext says which version each release is tested against, how an upgrade reaches an app you have already built, and what happens if vinext stalls: the app keeps running, and the fallback is Next.js on Workers through OpenNext.

The first changes that came from somebody else.

Seven pull requests are in this release. All of this is other people’s work, and it is the first time that sentence has been true of Flare.

From @PrinceBadru:

  • Sorting by id works — ?sort=id, ?sort=-id, and defaultSort: { field: "id" } in a descriptor. Both query engines always supported it; the parser and the descriptor validator refused it, and the OpenAPI document did not list it. (#11)
  • The OpenAPI document describes every string format. username, ip, uuid, timezone, locale, currency and postcode had no pattern, format or example in the spec, so a generated client validated less than the server did. The patterns match the runtime validators exactly. (#12)
  • IPv6 validation rejects what it should. :, :::, 12345::1 and oversized groups were accepted by an ip field. Same PR removed a duplicated apikeys branch in the CLI’s generator dispatch. (#12)
  • seedUser is in the generated seed again. The resource seed template destructured insertMany, fake and log but not seedUser, so seeding an account you could sign in as meant writing user and account rows by hand — and getting the accountId mapping wrong gives a 401 that explains nothing. (#7)
  • A trailing slash on BETTER_AUTH_URL no longer silently 403s the next-shop example. An Origin header never carries one, so https://example.com/ — which is what copying from a browser bar gives you — matched nothing. The CLI template already handled this; the example did not. (#9)
  • The Next.js README says how to migrate a production database. Vercel builds do not run migrations, prisma migrate dev cannot make a shadow database on serverless Postgres, and Vercel sets POSTGRES_PRISMA_URL where Prisma reads DATABASE_URL. Three ways to a 500 on a first deploy, now written down where you are standing when you hit them. (#10)
  • The next-shop example is off the vulnerable Next.js line, which Vercel refuses to build. (#8)

From @BillWGong:

  • Three broken links in a checked-in agent skill, pointing beside the file rather than into rules/. (#1)

If you want your name here, the issues are triaged and the roadmap items have write-ups. Nobody starts from a blank canvas.

0.11.030 October 2026

If you are on 0.10.0, upgrade: npm install is broken there.

Not by anything Flare did. @better-auth/passkey@1.7.7 pins its peer @better-auth/utils to exactly 0.4.2; when @better-auth/core@1.7.14 shipped it began pulling 0.5.x through better-call, and npm refuses to resolve the result. Every new app failed its first install. The whole @better-auth/* family moves to 1.7.14, where the peer range is widened to ^0.4.2 || ^0.5.0. Existing apps: change the @better-auth/* and better-auth versions in your own package.json together — they peer-depend on each other by exact version, so a mixed set resolves no better than the broken one.

Next.js is the default stack.

pnpm create flare-framework myapp now gives you Next.js 16, Postgres and Prisma on Vercel. --stack cloudflare gives you the other one, unchanged. Nothing about an existing app changes — the stack is recorded in its package.json and read from there.

A new resource is closed until you open it.

flare gen resource now writes an admin-only policy beside the descriptor, and the first account to sign up becomes the admin. Before this, a resource with no policy was readable and writable by anyone who signed up, and sign-up was open — so the quickstart, followed exactly and deployed, produced an admin panel open to the internet.

The two halves go together: admin-only resources with nobody holding the role is an app that cannot be opened at all, and the usual fix for that is to open everything. Widen a policy with flare gen policy Invoice --roles admin,staff, and grant roles with flare user:role you@example.com admin.

The Next.js stack comes with its services.

A new Next.js app has a docker-compose.yml holding Postgres, MinIO and Redis, and a .env already pointing at them:

Terminal window
docker compose up -d
npx flare migrate
npx flare dev

MinIO speaks S3 like R2 does and the Redis proxy speaks Upstash’s REST protocol, so lib/storage.ts and lib/redis.ts are the same code whether you are on your laptop or on the hosted versions. Without Docker, point the same variables at Neon, R2 and Upstash — the quickstart has both paths. The Cloudflare stack still needs neither: Wrangler simulates D1, R2 and KV locally.

Fields that only apply sometimes.

A billing cycle is required on a recurring project and meaningless on a one-off one. Say so where the field is declared:

Terminal window
npx flare gen resource Project --fields 'kind:enum(oneOff,recurring), billingCycle:enum(monthly,annually) if kind == recurring'

The form shows the input only while the condition holds, the validator requires it only while the condition holds, and the store nulls it when the condition stops holding — that last one is why this cannot live in the admin form. Hiding an input stops the form sending a value; it does not remove the value already in the row. != and in (a, b) work too, and the column is always nullable because the field is genuinely absent the rest of the time. See the field grammar.

Smaller things. Next.js apps no longer ship @better-auth/drizzle-adapter, a Drizzle adapter they never imported and the proximate cause of the install failure above. The template lockfiles are per stack — one file built from a Cloudflare app was being copied into Next.js apps as well, where it pinned packages the app did not have and missed every package it did. Descriptor options render as TypeScript rather than JSON.

0.10.05 October 2026

Money is stored in whole minor units. This one needs you to do something.

Three 7p items add up to 0.21000000000000002 in binary floating point, and a column of doubles drifts the same way — which is how a total comes out a penny short. A money field’s column now holds whole minor units: integer on D1, BigInt on Postgres, so nobody finds a $21m ceiling in a price column.

Nothing above the database changes. The API, the forms, the CSV export, computed values and the typed client all still speak the units people write: you send 19.99, the column holds 1999, you read 19.99 back.

If you have no money field, there is nothing to do here.

If you do, and it was created on 0.9.x, the column holds 19.99 and the new engine will read that as 19.99 minor units. Convert the data when you take the new lib/resource/store.ts, not before and not after. Multiply by the currency’s exponent — 100 for most, 1 for JPY and other currencies with no minor unit, 1000 for KWD and the Gulf currencies:

-- Postgres: the column type changes too
ALTER TABLE invoices ALTER COLUMN total TYPE bigint USING ROUND(total * 100);
-- D1/SQLite: loosely typed, so only the values need moving
UPDATE invoices SET total = CAST(ROUND(total * 100) AS INTEGER);

A money field declared as int is affected the same way unless its currency has no minor unit.

Next.js 16.0.4 carried two critical RCEs, and Vercel refused to deploy it.

A fresh app on the Next.js stack failed on its very first deploy with “Vulnerable version of Next.js detected”. The pin is 16.3.8, which clears all 36 advisories that version was sitting inside — among them unauthenticated remote code execution in the image optimizer and on Windows hosts. Existing apps: change next in your own package.json, since the pin only decides what a new app gets.

Seeding somebody you can sign in as.

seedUser({ email, password, name?, role?, emailVerified? }) is on the seed context now, on both stacks. It writes the user and the credential account together, which is the part that is easy to get wrong by hand: Better Auth finds a password account by its accountId column, and for a credential account that column holds the user’s own id — not the provider’s name, and not the email. Put the email there and the row exists, the hash verifies, and signing in still answers “User not found”.

A trailing slash on BETTER_AUTH_URL made every sign-in a silent 403.

Better Auth compares the request’s Origin against it, and an Origin never carries a trailing slash — so https://example.com/, which is what you get copying a domain out of a browser bar or the Vercel dashboard, matched nothing. lib/auth.ts now trims it and says so on startup, naming the variable and what to set it to, and warns separately for a value with no scheme.

prisma migrate dev can no longer offer to drop your data unanswered.

Smaller things. Sorting a Postgres list by a money field threw Do not know how to serialize a BigInt — cursors are built from the rows as the adapter gave them, and Prisma hands back a bigint. The dashboard sidebar spent more height on group headings than on its links, enough to put a nine-link nav into a scrollbar. The Vercel guide has a first-deploy database checklist: an empty Postgres answers 500 on sign-up, and POSTGRES_PRISMA_URL is not the variable Prisma reads.

0.9.14 October 2026

0.9.0 shipped a lockfile pnpm 12 refuses.

It pinned vite@8.3.2 and @vinext/types@1.0.1, both published that morning. pnpm 12 enforces a 24-hour minimum release age, so every new app failed its first command with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION until those versions aged out. The lockfiles are regenerated past the cutoff.

This repo pins pnpm 11, which has no such policy, so nothing here could have caught it. scripts/verify-lockfile.mjs now does, and getting it right took three attempts: the check needs pnpm 12 by name, a directory that did not just write the lockfile, and a cold store. Each of those, missing, makes the check pass on a file a user cannot install.

0.9.03 October 2026

Policies can say “only your own records”.

flare gen policy Invoice --roles '*' --own userId --own-except admin confines each person to the rows whose belongsTo(User) field holds their id. It applies to reads, writes and deletes, and to the dashboard’s counts.

API keys, for clients with no browser. flare gen apikeys adds @better-auth/api-key and a key-management screen, so a script or a service can call your API without a session.

Soft delete. flare gen resource Ticket --soft-delete stamps deletedAt instead of removing the row, and the dashboard grows a Trash view with restore.

Three more field kinds: markdown (long text with a preview), tags (a free list of labels) and json (a value whose shape is not declared).

Fixes. The CSRF guard refused writes from the right site on Next.js — request.url ignores the Host header, so it now also compares X-Forwarded-Host. flare update deleted files a generator had written. app/ was never tracked by flare diff. The Prisma model is Apikey, not ApiKey, because Prisma lowercases only the first letter and Better Auth looks up prisma.apikey. pnpm build never worked off Windows: an unquoted --filter ./packages/** was glob-expanded by the shell.

0.8.11 October 2026

The Export button never worked.

It asked the store for 500 rows a page; the page-size cap is 100 and applied to everything, so every export came back “Invalid query.” and the button showed an error toast — for every resource, on both stacks, since the feature shipped.

The cap is there to stop a request from outside asking for ten thousand rows at once, and should never have applied to the server paging through its own data on purpose. store.list now takes the limit its caller may use; the export passes its own, and a request from outside is capped exactly as before.

0.8.029 September 2026

Twelve more field types.

Seven string formats — username, ip, uuid, timezone, locale, currency, postcode — each with its own validation, input and display. A time zone is checked against the runtime’s own list rather than a regex, and the input offers that list; a currency code, a UUID and a postcode are normalised on the way in.

Three number shorthands: money, percent and rating. The column stays an ordinary float or int — the shorthand decides the input and the display. Money gets its currency where the number is and refuses negatives unless the descriptor allows them. A rating is a row of stars, bounded 0–5 unless you say otherwise. A percent is left alone, because 150% is a real number.

Two aliases: website for url, alongside the existing phone for tel.

flare rm resource also works on the Next.js stack now. It removed the files and then reached for drizzle-kit to write a drop migration, on an app that has no drizzle-kit.

New pages: relationships, covering one-to-many, one-to-one and many-to-many with how each appears in a form, and file uploads, with an example per category.

0.7.329 September 2026

Cursor paging failed on the Next.js stack for any date-sorted list — which is every list, by default.

A cursor travels through a URL, so a Date crosses it as a number. SQLite stores dates as milliseconds and wants that number back; Postgres wants a Date, and answered “Argument lt: Expected DateTime, provided Int”. The second page of the dashboard’s default view could not load.

The cursor now carries whether its column holds a date — the store knows that from the descriptor, and neither adapter can tell on its own — and the Prisma adapter converts it back before querying.

This had a test, and the test asserted the broken behaviour: a fake delegate accepts a number where Postgres will not, so { createdAt: { lt: 5 } } looked right. Found by running a real query against a real database and reading the SQL.

flare create also no longer stalls for seven seconds before printing anything: it asked whether pnpm existed by running pnpm --version, and now reads PATH.

0.7.228 September 2026

flare diff was only looking at a sixth of the code Flare copies.

It tracked lib/resource and nothing else, so an app could report “up to date” while its storage adapter, cache, API helpers and every dashboard component were an old version. 0.7.1’s upload fix lives in lib/storage.ts — a file flare update could not see, so the fix could never reach an app that already existed. That is the one job these commands have.

They now cover everything under lib/ and components/ — 116 files in a fresh app rather than 7 — minus the copies that are the app’s own: anything rewritten from a placeholder, and lib/auth-config.ts, which is written from the sign-in methods chosen at create time and differs from its template in every app there will ever be.

0.7.128 September 2026

Image uploads on the Next.js stack stored nothing, and said they worked.

Three faults in the same few lines of lib/storage.ts, found by uploading an actual PNG rather than reading the code. The request body arrives as a stream and S3 refuses a PUT it cannot measure — 411 MissingContentLength — so it is buffered now, with the length set by hand, because Next patches the global fetch and that one does not derive content-length even from a buffered body. The adapter never checked the response, so a rejected upload returned 201 and the field stored a key pointing at nothing. And the whole object key went through encodeURIComponent, turning products/2026/09/a.png into a single segment containing %2F.

Verified against a real S3 server: signed, uploaded, read back, byte-identical.

flare user:role also works on this stack now. It looked for a D1 database and failed with “No d1_databases in wrangler.jsonc” — on an app that has no wrangler.jsonc.

0.7.028 September 2026

Routes say what they do.

0.6.0 moved the engine into your app, but a generated route still read as eighteen lines of wiring — createResourceHandlers({ ... }), then four exports. The code was yours and you still had to be told where it was.

A route now contains its own handlers. Each shows the policy check, the cross-origin guard, the JSON read, the store call and the response it builds, in the order they happen. Nothing is repeated between them: the guards are named functions in lib/resource/http.ts, so the CSRF check, the content-type check and the body drain that stops an early return breaking the next request through wrangler’s dev proxy all still live in one place.

createResourceHandlers is still there and still works. Routes generated before this release keep running; regenerate one with flare gen resource <Name> --force to get the longer form, or leave them.

0.6.228 September 2026

The Prisma engines download could fail, and took the install with it.

0.6.1 allowed @prisma/engines to run its postinstall, which downloads engine binaries. When that download fails — a slow link, a proxy, anything — pnpm install failed with it, after the packages were already on disk, so the app looked installed and wasn’t.

It is refused now. A Flare app on the Next.js stack reaches Postgres through a driver adapter and never needs what that postinstall fetches. Confirmed without it: prisma generate, prisma validate, prisma migrate diff and next build all work, and the install is quicker for it.

0.6.128 September 2026

A new Next.js app couldn’t finish its first install.

pnpm install stopped with ERR_PNPM_IGNORED_BUILDS, naming four versions of esbuild. 0.6.0 gave each stack its own list of dependency build scripts to allow, and esbuild was left off the Next.js one — the Vercel CLI depends on it, so every new Next.js app hit this on the very first command after scaffolding.

The two lists are now one, the union of what either stack needs. Naming a package a stack doesn’t install costs nothing, because the entry is never consulted; leaving one out breaks the first thing somebody runs. Both stacks’ entries are pinned by tests, and the fix was confirmed by running both installs rather than re-reading the list.

0.6.028 September 2026

The code that runs your endpoints moved into your app.

Until now a generated route was eighteen lines that called createResourceHandlers, and the eight hundred lines that actually did the work — query parsing, validation, hooks, pagination, constraint errors, the database adapters — sat in node_modules. You could not read them, and you certainly could not change them.

They now live in your repository, at lib/resource/, on both stacks. Nothing generated imports the framework’s server entry any more. Ctrl-click createResourceHandlers and you land in your own code. Nothing is hidden explains the reasoning and states both halves of the trade, because there is a cost: a fix in a later release no longer reaches you on its own. flare diff shows what changed upstream and flare update applies it, refusing to run without --yes.

The store takes rows now instead of a table and a database, so the adapter is named where you can see it — drizzleRows(products, getDb) or prismaRows(prisma.product, prisma). You can tell which database a route talks to by reading the route.

Upgrading takes no work. An app made before the move has no lib/resource/, and its next flare gen resource writes one, naming each file as it goes. Routes generated by 0.5.x keep running untouched, because the package still exports what it always did.

Also in this release: flare dev, build, start and deploy work on the Next.js stack, delegating to Next and the Vercel CLI instead of telling you to run something else. Every app gets a 404 page, an error page and loading skeletons shaped like the page they wait for. File fields show a thumbnail rather than an object key. Dependencies install with pnpm whenever it is on the machine, because an app is around 340 packages and npm takes minutes where pnpm takes seconds. And there is an agent skill, an llms.txt and a prompt on every docs page, for building with an AI.

0.5.128 September 2026

The stack question, which 0.5.0 shipped without.

flare create --stack next worked, so every test passed and the gap stayed invisible: the interactive flare create never asked which stack you wanted. Anyone who answered the prompts rather than passing flags got Cloudflare and was never offered the choice, which is the opposite of the point.

It is the first question now, because it decides the database, the ORM and the host that everything else is built around. The docs learned to mention --stack too — it had been documented on the stacks page and nowhere a new person looks first.

0.5.025 September 2026

A second stack: Next.js, Postgres and Prisma.

flare create --stack next builds an app on Next.js 16, Neon Postgres and Prisma 7, from the same descriptors, with the same dashboard, the same validators and the same policies. What changes is the schema, the migrations and where it deploys. Choosing a stack compares them properly, including what the Next.js one cannot do.

The resource store was split from its database access to make this possible: one interface, about fifty lines, with a Drizzle implementation and a Prisma one behind it. Everything a resource means stayed shared, which is why a unique-constraint error says “Sku is already taken” on both.

A catalogue on Next.js builds one against a real Postgres, with ranked full-text search and honest measurements — including the case where the index is the slower choice.

0.4.123 September 2026

Selling a download, and the things that stopped it working.

The shop tutorial gained a till and digital delivery, the drive tutorial gained folders and multi-file upload, and building both turned up four gaps worth fixing rather than working around.

One deserves naming: every font 404’d in production. Tailwind inlines CSS @import rules, so the fontsource imports never became real requests. Moving them into the root layout emitted all fifty files.

flare deploy --yes arrived, and the file grammar was finally written down.

0.4.023 September 2026

Your code, in plain sight.

Hooks, computed values and endpoints of your own, with a page explaining where each belongs and what regeneration will overwrite.

Tables grew up: a chart, chosen columns, saved views, editing in place, and pagination built for tables that got big. Every record got its own page, every account section got its own, and the sidebar got headings.

The dev server starts in about a minute now, rather than several.

0.3.023 September 2026

One dashboard, and auth screens that say what they want.

The admin became a single coherent area rather than a set of generated pages, and every sign-in screen learned to explain itself — which method, what it needs, what happens next.

Seeding got serious: hundreds of thousands of rows, batched, with unique columns unique by construction rather than by retry.

0.2.022 September 2026

Auth, themes, and an API that documents itself.

Every sign-in method — passwords, magic links, email codes, passkeys, two-factor by app or email, and social providers — chosen at create time and wired for you. Six themes, also chosen at create time.

Field types grew the formats that change both validation and rendering: email, tel, url, domain, country, color, slug, plus select, radio and multiselect.

Every app now serves an OpenAPI 3.1 document generated from its descriptors and its policies, rendered as a reference page, so the documentation cannot drift from the API.

0.1.021 September 2026

The first one.

flare create, flare gen resource, and a resource descriptor that becomes a table, a migration, validators, a REST API, a typed client and admin screens.


The full history is in CHANGELOG.md and the GitHub releases.