# Laravel

> How midcode runs a Laravel app, edits its Blade views on the canvas, and handles the React, Vue and Livewire starter kits, without changing a file to do it.

- Page: https://midcode.app/docs/frameworks/laravel
- From the midcode docs. Every page as Markdown: https://midcode.app/llms.txt
- Status: This ships with the next release of midcode. The version you can download today (1.1.2) doesn't have it yet.

midcode opens a Laravel app and edits its Blade views on the canvas: text, classes, attributes, new elements and their order, written into the `.blade.php` file. Pages made with Inertia (React or Vue) and with Livewire are edited too. What PHP prints (`{{ $title }}`) is changed where the value is written, not on the canvas.

There are three layers, and they don't ship at the same time:

| Layer | What you get | Status |
| --- | --- | --- |
| The app on the canvas | A Laravel app that uses Vite, shown at its `APP_URL` when that's a `localhost` or `127.0.0.1` address: every breakpoint, Preview, comments. Nothing is editable | In 1.1.2 |
| Blade | Blade views edited on the canvas, with or without Vite | Next release |
| Starter kits | Inertia pages in React or Vue, and Livewire and Volt pages | Next release |

## At a glance

| | Laravel |
| --- | --- |
| Detected by | An `artisan` file at the top of the folder |
| Runs with | PHP's built-in server on a port midcode picks, with one file of midcode's loaded first. Vite runs beside it when the app uses Vite |
| Elements are marked by | Blade, as it compiles each view. Inertia pages by midcode's Vite plugin |
| Editing | Text, classes, attributes and tag; insert, move, duplicate and remove |
| Components | `<x-…>` and Livewire components are edited in their own view. An instance isn't selectable |
| Pages | The GET routes of `routes/web.php`. A new page is a Blade view and a `Route::view` line |
| Styles | Tailwind 4 classes, or `mid:` utilities with `public/midcode.css` |
| Tried with | Laravel 13 on PHP 8.5, and the React, Vue and Livewire starter kits of Laravel 12 |

## In the version you can download today

midcode 1.1.2 opens a Laravel app whose `package.json` has Vite. It starts Vite and shows the site at the `APP_URL` of your `.env` (`http://localhost:8000` when there's none).

- If nothing answers there five seconds after Vite is up, and `APP_URL` is a plain `http` address on this Mac, midcode runs `php artisan serve` on that host and port in your shell.
- You can check every breakpoint, use the site in Preview and leave comments. Blade views can't be edited.
- A site on a host name of its own, like the `https://shop.test` that Herd or Valet give it, stays blank in 1.1.2: that version only lets `localhost` addresses into the canvas. The next release shows any host.
- A Laravel app without Vite isn't recognised in 1.1.2.
- This layer was tried with a stand-in server answering at `APP_URL`, not with Herd or Valet themselves.

Everything below is the next release.

## How midcode runs it

When it can, midcode serves the app itself instead of running `php artisan serve`. It starts PHP's built-in server, the same one `artisan serve` runs, from your app's `public/` folder:

```bash
php -d auto_prepend_file=<midcode's data folder>/injected/blade-prepend.php \
    -S 127.0.0.1:<port> server.php
```

`php` is the first one on your login shell's PATH. `server.php` is your project's own if it has one, else the one inside `vendor/laravel/framework`. The port is a free one midcode picks. Two more things are set for that process only: `VIEW_COMPILED_PATH` points at a folder in midcode's data folder, and PHP's deprecation notices aren't printed into the page (they still go to the log).

The file loaded first is what gets the marks in:

1. It loads Composer's autoloader. When Laravel first asks for its Blade compiler, it defines that class from Laravel's own source with one line added at the top of `compileString()`.
2. That line hands each view of your project (never one under `vendor/`) to a small server midcode runs on `127.0.0.1`. It answers with the same source, with a `data-mc="file:line:col"` attribute on every element. Blade compiles that.
3. The compiled views land in midcode's folder, which is emptied at every start. `storage/framework/views` isn't used, so what Herd or `artisan serve` show later has no marks in it.
4. If anything looks unfamiliar (another Blade compiler, no answer in three seconds), Blade compiles the view as it is.

The same file adds one script to the HTML it serves, so the page reloads when a `.php`, `.css` or `.js` file under `resources/` changes.

midcode adds no file to your project and changes none to do this. Your app still writes its own logs, sessions and cache to `storage/`, as it does under any server.

### With Vite

When `package.json` has Vite, midcode starts it with your own Vite config plus its plugin, on a port it picks. Laravel's Vite plugin chooses the host, as it does for you. The canvas shows midcode's PHP server once Vite is up and the site answers.

### Without Vite

Only the PHP server runs.

### When midcode can't serve it

If there's no `php` on your PATH, no `server.php`, or no `public/` folder, midcode falls back to how the app is usually served: it shows `APP_URL` if something already answers there (Herd, Valet), and otherwise runs `php artisan serve`. Those pages arrive without Blade's marks. midcode then looks each element up in your views after the page loads, the way it does for [any site a server renders](https://midcode.app/docs/frameworks/server-templates.md). That path hasn't been tried with Herd.

### What you need

- PHP on your Mac, on your shell's PATH.
- The app in a state where it runs: `composer install` done, a `.env` with its key, its database reachable. midcode doesn't run Composer or migrations on a project it opens.
- For an app with Vite, its `node_modules`. midcode offers to install them when they're missing.

## What you can edit

Click any element of a view: the right panel shows its file and line under **Code**. Double-click a text to change it.

```diff title="resources/views/welcome.blade.php"
 <section class="px-6 py-16">
-    <h1 class="text-4xl font-semibold">Fresh bread, every morning</h1>
+    <h1 class="text-5xl font-semibold">Bread, pastries and coffee</h1>
     <p class="mt-4 text-neutral-600">{{ $tagline }}</p>
```

What Blade computes is read as "not written here" and never touched:

- `{{ $x }}`, `{!! $x !!}`, `@if(…)`, `@foreach(…)`, `@csrf` and every other directive, `@php … @endphp`, `<?php ?>`. `{{-- … --}}` is a comment.
- In an attribute, the written part and the PHP part are told apart: in `class="card {{ $active ? 'on' : '' }}"` the panel changes `card` and leaves the rest.
- `:name`, `@click`, `x-…`, `wire:…` and `@class([…])` on a tag are left as they are.

An element drawn inside `@foreach` is one line of the view, shown many times: editing its classes changes all of them. A tag opened in one branch of an `@if` and closed in another can't be moved or removed from the canvas.

### Blade components

`<x-card>` and `<livewire:counter />` aren't selectable as instances, and they have no props panel. What a component draws is marked in its own view (`resources/views/components/card.blade.php`), so you edit it there by clicking it. What you write between a component's tags is marked where you wrote it.

## Starter kits

Laravel's own kits were tried, on Laravel 12.

### React and Vue, through Inertia

The pages are components in `resources/js/pages`. midcode's Vite plugin marks them as Vite builds them, exactly as in a [Vite + React](https://midcode.app/docs/frameworks/react-vite.md) or [Vue](https://midcode.app/docs/frameworks/vue.md) project, and Blade marks `resources/views/app.blade.php`. On the page that was measured, 161 of 164 elements had a mark. A text or class edit shows through Vite's hot reload, without the page loading again.

### Livewire and Volt

Livewire's views are Blade, so they're marked like any view: 162 of 162 elements on the kit's home page. The marks survive a Livewire round trip, because the Blade it sends back is already marked. On a Volt page, what isn't marked are Flux's components: they come from `vendor/`.

New project in the hub makes any of them (PHP, then Laravel, Laravel + React, Laravel + Vue or Laravel + Livewire) with `composer create-project`. See [New project](https://midcode.app/docs/start/new-project.md).

## Pages

**Pages** in the top bar lists the GET routes written out in `routes/web.php`, and in the route files it brings in with `require __DIR__.'/auth.php'`.

| Route | Page | File it opens |
| --- | --- | --- |
| `Route::view('/about', 'about')` | `/about` | `resources/views/about.blade.php` |
| `Route::get('/', fn () => view('welcome'))` | Home | `resources/views/welcome.blade.php` |
| `Route::get('/', fn () => Inertia::render('welcome'))` | Home | `resources/js/pages/welcome.tsx` (or `.jsx`, `.vue`) |
| `Volt::route('/profile', 'settings.profile')` | `/profile` | `resources/views/livewire/settings/profile.blade.php` |
| `Route::get('/posts/{post}', [PostController::class, 'show'])` | `/posts/[post]` | None: the view isn't named in the route |

A route with a parameter is a dynamic page: midcode finds its real addresses from the running site's sitemap and links ([Pages and navigation](https://midcode.app/docs/editor/pages.md)). You can always type a path such as `/pricing` in **Pages** and press `Enter`.

"New page" in that menu writes a Blade view and the route that shows it, at the end of `routes/web.php`:

```php title="resources/views/about.blade.php"
@extends('layouts.app')

@section('content')
    <main class="mx-auto max-w-3xl px-6 py-24">
      <h1 class="text-4xl font-semibold">About</h1>
      <p class="mt-4">A new page, made in midcode.</p>
    </main>
@endsection
```

```diff title="routes/web.php"
 Route::get('/', fn () => view('welcome'));
+
+Route::view('/about', 'about');
```

The view is written like the home page's: under the same `@extends` and `@section` when the home view extends a layout, otherwise as a whole document with a copy of the home view's `<head>`. `/blog/first` is `resources/views/blog/first.blade.php`, shown by `Route::view('/blog/first', 'blog.first')`. The class names are written when Tailwind is in your `package.json`. A new page is a Blade view in an Inertia app too.

"Remove" moves a page's view to the Trash and takes its `Route::view` line out with it. Making a page, seeing it on the canvas and removing it were tried on a Laravel app.

## Site settings

The globe in the top bar opens [Site settings](https://midcode.app/docs/editor/site-settings.md). In a Laravel app the title, the description, the language, search engines, the favicon and the social image are tags in the `<head>` of a view: the first one that closes a `<head>`, in alphabetical order of its path. Images are copied into `public/`.

What Blade computes there is shown as code and isn't written: a `<title>{{ config('app.name', 'Laravel') }}</title>` shows in the form as that code, and stays your code's to set. There's one form for the whole app. Tried on a Laravel app.

## Styles

- **Tailwind 4** (what Laravel sets up through Vite): midcode writes Tailwind's classes and Vite compiles them. See [Tailwind CSS](https://midcode.app/docs/styling/tailwind.md).
- **No Tailwind, no Vite**: midcode writes `mid:` utilities, keeps their CSS in `public/midcode.css`, and adds `<link rel="stylesheet" href="/midcode.css">` before `</head>` in every layout view. See [Without Tailwind](https://midcode.app/docs/styling/without-tailwind.md).
- **Vite without Tailwind 4**: midcode writes `midcode.css` at the top of the project and says it couldn't tell where to import it. Import it from `resources/js/app.js`.

## Limits

- Text that PHP prints is edited where the value is written: the controller, a language file, the database. midcode's own search for a text reads `.json`, `.js`, `.ts` and `.md` files, never `.php` or `.blade.php`.
- Views that come from `vendor/` (a package's, Flux's) are never marked.
- Elements move inside one view. Moving one from a partial into the view that includes it is done in the code.
- Routes inside a group with a prefix are listed without the prefix, and `Route::resource` isn't read.
- The canvas shows the app at `http://127.0.0.1:<port>`, not at its `APP_URL`. Anything tied to the host name (a session domain, an OAuth callback) behaves as it would at that address.
- Not tried: Herd, Valet, Sail, Inertia with Svelte.
- [The free canvas](https://midcode.app/docs/editor/free-canvas.md) is for Next.js projects.
- "Remove" only knows the `Route::view` line midcode writes. For a page whose route is written another way (`Route::get` with a closure or a controller), the view goes to the Trash and the route stays: take that line out yourself.
- With several views that close a `<head>` (a layout for the site and one for sign-in pages), Site settings writes the first by path, which may not be the one your pages use. The note under the form's images names the file it writes.
- A MySQL or MariaDB database can't be opened yet. midcode's [Database](https://midcode.app/docs/data/database.md) finds the connection from the `DB_*` values of `.env`, and opens Postgres and SQLite.

## Troubleshooting

**"Vite is running, but the site isn't answering at … (APP_URL in .env)."** midcode's PHP server didn't come up and nothing else serves the app. Run `php -v` in a terminal: if it isn't found, PHP isn't on your PATH. If it is, open the **Dev server log** in the top bar to read what PHP said.

**Some elements of a Blade view have no file and line.** midcode is probably showing the app from another server and finding elements after the page loads, which finds fewer. When midcode serves the app itself, the log has a line starting with `$ php -S 127.0.0.1:`. If it's missing, check that `php` is on your PATH.

**A Laravel error page.** That's your app answering: a missing `.env`, no `APP_KEY`, a database that isn't running. Fix it as you would in a browser, then press **Reload breakpoints**.

**To serve it your own way**, use **Change how it runs** on the error card: a command, an address, or both ([Any other stack](https://midcode.app/docs/frameworks/custom-server.md)). Your command then replaces midcode's start altogether: Vite isn't started for you.
