Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jcc5018's avatar

Can't get dependent dropdowns to save related data in Filament

I have tried a few AI tools and have gone through multiple trials trying to make this work, but I am having difficulties.

I have an article model in which I want to attach a category. Seems simple enough.

But The Categories have a parent/ Child relation in which the Parent is the first SELECT, and the Child is the second SELECT that gets stored in my taggables table:

I can get the Dependent dropdowns to display the appropriate options, But I am having a hard time getting them to save because the relationship isn't being established in one "solution" and the other solution where I am trying to modify the query, it is giving an error "array_diff(): Argument #2 must be of type array, string given"

So i'm not entirely sure what I need to change for the proper response.

//PARENT CATEGORY -- DOES NOT SAVE TO DB
Select::make('category_id')
                                          ->options(Tag::type(12)
                                                       ->whereNull('parent_id')
                                                       ->pluck('tag', 'id'))
                                          ->reactive()
                                          ->afterStateUpdated(function (
                                              $state,
                                              $set
                                          ) {
                                              $set('subcategory_id', null);
                                          })
                                          ->default(function (Forms\Get $get) {
                                              $subcategory
                                                  = Tag::find($get('subcategory_id'));

                                              return $subcategory
                                                  ? $subcategory->parent_id
                                                  : null;
                                          })
                                          ->live(),

//CHILD SELECT OPTION ONE -- DOES NOT ESTABLISH THE RELATIONSHIP
                                    //                                    Select::make('subcategory_id')
                                    //                                          ->placeholder(fn(Forms\Get $get
                                    //                                          ): string => empty($get('category_id'))
                                    //                                              ? 'First select Category'
                                    //                                              : 'Select an option')
                                    //                                          ->options(function (Forms\Get $get) {
                                    //                                              $categoryId = $get('category_id');
                                    //                                              if (empty($categoryId)) {
                                    //                                                  return [];
                                    //                                              },

                                    //OPTION 2 -- REFERENCES THE RELATIONSHIP, BUT HAS ARRAY ERROR

                                    Select::make('subcategory_id')
                                          ->placeholder(fn(Forms\Get $get
                                          ): string => empty($get('category_id'))
                                              ? 'First select Category'
                                              : 'Select an option')
                                          ->relationship('categories', 'tag',
                                              modifyQueryUsing: function (
                                                  Builder $query,
                                                  Forms\Get $get
                                              ) {
                                                  return $query->where('parent_id',
                                                      $get('category_id'));
                                              }),

Since only the "subcategory_id" is saved in my taggables table, the selected option that is presented here, should also update the category_id based on the "parent_id"

TAGS TABLE

 Schema::create('tags', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('tag');
            $table->string('slug')->unique();
            $table->string('description')->nullable();
            $table->unsignedBigInteger('parent_id')->nullable();
            $table->foreign('parent_id', 'parent_fk_2997126')->references('id')
                  ->on('tags');
            $table->unsignedBigInteger('tag_type')->nullable();
            $table->foreign('tag_type', 'tag_type_fk_2997126')->references('id')
                  ->on('taxonomies');
            $table->string('icon')->nullable();

            $table->softDeletes();
            $table->timestamps();
        });

ARTICLES MODEL:

    public function categories()
    {
        return $this->morphToMany(Tag::class, 'taggable')->where('tag_type', 12)
                    ->withTimestamps();
    }
0 likes
3 replies
LaryAI's avatar
Level 58

To solve the issue of saving dependent dropdowns in Filament, you need to ensure that both the parent and child categories are properly saved and related in your database. Here’s a step-by-step solution:

  1. Define the Parent Category Dropdown:

    • This dropdown will list all parent categories.
    • When a parent category is selected, it will reset the child category dropdown.
  2. Define the Child Category Dropdown:

    • This dropdown will list child categories based on the selected parent category.
    • Ensure that the selected child category is saved correctly.
  3. Save the Parent Category Based on the Child Category:

    • When saving the article, ensure that the parent category is also saved based on the selected child category.

Here’s how you can implement this:

Step 1: Define the Parent Category Dropdown

Select::make('category_id')
    ->options(Tag::type(12)->whereNull('parent_id')->pluck('tag', 'id'))
    ->reactive()
    ->afterStateUpdated(function ($state, $set) {
        $set('subcategory_id', null);
    })
    ->default(function (Forms\Get $get) {
        $subcategory = Tag::find($get('subcategory_id'));
        return $subcategory ? $subcategory->parent_id : null;
    })
    ->live();

Step 2: Define the Child Category Dropdown

Select::make('subcategory_id')
    ->placeholder(fn(Forms\Get $get): string => empty($get('category_id')) ? 'First select Category' : 'Select an option')
    ->options(function (Forms\Get $get) {
        $categoryId = $get('category_id');
        if (empty($categoryId)) {
            return [];
        }
        return Tag::where('parent_id', $categoryId)->pluck('tag', 'id');
    })
    ->reactive();

Step 3: Save the Parent Category Based on the Child Category

In your Article model, you need to ensure that when saving the article, the parent category is also saved based on the selected child category. You can achieve this by overriding the save method or using an observer.

Here’s an example using an observer:

use App\Models\Article;
use App\Models\Tag;

class ArticleObserver
{
    public function saving(Article $article)
    {
        if ($article->subcategory_id) {
            $subcategory = Tag::find($article->subcategory_id);
            if ($subcategory) {
                $article->category_id = $subcategory->parent_id;
            }
        }
    }
}

// In your AppServiceProvider or any other service provider
use App\Models\Article;
use App\Observers\ArticleObserver;

public function boot()
{
    Article::observe(ArticleObserver::class);
}

Final Code

Here’s the complete code for the form and the observer:

Form Code

Select::make('category_id')
    ->options(Tag::type(12)->whereNull('parent_id')->pluck('tag', 'id'))
    ->reactive()
    ->afterStateUpdated(function ($state, $set) {
        $set('subcategory_id', null);
    })
    ->default(function (Forms\Get $get) {
        $subcategory = Tag::find($get('subcategory_id'));
        return $subcategory ? $subcategory->parent_id : null;
    })
    ->live();

Select::make('subcategory_id')
    ->placeholder(fn(Forms\Get $get): string => empty($get('category_id')) ? 'First select Category' : 'Select an option')
    ->options(function (Forms\Get $get) {
        $categoryId = $get('category_id');
        if (empty($categoryId)) {
            return [];
        }
        return Tag::where('parent_id', $categoryId)->pluck('tag', 'id');
    })
    ->reactive();

Observer Code

use App\Models\Article;
use App\Models\Tag;

class ArticleObserver
{
    public function saving(Article $article)
    {
        if ($article->subcategory_id) {
            $subcategory = Tag::find($article->subcategory_id);
            if ($subcategory) {
                $article->category_id = $subcategory->parent_id;
            }
        }
    }
}

// In your AppServiceProvider or any other service provider
use App\Models\Article;
use App\Observers\ArticleObserver;

public function boot()
{
    Article::observe(ArticleObserver::class);
}

This solution ensures that both the parent and child categories are properly saved and related in your database.

jcc5018's avatar

@LaryAI I know this is an AI response that wont have a reply, but I have no need to store the parent option, as that would be redundant data. If I have a parent category of HOW TO, and a sub category of rules, I should only need to store the rules selection that is auto associated as a "HOW TO" through the parent_id of the tag.

ID, Tag,slug,description,parent_id, tag_type
123,How To,how-to,Content meant to teach,,12
124,Instructions,instructions,Content should provide steps to complete a goal,123,12
125,Rules,rules,Provide official rules or variants.,123,12
...

If I were setting this in one big list, I probably wouldnt be having this problem, But the dependent dropdown solution is making it more difficult.

If i can make one list where the options are all listed together, but options where parent_id== null are non selectable option headings, I would be open to that solution as well.

jcc5018's avatar

Actually i tested just loading all the tags and still end up with the array error so it is something in that setup that I am getting wrong:

 Select::make('subcategory_id')

                                          ->relationship('categories', 'tag'),

Please or to participate in this conversation.