Skip to main content
Columns automatically resolve joined table fields. Use dot notation (table.column) in column definitions and the engine handles search, sort, and display.

How It Works

1

Query with Joins

Use Laravel’s query builder to join related tables in your query() method.
2

Dot Notation

Reference joined columns using table.column syntax in make().
3

Automatic Resolution

The engine uses qualified names for WHERE/ORDER BY clauses in SQL.
4

Display Values

Display values are resolved using the alias (table_column) or custom AS alias.
selectAs(string) is available as an optional override for custom aliases.

Basic Example

Join a categories table and make the category name searchable and sortable:
use LivewireTablesColumnsTextColumn;

public function query(): Builder
{
    return Product::query()
        ->join('categories', 'categories.id', '=', 'products.category_id')
        ->select('products.*', 'categories.name as category_name');
}

public function columns(): array
{
    return [
        TextColumn::make('products.name')
            ->label('Product'),

        TextColumn::make('categories.name')
            ->label('Category')
            ->sortable()
            ->searchable(),
    ];
}
The column categories.name will:
  • Search via WHERE categories.name LIKE '%term%'
  • Sort via ORDER BY categories.name ASC
  • Display: resolves the value from category_name on the result row
Always ->select(...) in your query() to include joined columns with aliases.

Multiple Joins

Join multiple tables and reference their columns:
OrdersTable.php
public function query(): Builder
{
    return Order::query()
        ->join('users', 'users.id', '=', 'orders.user_id')
        ->join('products', 'products.id', '=', 'orders.product_id')
        ->select(
            'orders.*',
            'users.name as user_name',
            'products.name as product_name',
        );
}

public function columns(): array
{
    return [
        TextColumn::make('orders.id')->label('Order #'),
        TextColumn::make('users.name')->label('Customer')->sortable()->searchable(),
        TextColumn::make('products.name')->label('Product')->sortable()->searchable(),
    ];
}

Joined Column with Filters

Filter on joined table columns:
OrdersTable.php
public function query(): Builder
{
    return Order::query()
        ->join('brands', 'brands.id', '=', 'orders.brand_id')
        ->select(
            'orders.*',
            'orders.id as orders_id',
            'brands.name as brands_name',
            'brands.country as brands_country',
            'brands.tier as brands_tier',
        );
}

public function columns(): array
{
    return [
        TextColumn::make('orders.id')
            ->label('#')
            ->sortable(),

        TextColumn::make('brands.name')
            ->label('Brand')
            ->sortable()
            ->searchable(),

        TextColumn::make('brands.country')
            ->label('Country')
            ->sortable()
            ->searchable(),

        TextColumn::make('brands.tier')
            ->label('Tier')
            ->sortable(),
    ];
}

public function filters(): array
{
    return [
        SelectFilter::make('brands.tier')
            ->label('Brand Tier')
            ->setOptions([
                '' => 'All Tiers',
                'premium' => 'Premium',
                'standard' => 'Standard',
                'budget' => 'Budget',
            ])
            ->filter(fn (Builder $query, mixed $value) => $query->where('brands.tier', $value)),

        SelectFilter::make('brands.country')
            ->label('Brand Country')
            ->setOptions([
                '' => 'All Countries',
                'USA' => 'USA',
                'South Korea' => 'South Korea',
                'Japan' => 'Japan',
                'Switzerland' => 'Switzerland',
            ])
            ->filter(fn (Builder $query, mixed $value) => $query->where('brands.country', $value)),
    ];
}

Alias Resolution

The display value is automatically resolved using the table + column name as an alias:
Column DefinitionSQL Qualified NameAuto-Resolved Alias
categories.namecategories.namecategories_name
users.emailusers.emailusers_email
brands.countrybrands.countrybrands_country
The auto-resolved alias replaces the dot with an underscore: table.column becomes table_column.

Custom Aliases

If your query uses a different alias, you can specify it:
public function query(): Builder
{
    return Product::query()
        ->join('categories', 'categories.id', '=', 'products.category_id')
        ->select('products.*', 'categories.name as cat_name'); // Custom alias
}

public function columns(): array
{
    return [
        TextColumn::make('categories.name')
            ->label('Category')
            ->selectAs('cat_name') // Override auto-resolution
            ->sortable()
            ->searchable(),
    ];
}
selectAs() is only needed when your SQL alias differs from the auto-resolved table_column pattern.

Aggregates and Computed Columns

Join with aggregate functions:
public function query(): Builder
{
    return User::query()
        ->leftJoin('orders', 'orders.user_id', '=', 'users.id')
        ->select(
            'users.*',
            DB::raw('COUNT(orders.id) as order_count'),
            DB::raw('SUM(orders.total) as total_revenue')
        )
        ->groupBy('users.id');
}

public function columns(): array
{
    return [
        TextColumn::make('name')
            ->label('Customer')
            ->sortable()
            ->searchable(),

        TextColumn::make('order_count')
            ->label('Orders')
            ->sortable(),

        TextColumn::make('total_revenue')
            ->label('Revenue')
            ->sortable()
            ->format(fn ($value) => '$'.number_format($value, 2)),
    ];
}

Key Points

Always select columns

Use ->select(...) in your query to include joined columns with aliases.

Use table.column syntax

Reference joined columns as table.column in make() for unambiguous queries.

Auto-alias resolution

Display values automatically resolve from table_column aliases.

Override when needed

Use selectAs() only when your SQL alias differs from the auto-pattern.

Common Pitfalls

Missing Select StatementDon’t forget to select the joined columns:
// ❌ Wrong - category_name not selected
return Product::query()
    ->join('categories', 'categories.id', '=', 'products.category_id');

// ✅ Correct - includes category_name
return Product::query()
    ->join('categories', 'categories.id', '=', 'products.category_id')
    ->select('products.*', 'categories.name as category_name');
Ambiguous Column NamesAlways qualify column names when multiple tables have the same column:
// ❌ Wrong - ambiguous 'id'
TextColumn::make('id')

// ✅ Correct - qualified
TextColumn::make('orders.id')

Performance Tips

Use Eager Loading for RelationshipsFor simple relationships, consider using Eloquent relationships with eager loading instead of manual joins:
public function query(): Builder
{
    return Product::query()->with('category');
}

public function columns(): array
{
    return [
        TextColumn::make('category.name')
            ->label('Category'),
    ];
}
This may perform better for certain queries, especially with nested relationships.

Build docs developers (and LLMs) love