v7 is feature-complete along with Lucid schema generation + revamped docs. See what's new.
We have changed how transformer serialization works in API-only projects. Instead of shipping a static serialize method from the framework core, we now provide a reusable serializer that lives inside the starter kits.
This change does not affect Inertia applications. It only impacts projects that were using the ctx.serialize method directly.
API projects need flexibility and consistency in how HTTP responses are shaped. This usually includes wrapping responses under a top-level key (for example data), controlling pagination metadata, and standardizing error responses under an errors key.
At the same time, these responses must remain type-safe. If you choose to wrap responses under a key other than data (for example json), that decision must be reflected both at runtime and at the type level. While runtime configuration is straightforward, TypeScript requires explicit types to ensure correctness.
Instead of hiding the implementation behind ctx.serialize, the serializer is now explicitly created inside the starter kit, within the providers/api_provider.ts file. The ApiSerializer extends BaseSerializer to simplify the implementation and gives you control over a small, well-defined set of API concerns.
import { HttpContext } from '@adonisjs/core/http'
import { BaseSerializer } from '@adonisjs/core/transformers'
import { type SimplePaginatorMetaKeys } from '@adonisjs/lucid/types/querybuilder'
class ApiSerializer extends BaseSerializer<{
Wrap: 'data'
PaginationMetaData: SimplePaginatorMetaKeys
}> {
wrap: 'data' = 'data'
definePaginationMetaData(metaData: unknown): SimplePaginatorMetaKeys {
if (!this.isLucidPaginatorMetaData(metaData)) {
throw new Error(
'Invalid pagination metadata. Expected metadata to contain Lucid pagination keys'
)
}
return metaData
}
}
declare module '@adonisjs/core/http' {
export interface HttpContext {
serialize: ApiSerializer['serialize']
}
}
const serializer = new ApiSerializer()
HttpContext.macro('serialize', function (this: HttpContext, values: any): any {
return serializer.serialize(values, this.containerResolver)
})
This approach allows you to control:
data).To preserve type safety, these are intentionally the only extension points exposed by BaseSerializer. If you need deeper control, you can bypass BaseSerializer entirely and implement your own serialize method tailored to your application's needs.
The following changes have been made to the @adonisjs/assembler package for a more streamlined development experience.
Release notes
VITE_HMR_PORT, allowing multiple AdonisJS applications to run on the same machine without port conflicts.DEV_MODE environment variable into the dev server process, enabling @adonisjs/vite package to start the Vite dev server only when DEV_MODE is present.If you weren't already aware, the report method on the ExceptionHandler was previously not awaited by the HTTP server. This allowed exceptions to be reported to third-party error tracking services in the background.
However, this behavior had an important side effect. If your error reporting relied on AsyncLocalStorage, then that context was no longer available inside the report method. To address this, the HTTP server now awaits the report method, ensuring the request context remains intact during error reporting.
If you're concerned about slowing down responses while reporting errors, you can explicitly defer the reporting logic using the defer helper. For example:
import defer from '@adonisjs/core/services/defer'
export default class HttpExceptionHandler extends ExceptionHandler {
async report(error: unknown, ctx: HttpContext) {
defer(() => Sentry.captureException(error))
}
}
AdonisJS now exposes runtime signals to detect when your application is being executed under the control of an AI coding agent. This is useful for tooling, developer experience, and environment-aware behavior during automated or agent-driven workflows. Release notes
For example: Japa tests runner now uses the
dot-reporter
to reduce the amount of tokens being used by the coding agents when inferring if a test has passed or failed.
You can detect the coding agent using one of the following APIs.
import app from '@adonisjs/core/services/app'
app.detectedAIAgent()
// → 'claude' | 'gemini' | 'copilot' | ... | null
app.runningInAIAgent
// → boolean
The encryption module has been written from the scratch (as
@boringnode/encryption
) to support multiple algorithms and key-rotation. As a result of this, the existing application will have to make the following changes.
appKey export from the config/app.ts file.config/encryption.ts file. Check below for file contents.import env from '#start/env'
import { defineConfig, drivers } from '@adonisjs/core/encryption'
export default defineConfig({
default: 'cbc',
list: {
cbc: drivers.aes256cbc({
id: 'cbc',
keys: [
env.get('APP_KEY')
],
}),
},
})
Everything else should work as expected. Release notes
Lucid now supports auto-generated schema classes, which are derived directly from your database tables and extended by Lucid models.
Instead of manually declaring every column inside models, Lucid follows a migrations-first approach. Migrations continue to be the source of truth for your database schema, and Lucid generates strongly typed schema classes by scanning the database after migrations run. Models extend these generated classes and inherit all column definitions automatically.
This change was introduced to address several pain points in existing workflows:
Cleaner models: Models no longer need to manually declare every database column. Column definitions are generated from the database schema and inherited via schema classes, allowing models to focus on relationships, hooks, and business logic.
Seamless support for existing databases: Projects with legacy or pre-existing databases can generate schema classes without recreating migration history.
Schema classes are regenerated automatically whenever migrations are run, ensuring models stay in sync with the actual database structure. For advanced use cases, projects can further customize generated types using schema rules or refine column types at the model level.
Added a new Form component for Inertia + React applications. Similar to the Link component, it supports passing route and routeParams as props. See the
release notes
for more details.
The component must be imported from the @adonisjs/inertia/react package, not from the official @inertiajs/react package. To enforce this, the default AdonisJS ESLint configuration includes a rule that prefers AdonisJS imports.
import { Form } from '@adonisjs/inertia/react'
export default function PostsUpdate() {
return (
<Form route="posts.update" routeParams={post}>
{/* form fields */}
</Form>
)
}
The Request class is now
HttpRequest
, and the Response class is now
HttpResponse
. Most projects will not be affected by this change since the majority of codebases interact with the HttpContext object rather than importing these classes directly.
However, you will need to update your code if you:
Request or Response classesimport { Request } from '@adonisjs/core/http'
import { HttpRequest } from '@adonisjs/core/http'
declare module '@adonisjs/core/http' {
interface Request {
someMethod(): void
}
interface HttpRequest {
someMethod(): void
}
}
Request.macro('someMethod', () => {})
HttpRequest.macro('someMethod', () => {})
You can now return platform-native Response instances directly from your route handlers and controller methods. AdonisJS will automatically handle these responses.
This feature enables seamless integration with third-party libraries that return native Response objects, such as Vercel's AI SDK, without requiring manual conversion or wrapper code.
Release notes
import { HttpContext } from '@adonisjs/core/http'
import { streamText } from 'ai'
export default class AiController {
async chat({ request }: HttpContext) {
const result = await streamText({
model: yourModel,
prompt: request.input('prompt')
})
return result.toUIMessageStreamResponse()
}
}
This release brings two significant areas of improvement, ie a cleaner, more consistent paginator/transformer API, and major performance gains in the type system. Release notes
The paginator now follows a standardized output shape. Results are always placed under a data property, and all pagination details are grouped inside meta. Earlier, the data property could be renamed and metadata was merged as top-level properties - this is a breaking change.
Recursive type resolution has been optimized significantly, reducing type instantiations from ~20,000 to ~1,700. This results in faster builds and a smoother developer experience when working with complex transformers.
Date automatically convert to strings unless configured otherwise.ExtendedJSONTypes interface lets you opt into retaining specific data types (such as keeping Date objects as actual Date instances during type-level serialization).We've added a new package to the ecosystem:
@adonisjs/content
, a small content management layer for working with typed collections in AdonisJS. We originally built it for our own documentation and blog needs, as well as for loading GitHub sponsors and release data, and it's now available for general use.
The package lets you define collections backed by VineJS schemas and load data from JSON files or GitHub. Each collection exposes a simple query interface, along with support for defining custom views so you can add your own filtering or grouping logic.
Some features that may be useful:
If you're building a docs site, a blog, or anything that needs structured content, this package helps keep everything typed and consistent.
The Inertia-React starter kit now includes a type-safe URL builder that could be used within the frontend codebase. The frontend URL builder is powered by the next version of Tuyau .
We are working on the Tuyau documentation and will share more updates on it soon. However, for now you can use the urlFor helper and the type-safe Link component as follows.
import { Link } from '@adonisjs/inertia/react'
import { Link } from '@inertia/react'
<>
<Link route="new_account.create">Signup</Link>
<Link route="session.create">Login</Link>
</>
import { urlFor } from '~/client'
<>
<Form action={urlFor('new_account.store')} method="POST">
</Form>
</>
The Vite integration receives a couple of quality-of-life improvements in this release.
AdonisJS already assigns a random PORT when multiple apps are running locally, but Vite's HMR server could still collide with other processes. To fix this, the package now supports a VITE_HMR_PORT environment variable. The AdonisJS dev server will automatically set this value when Vite's default port is already taken, preventing HMR conflicts without requiring any manual configuration.
We've also added an IoC binding for the Vite class. This allows you to type-hint and inject Vite wherever you need it, just like other framework classes.
The React Starter Kit has been updated for AdonisJS v7.
This update includes minimal login and signup flows to help you get started quickly with authenticated applications.
You can learn more about the philosophy behind this direction in the article: Building from the Base Up: Rethinking Starter Kits in AdonisJS .
We have tagged a
new release of the @adonisjs/limiter
package that ships with a MultiLimiter. Multi-limiter allows you to create multiple instances of a limiter and act upon them together.
A classic use case for this is implementing dual rate limiting during login. In this case, we want to penalize a user when they try to log in with invalid credentials based on their IP Address and the email address.
Without the MultiLimiter, you will have to create two limiter instances, execute the code to verify the user credentials, and, on failure, consume one point from each limiter instance.
const ipKey = `login_${request.ip()}`
const emailKey = `login_${request.ip()}_${payload.email}`
const ipLimiter = limiter.use({ duration: '1 min', requests: 10 })
const emailLimiter = limiter.use({ duration: '1 min', requests: 5, blockDuration: '20 mins' })
try {
await User.verifyCredentials(payload.email, payload.password)
await ipLimiter.delete(ipKey)
await ipLimiter.delete(emailKey)
} catch (error) {
await ipLimiter.consume(ipKey)
await emailLimiter.consume(emailKey)
}
With MultiLimiter, you can simplify this code as follows. It's not about the lines of code, but more about the simplicity of code.
const ipKey = `login_${request.ip()}`
const emailKey = `login_${request.ip()}_${payload.email}`
const loginLimiter = limiter.multi([
{ duration: '1 min', requests: 10, key: ipKey },
{ duration: '1 min', requests: 5, blockDuration: '20 mins', key: emailKey }
])
/**
* The penalize method will consume one point if the provided callback
* throws an exception. Otherwise, it will reset the points
*/
await loginLimiter.penalize(() => {
return User.verifyCredentials(payload.email, payload.password)
})
The Hypermedia Starter Kit has been updated for AdonisJS v7.
This update introduces base components for common UI elements such as forms, buttons, avatars, and layouts. These components are unstyled and focus on structure and behavior, leaving design decisions up to you.
The starter kit also includes minimal login and signup flows to help you get started quickly with authenticated applications.
You can learn more about the philosophy behind this direction in the article: Building from the Base Up: Rethinking Starter Kits in AdonisJS .
AdonisJS v7 apps now support VineJS v4 out of the box.
VineJS v4 brings several improvements, including support for standard schema, pick/omit helpers for schema re-use, support for defining optional and nullable unions, and a handful of bug fixes.
Existing validation logic will continue to work as before, but you can now take advantage of these new APIs and enhancements.
You can read more about the changes in VineJS v4 in its release notes .
Transformers provide asynchronous, structured serialization for complex data types like models. They also generate TypeScript types that can be used by your frontend clients to ensure end-to-end type-safety between server and client.
We recommend using transformers when building APIs or Inertia applications. Since this is a new concept in AdonisJS, make sure to check out the documentation to learn how to use them effectively.
Youch is the error page you see in the browser during development whenever an error occurs. Youch was initially written in 2016 and started to show rough edges.
Last year, we rewrote Youch from the ground up—making it lighter (on-disk size dropped from 1.3 MB to 700 KB), adding support for error causes, ensuring proper compatibility with ESM modules, and giving it a refreshed UI design.
| Old UI |
|---|
|
| New UI |
|---|
|
Distributed tracing has been something we've wanted to add for a very long time. However, we were reluctant to tie the core of the framework to the OpenTelemetry SDK. While OpenTelemetry is a great project, its SDKs introduce significant overhead and can lead to performance bottlenecks.
Recently, Node.js introduced Diagnostic channels , which are purpose-built for timing actions and tracing critical parts of your application, while being performant and very easy to use. Check out the following links to see how to create and use a diagnostic channel.
Support for tracing is currently in its early stages of development. We have yet to add support for Collectors (which will share traces with monitoring tools) and may also develop our own monitoring tool for use during development.
We've replaced ts-node with a new in-house package called ts-exec — a lightweight, modern JIT compiler for Node.js applications.
ts-node is no longer actively maintained and comes with unnecessary overhead for modern development needs. In contrast, ts-exec is built on top of SWC, a fast Rust-based TypeScript compiler, and targets Node.js 24 and above.
At just 15 KB and roughly 400 lines of code, ts-exec focuses on doing one thing well — executing TypeScript directly without writing compiled files to disk. It's used only during development and can be adopted by any Node.js project, not just AdonisJS.
This change brings faster startup times and a cleaner, more reliable TypeScript execution model for local development.
We've exposed all the internal TypeScript helper utilities used across AdonisJS packages through a single module.
These helpers are now available for both applications and third-party packages, making it easier to reuse the same type definitions and ensure consistency across your codebase.
You can learn more about the available helpers and how to import them in the documentation .
We've introduced Edge Markdown, a new package that lets you render Markdown content directly inside your Edge templates.
Built on top of the MDC (Markdown Components) format, Edge Markdown allows you to use Edge components within Markdown files, enabling a seamless mix of content and dynamic UI elements. It's ideal for documentation sites, blogs, and content-heavy applications that need the power of templating within Markdown.
You can learn more and see usage examples in the official documentation .
The router.makeUrl and router.makeSignedUrl have been deprecated in favor of the new type-safe
URL builder
. Also, the route helper inside Edge templates is deprecated in favor of the urlFor helper.
We've introduced a new type-safe URL builder to replace the deprecated router.makeUrl and router.makeSignedUrl methods.
The new URL builder eliminates the need to hardcode URLs across your app and ensures that all generated links stay in sync with your route definitions. By leveraging route names and TypeScript's type system, it catches broken or invalid links at compile time — before they reach production.
This addition makes URL generation safer, cleaner, and more maintainable across both APIs and Inertia applications.
You can learn more about the new URL builder in the documentation .
We've added support for auto-generated barrel files to help you organize your imports and keep your codebase tidy.
Barrel files are now automatically created for controllers, events, and listeners, allowing you to import them from a single, centralized location. In addition, you can define custom barrel files for your own entities using assembler hooks — giving you full control over how imports are structured across your project.
You can learn more about configuring and extending barrel files in the documentation .
The new type-safe URL builder and RPC client work only with named routes. This means that if a route doesn't have a name, you won’t be able to generate a URL for it.
Naming routes has always been a good practice, but we know it can be tedious to enforce and easy to forget. To simplify things, AdonisJS now automatically generates route names based on the controller and method handling the request.
In the rare case where two routes use the same controller method, an error will be displayed. When that happens, you'll need to explicitly assign a name to at least one of those routes.
The
status pages
rendered by the global exception handler are no longer considered during API requests with Accept header asking for a JSON response. This was more of a bug fix that leads to a breaking change.
Validation errors have always been available under the inputErrors key. To remain backwards-compatible, we previously duplicated the same messages under an additional errors key, which unnecessarily increased session payload size.
In the latest version of the @adonisjs/session package, the deprecated errors key has been removed.
Assembler hooks were introduced to integrate Vite and run it within the same process as your AdonisJS application, allowing you to use hooks to bundle frontend assets when creating a production build.
We have revamped the entire API of assembler hooks, changed their names, introduced new hooks, and also changed the arguments they receive. In most cases, these changes will not impact you until you are maintaining a package and rely on the hooks.
However, the existing applications must update the adonisrc.ts file and make the following changes.
{
hooks: {
onBuildStarting: [() => import('@adonisjs/vite/build_hook')],
buildStarting: [() => import('@adonisjs/vite/build_hook')],
}
}
The following helpers have been removed from the @adonisjs/core/helpers export.
parse-imports package directlyuuids internally, instead of pulling in an external packagenew URL()import.meta.dirnameimport.meta.filenamestringHelpers.toUnixSlashStarting from October 2025, Node.js 24 will become the LTS version. Therefore, all framework packages have been updated to work with the features that are available in Node 24.
Moving along with the platform allows us to eliminate unwanted dependencies and utilize newer APIs, ultimately improving the framework's size, performance, and reducing the surface area for supply chain attacks.
Before creating a new v7 app, ensure you are running Node.js 24 or above.
node -v
# v24.8.0