Filament Table Actions: Pro Tips Every Developer Should Know

Discover advanced tips to customize Filament table actions in Laravel. Learn how to auto-mutate data, load custom views in modals, and create state-toggling actions with confirmation and notifications.

Filament resource table actions offer powerful customization for crafting seamless admin experiences. Let’s explore some high-impact tricks every Laravel developer should know:

Trick 1: Automatically Mutate Form Data in the Edit Action

Need to auto-update a field every time you edit from the table? You can do this with the mutateFormDataUsing() method in your EditAction:

use Filament\Tables;
use Filament\Tables\Table;

public static function table(Table $table): Table
{
    return $table
        ->columns([
            // Your columns...
        ])
        ->actions([
            Tables\Actions\EditAction::make()
                ->mutateFormDataUsing(function (array $data): array {
                    $data['status'] = 'updated'; // auto-set status before save
                    return $data;
                }),
        ]);
}

This ensures the status field is automatically set to "updated" whenever an edit occurs.

Trick 2: Customize an Action to Load a View in a Modal

Standard actions don’t always cut it. For instance, you might want to display a list of users who liked a blog post in a modal. Here’s a clean way to do it:

Action::make('showLikes')
    ->label('Likes')
    ->icon('heroicon-o-heart')
    ->visible(fn($record) => $record->likes()->count() > 0)
    ->modalHeading('Likes')
    ->modalContent(function ($record) {
        return view('filament.components.likes-list', [
            'blogId' => $record->id,
        ]);
    })
    ->modalSubmitAction(false)
    ->modalCancelAction(false)
    ->modalWidth('lg'),

This opens a modal showing your custom view, without extra buttons.

Trick 3: Add a State-Toggling Action with Confirmation and Notification

Want a sleek toggle for boolean flags like activation status, complete with confirmation and feedback? Try this:

Tables\Actions\Action::make('toggleActive')
    ->label(fn($record) => $record->is_active ? 'Deactivate' : 'Activate')
    ->icon(fn($record) => $record->is_active ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle')
    ->color(fn($record) => $record->is_active ? 'danger' : 'success')
    ->requiresConfirmation()
    ->action(function ($record) {

        $actionString = $record->is_active ? 'deactivated' : 'activated';

        $record->update([
            'is_active' => !$record->is_active,
        ]);

        Notification::make()
            ->title('Blog has been ' . $actionString . ' successfully')
            ->success()
            ->send();
    }),

This delivers an intuitive toggle experience—confirm first, then notify on change.


Why These Tips Matter

These action enhancements help you:

  • Automate workflows (e.g., setting status instantly in edit forms).
  • Visualize complex data elegantly (e.g., view likes in a modal).
  • Ensure user clarity and feedback with confirmation dialogs and notifications.

This is especially valuable when building advanced admin panels with Filament—turning UI logic into polished user experiences.

How to Insert Repeater Field Entries as Rows to Table in Laravel Filament

Learn how to convert repeater field JSON data into individual table rows in Laravel Filament using a custom button action. A practical guide for syncing structured form data.

When building admin panels using Laravel Filament, the Repeater field is a powerful way to collect dynamic sets of data — such as specifications, tags, or features. Often, these repeater entries are stored as a JSON array in the database. But what if you want to convert those JSON entries into individual rows in another table — for analytics, reporting, or normalization?

In this article, we’ll walk through how to:

  • Collect data using a Repeater field (stored as JSON)
  • Add a button to your Filament admin to insert each entry as a row in another table
  • Do this on-demand, without preloading data from the related table

The Use Case

Let’s assume you’re managing products with technical specifications.

  • You store specifications in a specifications JSON column of the products table using a Filament Repeater.
  • When a button is clicked (e.g., “Insert Repeater Entries”), each specification should be copied into a product_specifications table, with one row per entry.

Step-by-Step Guide

Step 1: Set Up the Repeater Field

Add a specifictions repeater field with multiple fields in product form as follows:

Repeater::make('specifications')
    ->schema([
        TextInput::make('key'),
        TextInput::make('value'),
        TextInput::make('unit'),
        TextInput::make('description'),
        TextInput::make('notes'),
    ])

This will create specifications repeater field in which you can add multiple rows of specifications for any product. This data is stored in the product_pecifications column of your products table as a JSON array.

Step 2: Create the Target Table

Create a migration for new table to hold individual specification entries using this command:

php artisan make:model ProductSpecification -m

It will create 2 files as follows:

  • Model File: app/Models/ProductSpecification.php
  • Migration File: database/migrations/xxxx_xx_xx_xxxxxx_create_product_specifications_table.php

Step 3: Run the Migration File

Update the migration file as per your repeater field entry as follows:

Schema::create('product_specifications', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_id')->constrained()->onDelete('cascade');
    $table->string('key')->nullable();
    $table->string('value')->nullable();
    $table->string('unit')->nullable();
    $table->text('description')->nullable();
    $table->text('notes')->nullable();
    $table->timestamps();
});

You can now run the migration using migrate command.

php artisan migrate

It will create a product_specifictions table in the database.

Step 4: Add a Button to Trigger the Insert

In your ProductResource\Pages\ViewProduct or EditProduct, add a custom action to generate specifications entries from the repeater field in product form.

use Filament\Actions\Action;

public function getHeaderActions(): array
{
    return [
        Action::make('Insert Repeater Entries')
            ->requiresConfirmation()
            ->action(function () {
                $specs = $this->record->specifications ?? [];

                if (!is_array($specs)) {
                    $specs = json_decode($specs, true) ?? [];
                }

                foreach ($specs as $spec) {
                    \App\Models\ProductSpecification::create([
                        'product_id'  => $this->record->id,
                        'key'         => $spec['key'] ?? null,
                        'value'       => $spec['value'] ?? null,
                        'unit'        => $spec['unit'] ?? null,
                        'description' => $spec['description'] ?? null,
                        'notes'       => $spec['notes'] ?? null,
                    ]);
                }

                $this->notify('success', 'Specifications inserted successfully.');
            }),
    ];
}

You can name the button anything, such as “Sync Specifications” or “Publish to Table”. It will manually extract the JSON data and insert it as rows in the product_specifications table.

Benefits of This Approach

  • Keeps your form simple and user friendly by storing repeater data in JSON.
  • Normalizes data later when needed — perfect for one-time inserts or batch operations.
  • Doesn’t require eager loading or nested relationship editing.

Avoid Duplicate Inserts

You can prevent duplicate imports by checking if rows already exists by adding the following check before adding the specifications in above code:

if ($this->record->productSpecifications()->exists()) {
    $this->notify('warning', 'Specifications already exist.');
    return;
}

Conclusion

Using repeater field gives you flexibility in how you manage structured, dynamic data in Laravel Filament. You can let users manage repeater fields easily, while keeping your database clean and relational by syncing data to separate tables on demand.

Whether for analytics, reporting, or integration, separating repeater entries into rows gives you the best of both worlds: JSON-based forms with relational data power.

Using .env File in CodeIgniter 3

Learn how to use the .env file in CodeIgniter 3 to securely manage your environment variables and improve your application’s configuration structure.

Using environment variables via a .env file is a common best practice to keep sensitive configuration (like database credentials or any other secret or api keys) out of your codebase. .env file support is not provided in CodeIgniter 3 out of the box, but you can easily integrate it using the vlucas/phpdotenv library.

This guide will show you how to add .env file support in a CodeIgniter 3 application using the vlucas/phpdotenv library with Composer autoload enabled.

Prerequisites

Ensure your CodeIgniter project has Composer enabled by checking the following in application/config/config.php:

$config['composer_autoload'] = TRUE;

Step-by-Step Setup

The following are the steps to implement .env file support.

Step 1. Install vlucas/phpdotenv via Composer

In Codeigniter 3, composer.json is not available at the project root, but inside the application directory. So, to install any composer library, you have to first navigate to the application directory.

cd application/
composer require vlucas/phpdotenv

It will install the core files to add support for .env files.

Step 2. Create the .env File

At the root of your project (same level as index.php), create a file named .env with database configuration variables as a content:

DB_HOST=localhost
DB_USERNAME=root
DB_PASSWORD=secret
DB_NAME=my_database

3. Load the .env in index.php

Open your index.php file and add the following code before the line that bootstraps CodeIgniter:

require_once __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

Add the above code in index.php file before the following line:

require_once BASEPATH.'core/CodeIgniter.php';

For older versions (PHP < 7.1 or Dotenv v2):

$dotenv = new Dotenv\Dotenv(__DIR__);
$dotenv->load();

This will load the .env file variables using the phpdotenv library. Now, all the variables used in .env file can be used in any code of the project.

4. Use Environment Variables in database.php

We defined the database configuration variables inside the .env file. To use these variables, open application/config/database.php and update the code as follows:

$db['default'] = array(
    'hostname' => getenv('DB_HOST'),
    'username' => getenv('DB_USERNAME'),
    'password' => getenv('DB_PASSWORD'),
    'database' => getenv('DB_NAME'),
    'dbdriver' => 'mysqli',
    'db_debug' => (ENVIRONMENT !== 'production'),
    // ... other settings
);

Note: In some cases, getenv function may not work. Use $_ENV as an alternative.

Secutiry Tip

Never commit your .env file to version control. Add it to .gitignore

Conclusion

Now your CodeIgniter 3 app can securely use environment variables just like modern frameworks. This keeps your config clean, safe, and easy to manage across environments.

Develop Contact Form using Livewire in Laravel

Learn how to create a responsive contact form using Livewire in Laravel. Step-by-step tutorial with validation, form submission, and real-time updates.

Developing a Contact form using Livewire in Laravel is straightforward and involves a very few key steps.

In this post, we will learn to develop contact form using Livewire component in Laravel. Follow the below steps:

Step 1: Install Livewire

To create any Livewire form, you must have Livewire installed over Laravel project. If you haven’t installed Livewire yet, you can do so via Composer:

composer require livewire/livewire

It will install Livewire package to you Laravel project.

Step 2: Create a Livewire Component

Now, we need to create a Livewire component for contact form. You can generate a Livewire component using Artisan command as follows:

php artisan make:livewire ContactForm

This will create 2 files as follows:

  • A Livewire component file at app/Http/Livewire/ContactForm.php
  • A Blade view at resources/views/livewire/contact-form.blade.php

Your contact form component is now ready to be used. But, we haven’t added any logic or any design to this component. So, if you add this component to any file, it will display blank page.

Step 3: Define the Livewire Component Logic

Open app/Http/Livewire/ContactForm.php and add all contact form related logic to the file as follows:

namespace App\Http\Livewire;

use Livewire\Component;
use App\Models\Contact;
use Illuminate\Validation\Rule;

class ContactForm extends Component
{
    public $name, $email, $message;

    protected $rules = [
        'name' => 'required|min:3',
        'email' => 'required|email',
        'message' => 'required|min:5',
    ];

    public function submit()
    {
        $this->validate();

        // Save data
        Contact::create([
            'name' => $this->name,
            'email' => $this->email,
            'message' => $this->message,
        ]);

        // Reset the form
        $this->reset(['name', 'email', 'message']);

        session()->flash('success', 'Your message has been sent!');
    }

    public function render()
    {
        return view('livewire.contact-form');
    }
}

Step 4: Create the Livewire Blade View

Open resources/views/livewire/contact-form.blade.php and update the HTML form as per your requirement. I have added my blade file design as follows:

<div>
    @if (session()->has('success'))
        <div class="p-3 mb-4 text-green-600 bg-green-200 border border-green-600 rounded">
            {{ session('success') }}
        </div>
    @endif

    <form wire:submit.prevent="submit">
        <div class="mb-4">
            <label for="name" class="block font-bold">Name:</label>
            <input type="text" id="name" wire:model="name" class="w-full p-2 border rounded">
            @error('name') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <div class="mb-4">
            <label for="email" class="block font-bold">Email:</label>
            <input type="email" id="email" wire:model="email" class="w-full p-2 border rounded">
            @error('email') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <div class="mb-4">
            <label for="message" class="block font-bold">Message:</label>
            <textarea id="message" wire:model="message" class="w-full p-2 border rounded"></textarea>
            @error('message') <span class="text-red-600">{{ $message }}</span> @enderror
        </div>

        <button type="submit" class="px-4 py-2 text-white bg-blue-600 rounded">Send</button>
    </form>
</div>

I have added some additional features like displaying success message and added error message display elements to each fields.

Step 5: Add Livewire to a Page

Now it is ready to be included to any page. You can include this component using @livewire directive provided by Livewire package. Include this Livewire component in your Blade view (e.g., resources/views/contact.blade.php):

@extends('layouts.app')

@section('content')
    <div class="container mx-auto p-4">
        <h1 class="text-xl font-bold">Contact Us</h1>
        @livewire('contact-form')
    </div>
@endsection

Still it is not visible to the page. Because, Livewire scripts and styles are missing.

Step 6: Include Livewire Scripts

You have to add Livewire scripts and styles to the layout, where you want to display any Livewire Component. You can add Livewire scripts and styles to the layout using their directives @livewireScripts and @livewireStyles respectively.

Add Livewire scripts in your layouts/app.blade.php file before the closing </body> tag of the layout file:

@livewireScripts
</body>
</html>

And add the Livewire styles in <head> tag of the layout file:

@livewireStyles

Note: If you have multiple layouts and these layouts are used to display Livewire Compoment pages, then you have to add Livewire scripts and styles in all of these layouts.

Step 7: Run Migrations and Serve the App

In component file, we have added a code to save contact form details to the database. So, If you don’t have contacts table already, create a migration for the contacts table:

php artisan make:migration create_contacts_table

Open the migration file (database/migrations/xxxx_xx_xx_xxxxxx_create_contacts_table.php) and add the necessary fields as follows:

public function up()
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email');
        $table->text('message');
        $table->timestamps();
    });
}

public function down(): void
{
    Schema::dropIfExists('contacts');
}

Run the migration:

php artisan migrate

It will create the contacts table into the project database. Now, create a model for this migration:

php artisan make:model Contact

It will create contact model file app/models/Contact.php. Open this model file and update it as follows,

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Contact extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'email',
        'message',
    ];
}

Finally, start your Laravel development server:

php artisan serve

Now, visit http://127.0.0.1:8000/contact, and you should see your Livewire-powered Contact Form working dynamically!

Additional Features You Can Add

  • Real-time validation: Add wire:model.blur="name" to inputs.
  • Loading indicators: Use wire:loading to show a spinner while submitting.

Git Commands to Remove Merged or Nonexistent Local Branches

Discover git commands to delete local branches that have been removed from remote or merged into the main branch.

Over time, your local git branches list can become overwhelming, particularly if you develop on a single branch, generate a pull request, merge it into the main branch, and then remove the remote git branch once it has been merged. After the branch is removed from the remote repository, there is no need to keep it on your local machine.

The following git command will delete all local branches that have been merged into the main branch. If your git trunk branch is not named main or you wish to remove all branches that have been merged into a branch other than main, simply modify the two instances of the word main in the command to reflect the name of your branch.

List of git commands to remove local merged git branches

To remove all the local branches, which are merged into the main branch, navigate to the root of the repository and run the following git commands,

  • Fetch the latest updates from the git repository
git fetch
  • See the list of local branches available in the repository
git branch
  • Delete all local branches that have been merged to main branch
git branch --merged main | grep -v "^\* main" | xargs -n 1 -r git branch -d

Explanation:

This command is a combination of several Git commands and shell commands that work together to delete all the local Git branches that have been merged into the main branch, except for the main branch itself.

Here’s a breakdown of the individual commands and what they do:

  1. git branch --merged main: This command lists all the local branches that have been merged into the main branch. The --merged option tells Git to only list branches that have been fully merged into main.
  2. grep -v "^\* main": This command filters out the main branch from the list of branches. The -v option tells grep to invert the match, i.e., show only the lines that do not match the pattern. The pattern in this case is "^\* main", which matches lines that start with an asterisk (\*) followed by the text main.
  3. xargs -n 1 -r git branch -d: This command passes the list of merged branches (excluding main) to the git branch -d command, which deletes each branch. The -n 1 option tells xargs to pass one branch name at a time to the git branch -d command. The -r option tells xargs to only run the git branch -d command if there is at least one branch name to pass to it. The -d option tells Git to delete the branches, but only if they have been fully merged into the current branch (in this case, main). Note that this option will fail if the branch has unmerged changes, in which case the -D option could be used instead to force-delete the branch.

Want to learn such useful commands? Get a Mastering Git book from https://amzn.to/3Hp527B

List of git commands to remove local nonexistent git branches

Similarly, run the following git commands to remove all the deleted branches from the local computer:

  • Fetch the latest updates from the git repository
git fetch
  • See the list of local branches available in the repository
git branch
  • Delete all local branches that have been merged to main branch
git branch -vv | grep ': gone]' | grep -v '\*' | awk '{ print $1; }' | xargs -r git branch -d

Explanation:

This command is a combination of several commands in a shell pipeline that work together to delete Git branches that are marked as "gone".

Here’s a breakdown of the individual commands and what they do:

  1. git branch -vv: This command lists all the Git branches in the local repository, with additional information about their upstream branches (if any). The -vv option adds more information about whether each branch is “up to date”, “behind”, or “ahead” of its upstream branch.
  2. grep ': gone]': This command filters the output of the git branch -vv command to only show the branches that are marked as "gone". The grep command searches for lines that contain the text ": gone]", which indicates that a branch is gone.
  3. grep -v '\*': This command further filters the output to exclude any branches that are currently checked out (indicated by the asterisk symbol). The -v option tells grep to invert the match, i.e., show only the lines that do not contain an asterisk.
  4. awk '{ print $1; }': This command extracts only the branch names from the filtered output. The awk command splits each line of input into fields and prints only the first field, which is the branch name.
  5. xargs -r git branch -d: This command passes the branch names to the git branch -d command, which deletes each branch. The -r option tells xargs to only run the git branch -d command if there is at least one branch name to pass to it. The -d option tells Git to delete the branches. Note that this option will fail if the branch has unmerged changes, in which case the -D option could be used instead to force-delete the branch.

Note: you can test these git commands by creating temporary repository on any git platform like, GitHub, GitLab or Bitbucket.

Disclaimer: This post contains affiliate links. If you use these links to buy something, I may earn a commission at no extra cost to you.