shreejan/dash-arrange
shreejan/dash-arrange is a Laravel package for building rearrangeable dashboards. It lets users reorder cards, widgets, or list items via drag and drop and persist their preferred layout, making customizable dashboard arrangements simple to implement.
This is a small, single-maintainer package (4 stars, 0 dependents), so treat the source as the documentation. Budget 20–30 minutes to read src/, the published config, and the migration before wiring it in — it will be faster than guessing at conventions.
1. Install and publish
composer require shreejan/dash-arrange
php artisan vendor:publish --provider="Shreejan\DashArrange\DashArrangeServiceProvider"
php artisan migrate
Publish tags may not all exist; if --provider produces nothing, check the service provider for the exact tag names (config, migrations, assets) and publish individually.
2. Look here first, in this order
config/dash-arrange.php — the widget registry and grid/column settings. This is the contract everything else depends on.user_id + JSON layout column) and therefore how you'll scope layouts.routes/ or the provider's boot() — find the endpoint that persists a layout, and confirm what middleware it gets.3. First use case: a personal dashboard
The canonical fit is "each user has a dashboard of cards they can reorder and hide." Register two or three widgets, drop the component into your dashboard view, and confirm the round-trip:
// config/dash-arrange.php
'widgets' => [
'revenue' => ['view' => 'dashboard.widgets.revenue', 'label' => 'Revenue'],
'open_tasks' => ['view' => 'dashboard.widgets.open-tasks', 'label' => 'Open Tasks'],
'activity' => ['view' => 'dashboard.widgets.activity', 'label' => 'Recent Activity'],
],
{{-- resources/views/dashboard.blade.php --}}
<x-dash-arrange::board :widgets="config('dash-arrange.widgets')" />
Verify three things before building further: (a) reordering survives a page reload, (b) a brand-new user gets a sensible default rather than an empty board, (c) the save request is authenticated. If any of those fail, fix it in your own layer — don't wait on upstream.
Wrap the package behind your own service, not scattered calls. With a 4-star, single-maintainer dependency, your biggest risk isn't bugs — it's abandonment. Keep DashArrange references in one place so swapping to spatie/laravel-dashboard, a hand-rolled JSON column, or gridstack.js is a contained refactor:
final class DashboardLayout
{
public function forUser(User $user): array
{
return DashArrange::layoutFor($user)
?? $this->defaultLayout($user->role);
}
public function save(User $user, array $payload): void
{
DashArrange::saveLayout($user, $this->sanitize($payload));
}
}
Always have a default-layout fallback. First-login users have no persisted row. Compute defaults by role or tenant rather than returning an empty board:
protected function defaultLayout(string $role): array
{
return match ($role) {
'admin' => ['revenue', 'open_tasks', 'activity'],
'billing' => ['revenue', 'invoices'],
default => ['activity'],
};
}
Sanitize widget keys on save. Never persist whatever the client posts. Diff the submitted keys against array_keys(config('dash-arrange.widgets')) and drop unknowns — otherwise a stale browser tab silently writes a layout referencing widgets you've since renamed, and the board renders gaps.
Render each widget in isolation. Give every card its own Blade partial with its own data-fetching, and consider @each or a per-widget view composer so one slow widget (a costly aggregate query) doesn't delay the whole board. If the package supports lazy loading, use it for anything heavier than a trivial query.
Version your layout payloads. Store a layout_version alongside the JSON. When you add a widget to the config, you can then migrate persisted layouts (insert the new widget in a default position) instead of leaving existing users without it:
if (($layout['version'] ?? 1) < 2) {
$layout['widgets']['invoices'] = ['visible' => true, 'order' => 99];
$layout['version'] = 2;
}
Scope by tenant, not just user. If you're multi-tenant, add a tenant_id to the published migration before you run it in production. Retrofitting a scope onto an already-populated table is far more painful than editing the migration on day one, and this is exactly the kind of thing the package won't have anticipated.
Save debounced, and optimistically. Drag-drop fires many events. Debounce the persist call (~500ms) and update the DOM immediately; if the request fails, show a non-blocking toast rather than reverting the drag, which feels broken. Include a CSRF token — a fetch() without X-CSRF-TOKEN will 419 and the layout silently won't stick.
Test the round-trip, not the library. Feature-test your save endpoint with actingAs, an invalid widget key, and an oversized payload. Don't write tests against the package's internals — they'll break on any patch release.
arragne-dashboard (typo) while the package is shreejan/dash-arrange. Confirm the exact Composer name with composer show shreejan/dash-arrange before writing docs for your team, and pin a version ("shreejan/dash-arrange": "^1.0") rather than using a floating constraint — with 0 dependents there's no ecosystem pressure to keep BC.resources/js source you can import into your own bundle instead of relying on a vendor:publish'd asset; if you must use the published assets, add a cache-busting query string, since published files often lack versioning.array vs json) matches how the package reads it back — a mismatch between your model cast and the package's internal decoding produces confusing "layout is empty" bugs.web group without auth. Check php artisan route:list | grep -i dash and either wrap the route in your own group or gate the controller.label freely, never the key. If you must rename, write a data migration that rewrites the JSON for all users.updated_at check and 409 on stale writes.config/dash-arrange.php, run php artisan config:clear — otherwise newly registered widgets appear to "not exist" in production while working locally.Log::info() the sanitized payload at the controller. Nine times out of ten it's CSRF or a widget-key filter silently dropping everything.composer update, and with one maintainer, updates will be infrequent and unannounced when they come.How can I help you explore Laravel packages today?