[FromQuery]
Bind query-string values to parameters and filter objects, rename keys such as page-size, and handle missing, repeated and malformed values.
[FromQuery(Name = "page-size")] int pageSize = 20 | [FromQuery] OrderFilter filter- Added in .NET ASP.NET Core 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$Nameoptional | string?= null (the parameter name) | The query-string key to read when it differs from the parameter name, for example "page-size" or "sort_by". Keys are matched case-insensitively. |
Return value
n/aBinds the parameter (or each property of a complex type in MVC) from the query string, converting text with invariant culture.
Errors
Nothing is thrown for a bad value: MVC records a model-state error (400 under [ApiController]); minimal APIs return 400. A minimal API [FromQuery] on a complex type without TryParse fails when the endpoint is built.
Changelog
- PHP ASP.NET Core 6.0Minimal APIs honour [FromQuery]; simple parameters that are not route values are inferred from the query string.
- PHP ASP.NET Core 7.0Minimal APIs bind arrays and StringValues from repeated query keys.
Query-string binding is what both MVC and minimal APIs fall back to for simple parameters that are not route values. You add [FromQuery] for three reasons: the key is not a valid C# name (page-size), a complex type in a controller must bind from the query instead of the inferred body, or you want the source to be obvious to the next reader.
Missing, repeated and malformed values
| Request | Controller, int page | Minimal API, int page |
|---|---|---|
?page=3 | 3 | 3 |
| key missing | 0, no error | 400 (required) |
?page=abc | model-state error, 400 under [ApiController] | 400 |
?status=paid&status=shipped into string[] | both values | both values |
That first difference catches people moving code between the two models. In a controller a missing value type quietly becomes its default; add a default (int page = 1), make it nullable, or mark it [BindRequired] when absence must be an error. In a minimal API a non-nullable parameter without a default is required.
Culture and formats
Query values are parsed with the invariant culture, unlike form values which use the current culture. ?from=2026-10-01 binds to DateOnly and DateTime predictably; 01/10/2026 means January 10th. Document ISO 8601 for dates and a dot as the decimal separator.
Filter objects
In a controller, [FromQuery] OrderFilter filter binds each public settable property from the key of the same name (or filter.Status). Minimal APIs do not bind complex types from the query; use [AsParameters] on a record whose members carry their own sources.
Production failure modes
- An unbounded
?pageSize=100000turns one request into a full table scan. Validate a range or clamp it. - Query strings end up in access logs, proxy logs and browser history. Never accept tokens, emails or other personal data as query parameters.
- Renaming a C# parameter renames the public key unless
Namepins it.
Example
When to use it
- Accept ?page=2&page-size=50 on GET /api/orders with defaults when either key is missing
- Filter orders by several statuses with ?status=paid&status=shipped bound to a string array
- Reject GET /api/reports/revenue without ?year= instead of producing a report for year 0
Frequently asked questions
Why is my int query parameter 0 instead of an error?
Controllers bind a missing value type to its default and record no error. Use [BindRequired], make it nullable with [Required], or give it a meaningful default.
Can minimal APIs bind a class from the query string?
Not with [FromQuery] on the class. Use [AsParameters] on a record or class whose properties are bound individually, or give the type a static TryParse or BindAsync method.
Are query keys case-sensitive?
No. ?Page=2 and ?page=2 bind the same parameter. Values are case-preserved, so compare them case-insensitively where that matters (status=Paid vs paid).
More examples
Required query values in a controller
<code>[BindRequired]</code> makes absence of the key a model-state error, so <code>[ApiController]</code> answers 400. <code>[Required]</code> would not help on a non-nullable <code>int</code>: the value is never null, so the validator always passes. Use <code>[Required]</code> on <code>int?</code> or <code>[BindRequired]</code> on <code>int</code>.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Shop.Api;
[ApiController]
[Route("api/reports")]
public class ReportsController : ControllerBase
{
// Without [BindRequired], a missing ?year= silently becomes 0 and the report covers year 0.
[HttpGet("revenue")]
public IActionResult Revenue([FromQuery, BindRequired] int year, [FromQuery] int? month) =>
Ok(new { year, month, revenue = 182_340.75m });
}
Clamp paging in one place
Nullable parameters make every query key optional in a minimal API, and a small value type turns them into safe numbers. Clamping instead of rejecting suits public list endpoints; reject with 400 when a silently smaller page would surprise a client that iterates pages.
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/products", ([FromQuery] int? page, [FromQuery(Name = "page-size")] int? pageSize) =>
{
var paging = Paging.From(page, pageSize);
return TypedResults.Ok(new { paging.Page, paging.Size, skip = paging.Skip });
});
app.Run();
public readonly record struct Paging(int Page, int Size)
{
public const int MaxSize = 100;
public int Skip => (Page - 1) * Size;
public static Paging From(int? page, int? size) =>
new(Math.Max(page ?? 1, 1), Math.Clamp(size ?? 20, 1, MaxSize));
}
Repeated keys versus comma-separated values
Array binding uses repeated keys. A comma-separated value arrives as one string, so either document the repeated-key form or accept a single string and split it yourself; mixing both styles across endpoints is a common source of client bugs.
@api = http://localhost:5080
### Binds string[] status = ["paid", "shipped"]
GET {{api}}/api/orders?status=paid&status=shipped
### Binds ONE element: "paid,shipped" (no splitting on commas)
GET {{api}}/api/orders?status=paid,shipped
### Keys are case-insensitive: binds pageSize = 50
GET {{api}}/api/orders?PAGE-SIZE=50
Discussion