antiwork/gumroad · error · Link::LinkInvalid

Memberships should have at least one tier.

Error message

Memberships should have at least one tier.

What it means

Raised as Link::LinkInvalid by Link#valid_tier_version_structure when a membership's single alive variant category contains zero alive variants. The membership must sell at least one tier, so an empty Tier category aborts the save. Same skip conditions as its sibling check: deleted, archived, or purchase_disabled_at-changed links are exempt.

Source

Thrown at app/models/link.rb:1890

      if has_alive_categories_without_variants
        errors.add(:base, "Sorry, the product versions must have at least one option.")
      end
    end

    def valid_tier_version_structure
      return if deleted_at.present?
      return if archived?
      return if purchase_disabled_at.present? && purchase_disabled_at_changed?

      if variant_categories.alive.size != 1
        errors.add(:base, "Memberships should only have one Tier version category.")
        raise LinkInvalid, "Memberships should only have one Tier version category."
        return
      end

      if variant_categories.alive.first.variants.alive.size == 0
        errors.add(:base, "Memberships should have at least one tier.")
        raise LinkInvalid, "Memberships should have at least one tier."
      end
    end

    def quantity_enabled_state_is_allowed
      if quantity_enabled && !can_enable_quantity?
        errors.add(:base, "Customers cannot be allowed to choose a quantity for this product.")
      end
    end

    def custom_permalink_or_is_licensed_changed?
      custom_permalink_changed? || is_licensed_changed?
    end

    def description_scrubber
      unless Loofah::HTML5::SafeList::ACCEPTABLE_CSS_PROPERTIES.include?("position")
        # TODO (vishal): iframe.ly adds "position" style, which is not allowed by default.
        # We should be able to remove this once https://github.com/flavorjones/loofah/pull/258 is merged.
        Loofah::HTML5::SafeList::ACCEPTABLE_CSS_PROPERTIES.add("position")

View on GitHub (pinned to afeacbd394)

Solutions

  1. Keep at least one alive tier variant on the Tier category — block removal of the last row in the UI.
  2. For imports, create category and at least one variant in the same transaction.
  3. Rescue Link::LinkInvalid and show errors.full_messages.

Example fix

# before
link.save! # LinkInvalid: zero alive tiers

# after
if link.variant_categories.alive.first.variants.alive.empty?
  return { error: "Memberships should have at least one tier." }
end
link.save!
Defensive patterns

Strategy: validation

Validate before calling

return { error: 'At least one tier required.' } if link.is_membership && link.variant_categories.alive.first&.variants&.alive&.empty?

Try / catch

begin
  link.save!
rescue Link::LinkInvalid
  render json: { errors: link.errors.full_messages }, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: Saving an alive membership link where variant_categories.alive.first.variants.alive is empty — seller deleted every tier row before saving, or an import created the category but no variants, or all variants were soft-deleted.

Common situations: Tier editor grid allowing the last row to be removed; imports partial-failing after category creation; cleanup jobs soft-deleting variants without checking membership integrity.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/44052408a9c2c1f8. Report an issue: GitHub.