Summary#
A row stamp is a field the platform writes for you when a row is saved. You declare the field, mark it with one of four attributes, and never assign it again:
| Attribute | Type | Written |
|---|---|---|
[CreatedAt] | DateTime | the instant the row's unit of work committed — once, when the row is created |
[CreatedBy] | Guid? | the id of whoever was signed in — once, when the row is created |
[ModifiedAt] | DateTime | the instant of the last commit that wrote the row, its creation included |
[ModifiedBy] | Guid? | the id of whoever made that write |
Nothing is stamped that you did not declare. An entity with no stamp has no such column — the platform adds nothing to a row behind your back.
Signature#
entity Order {
[CreatedAt] DateTime Placed; // the field's name is yours
[CreatedBy] Guid? PlacedBy; // a WHO stamp is a nullable Guid: a system write has nobody signed in
[ModifiedAt] DateTime Changed;
[ModifiedBy] Guid? ChangedBy;
}Description#
When does the platform write them?#
On the commit that saves the row. [CreatedAt] and [CreatedBy] are written once, when the row is created;
[ModifiedAt] and [ModifiedBy] on that commit and on every commit that changes the row after it. The instant is the
commit's own — the same for every row it writes, and the pinned clock in a test (see TestClock.Advance and TestClock.Set).
A write the system makes with nobody signed in — a scheduled job, a migration — writes the instant stamps and leaves the
Guid? ones as they were.
entity Sighting {
[Required, MaxLength(200)] string Species;
int Count;
[CreatedAt] DateTime Logged;
[CreatedBy] Guid? LoggedBy;
[ModifiedAt] DateTime Changed;
}
void LogSighting(string species, int count) {
new Sighting { Species = species, Count = count }; // no stamp is set here — the commit writes them
}
List<Sighting> Newest() {
return Sighting.OrderByDescending(s => s.Logged).Take(20).ToList();
}
int LoggedToday() {
var today = DateTime.UtcNow.Date;
return Sighting.Where(s => s.Logged >= today).Count();
}Can my code write a stamp?#
No — and the compiler says so rather than letting the value vanish. A stamp in an object initializer is refused,
because the commit would replace it; so is any assignment to a [ModifiedAt]/[ModifiedBy] field, which every
commit rewrites. To make a row as someone — a test world's rows, say — create it inside runas (Ada) { … }.
A [CreatedAt] assigned on a row that already exists is kept: that is how a test backdates something to see it age.
A client, an API call and an MCP tool cannot write a stamp either: a value sent for one is ignored, on every surface.
Who owns a row?#
[CreatedBy] is the field an ownership rule reads, and it is safe to read because nobody can forge it — the platform
writes the signed-in person's id over whatever was sent:
[Principal]
entity Member {
[Required, MaxLength(200)] string Email;
security { allow read where Id == user.Id; }
}
entity Note {
[Required, MaxLength(200)] string Text;
[CreatedBy] Guid? Author;
security {
allow read, update, delete where Author == user.Id;
allow create when IsAuthenticated;
}
}What if I name a field CreatedAt without the attribute?#
Then it is an ordinary field: you set it, and the platform leaves it alone. That is the shape for a date that comes from somewhere else — the moment an imported order was placed in the shop it came from, say.
Reading a stamp an entity does not declare is a compile error that names the line to add:
entity 'Ticket' has no property 'CreatedAt' — the instant a row was created is not a column the platform adds
behind your back: declare it on 'Ticket' with `[CreatedAt] DateTime CreatedAt;`, and the platform writes it.Where can a stamp go?#
On a stored entity's field of the right type: DateTime (or DateTime?) for an instant, Guid? for a person. One
field per stamp, one stamp per field. A class has no rows to stamp, and an external entity's rows are written by its
data source, so both refuse the attributes by name.
See also#
- entity: what an entity is, and the
Idevery one has - security { }: rules that read a row's fields,
[CreatedBy]among them - TestClock.Advance and TestClock.Set: a pinned clock is the instant a commit stamps