When working with Ecto in Elixir, you often update a changeset using Ecto.Changeset.put_change/3. But in some cases — especially in Phoenix LiveView forms — you might notice that a change doesn’t "stick" if the value hasn’t actually changed from the original. That’s where force_change/3 comes in.

Let’s take a look at the difference and when you should use each.

put_change/3: the smart setter

put_change(changeset, :field, value)
  • Adds the field to changes only if the new value is different from what's in changeset.data.
  • If the value is equal to the original, no change is recorded.
  • Ideal for avoiding unnecessary updates (e.g., in database writes).

Example:

changeset = put_change(changeset, :title, "My Title")

If changeset.data.title is already "My Title", this won't add anything to changes.

force_change/3: the always-setter

force_change(changeset, :field, value)
  • Always puts the value into changes, even if it’s the same as the original.
  • Useful when you want to force re-validation, trigger change tracking, or update a UI regardless of actual value difference.

Example:

changeset = force_change(changeset, :title, "My Title")

Even if title hasn't changed, :title will appear in changes.

When to Use put_change

  • You’re updating user-submitted form data before saving to the DB.
  • You want to avoid unnecessary updates.
  • You only care about changes that differ from the original.

When to Use force_change

  • You want to explicitly mark a field as changed, even if it’s the same as before.
  • You’re building a LiveView form and want to reflect the change in the UI.
  • You’re triggering behavior that depends on a field being in changes (e.g., audit logging, conditionally showing "modified" fields).

LiveView Tip

In Phoenix LiveView, put_change may not update a form input if the value hasn't changed from the original data — even if the user clicks a button to "reset" or reapply it. Using force_change ensures the change is tracked and visible in the form.