Values derived from other fields that belong in the output but were never an input.
Overview
The gap it fills
Sometimes a value is not stored at all. It is worked out from other fields. A full name comes from first and last. A total comes from price and quantity. A model can compute these with a normal Python property, but a property does not appear when you dump the model to a dictionary or JSON.
A computed field fixes that. You mark a property with a decorator, and Pydantic includes it in the output and in the schema. The value is still calculated on demand. It is never accepted as input, which keeps the model honest about what the caller can set and what the model derives.
This article explains the gap that computed fields fill and the shape of the decorator. It makes the point that they are output only, and why that is the right design. You will see how they show up in the JSON Schema, and why there are two schemas for a model that has them, one for input and one for output. The last section covers how to leave them out of a dump when you do not want them. They are a small feature that removes a lot of repeated code.
Some values aren't data you receive. They're results of data you receive. A full name from a first and last. A total from a quantity and a price. A reading time from a word count. An is_expired flag from an expiry date and the clock.
Storing them as fields is wrong. They can then disagree with the values they came from. Accepting them from a caller is worse, because the caller can lie.
The obvious answer is a @property. It's half right. A property finds from the current values and can't drift. But it's invisible to output. model_dump() doesn't include it, model_dump_json() doesn't include it, and the schema doesn't know it exists. For a value that exists purely for inner use, that is fine. For one your API consumers need, it isn't.
@computed_field closes the gap: the value is derived, and it appears in the output.
Worth knowing
A plain @property works on the object but never appears in model_dump(). @computed_field is what adds it to the output.
The decorator goes above@property. The other order does not work.
It is output-only: it cannot be passed to the constructor, is not validated, and is recalculated from the current field values each time.
The return annotation is required — it becomes the field's type in the serialisation schema.
It runs on every serialisation, so keep it cheap. There is no caching.
A computed field that can raise turns a valid model into one that cannot be serialised. Guard the edge cases or use a plain property instead.
Computed Fields: Derived Values in the Output
Values found from other fields that belong in the response but were never an input.
A property is invisible to serialisation
An ordinary @property works on the object and does not appear in model_dump(). Often that is right — sometimes it is not.
example_01.pyPydantic
from pydantic import BaseModel
class Module(BaseModel):
title: str
minutes: int
lessons: int
@property
def pace(self) -> float:
return round(self.minutes / self.lessons, 1)
m = Module(title="Vectors", minutes=30, lessons=5)
print("on the object :", m.pace)
print("in the dump :", m.model_dump())
print("in the json :", m.model_dump_json())
Output
computed_field puts it in the output
Add the decorator and the value appears in every serialisation, while still being a normal property on the object.
example_02.pyPydantic
from pydantic import BaseModel, computed_field
class Module(BaseModel):
title: str
minutes: int
lessons: int
@computed_field
@property
def pace(self) -> float:
return round(self.minutes / self.lessons, 1)
m = Module(title="Vectors", minutes=30, lessons=5)
print("on the object :", m.pace)
print("in the dump :", m.model_dump())
print("in the json :", m.model_dump_json())
Output
It is output only
A computed field cannot be supplied. It is not an input, does not appear in the constructor, and is recomputed from the fields every time.
example_03.pyPydantic
from pydantic import BaseModel, computed_field, ValidationError
class Module(BaseModel):
minutes: int
lessons: int
@computed_field
@property
def pace(self) -> float:
return round(self.minutes / self.lessons, 1)
m = Module(minutes=30, lessons=5)
print("computed:", m.pace)
# Supplying it is ignored (or rejected, with extra="forbid"):
m2 = Module(minutes=30, lessons=5, pace=999)
print("supplied:", m2.pace, "<- ignored; it is derived, not stored")
# It follows the fields:
m3 = Module(minutes=60, lessons=5)
print("recomputed:", m3.pace)
Output
Describing it properly
The return annotation becomes its type in the schema. Add a description and it documents itself like any other field.
example_04.pyPydantic
import json
from pydantic import BaseModel, computed_field
class Module(BaseModel):
title: str
minutes: int
@computed_field(description="Reading time with a 20% margin, in minutes.")
@property
def estimated_minutes(self) -> int:
return round(self.minutes * 1.2)
m = Module(title="Vectors", minutes=10)
print("value :", m.estimated_minutes)
print("dump :", m.model_dump())
print()
schema = m.model_json_schema(mode="serialization")
print("in the serialization schema:")
for name, spec in schema["properties"].items():
print(" %-20s %-8s %s" % (name, spec.get("type"), spec.get("description", "")))
Output
Excluding it when you do not want it
It behaves like a field for include and exclude, so an internal calculation can be kept out of a public response.
It runs on every serialisation. That is fine for arithmetic and wrong for anything expensive — and it must never fail.
example_06.pyPydantic
from pydantic import BaseModel, computed_field
calls = {"n": 0}
class Module(BaseModel):
minutes: int
lessons: int
@computed_field
@property
def pace(self) -> float:
calls["n"] += 1
return round(self.minutes / self.lessons, 1)
m = Module(minutes=30, lessons=5)
m.model_dump(); m.model_dump(); m.model_dump_json()
print("serialisations: 3, property calls:", calls["n"])
# And the trap: a computed field that can raise breaks serialisation.
class Risky(BaseModel):
minutes: int
lessons: int
@computed_field
@property
def pace(self) -> float:
return self.minutes / self.lessons # lessons could be 0
r = Risky(minutes=30, lessons=0)
try:
r.model_dump()
except ZeroDivisionError as e:
print()
print("dump failed:", type(e).__name__, "-", e)
print("A model that validated fine can no longer be serialised.")
Decorator order.@computed_field goes above @property. The reverse doesn't work, and the failure isn't self-explanatory.
The return annotation is needed. It turns the field's type in the output schema. Pydantic will complain if it's missing. It isn't merely docs.
It reads the model. Because it takes self, all on the model is on hand. And check has already happened. Those values are real converted types.
Output only, and why that matters
A found field isn't an input. It doesn't appear in the constructor signature, it isn't checked. Passing it's skipped. Or rejected, if you've set extra="forbid".
That skew is the whole point. The value is a function of the model. Allowing it to be supplied would allow it to be supplied *wrongly*. You'd have two sources of truth for one fact.
It also means the value is always current. Change minutes and pace changes with it. It's recomputed on access rather than stored. There's no stale-derived-value bug on hand. This Is the class of bug this feature drops.
The schema, and the two modes
A found field appears in the output schema and not the check one. This Is just right. It's a thing you emit, never a thing you accept.
That gap surfaces in model_json_schema(mode=...). The default, mode="check", shows what the model accepts. Found fields are absent. mode="output" shows what it makes, and they're present.
For a FastAPI response model this is handled for you. The response schema is the output one. Found fields appear in the docs as fields consumers can expect. That is often the reason to reach for the feature in the first place.
You can pass description, title and examples to the decorator. They land in the schema like any other field's metadata. A derived value often needs a description more than a stored one does. The name alone rarely shows the formula.
Excluding them
Found fields behave like fields for include and exclude:
m.model_dump(exclude={"cost_pence", "margin"})
This is worth knowing because derived values are often the ones you don't want in a public response. A margin found from a cost is a just right fair inner field and an unfortunate thing to leak. It's easy to forget that adding a found field changes what each existing output emits.
That is truly a footgun: unlike a normal field. This You had to add to the model on purpose, a found field can be added for one inner purpose and silently appear in each API response the model feeds.
The cost
A found field runs on each output. There's no caching.
For math on a couple of fields that is irrelevant. For anything heavier it isn't. The cost times. Serialising a list of a thousand models runs each found field a thousand times.
If a value is costly and truly needs to be in the output, find it once and store it in a real field. Accepting that you now own keeping it steady. functools.cached_property isn't a substitute here. It doesn't participate in output.
The rule: found fields are for cheap derivations. Math, string formatting, a compare, a lookup in a small dict. Anything that touches I/O or loops over data doesn't belong.
The failure mode to guard against
This is the sharpest edge in the module, and it's easy to walk into.
A found field that can raise turns a valid model into one that can't be serialised.
@computed_field
@property
def pace(self) -> float:
return self.minutes / self.lessons # lessons could be 0
Module(minutes=30, lessons=0) validates just right. each field is the right type and within its constraints. And then model_dump() raises ZeroDivisionError. Not a ValidationError, not at the boundary. At the moment you try to send a response.
The failure is far from the cause and it isn't a check error. None of the error handling you built for check catches it.
Two defences. Make the hopeless state hopeless: lessons: int = Field(gt=0) means the divisor can't be zero and the found field can't fail. Or handle the edge inside the property and return a thing sensible. The first is better. It fixes the model rather than the symptom.
The general principle: a found field should be total. Defined for each mix of values the model permits. If it isn't, either the model is too permissive or the value isn't really a found field.
Computed field, property, or stored?
**Plain @property** when the value is for inner use and shouldn't be serialised. Most helper methods on a model are this.
**@computed_field** when the value is derived, cheap, total, and consumers need it in the output.
A stored field when the value is costly, when it must be preserved as it was at a point in time (a price at the moment of sale, not the current price), or when it truly is an input.
That middle case — historical values — is worth flagging. A found field always reflects the *current* inputs. If you need what the total was when the order was placed, that is data, not a derivation. It belongs in a column.
Naming and the schema
A found field's name is the method name. It appears in the output just as written. That is worth a moment's care. Renaming it later is a breaking change for each consumer.
The return annotation isn't optional, and it does real work. It turns the field's type in the output schema. Consumers and made clients know what to expect.
A description in the decorator is more valuable here than on plain fields. total doesn't explain whether tax is included. minutes doesn't explain the margin applied. The name is a label. Derived values often need a sentence.
Serialisation aliases work too
Found fields accept alias. This Matters if the rest of your API is camelCase:
Without it, a model whose plain fields are aliased will emit one stubbornly snake_case key among them. An alias_generator on the model doesn't apply to found fields. This is one place the whole-model rule needs a per-field top-up.
Ordering in the output
Found fields appear after regular fields in the serialised output, in the order they're defined.
That is stable and predictable, and worth knowing if anything downstream cares about key order. A snapshot test, a diff, a human reading a log. You can't interleave them with declared fields.
A pattern: flags derived from state
One of the most handy uses is turning inner state into a flag a consumer can act on:
The consumer doesn't have to know your expiry rules, parse a date, or get the timezone right. They get a boolean.
Two cautions with this exact shape. It depends on the clock. Serialising the same model twice can give other output. Which breaks snapshot tests and can surprise a cache. And it makes the model's output non-fixed. This Is a property worth being deliberate about rather than discovering.
If determinism matters, pass the link time in rather than reading the clock, or find the flag in the layer that is making the response.
Summary of the trade
A found field buys you a derived value in the output that can never disagree with its inputs, given in the schema, with no storage and no sync.
It costs a function call on each output. It demands that the function be total. Defined for each state the model permits. Because a failure there breaks output of an object that checked just right.
Cheap, total, and truly derived. Those three rules are the whole rule.
Computed Fields: summary
@computed_field above @property, with a return annotation. The value is derived, output-only, recomputed each time. Appears in the output schema.
Keep them cheap, because they run on each dump. Keep them total, because a found field that raises breaks output of a model that checked just right. And note that adding one changes the output of each output the model already feeds. Including the public ones.
One more use worth knowing
A found field is a good place to expose a value the model stores in a form consumers shouldn't have to understand.
Storage often wants one shape and a consumer wants another. A duration in seconds inside, minutes in the response. A status code inside, a readable label outside. Split first and last names, a display name in the output.
The found field bridges that without duplicating state. The stored field remains the single source of truth. The derived view is sure to agree with it because it's found from it.
This is a better pattern than storing both. This Is where the two eventually disagree and no one can say which is right.
Mistakes people make with Computed Fields
Writing one that can raise. A division by a field that might be zero turns a just right valid model into one that can't be serialised. And the failure isn't a ValidationError. None of your check error handling catches it. Constrain the inputs so the hopeless state can't occur.
Putting costly work in one. It runs on each output with no caching. Serialising a thousand models runs it a thousand times. Anything touching I/O or looping over data doesn't belong.
Losing the return annotation. It isn't docs. It's the field's type in the output schema, and Pydantic needs it.
Reading the clock. A field derived from datetime.now() makes output non-fixed. This Breaks snapshot tests and surprises caches. Pass the link time in, or find the flag where the response is made.
Not aliasing it. An alias_generator on the model doesn't reach found fields. A camelCase API ends up with one stubbornly snake_case key. The decorator takes alias for just this.
Adding one without checking what already consumes the model. Unlike a regular field. This You on purpose added to a schema, a found field written for one inner purpose at once appears in each existing output. Including the public ones.
Questions people ask about Computed Fields
What is a computed field? A property decorated with @computed_field that Pydantic includes in model_dump() and the JSON schema. It is derived from other fields and is never accepted as input.
How is @computed_field different from a plain @property? A plain property is invisible to serialisation and the schema. A computed field appears in the dump, in model_dump_json() and in the generated schema.
Do I need to put @property under @computed_field? Yes for a read-only value. Stack @computed_field on top of @property so Python still treats it as a property and Pydantic registers it.
Can a client send a value for a computed field? No. It is output only, so a value supplied in the input is ignored by default. If you set extra="forbid", the unknown key is rejected.
Is the value cached? No. The property runs every time you dump the model. Add functools.cached_property below the decorator if the calculation is expensive and the inputs do not change.
Can I leave a computed field out of one dump? Yes. Pass exclude={"reading_time"} to model_dump, or give the field repr=False to hide it from the printed form.
Deciding, in one line
Cheap, total, and truly derived.
Cheap, because it runs on each output and there's no caching. Total, because a failure breaks the output of a model that checked just right. truly derived, because if a caller could fairly supply it, it's an input with a default rather than a found value.
Fail any of those three and the answer is a thing else. A stored field, a plain property, or a value found in the layer making the response.
Check yourself
0 of 4
Answer without scrolling back up.
Why does a plain `@property` not appear in `model_dump()`?
A property is ordinary Python and Pydantic does not serialise it. `@computed_field` is the opt-in that makes a derived value part of the model's output.
What happens if you pass a computed field to the constructor?
Computed fields are output-only. Allowing them as input would create a second source of truth for a value that is a function of the model.
A computed field divides by another field that can be zero. When does this fail?
The model validates fine - every field is the right type. The failure arrives at `model_dump()` and is not a ValidationError, so validation error handling does not catch it.
Which schema mode shows computed fields?
They describe what the model emits, never what it accepts, so they appear in `model_json_schema(mode="serialization")` - which is what a FastAPI response model uses.
Cheat sheet
Computed Fields
Add read-only fields that compute their value from other attributes and appear in serialized output without being part of input.
Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.