Question
How can I add a custom property to the Express Request object from middleware in TypeScript, so that I can use dot notation such as req.property rather than bracket notation?
app.use((req, res, next) => {
req.property = setProperty();
next();
});
Short Answer
You will learn how TypeScript declaration merging extends Express's Request type, how middleware adds the value at runtime, and how to ensure the property is available before a route uses it.
Concept
Express request objects are ordinary JavaScript objects. A middleware can attach a new property at runtime:
req.property = setProperty();
However, TypeScript checks code against type definitions. Express's built-in Request type does not know that property exists, so TypeScript reports an error even though JavaScript would allow the assignment.
The solution is declaration merging. You add a type declaration that tells TypeScript: “Every Express request may include this property.” TypeScript combines your interface declaration with Express's existing Request interface.
This has two separate parts:
- Type declaration: makes
req.propertyvalid to TypeScript. - Middleware assignment: actually puts a value on the request while the application runs.
The declaration does not create the property by itself. Middleware does.
Mental Model
Think of an Express request as a delivery box moving through a conveyor belt of middleware.
- Middleware can place a label or item in the box:
req.property = value. - TypeScript is the inventory system that tracks what a box is allowed to contain.
- Declaration merging updates the inventory system so it recognizes the new item.
A later route can safely use the item only if the middleware that adds it ran earlier on the conveyor belt.
Syntax and Examples
Create a declaration file, for example src/types/express.d.ts:
export {};
declare global {
namespace Express {
interface Request {
property?: string;
}
}
}
Then add the value in middleware:
import express from "express";
const app = express();
function setProperty(): string {
return "value created by middleware";
}
app.use((req, res, next) => {
req.property = setProperty();
next();
});
app.get("/", (req, res) => {
res.json({ property: req.property });
});
property?: string means the property is optional. This is usually accurate because not every request is guaranteed to pass through the middleware.
If a route is guaranteed to run only after the middleware, you can declare it as required:
interface Request {
property: string;
}
Use a meaningful name in real applications, such as req.user, req.requestId, or req.tenant.
Step by Step Execution
Consider this request flow:
app.use((req, res, next) => {
req.requestId = "req-123";
next();
});
app.get("/orders", (req, res) => {
res.json({ id: req.requestId });
});
Assuming requestId was declared on Express.Request, this is what happens:
- A client requests
GET /orders. - Express creates a request object for this request.
- The first middleware runs and assigns
"req-123"toreq.requestId. next()passes the same request object to the next matching handler.- The
/ordersroute receives that same object, includingrequestId. - The route returns
{ "id": "req-123" }.
If the route were registered before the middleware, or mounted outside the middleware's router path, req.requestId might not have been assigned yet.
Real World Use Cases
Custom request properties are useful for data that middleware prepares for later handlers:
- Authentication: attach the authenticated user as
req.user. - Request tracing: attach a unique
req.requestIdfor logs and error reports. - Multi-tenant applications: attach the selected
req.tenantafter reading the host name or token. - Locale selection: attach
req.localeafter parsing headers or cookies. - Authorization context: attach permissions or roles after validating an access token.
For example, a request ID middleware can make all logs for one HTTP request easy to find:
app.use((req, res, next) => {
req.requestId = crypto.randomUUID();
next();
});
Real Codebase Usage
In production code, developers usually combine declaration merging with middleware ordering and defensive checks.
Authentication middleware
app.use((req, res, next) => {
const user = findUserFromToken(req.headers.authorization);
if (!user) {
res.status(401).json({ error: "Unauthorized" });
return;
}
req.user = user;
next();
});
Protected route with a guard clause
Even when a type declares user as optional, a route can validate it before use:
app.get("/profile", (req, res) => {
if (!req.user) {
res.status(401).json({ error: "Unauthorized" });
return;
}
res.json({ name: req.user.name });
});
Prefer scoped middleware when appropriate
Authentication should often apply only to protected endpoints:
app.use("/api", authenticate);
app.get("/api/profile", profileHandler);
This makes the request flow clearer: routes under /api receive the value after authenticate succeeds.
For data that should not be attached to the HTTP request, consider Express's res.locals. It is designed for values shared across middleware and response rendering, but it is accessed as res.locals.value, not req.value.
Common Mistakes
Adding the value without extending the type
This runs in JavaScript but fails TypeScript checking:
app.use((req, res, next) => {
req.property = "hello"; // Property 'property' does not exist on type 'Request'
next();
});
Add a .d.ts declaration file rather than using as any.
Using as any to silence the error
(req as any).property = "hello";
This removes useful type checking. A typo such as req.proprety would no longer be caught. Declaration merging preserves dot notation and type safety.
Forgetting that the property may be missing
If the declaration uses property?: string, this is unsafe:
const upper = req.property.toUpperCase();
Use a guard or a fallback:
const upper = req.property?.toUpperCase() ?? "UNKNOWN";
Registering middleware after the route
app.get("/", handler);
app.use(addProperty); // Too late for the route above
Register addProperty before any route that needs its value.
Placing the declaration outside TypeScript's project
Make sure the .d.ts file is included by tsconfig.json. If your configuration has an include list, include the source directory containing the declaration:
{
"include": ["src"]
}
Restart the TypeScript server or development process after adding a new declaration file if the editor does not recognize it immediately.
Comparisons
| Approach | What it does | When to use it |
|---|---|---|
| Declaration merging | Extends Express's global Request type | A property is used across multiple handlers or files |
req as any | Disables checking for that access | Avoid; it hides mistakes |
| Local intersection type | Types one particular handler's request | Useful for narrowly scoped code, but less convenient across an app |
res.locals | Stores request-scoped values on the response object | Values intended for later middleware or template rendering |
| Passing function arguments | Makes dependencies explicit | Business logic outside Express handlers |
A local type can look like this:
type RequestWithProperty = Express.Request & {
property: string;
};
But a handler typed with RequestWithProperty does not make Express automatically provide the property. You must still assign it in middleware. For application-wide request data, declaration merging is generally clearer.
Cheat Sheet
// src/types/express.d.ts
export {};
declare global {
namespace Express {
interface Request {
requestId?: string;
}
}
}
// Add the runtime value before dependent routes
app.use((req, res, next) => {
req.requestId = crypto.randomUUID();
next();
});
// Safely read an optional property
if (req.requestId) {
console.log(req.requestId);
}
Rules:
- A
.d.tsfile changes TypeScript's knowledge, not the runtime object. - Middleware must assign the property before another handler reads it.
- Use
?:unless every relevant request is guaranteed to have the value. - Keep request property names specific, such as
user,tenant, orrequestId. - Ensure the declaration file is included by
tsconfig.json.
FAQ
How do I add a property to Express Request in TypeScript?
Create a .d.ts file that augments Express.Request, then assign the property in middleware. Declaration merging enables typed dot notation such as req.user.
Why does TypeScript say a property does not exist on Request?
Express's original type definitions do not include your custom field. TypeScript needs a declaration merge before it accepts the property.
Does declaration merging add the property at runtime?
No. It only changes type information. Your middleware must assign the value during request handling.
Should my custom request property be optional?
Usually, yes. Make it required only when your routing structure guarantees that the assigning middleware always runs first.
Where should the Express declaration file go?
A common location is src/types/express.d.ts. Any location is fine as long as TypeScript includes it in the project.
Can I use req.property without bracket notation?
Yes. After augmenting Express.Request, TypeScript recognizes normal dot notation.
When should I use res.locals instead of extending req?
Use res.locals for values shared during response processing, especially template data. Extend req when the value represents request context such as an authenticated user or request ID.
Mini Project
Description
Build a request-tracing feature for an Express API. A middleware creates a unique request ID, attaches it to every request, and returns it in both logs and API responses. This is a small version of the tracing context used to investigate production errors.
Goal
Create a typed req.requestId property that is assigned by middleware and used by an API route.
Requirements
Requirement 1 Requirement 2 Requirement 3
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.