The AI harness for AdonisJS
Turn your coding agent from producing slop to writing AdonisJS like you would. Flow makes it work the way a careful engineer does, reading the right docs, planning the change, and following your project's conventions instead of guessing at them.
You can't fix the model. You can fix what it reads.
Every AI coding agent runs on a large language model, and that model decides what your AdonisJS code looks like. Here's what you can and can't do about it.
A language model is billions of numerical parameters with no human-readable record of what it knows. The researchers who train these models can't open one up and correct its understanding of Lucid v22. Neither can you. There is no setting, no patch, no version pin.
What the model reads at the moment it writes your code is the one thing you can control. The docs in front of it, the plan it's working from, the conventions it sees in your project. Change that input and you change the output. The discipline has a name now, context engineering.
You can paste the right doc page, write a plan, and remind the agent of your service pattern on every prompt. Most developers try this for a week and stop, because feeding the agent by hand eats the time the agent was supposed to save.
Reference, written for the agent.
When your agent picks up a task, it pulls the matching cookbook from Flow's knowledge base before touching your code. The cookbook set is curated for the major versions of the AdonisJS packages you have installed. Each entry is a single decision, fully spelled out, with the recipe, the edge cases, and the patterns to stay away from.
.flow/docs/ ├── authorization.md ├── controllers.md ├── frontend.md ├── middleware.md ├── transactions.md ├── validation.md ├── vine/ │ └── types/ ← string, number, object, ... └── ...
# Authorization: Inertia React ## Recommended Defaults - Identify the subject first. Global apps use User. Scope-membership apps use the membership entity, TeamMember or OrgMember. - Define permissions in start/permissions.ts via definePermissions(...). Export PermissionKey for type-safe references. - Apply middleware.authorize('key') on the route for pure permission-key gates. No bouncer.* call in the controller. - For ownership or conditional state, use a Bouncer policy. Gate the key first, then check the runtime condition. - Authorize before validating. Call bouncer before request.validateUsing(...). ## Avoid by default - Encoding ownership in permission keys. No post.editOwn. Use a Bouncer policy. - Inlining ownership checks in controllers. - Hardcoding permission keys outside permissions.getKey(...).
# Controllers: Inertia React ## Recommended Defaults - Scaffold via node ace make:controller. One class per file, default-exported as <Name>Controller. - Type actions with HttpContext first. Destructure only what the action needs. - Wire routes via [controllers.<Name>, 'action'] from #generated/controllers. The barrel is auto-maintained. - Validate at the controller boundary with request.validateUsing(<validator>). - Form submissions end with session.flash(...) then response.redirect().toRoute(name). ## Avoid by default - Reading the request body via request.all() or request.input(...). Use validateUsing. - Generating a resource controller for one or two unrelated actions. - Splitting one resource into two router.resource calls to separate public and gated actions. Apply per-action middleware on a single resource call instead.
# Frontend: Inertia React ## Recommended Defaults - Generate every page with node ace make:page <path>. - Type page props with InertiaProps<{...}> from ~/types. Reference transformer shapes via Data.<Resource> from @generated/data. Never redeclare inline. - Navigate with <Link route="..." routeParams={...}> from @adonisjs/inertia/react. Method derives from the route. Never pass method. - Submit with <Form route="...">. Render-prop API exposes errors, processing, progress. - Validation errors propagate from server flash. Never duplicate Vine validators on the page. - Encode filters and pagination in the URL query string. Do not mirror into useState. ## Invalid patterns - Importing Link or Form from @inertiajs/react. Use @adonisjs/inertia/react. - Installing react-router, @tanstack/react-router, or any client router. Inertia handles routing. - Introducing redux, zustand, jotai, recoil, or any global store for server-derived data. Controller props are the source of truth. - useEffect + fetch to load page data. Use inertia.render props instead.
# Middleware ## Recommended Defaults - Use server.use([...]) for middleware that must run on every HTTP request, even when no route matches. - Use router.use([...]) for middleware that should run only after a route matches. - Use export const middleware = router.named({...}) for route-level middleware applied via .use(). - Lazy-import middleware files via () => import('#middleware/...'). - Type handlers with HttpContext first and NextFn second. - Put pre-handler logic before await next(). Put post-handler logic after. ## Invalid patterns - Injecting dependencies as extra handle(...) parameters. DI is constructor-only. - Registering middleware classes directly in start/kernel.ts without lazy import. - Defining named middleware inline inside start/routes.ts.
# Transactions ## Recommended Defaults - Default to db.transaction(async (trx) => ...). Returns the callback's value, rethrows on rollback. - Build every query off trx, never db. Queries through db inside the callback run outside the transaction and commit immediately. await db.transaction(async (trx) => { await user.useTransaction(trx).save() await profile.useTransaction(trx).save() }) - Pass { client: trx } to model finders so reads see the transaction's writes. - Register post-commit side effects via trx.after('commit', cb). A rollback never produces orphan side effects. ## Avoid by default - Firing side effects inline inside the callback. Use trx.after('commit', cb). - Time-consuming work inside a transaction. Long transactions hold pool slots and row locks.
# Validation ## Recommended Defaults - Scaffold via node ace make:validator. Singular naming, under app/validators/. - Pre-compile with vine.create({...}). - Export one named validator per use case, createUserValidator, updateUserValidator. - Fields are required by default. Use .optional() to allow undefined, .nullable() to require the field but allow null. - Apply request.validateUsing(<validator>) in controllers. Body at top level, headers under a nested vine.object key. - Ship VineJS default messages. Customize only when explicitly asked. ## Avoid by default - Synthesizing rule names from training data. If a rule is not in the type catalog, register a custom rule via the Vine extension API. - Route params and cookies through validators. Read via params or request.cookie directly.
Plan first, then write.
Planning-first is where serious AI engineering is heading. An agent in execution mode is eager to finish. It skips the reading, picks the first approach that looks plausible, and hands you a diff. Flow inverts that. Before any file changes, the agent produces a plan against your AdonisJS project, you approve it, and only then does it write code.
.flow/changes/create-team ├── brief.md ← PM brief. Stack agnostic ├── blueprint.md ← implementation plan ├── assert.md ← testing plan └── deltas/ ← spec deltas └── teams.md
--- affects: [teams] --- # Create Team — Brief ## Entry point, user goal, status A new user enters from signup and wants to start using the app immediately. This is a new capability extension. Signup now creates a personal team workspace automatically, without asking the user to name it. ## The journey, step by step 1. The user sees the signup experience and provides the existing account details. 2. The user submits signup. The app accepts the account details and creates a team workspace for the user. 3. The team name is generated from the user's full name when present. If the full name is missing, the team name is generated from the email address. 4. The new user becomes the owner of the created team. 5. On success, the user lands on the dashboard already operating inside the new team workspace. ## Constraints the journey places on implementation - A team must be created automatically for every successful signup. - The user must not be asked to provide a team name during signup. - The newly signed-up user must become the owner of the generated team. - If the team cannot be created, the user must not end up with an account that has no team.
--- built: 2026-05-18 --- # Create Team — Implementation Plan > Task type: extension > Stack: AdonisJS + Inertia React > Database: SQLite ## Logical schema ```dbml Table teams { id integer [pk, increment] name varchar [not null] owner_user_id integer [not null, ref: > users.id] created_at timestamp [not null] updated_at timestamp [null] } Table team_memberships { id integer [pk, increment] team_id integer [not null, ref: > teams.id] user_id integer [not null, ref: > users.id] role tinyint [not null] created_at timestamp [not null] indexes { (team_id, user_id) [unique] } } ``` ## Service design node ace make:service personal_workspace_provisioner - PersonalWorkspaceProvisioner - provisionForSignup(payload) creates the user, generated personal team, and owner membership in one managed transaction. - Does NOT validate request input, log the user in, write session, or own the team name formatting rule.
--- built: 2026-05-18 --- # Create Team — Test Plan ## Behavior coverage | Behavior | Status | |---------------------------------------|--------| | Visit /signup, no team-name rendered | tested | | Valid signup, lands on /dashboard | tested | | Valid signup, user row exists | tested | | Full name, team named <name>'s team | tested | | Email fallback, team named <email>'s | tested | | Owner membership row exists | tested | | Session stores current team id | tested | ## Test list (ordered) 1. signup does not ask for a team name 2. creates an owned team from the full name and lands on the dashboard 3. stores the created team as the current workspace 4. creates an owned team from the email when full name is blank ## Order rationale NewAccountController.create comes first because it proves the signup surface remains unchanged before the store tests drive that same form. NewAccountController.store follows because it covers the new durable signup outcome, user, team, owner membership, dashboard redirect, and current team session state.
--- capability: teams change: create-team synced: 2026-05-18 --- # Delta — teams ## ADDED ### Automatic team creation during signup - Given a new user is signing up with valid account details, when they complete signup, then a team workspace is created for them automatically. - Given the new user has a full name, when their team workspace is created, then the team is named <full name>'s team. - Given the new user has no full name, when their team workspace is created, then the team is named <email>'s team. - Given team creation cannot be completed, when the user attempts signup, then signup does not leave the user with an account that has no team. ### Signed-up user owns the created team - Given a new user completes signup and their team workspace is created, when they reach the dashboard, then they are operating inside that team workspace as its owner. ## MODIFIED _None._ ## REMOVED _None._
Encode your own best-practices.
Every codebase has decisions the framework doesn't make for you. Sticking to seven conventional controller methods. Eliminating helpers by introducing roles. The shape of a service. Flow ships a starter set of skills for the calls every AdonisJS app makes, and gives you the format to add the ones your team cares about. The agent applies them every time it writes code.
Stick to seven conventional controller methods. No custom actions, no business logic in the controller body.
When helpers pile up around a concept, that's a missing role. Pull the scattered behaviour into an object that owns it.
Get in early
Plus is being built in the open. Each piece ships as it's ready. Join now and you get access to everything as it lands, a private Discord where you see the process unfold, and a say in how things get built.
That rate stays locked in as long as you're subscribed. If you ever cancel, you keep every component, skill, and runbook you've used. It's your code. You just lose access to new updates, docs, and the private Discord.
- Private Discord access
- Persona — user management package
- Permissions — RBAC package
- Documentation
- Kit components
- Flow - AI harness
- Extended Kit components catalogue
- Flow enhancements
- Templates
Frequently asked questions
In the FAQs below, "Plus materials" refers to everything included in AdonisJS Plus: Kit components, Flow AI skills, sub-agents, runbooks, packages, and documentation.
What is your refund policy?
If you're unhappy with your purchase for any reason, email virk@adonisjs.com within 30 days and we'll refund you in full, no questions asked.
What can I use right now?
First set of Kit components, Flow - AI harness layer, premium packages, and access to private Discord server is available
What happens if I cancel?
You keep every component, skill, and runbook you've used. It's your code. You lose access to new updates, documentation, and the private Discord. Your access continues until the end of your billing cycle.
Do I keep the early bird rate if I cancel and come back?
No. If you cancel, you keep access until the end of your billing cycle. After that, the early bird rate is gone. You'd resubscribe at the regular $249/year.
Is there a monthly payment option?
No, it's annual only.
Do I need a new subscription for every project?
No. Your subscription covers unlimited projects.
Can I use Plus materials in commercial projects?
Yes. You can build unlimited commercial projects, whether it's a public website or a paid SaaS product. However, redistributing Plus materials in any form is not allowed.
Can I use Plus materials for client projects?
Yes. You can create custom websites and products for your clients. However, packaging Plus materials in a template or bundle that is resold to multiple clients is not allowed.
Can I use Plus materials in an open-source project?
Yes. You can create open-source projects that use Plus materials in the final product. However, redistributing Plus materials for learning, as a boilerplate, or as a starter kit is not allowed.
Can I resell or redistribute Plus materials?
No. Reselling and redistributing Plus materials is not allowed. Creating starter kits, boilerplates, or publishing them publicly without using them in a real project is not allowed.
Which frontend framework does Kit use?
Kit components are not tied to any particular frontend framework. We share unstyled templates for Vue, React, and Edge. You're expected to modify them for your project.
What version of AdonisJS is supported?
Everything in Plus is built for the latest version of the framework, which is AdonisJS v7.
Does AdonisJS stay free?
Yes. AdonisJS is free and open source. Nothing changes. Plus is a separate commercial product that funds the ongoing work on the framework.
Do I need Plus to use AdonisJS v7?
No. v7 is a free, open source release. Plus is completely separate.
Is there a team license?
Not yet. Right now Plus is a personal license. A team license is coming later. If you want to be notified when it's available, email virk@adonisjs.com.
How do I access the Discord after buying?
You'll receive a welcome email with the invite link right after purchase.
Do you offer technical support?
Yes. Email virk@adonisjs.com to learn more about the support program.