antiwork/gumroad · error · Link::LinkInvalid
The product needs to be shippable to at least one destinatio
Error message
The product needs to be shippable to at least one destination.
What it means
Raised as Link::LinkInvalid by Link#save_shipping_destinations! when a published (alive?) physical product submits an empty shipping_destinations array (or a single blank string). A published physical product must remain shippable to at least one destination, so clearing all destinations in one save is rejected after the uniqueness check.
Source
Thrown at app/models/link.rb:958
[]
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"])
multiple_items_rate_cents ||= string_to_price_cents(price_currency_type, destination["multiple_items_rate"])
begin
shipping_destination.one_item_rate_cents = one_item_rate_cents
shipping_destination.multiple_items_rate_cents = multiple_items_rate_cents
shipping_destination.deleted_at = nil
shipping_destination.is_virtual_country = ShippingDestination::Destinations::VIRTUAL_COUNTRY_CODES.include?(shipping_destination.country_code)
View on GitHub (pinned to afeacbd394)
Solutions
- Keep at least one destination row for published physical products — enforce '≥1 row' in the form before submit.
- If the seller truly wants no shipping, unpublish the product first (the guard only applies to alive? products) or convert it to a non-physical product.
- Rescue Link::LinkInvalid and surface 'The product needs to be shippable to at least one destination.'
Example fix
# before
link.save_shipping_destinations!([]) # LinkInvalid: published physical product
# after
if link.alive? && link.is_physical && destinations.blank?
return { error: "The product needs to be shippable to at least one destination." }
end
link.save_shipping_destinations!(destinations) Defensive patterns
Strategy: validation
Validate before calling
if link.alive? && link.is_physical && destinations.select { |d| d['country_code'].present? }.empty?
return { error: 'Published physical products need >=1 destination.' }
end Try / catch
begin
link.save_shipping_destinations!(destinations)
rescue Link::LinkInvalid
render json: { errors: link.errors.full_messages }, status: :unprocessable_entity
end Prevention
- Block deletion of the last destination row for published physical products.
- Offer unpublish-first flow when the seller wants to strip all destinations.
- Remember the guard keys on alive? — unpublished products may clear destinations.
When it happens
Trigger: Updating a published, alive physical product with shipping_destinations: [] or ['']. Unpublished/deleted products may clear destinations; the guard keys on alive?.
Common situations: Seller deletes every destination row in the shipping table and hits save while the product is live; front-end sends an empty array when the last row is removed; converting product content while forgetting the shipping requirement.
Related errors
- Sorry, shipping destinations have to be unique.
- 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/3fbe93b0fbe5a53c.
Report an issue: GitHub.