Defining read-only fields in Ecto Schema

I have an Ecto Schema which needs to restrict certain fields from being updated (for business reasons). What is the best way to do that? I saw this old issue in git: but I couldn’t get before_insert or before_update to work anywhere (a full example would have helped). Is this even possible with the current version of Ecto? How are others accomplishing this?


The before_* hooks have been removed in the very early days.

But perhaps you can build something around validate_change/3?

validate_change(cs, :read_only_field, fun :read_only_field, _ -> [read_only_field: "not allowed to change"] end)
1 Like

That’s a good idea. Won’t the validate_change field raise an exception (error?) if a read-only field is submitted? For educational purposes, how could this be accomplished in a way that would let a user submit read-only fields, but the app would quietly omit them from the changeset?

No, it will invalidate the Changesset and append to the error list.

Don’t do this. Tell the user that (s)he is wrong, do not simply alter his/her provided data, (s)he will complain. But it should be possible as well, but you’ll need to do some extra work, as ecto isn’t build for that.

1 Like

Just remove the “read only” fields from your Ecto.Changeset.cast or Ecto.Changeset.change function.

1 Like

Hmm… this is a bit trickier than I thought since I need this to occur only on UPDATES. By defining fields using validate_change() it looks like both inserts and updates get checked. When I inspect the changeset variable, it doesn’t seem to know what action is being taken until AFTER the database gets hit.

This is a perfect scenario for two changeset functions!

def changeset(struct, params) do
  |> cast(params, list_of_all_fields())
  |> validation_one()
  |> validation_two()

def update_changeset(struct, params) do
  |> cast(params, list_of_updateable_fields())
  |> validation_three()
  |> validation_four()

Never feel like you have to pack everything into a single changeset function. You can always create as many as you need for a given module.


Ah, that makes sense, thank you! I didn’t think of that. Great, so this works like I need it to now: the data comes in and I store it, but now only certain fields are editable.