.ai/guidelines/snipe-it-architecture.md
Two parallel controller trees:
app/Http/Controllers/ — web/UI controllers returning Blade views.app/Http/Controllers/Api/ — REST API controllers returning JSON, consumed by datatables and select2.Both trees use the same subdirectory groupings: Assets/, Licenses/, Users/, Accessories/, Consumables/, Components/, Kits/, Account/, Auth/.
Every API controller returns data through a Transformer in app/Http/Transformers/. Never return raw model attributes from an API controller. DatatablesTransformer wraps paginated results.
return (new AssetsTransformer)->transformAssets($assets, $assets->count());
This supersedes the generic advice to reach for Eloquent API Resources — follow the existing transformer convention.
app/Policies/.CheckoutablePermissionsPolicy is the base for assets, licenses, accessories, and consumables.checkout() / checkin() methods accept $item = null, so @can('checkout', \App\Models\Asset::class) works without an instance.routes/web.php and in the per-entity files under routes/web/ (hardware.php, users.php, licenses.php, accessories.php, components.php, consumables.php, kits.php, models.php, fields.php, locations.php). Check both when adding or locating a UI route.routes/api.php.->breadcrumbs(fn (Trail $trail) => ...) from tabuna/breadcrumbs. Every UI route should have a breadcrumb.route('reports/unaccepted_assets').Setting::getSettings()->full_multiple_companies_support == '1' gates company-scoped filtering. The select2 endpoints (selectlist() methods) accept a companyId query param:
if ((Setting::getSettings()->full_multiple_companies_support == '1') && ($request->filled('companyId'))) {
$query->where('table.company_id', $request->input('companyId'));
}
Wire it up from Blade with data-company-id="{{ $user->company_id }}".
Use class="js-data-ajax" with data-endpoint="hardware|licenses|consumables|...". snipeit.js auto-initializes these, forwarding data-company-id as companyId and data-asset-status-type as statusType to the API.
After checkout, Helper::getRedirectOption() reads $request->redirect_option. To redirect back to the assigned user, the form must set:
redirect_option=targetcheckout_to_type=userassigned_user={{ $user->id }}UI strings are translation keys in resources/lang/en-US/general.php and its sibling files. Always add a new key rather than hard-coding English in a view.
$snipeSettings is shared with every view by SettingsServiceProvider. Use it directly in Blade — do not pass Setting::getSettings() from the controller.
app/Helpers/Helper.php)Helper::deployableStatusLabelList() — status labels for checkout forms.Helper::defaultChartColors(int $index = 0) — 10-color chart palette.Helper::getRedirectOption($request, $id, $table, $item_id = null) — post-checkout redirect logic.