Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Larasort Laravel Package

s-damian/larasort

LaraSort is a lightweight Laravel package for adding sortable columns to Eloquent queries and tables. It handles sort parameters, column whitelisting, and direction toggling with minimal configuration, making it easy to build interactive, safe sorting in Blade views and APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require s-damian/larasort

The package is a trait, so there's no service provider you need to register manually under Laravel's package discovery. If your install includes a config file you can publish it (check vendor/s-damian/larasort/config after install):

php artisan vendor:publish --tag=larasort-config

Where to look first

  • The trait itself (Sdamian\Larasort\Sortable) — it's small, read the whole thing. You'll see exactly which query params it reads and how it maps them to orderBy.
  • The $sortable and $sortableDefault properties on your model — these are your entire configuration surface.
  • The README's example controller/Blade pair before writing any of your own.

First use case: a sortable index table

use Sdamian\Larasort\Sortable;

class User extends Model
{
    use Sortable;

    protected array $sortable = [
        'id',
        'name',
        'email',
        'created_at',
    ];

    protected array $sortableDefault = [
        'sort'  => 'id',
        'order' => 'desc',
    ];
}
// Controller
public function index()
{
    return view('users.index', [
        'users' => User::sortable()->paginate(25),
    ]);
}

Hit /users?sort=name&order=asc and the results flip. That's the entire happy path — one trait, one whitelist array, one scope call.

Implementation Patterns

Whitelist, then call sortable() last in the chain

The scope only appends an ORDER BY. Call it after your filters so the builder is already constrained:

User::query()
    ->where('active', true)
    ->when($request->q, fn ($q, $search) => $q->where('name', 'like', "%{$search}%"))
    ->sortable()
    ->paginate(25)
    ->withQueryString();   // preserves ?sort=&order= across pages

Reusable Blade header links

Don't hand-write sort URLs per column. Build a small partial that toggles direction based on the current request:

@php
    $currentSort  = request('sort');
    $currentOrder = request('order', 'desc');
@endphp

<th>
    <a href="{{ request()->fullUrlWithQuery([
        'sort'  => 'name',
        'order' => ($currentSort === 'name' && $currentOrder === 'asc') ? 'desc' : 'asc',
    ]) }}">
        Name
        @if ($currentSort === 'name') {{ $currentOrder === 'asc' ? '▲' : '▼' }} @endif
    </a>
</th>

fullUrlWithQuery is preferable to route(...) here because it carries the existing search/filter params forward for free.

Alias columns to real ones

If your table header is a human label but the DB column differs, expose only the DB column in $sortable and map presentation in the view. Keep the whitelist as the single source of truth — don't let controllers reach into it.

Shared base model

Larasort's configuration lives on the model, which means every model needs its own $sortable. If you have many models, extract a SortableModel base class:

abstract class SortableModel extends Model
{
    use \Sdamian\Larasort\Sortable;

    protected array $sortableDefault = ['sort' => 'id', 'order' => 'desc'];
}

Child models then only declare their whitelist.

Integrate with a table component

If you're using a DataTable or inline-table component, pass the sortable column list into the view from the model rather than duplicating it:

$columns = (new User)->getSortable();

(wrap this behind a small accessor if the trait doesn't expose a public getter in your version — you can always mirror the array on the model itself).

Gotchas and Tips

Never leave $sortable empty "to sort on anything." The column name is injected straight into ORDER BY. If the trait falls back to the raw query param when no whitelist is present, that's a SQL injection vector. Always declare an explicit array; treat it like a $fillable.

Direction is not case-insensitive by default in most ORDER BY implementations. Normalize in your controller/middleware (strtolower) if you're accepting ?order=ASC from third-party clients — don't assume the scope validates it.

Query param names matter for cache and frontend contracts. If you publish a config or override the query-string keys, document the change immediately — any frontend link builder or cached page that assumes ?sort= will silently break. Prefer sticking with the defaults.

sortable() composes poorly with select() aliases. If you select('name as display_name') and then sort by display_name, MySQL/Postgres may or may not resolve it depending on the driver. Sort on the real column instead.

Combine with eager-loads, not joins, for relation display. Sorting by columns from a joined relation requires the join to already exist in the query. Larasort only appends ORDER BY — it doesn't add joins for you. If you need relation sorting, add the join() yourself before calling sortable() and make sure the joined column is in the whitelist.

Testing. A single feature test per sortable endpoint pays for itself:

$this->get('/users?sort=name&order=asc')
    ->assertSeeInOrder(['Alice', 'Bob', 'Carol']);

Also add one test asserting an invalid ?sort=password is ignored and returns the default order — that's the regression guard against the injection issue above.

Debugging. Log the builder before executing:

$q = User::sortable();
dump($q->toSql(), $q->getBindings());

If no ORDER BY appears, the trait silently ignored the param — usually a typo in the whitelist key or a model that overrode $sortable down the inheritance chain.

Default order is your safety net. Set $sortableDefault explicitly on every model. Without it, an unsorted first page can be non-deterministic under pagination, which makes "page 2 returns items from page 1" bugs very confusing to chase.

Extension points are limited by design. The trait is intentionally thin. If you need multi-column sorting, custom direction validation, or sorting on computed values, you'll be writing your own scope — treat Larasort as the 90% case and drop out of it locally rather than forking.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox