antiwork/gumroad · error · Link::LinkInvalid
Sorry, shipping destinations have to be unique.
Error message
Sorry, shipping destinations have to be unique.
What it means
Raised as Link::LinkInvalid (app/models/link.rb:1702) by Link#save_shipping_destinations! when the submitted destination list contains two entries with the same country_code (uniq by country_code shrinks the list). One shipping rate row per country per product is the data model's invariant, so duplicates are rejected before any ShippingDestination find_or_create runs.
Source
Thrown at app/models/link.rb:950
end
end
def removed_file_info_attributes
removed_file_info_attributes = self.json_data.present? ? self.json_data["removed_file_info_attributes"] : []
if removed_file_info_attributes.present?
removed_file_info_attributes.map(&:to_sym)
else
[]
end
end
def save_shipping_destinations!(shipping_destinations)
shipping_destinations ||= []
deduped_destinations = shipping_destinations.uniq { |destination| destination["country_code"] }
if deduped_destinations.size != shipping_destinations.size
errors.add(:base, "Sorry, shipping destinations have to be unique.")
raise LinkInvalid, "Sorry, shipping destinations have to be unique."
end
remaining_shipping_destinations = self.shipping_destinations.alive.pluck(:id)
# Cannot empty out shipping destinations for a published physical product
if alive? && (shipping_destinations.empty? || shipping_destinations.first == "")
errors.add(:base, "The product needs to be shippable to at least one destination.")
raise LinkInvalid, "The product needs to be shippable to at least one destination."
end
shipping_destinations.each do |destination|
next if destination.try(:[], "country_code").blank?
shipping_destination = ShippingDestination.find_or_create_by(country_code: destination["country_code"], link_id: id)
# TODO: :product_edit_react cleanup
one_item_rate_cents = destination["one_item_rate_cents"]
multiple_items_rate_cents = destination["multiple_items_rate_cents"]
one_item_rate_cents ||= string_to_price_cents(price_currency_type, destination["one_item_rate"])View on GitHub (pinned to afeacbd394)
Solutions
- Dedupe by country_code client-side before submit — replace the existing row for that country instead of adding a second.
- Prevent selecting an already-chosen country in the destination picker.
- Server-side callers can shipping_destinations.uniq { |d| d['country_code'] } before invoking save_shipping_destinations! when last-write-wins is acceptable.
- Rescue Link::LinkInvalid and show errors.full_messages.
Example fix
# before
link.save_shipping_destinations!(params[:shipping_destinations]) # LinkInvalid: duplicates
# after
save_shipping_destinations!(params[:shipping_destinations])
# where the controller/serializer first normalizes:
normalized = raw_destinations.index_by { |d| d['country_code'] }.values # last row per country wins
link.save_shipping_destinations!(normalized) Defensive patterns
Strategy: validation
Validate before calling
deduped = shipping_destinations.uniq { |d| d['country_code'] }
raise ArgumentError, 'duplicate country' if deduped.size != shipping_destinations.size Try / catch
begin
link.save_shipping_destinations!(destinations)
rescue Link::LinkInvalid
render json: { errors: link.errors.full_messages }, status: :unprocessable_entity
end Prevention
- Disallow selecting an already-listed country in the shipping UI.
- Replace-in-place (by country_code) rather than append when editing a destination row.
- Guard against double form submission re-appending rows.
When it happens
Trigger: POST/PATCH product params whose shipping_destinations array includes e.g. two {'country_code' => 'US'} entries — typically a UI bug appending an edited row instead of replacing it, or a client merging destination lists.
Common situations: React/jQuery shipping table letting the seller pick the same country twice; spreadsheet/CSV import concatenating rows; duplicate form submission re-appending the row.
Related errors
- The product needs to be shippable to at least one destinatio
- You have to confirm your email address before you can do tha
- Memberships should only have one Tier version category.
- Memberships should have at least one tier.
- This product was unpublished by Gumroad and cannot be republ
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/949db8eafe508834.
Report an issue: GitHub.