Skip to content

Saving a "Limit to Countries" shipping zone fails validation: "The selected countries is invalid" #8

Description

@conveydk

Describe the bug

Saving a shipping zone whose type is "Limit to Countries" always fails validation with:

The selected countries is invalid.

This happens for every newly created zone and whenever you add a country that isn't already persisted on the record. It makes the "Limit to Countries" zone type effectively unusable.

Steps to reproduce

  1. Register the ShippingPlugin and open Shipping → Zones in the Lunar admin panel.
  2. Create/edit a shipping zone and set Type to "Limit to Countries".
  3. Select one or more countries.
  4. Save.

Expected behaviour

The zone saves and the selected countries are synced to the country_shipping_zone pivot.

Actual behaviour

Validation fails on the countries field with "The selected countries is invalid." (Laravel's in rule message), so the form never saves.

Root cause

In src/Filament/Resources/ShippingZoneResource.php, getCountriesFormComponent() declares both ->options(...) and a ->getOptionLabelsUsing(...) callback:

Forms\Components\Select::make('countries')
    ->options(Country::get()->pluck('name', 'id'))
    ->multiple()
    ->required()
    // ...
    ->getOptionLabelsUsing(static function (Model $record): array {
        $record->loadMissing('countries');

        return $record->countries
            ->pluck('name', 'id')
            ->toArray();
    })

Filament builds the field's in validation rule from getInValidationRuleValues() (Filament\Forms\Components\Select), which — when getOptionLabelsUsing is set — derives the valid values from getOptionLabels(withDefaults: false), i.e. from that callback rather than from options().

Because the callback returns only $record->countries (the countries already persisted on the record), any selected country that isn't already saved is not in the allow-list:

if (count(array_diff($state, array_keys($optionLabels)))) {
    return []; // -> Rule::in([]) -> always fails
}

getInValidationRule() then turns that empty array into Rule::in([]), which rejects every value. For a new zone $record->countries is empty, so it fails for all selections.

The same pattern is present on the singular country field via getOptionLabelsUsing, but that field only renders for the states/postcodes types and single-select validation uses getOptionLabelUsing (singular, unset), so it isn't affected.

Suggested fix

The static ->options(Country::get()->pluck('name', 'id')) already provides labels for every country, so the getOptionLabelsUsing callback on the countries field is redundant for display and harmful for validation. Either remove it, or have it resolve labels for the selected values rather than the persisted relationship, e.g.:

->getOptionLabelsUsing(fn (array $values): array =>
    Country::query()->whereKey($values)->pluck('name', 'id')->all()
)

Environment

  • lunarphp/table-rate-shipping: 1.5.0-beta.6
  • lunarphp/lunar + lunarphp/core: 1.5.0-beta.6
  • filament/filament: v4.11.7
  • Laravel: 12.x / 13.x
  • PHP: 8.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions