Laravel Idea LiteAll-in-one Laravel toolkit for VS Code — navigation, model-aware autocomplete, PhpStorm-style postfix templates, Eloquent builder completion, artisan generators, a DB explorer with inline editing and a SQL console, rename symbol, sidebar views, and an IDE-setup doctor. Full nwidart/laravel-modules support (v9, v10 and v11). Publisher: AriyaTech · Requires VS Code 1.75+ · Activates on any workspace containing an Contents
Install & first run
The status bar shows Recommended extensionsThis extension supplements a PHP language server, it does not replace one. Install Intelephense first — without it you get navigation and Laravel-aware completion, but no PHP type checking, no class completion, and no go-to-definition on PHP symbols.
Commit this to
Keyboard shortcuts
Go to Translation (by key) has no default binding — bind it yourself, or run it from the palette / Commands view. Standard VS Code keys that this extension extends:
To rebind anything, open Preferences: Open Keyboard Shortcuts and search for Jumps are centred. Every navigation this extension performs — route tree, Go to Route, Go to Translation, relationship CodeLens, DB Explorer's Open Model — reveals the target line in the middle of the viewport instead of leaving it wherever the scroll lands.
NavigationClick the string argument inside any of these helpers and jump straight to the source:
Also works with AutocompleteRoutes, views, config, translations, envType inside
Eloquent statics —
|
| Context | Suggests |
|---|---|
->where(', ->orderBy(', ->select(', ->pluck(', ->sum(' … |
Column names for that model |
->with(', ->has(', ->load(', ->whereHas(', ->withCount(' … |
Relationship names |
$this-> |
Columns and relationships of the current model |
$server-> |
Columns and relationships of the model that variable holds |
$server->owner->profile-> |
Columns of the model at the end of the chain — any depth |
[' / , ' inside an array |
Column names — handy for $fillable, $hidden, $casts |
=> in an array literal |
Column names, inserted as 'col' => $this->col |
Which model a query runs against is taken from the receiver first — in $server->load(' the answer is Server no matter what the line above mentions — and only then from the nearest Foo:: or new Foo above the cursor.
Local variables are traced back to whatever introduced them, without running any PHP:
| Where the type comes from | Example |
|---|---|
@var docblock |
/** @var Server $box */ |
| Type-hinted parameter | public function show(Server $server) |
foreach binding |
foreach (Server::all() as $server) |
| Assignment | $server = Server::find($id); or new Server() |
| Relationship hop | $owner = $server->owner; |
A terminal call decides model vs collection: find(), first(), create(), sole() give a model; all(), get(), paginate(), pluck() are collections and stay quiet — unless they're the subject of a foreach, where the element is the model. Variables the index has no model data for — $request, $response, your own services — produce nothing rather than a wrong list.
Multi-level relations
Relationship chains resolve to any depth, in both syntaxes:
$app->sellerAppData->seller->servers-> // → columns of Server
$this->sellerAppData->seller-> // → columns + relations of Seller
AppAttributes::with('sellerAppData.seller.servers') // each dot narrows to the related model
AppAttributes::whereHas('sellerAppData.seller', fn ($q) => …)
AppAttributes::with('sellerAppData.seller:id,name') // columns of the last hop
Each hop is walked through the relationship graph, so with('sellerAppData. offers SellerAppAttributes's relations rather than repeating the root model's. A hop that names a column, or a relation the index doesn't know, ends the walk and the list stays empty rather than guessing.
Method calls deliberately break the chain: $user->posts()-> gets nothing, because that's a query builder, not a model.
Validation rules
Inside rules(), $request->validate([…]), Validator::make(…), validator(…) or a $rules = [ array — including after a | separator and inside the array form:
'email' => 'required|max:255|unique:users,email',
- 80+ rule names, each with a one-line description; rules taking parameters insert with tabstops (
max:255,between:1,100,date_format:Y-m-d). exists:andunique:complete real tables and columns from your indexed models — pick the table and the column list narrows to it. Works for the object form too:Rule::exists('users', 'id').- Field names are left alone: the key half of
'email' => …never gets rule suggestions.
Postfix templates
PhpStorm-style. Type . plus a template name after an expression and accept with Tab or Enter.
| You type | You get |
|---|---|
new BlockChain().var |
$blockChain = new BlockChain(); |
Server::all().foreach |
foreach (Server::all() as $server) { … } |
$user->isActive().if |
if ($user->isActive()) { … } |
$user->email.notnull |
if ($user->email !== null) { … } |
Http::get($url).try |
try { Http::get($url); } catch (\Throwable $e) { … } |
$payload.dd |
dd($payload); |
$user.return |
return $user; |
$items.collect |
collect($items) |
Full set: var, foreach, if, notnull, isnull, not, try, return, dd, dump, log, echo, throw, collect, count, isset, empty, json.
Variable naming for .var / .foreach: constructor → the class name; Model::find()/first()/create() → the model name; Model::all()/get()/pluck() → the plural; otherwise the last method name with a get/fetch/make/build/load/to prefix stripped; falling back to $value.
Staying out of the way: . is PHP's concatenation operator, so templates only offer themselves when the text before the dot is a call, an index, or a variable/property chain. 'Hello ' . $name is never touched, and neither are string literals, numbers or bare constants. .var, .try and .throw additionally require a call or index.
Snippets
15 PHP snippets: hasMany, belongsTo, hasOne, belongsToMany, morphMany, morphTo, hasManyThrough, scope, accessor, mutator, fillable, casts, formRules, resourceArray, factoryDef.
Rename symbol (F2)
Place the cursor on any PHP symbol and press F2 to rename it across the workspace. Powered by AST parsing via php-parser.
| Symbol | Example | Scope |
|---|---|---|
| Variable | $customer |
Current function/method |
| Class / Interface / Trait | Customer |
All PHP files |
| Method | register() |
All PHP files |
| Property | $this->auth |
All PHP files |
| Function | helper() |
All PHP files |
Laravel-aware: it recognises the [Controller::class, 'method'] array used in routes, listeners and jobs, so renaming a method updates those string references too:
// Renaming 'show' via F2 updates this too
Route::get('stores/{store}', [CatalogController::class, 'show']);
Sidebar explorer
The AriyaTech Laravel activity-bar icon opens four views:
| View | Contents |
|---|---|
| Routes | Every route, grouped by name prefix, with search, flat/tree toggle and refresh |
| Translations | Translation keys by locale, including module translations and flat lang/en.json files |
| Database | Models, tables and columns from _ide_helper_models.php or model source |
| Commands | One click for every AriyaTech-Laravel command |
Routes — searching by path
Routes are indexed with their full URI, assembled from the module's RouteServiceProvider mount prefix and every enclosing Route::prefix(…)->group(…). So a route written as
// Modules/Store/routes/customer_api.php
Route::prefix('shop')->group(function () {
Route::get('provinces', [ProvinceController::class, 'index']);
});
is indexed as GET /shop/provinces and found by searching shop/provinces, not just by name.
Each route also records the controller action behind it, resolved through the route file's use statements, so all of these find the same route:
shop/provinces path
customer.shop.provinces name
POST method
ProvinceController::index short action
Modules\Store\Http\Controllers\Api\Province… fully qualified action
Actions are read from [Controller::class, 'method'], 'Controller@method' and single-action Controller::class routes; closure routes simply have none.
- Search Routes (sidebar) filters on name, path, HTTP method and action, case-insensitively.
- Go to Route (
Ctrl+Alt+R) is a palette picker over every route, labelledMETHOD /path, that jumps straight to the definition. - Routes without
->name()are included. They can't be used withroute('…'), so they're kept out of route-name completion, but they appear in the tree and the picker grouped under<segment> (unnamed)— often the majority of an API-only project.
Translations — searching by key
Search Translations matches the key and the value, not just the file name. Searching store_not_found finds it inside store::api in every locale that defines it, and searching Store not found finds it by its English text. Groups whose own name matches show all their keys; groups that matched because of a key show only the matching ones.
Go to Translation (by key) is a palette picker over every key in the project — group, key, value, locale and file all searchable — that jumps to the defining line.
Both lang/xx/group.php and flat lang/xx.json files are read, in the app and in every module (Modules/*/lang, Modules/*/Resources/lang).
Database & DB Explorer
Click any model in the Database view to open an interactive browser:
- Live data through
php artisan tinker - Paginated results (50 rows/page) with prev/next
- Inline editing — double-click a cell, Enter to save
- Row deletion — select and press Delete, or the X button
- Search within the table across all columns
- Query console for Eloquent expressions or raw SQL
- UUID and numeric primary keys
- Open Model button to jump to the source
Search Tables (Ctrl+Alt+T) finds any model by class name, table name or FQCN. Models and columns also appear in Ctrl+T workspace symbol search.
Table names follow Laravel's own rules — an explicit protected $table always wins, otherwise the class name is snake-cased and only its final word pluralised: Company → companies, PlatformSettings → platform_settings, CheckHost → check_hosts.
Commands
Every command lives under the AriyaTech-Laravel category in the palette and in the Commands sidebar view.
| Command | Description |
|---|---|
| Generate... | Pick a generator type and create a file (Ctrl+Shift+,) |
| New Model / Controller / Migration / Command / Helper Class | Shortcuts to the same generator |
| Generate IDE Helpers | Facades, models or PhpStorm meta (Ctrl+Shift+.) |
| Init IDE Helper | Install and configure barryvdh/laravel-ide-helper end to end |
| Doctor: Check IDE Setup | Diagnose and fix broken PHP completion |
| Go to Route (by path) | Palette picker over every route (Ctrl+Alt+R) |
| Go to Translation (by key) | Palette picker over every translation key |
| Setup PHP CS Fixer | Write .php-cs-fixer.php and enable format-on-save |
| Run artisan command... | Pick any artisan command, with prompts for its arguments |
| Refresh Index | Re-scan the workspace |
| Migrate / Rollback / Fresh / Fresh --seed / Status | Migration commands |
| Database Seed | Run seeders |
| Switch .env | Swap .env for .env.local, .env.staging, … |
| Tail Log / Clear & Tail Log | Follow storage/logs/laravel.log in an output channel |
| Module Overview | Webview summarising every module's structure |
| Search Routes / Translations / Database | Filter the matching sidebar view |
| Search Tables | Find and open a table in the DB Explorer (Ctrl+Alt+T) |
| Explore Table | Open the DB Explorer for one model |
| Toggle Flat/Tree View | Switch grouping in the Routes and Database views |
Generators
Pick a type, enter a name, choose a module (or the root app), toggle options — the extension runs the right artisan command, module-aware:
- Root:
php artisan make:model Invoice -m -c - Module:
php artisan module:make-model Invoice Billing -m -c
Model, Controller, Migration, Command, Seeder, Form Request, Middleware, API Resource, Event, Listener, Job, Mailable, Notification, Policy, Validation Rule, Service Provider, Factory, Test, plus a Helper Class (written directly) and a new Module (module:make).
Right-clicking a folder under Modules/<Name>/ preselects that module.
IDE helper generation
Init IDE Helper does the whole bootstrap: composer require --dev barryvdh/laravel-ide-helper (no version pin, so Composer picks 2.x for Laravel 9 and 3.x for Laravel 10+), publishes the config, adds Modules/*/app/Models to model_locations, turns on include_fluent, include_class_docblocks and write_model_relation_count_properties, adds a post_migrate hook so models regenerate after every migration, and generates all three files.
Generate IDE Helpers runs individual pieces. Picking Models asks for a write mode:
| Mode | What it does |
|---|---|
--nowrite (default) |
Everything goes to _ide_helper_models.php; your model files are never touched. |
--write-mixin |
One @mixin IdeHelperFoo line per model; properties live in IdeHelperFoo stubs. |
--write |
The full @property docblock inside each model file. |
The extension understands the IdeHelperFoo stub naming and maps it back to Foo everywhere — Database view, DB Explorer, completions and @mixin Ctrl+Click.
Doctor — fixing broken completion
AriyaTech-Laravel: Doctor: Check IDE Setup runs the checks behind every "completion just stopped working" report, writes a report to its output channel, and offers the fixes as a multi-select QuickPick. Nothing is written unless you tick it.
| Check | Why it matters |
|---|---|
| Helper files present | _ide_helper.php is the only place \Str, \Route, \DB and the other root-namespace aliases are declared — Laravel registers them at runtime. |
intelephense.files.maxSize |
Files above this limit are skipped silently. On Laravel 11/12 _ide_helper.php is over 1 MB and the default limit is exactly 1,000,000 bytes, so it drops out of the index with no warning. |
| Exclude globs | An entry matching the helper files in intelephense.files.exclude or files.exclude defeats the whole setup. |
| Duplicate model declarations | --nowrite re-declares each model as class Foo extends \Eloquent {}; PhpStorm merges the two declarations, Intelephense keeps one. |
| Two language servers | Intelephense and DEVSENSE PHP Tools both answering completion. |
It also runs quietly when the workspace opens and only speaks up for problems that actually break completion. Disable with laravelIdea.doctorOnStartup.
PHP CS Fixer setup
One command for format-on-save with unused imports removed automatically:
- Checks for
junstyle.php-cs-fixerand offers to install it. - Writes a Laravel-aware
.php-cs-fixer.php— PSR-12 plusno_unused_imports,ordered_imports, short arrays, single quotes, blank line beforereturn. Only directories that exist are passed to the Finder,*.blade.phpis excluded, and yourlaravelIdea.modulesPathfolder is included. - Wires up
php-cs-fixer.config,php-cs-fixer.onsave, and a[php]block witheditor.defaultFormatterandeditor.formatOnSave. - Offers
composer require --dev friendsofphp/php-cs-fixerif the binary is missing, then pointsexecutablePathat it. - Adds
.php-cs-fixer.cacheto.gitignore.
Settings go through the VS Code configuration API rather than editing .vscode/settings.json as text, so comments and formatting survive. If the config file already exists you're asked whether to overwrite it or only apply the settings.
Laravel has shipped Pint (
vendor/bin/pint) since 9.x, which is php-cs-fixer with a Laravel preset. If you already use Pint, you probably don't want a second formatter.
Migrations, artisan, logs, env
- Migrate / Rollback / Fresh / Fresh --seed / Status / Seed run in the output channel and trigger a re-index when they finish.
- Run artisan command... lists every artisan command from
artisan list --format=jsonand prompts for each argument. - Tail Log follows
storage/logs/laravel.log; Clear & Tail Log truncates it first. - Switch .env swaps in
.env.local,.env.stagingand friends.
All PHP invocations honour laravelIdea.phpCommand, so "docker compose exec -T app php" works.
Settings
| Setting | Default | Description |
|---|---|---|
laravelIdea.phpCommand |
"php" |
Command used to run PHP/artisan, e.g. "docker compose exec -T app php" |
laravelIdea.modulesPath |
"Modules" |
Folder where nwidart modules live |
laravelIdea.viewExtensions |
[".blade.php", ".php"] |
Extensions treated as views when indexing |
laravelIdea.logFile |
"storage/logs/laravel.log" |
Path to the Laravel log |
laravelIdea.relationshipCodeLens |
true |
CodeLens above Eloquent relationship methods |
laravelIdea.modelStaticCompletion |
true |
Offer find, where, first, … after Model:: |
laravelIdea.doctorOnStartup |
true |
Check the Intelephense setup when the workspace opens |
Intelephense settings worth having in .vscode/settings.json (Doctor writes the first one for you):
{
"intelephense.files.maxSize": 5000000
}
nwidart/laravel-modules support
Both directory conventions are scanned at once, so mixed projects work:
| Resource | v9 / v10 path | v11 path |
|---|---|---|
| Routes | Modules/*/Routes/**/*.php |
Modules/*/routes/**/*.php |
| Views | Modules/*/Resources/views/** |
Modules/*/resources/views/** |
| Config | Modules/*/Config/*.php |
Modules/*/config/*.php |
| Translations | Modules/*/Resources/lang/** |
Modules/*/lang/** |
| Models | Modules/*/Models/, Modules/*/Entities/ |
Modules/*/app/Models/, Modules/*/app/Entities/ |
| Blade components | Modules/*/Resources/views/components/ |
Modules/*/resources/views/components/ |
Model directories are scanned recursively, so app/Models/Billing/Invoice.php is indexed too. Name and URI prefixes declared in each module's RouteServiceProvider are applied to every route in the files it mounts.
Troubleshooting
\Str, \Route or \DB show as undefined types.
Those aliases are registered at runtime by Laravel; the only static declaration is namespace { class Str extends \Illuminate\Support\Str {} } inside _ide_helper.php. Check, in order:
- Two language servers. If Intelephense and DEVSENSE PHP Tools are both installed, one can report a type as undefined while the other resolves it fine. This is the most likely cause — disable one for the workspace.
- The declarations are actually there. Doctor greps
_ide_helper.phpfor theStr,Route,DBandCachealiases and offers to regenerate if any are missing. intelephense.files.maxSize. Oversized files can be dropped from the index without a log entry. Measured against Intelephense 1.18.5, a 1.04 MB_ide_helper.phpstill indexed at the 1,000,000-byte default and\Str::completed normally — so treat this as a guard to rule out, not a diagnosis.
Model::find() isn't suggested.
find comes from __callStatic and exists only on \Eloquent. If your model has a custom base class, the ide-helper stub that would provide it collides with your real class declaration and loses. This extension supplies those methods itself; make sure laravelIdea.modelStaticCompletion is on.
No completion on $model->column.
Check that _ide_helper_models.php actually contains your model. A generation run that dies part-way leaves a truncated file — the file is rewritten from scratch each time, so one failing model can cost you every model after it. Doctor reports how many of your models are documented and flags the shortfall.
A translation key can't be found in the sidebar.
Search matches keys and values, not only group names, and covers lang/xx/group.php plus flat lang/xx.json in the app and every module. If a key still doesn't show, it's in a directory none of those globs reach — check that the module uses lang/ or Resources/lang/.
Duplicate suggestions for everything. Two PHP language servers are running. Check the Extensions view for both Intelephense and DEVSENSE PHP Tools and disable one.
A @mixin on a base class doesn't help its children. @mixin applies only to the class carrying it and is not inherited; extends is. Nor does @mixin contribute static members, so @mixin \Eloquent will not give you Model::find().
Changes to the extension don't show up. Repackage with ./build.sh and reinstall the VSIX — source edits alone don't reach the installed extension.
Routes are missing from the sidebar. Routes are found by scanning routes/** and Modules/*/[Rr]outes/**. Route::resource() and apiResource() are not expanded into their individual named routes.
How it works
On activation the extension scans the workspace and builds in-memory lookup tables for routes, views, config keys, translation groups, model tables, columns, relationships and .env keys. File watchers re-index on change, debounced at 500 ms; generators trigger a refresh when they finish.
Navigation and completion never execute PHP — files are parsed with regex, which keeps everything fast and offline. Only the DB Explorer and the generators shell out (php artisan tinker, php artisan make:*, composer).
Support us
If this extension saved you time, consider supporting development:
| Currency | Address |
|---|---|
| TRX | TY4wx4Pd5ftsmRLjZwRq8ikdNenoyXXWsx |
| ETH | 0x872B453BbE046662990e7048d6dC992d09c16e7b |
Contributing
Contributions are welcome — open an issue or submit a merge request.