# Setting updated_at yourself in Repo.update_all/3

`Repo.update_all/3` writes the columns you give it and nothing else, so `updated_at` keeps its old value:

```elixir
now = DateTime.utc_now(:second)

Invoice
|> where(id: 1)
|> Repo.update_all(set: [status: "void"])

Invoice
|> where(id: 2)
|> Repo.update_all(set: [status: "void", updated_at: now])

Invoice
|> order_by(:id)
|> select([i], {i.id, i.updated_at})
|> Repo.all()
#=> [{1, ~U[2026-01-01 00:00:00Z]}, {2, ~U[2026-09-22 17:40:25Z]}]
```

Both invoices are void now, but only the second one looks like it changed.

`timestamps()` values are filled in when `Repo.insert/2` or `Repo.update/2` works with a struct or changeset. `update_all/3` sends a single `UPDATE` built from your query, so there's nothing for Ecto to fill in. Anything that syncs, caches or sorts by `updated_at` can miss those rows.

Put `updated_at` in the `set:` list whenever the bulk change should count as a change. `Repo.insert_all/3` and upserts with a query in `on_conflict` don't autogenerate timestamps either.

https://hexdocs.pm/ecto/Ecto.Repo.html#c:update_all/3


---

Created by: almirsarajcic
Date: September 22, 2026
URL: https://elixirdrops.net/d/eGNMsW1q
