Skip to main content

How to Use Route Constraints in ASP.NET Core Minimal APIs

· 6 min read
Jagdish Kumawat
Founder @ Dewiride

{id:int} in a route means "this route only matches if the value is an integer". The difference from a plain {id} shows when someone sends /categories/abc: one answers 404, the other 400. This guide tries every common constraint with real requests, and covers where Microsoft says to stop using them.

Prerequisites​

  • An ASP.NET Core minimal API. The examples use .NET 10, with problem details set up, so errors have a body.

Step 1: Add a Constraint​

The constraint goes after the parameter's name, with a colon:

src/Dewiride.Inventory.Api/Features/Categories/CategoryEndpoints.cs (excerpt)
group.MapGet("/{id:int}", GetAsync).WithSummary("Get one category");

An integer reaches the handler, and finds a row or not:

Response (GET /api/v1/categories/1)
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"id": 1,
"name": "Cables",
"createdAtUtc": "2026-11-04T10:48:37.000261Z"
}

Step 2: See What Happens to Other Values​

Text doesn't match the route, so the handler never runs:

Response (GET /api/v1/categories/abc)
HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"traceId": "00-65c2632d9218b425d27d723836600bc6-1e5b0f5c4d29c6d7-00"
}

A number too big for an int doesn't match either:

Response (GET /api/v1/categories/99999999999)
HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"traceId": "00-32ebf32639fbf019be61d78c4781ef6e-2b97d1ca4b95f9f5-00"
}

Step 3: Compare with No Constraint​

Here's the same int parameter, with no constraint:

A temporary endpoint
d.MapGet("/noconstraint/{id}", (int id) => Results.Ok(new { id }));

A number works as before. Text now matches the route, fails to convert, and answers 400:

Response (GET /demo/noconstraint/abc)
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Bad Request",
"status": 400,
"traceId": "00-915abb625271bf36750959e23709c5d4-2104321bc107d649-00"
}

A terminal comparing two requests with the value abc: the route with the int constraint answers 404 Not Found, and the route with no constraint answers 400 Bad Request.

Route/5/abcThe handler runs for abc?
{id:int}200404No
{id} with int id200400No

So what's the point of a constraint? The handler is safe either way, because the parameter is an int. What changes is which answer the client gets, and whether another route can match instead. With a constraint, two routes can sit side by side, and the value decides between them:

Temporary endpoints
d.MapGet("/things/{id:int}", (int id) => Results.Ok(new { matched = "the int route", id }));
d.MapGet("/things/{name}", (string name) => Results.Ok(new { matched = "the text route", name }));
Response body (GET /demo/things/5)
{
"matched": "the int route",
"id": 5
}
Response body (GET /demo/things/abc)
{
"matched": "the text route",
"name": "abc"
}

Step 4: Try the Other Constraints​

They can be combined, separated with colons. The routing page has the full table. These are the ones I ran:

Temporary endpoints
d.MapGet("/min/{id:int:min(1)}", (int id) => Results.Ok(new { id }));
d.MapGet("/guid/{id:guid}", (Guid id) => Results.Ok(new { id }));
d.MapGet("/sku/{sku:regex(^[A-Z0-9-]+$)}", (string sku) => Results.Ok(new { sku }));
RequestAnswerWhy
/demo/min/5200An integer of at least 1
/demo/min/0404Below min(1)
/demo/guid/3f2504e0-4f89-11d3-9a0c-0305e82c3301200A GUID
/demo/guid/5404Not a GUID
/demo/sku/CBL-USBC-2M200Matches the pattern
/demo/sku/cbl%20usbc404The space doesn't match

Don't Use Them to Validate​

That 404 for min(1) looks handy, but the routing page warns against it: "Don't use constraints for input validation. If constraints are used for input validation, invalid input results in a 404 Not Found response. Invalid input should produce a 400 Bad Request with an appropriate error message."

A 404 tells a client the address doesn't exist, not what's wrong with the value. The same section describes a constraint as making "a true or false decision about whether the value is acceptable" for that route. So use {id:int} to say which route handles which shape of address, and AddValidation to check values and explain what's wrong.

This API uses {id:int} on its IDs. /categories/abc answering 404 is also true: no such category exists.

A route that matches the address, but not the method, answers 405, and the Allow header lists what would work:

Response (PATCH /api/v1/categories/1)
HTTP/1.1 405 Method Not Allowed
Content-Type: application/problem+json
Allow: DELETE, GET, PUT

{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.6",
"title": "Method Not Allowed",
"status": 405,
"traceId": "00-8dc1a63b80d8e4e7d6f41dff15c5ebb0-6b45158ad376328c-00"
}

Common Mistakes​

MistakeWhat happensDo this instead
Using min or range to validate inputClients get a 404 with no explanationUse validation for values
A constraint that doesn't match the parameter's typeRequests pass the route, then fail to bindKeep them the same type
Regex tokens in a route stringThe page says they must be escapedSee its regex tips
Expecting {id:int} to allow large IDsAnything over int's limit answers 404Use {id:long} with long id

FAQ​

Should my API use constraints at all?​

For IDs, {id:int} is common and harmless. Beyond separating routes, use validation.

Does the int constraint protect me from SQL injection?​

Parameters already do that. A typed int id and EF Core's parameters mean the text never reaches the SQL.

Conclusion​

A constraint decides whether a route matches, so a value that fails answers 404, and the handler never runs. A plain int parameter answers 400 instead. Use constraints to tell routes apart, and validation to check values, as the routing page advises.

Additional Resources​

Stay Updated

Subscribe to our newsletter for the latest tutorials, tech insights, and developer news.

By subscribing, you agree to our privacy policy. Unsubscribe at any time.