When a handler returns a plain string, how does a web framework decide whether that is the response body or a view name?
answer
- one return type, two meanings
- registration decides, not the text
- declared produced type settles it
- template name shipped as the body
- never let user input name a view
basics
~20 sThe handler's registration decides, not the string's contents: a page-oriented handler treats it as a view name for the template layer, a data-oriented one writes it as the body. Declare the shape and the ambiguity disappears.
solid answer
~50 sA returned string is the one return type with two plausible meanings, so frameworks resolve it from the **handler's registration**, never from the text itself. In page-oriented registration the string is a **view name** handed to the template layer to locate and render; in data-oriented registration — a handler declared to produce a data media type, or marked as a body-producing handler — the same string is written straight out as the body. Some frameworks default one way and let you opt into the other; others require you to say. The safe habit is to stop returning bare strings: return a **wrapper or an explicit body type** when you mean bytes, and a **view-model type** when you mean a page. Then the meaning lives in the type, not in a registration detail a reader has to go look up.
go deeper
Know that a returned string can mean two different things and that the handler's registration decides which. Do not assume the behaviour you saw in one codebase is universal.
Name the deciding signals — declared produced media type, a body-producing marker, the registration style, then the framework default — and describe the symptom of each misinterpretation.
Bring up the untrusted-view-name hazard and the operational trap: the wrong interpretation still answers 200, so it passes health checks and surfaces as a user complaint.
Treat it as a convention problem. Decide once whether page and data routes share a registration space, and make the return types state intent so the default never has to be remembered.
## Why a string is the ambiguous case Every other return shape maps cleanly. An object is a body model. Nothing returned means no body. A wrapper says exactly what it carries. A **plain string** is the exception, because server-side web frameworks historically used the same return position for two different jobs: - **"Here is the body."** The characters are the payload — a plain-text answer, a health probe's `ok`, a pre-rendered fragment. - **"Here is the name of the page to render."** The characters are a *lookup key* the template layer resolves to a template, which is then rendered with whatever model the handler exposed. (How that resolution and rendering works is a separate concern from the mapping decision described here.) One return type, two meanings, and the difference is invisible at the call site. ## What actually decides it The deciding input is the **handler's registration**, not the string's content. Frameworks describe registration differently, but the signals are the same family: 1. **The declared produced media type.** A handler declared to produce a data media type has no template step available, so the string is body text. 2. **A body-producing marker on the handler or its group.** Many frameworks let you mark a whole group of handlers as data-returning, which switches every string in it to body text. 3. **The registration style itself.** Page-oriented registration paths run a view-resolution step after the handler; data-oriented paths do not. 4. **The framework's own default** when none of the above is stated — and this is the part that differs most: some frameworks default a bare string to a view name, others default it to body text. Because item 4 differs, "what does returning a string do?" has no universal answer, and an interviewer asking it usually wants to hear you name the registration as the deciding factor rather than guess a default. ## The two failure modes | Intended | Interpreted as | What the client sees | |---|---|---| | Body text | View name | An error from template lookup, or an unrelated page if a template happens to match the name | | View name | Body text | The literal template name as the body, with a success status | The second is the nastier one: the request returns `200` with a short, plausible-looking payload, so monitoring stays green while every page in that route group serves its own filename. ## The security edge If a framework treats strings as view names and a handler returns something derived from user input, the user is choosing which template the server loads. That is a lookup driven by untrusted data, with traversal and information-disclosure consequences depending on how the template layer resolves names. The rule is the same as for any other untrusted lookup key: **never let request data reach the view-name position**; map it through a fixed allowlist of known names first. ## Why frameworks kept the ambiguity at all It is worth understanding why this was not simply designed away. A returned string is the cheapest possible handler in both worlds: a health probe that answers two characters, and a page handler that names its template and nothing else. Forcing either into a wrapper type would tax the simplest case in the name of the rarest bug, so frameworks kept the terse form and pushed the disambiguation into registration, where a route group states its intent once for many handlers. That is a reasonable trade as long as the two kinds of route never share a group -- which is exactly the condition that erodes as a service grows and someone adds a data endpoint next to the page handlers. ## Removing the ambiguity Mature codebases stop leaning on the default entirely: - Return a **typed wrapper** when you mean a body, so status, headers and payload travel as one value and the intent is stated. - Return a **view-model type** — a value the framework recognises as "render this template with this data" — when you mean a page, instead of a bare name. - **Separate the route groups.** Keep page-oriented handlers and data-oriented handlers in different registration groups, so one group's convention never leaks into the other. - **Declare the produced media type** on data routes. It settles both the writer and the string question at once. ## What a good answer sounds like Say that a returned string is ambiguous by design, that the registration — declared produced type, body-producing marker, or registration style — decides which meaning applies, and that frameworks differ on the default. Then give the failing symptom in each direction, and say you would avoid the whole question by returning a type that states its own intent. Mentioning the untrusted-view-name hazard is what separates a middle answer from a senior one.
- An endpoint starts returning the literal text of a template name as its body. What changed?The handler is no longer being treated as page-oriented. Typically the route moved into a data-producing group, gained a declared data media type, or picked up a body-producing marker, so the string that used to be resolved as a view name is now written straight out. The status stays `200`, which is why it survives a smoke test.
- Why is returning a request-derived string risky in a framework that treats strings as view names?It makes the caller choose which template the server loads. Depending on how names are resolved, that is a traversal and disclosure hazard — the attacker probes for templates that exist and gets their rendered contents or a revealing error. Map untrusted input through a fixed allowlist of view names, or never return it in that position.
saying these in an interview costs you the question
- Says the framework inspects the string for a slash or extension
- Claims every framework defaults a returned string to body text
- Thinks the media type of the response decides the meaning after the fact
- Sees no problem returning a request-derived string as a view name
- Cannot explain why a template name appeared as the body