Introduction
Laravel officially uses Laravel Maestro to streamline Starter Kit development. However, there is currently not much information available on how to actually contribute to Starter Kits using Maestro.
When I first started, I was confused about things such as:
- The difference between the build/, kits/, and orchestrator/ directories
- The role of the watcher
- Which directories should be included in a
git commit
In this article, using a real example of modifying text in a Starter Kit, we will cover:
- The basic structure of Maestro
- The relationship between build / kits / orchestrator
- How to run tests
- The workflow up to creating a PR
This article is intended less for Laravel users in general and more for developers considering contributing to Starter Kits or Maestro itself. If you are thinking about adding features or fixing functionality in a Starter Kit upstream, I encourage you to read through this hands-on tutorial and get a feel for the contribution workflow up to the PR stage (note that we will not actually submit a PR).
Clone the Repository and Get the Source Tree Locally
First, clone the repository:
git clone https://github.com/laravel/maestro.git
This creates a local copy of the laravel/maestro source tree.
Running a Starter Kit Through Laravel Maestro
At present, containers are not officially supported, and execution is always native. (If you want to run it inside a container, you will likely need to first understand the native workflow and then build your own setup.) Internally, it essentially runs something similar to artisan serve.
In other words, because everything runs natively, your system must have:
- php-cli
- composer
installed locally. Otherwise, you will not be able to follow the steps below.
Start from orchestrator/
Before launching a Starter Kit through Maestro, let's first check the current directory structure.
% tree -L1 -D
.
├── browser_tests
├── kits
└── orchestrator
Currently, these three directories exist. To get started, change into orchestrator/ and install the Composer and npm dependencies.
cd orchestrator
composer install
npm install
Next, build the project with php artisan build. If you run it without options, it starts in interactive mode:
% php artisan build
┌ Which starter kit would you like to build? ──────────────────┐
│ › ● Livewire │
│ ○ React │
│ ○ Svelte │
│ ○ Vue │
└──────────────────────────────────────────────────────────────┘
For example, selecting Livewire gives:
┌ Which starter kit would you like to build? ──────────────────┐
│ Livewire │
└──────────────────────────────────────────────────────────────┘
┌ Which variant would you like to use? ────────────────────────┐
│ ○ Blank (no authentication) │
│ › ● Fortify (authentication using Fortify) │
│ ○ WorkOS (authentication using WorkOS) │
└──────────────────────────────────────────────────────────────┘
The final selection is whether to enable Teams:
┌ Would you like to enable the Teams feature? ─────────────────┐
│ ○ Yes / ● No │
└──────────────────────────────────────────────────────────────┘
However, Livewire has one additional option:
┌ Which Livewire variant would you like to use? ───────────────┐
│ › ● Single File Components │
│ ○ Multiple File Components │
└──────────────────────────────────────────────────────────────┘
These branches make it possible to represent the following 21 variants:
| Framework | Blank | Fortify | Fortify + Teams | WorkOS | WorkOS + Teams | Components |
|---|---|---|---|---|---|---|
| Livewire | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| React | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Svelte | ✓ | ✓ | ✓ | ✓ | ✓ | — |
| Vue | ✓ | ✓ | ✓ | ✓ | ✓ | — |
- React: 5
- Vue: 5
- Svelte: 5
- Livewire: 6
- Total: 21
When submitting modifications as contributions, you should always keep this matrix in mind.
If you already know which matrix combination you want, you can pass options to php artisan build and avoid interactive mode altogether (this approach will also be introduced later).
The build/ Directory
Looking at the directory tree again:
% tree -L1 -D
.
├── browser_tests
├── build # <------------- This was added
├── kits
└── orchestrator
A build/ directory has been generated. This directory is created by orchestrator/ and serves as a working directory. Essentially, it is a copy of the Laravel Starter Kit corresponding to the variant selected in orchestrator/.
However, the Actual Kit Is Started from orchestrator/
Now let's run it. The application is not launched from build/; instead, from orchestrator/ run:
composer kit:run
This runs things such as composer setup, injects a .env file, and starts the selected variant.

The selected variant has started
Note that kit:run occupies the terminal while running, so plan your workflow accordingly. Also, the port is fixed at 8000, so if that port is unavailable, development may become inconvenient. Changing the port is not particularly difficult, but requires a somewhat hacky approach, so it is best to start in an environment where port 8000 is available.
Practicing Contributions with Maestro
Next, let's practice making modifications using Maestro. The example used here is not intended to be an actual contribution candidate.
Case 1: Change "Log in" to "Log on" on the Login Page
There is also a Log in link on the Welcome page, but for this tutorial we will focus only on the login page.

Other occurrences of Log in, such as on the Welcome page, are out of scope for this tutorial
Looking at the matrix:
| Framework | Blank | Fortify | Fortify + Teams | WorkOS | WorkOS + Teams | Components |
|---|---|---|---|---|---|---|
| Livewire | ✓ | ✓ | ||||
| React | ✓ | ✓ | — | |||
| Svelte | ✓ | ✓ | — | |||
| Vue | ✓ | ✓ | — |
These variants will need to be updated. WorkOS variants do not have a login page, so they are not affected. (Again, this tutorial focuses only on the login page, and WorkOS variants delegate authentication externally.)
Change the File in ../build
Work is performed in build/.
First, move into the build directory.

Maestro is launched from orchestrator/, but development work is done in build/
Since build/ is the actual Laravel application, search for the target string using rg or a similar tool.
We find that the relevant file is resources/views/pages/auth/login.blade.php, so edit it as follows:
<div class="flex items-center justify-end">
<flux:button variant="primary" type="submit" class="w-full" data-test="login-button">
- {{ __('Log in') }}
+ {{ __('Log on') }}
</flux:button>
</div>
Commit & PRs Are Made from kits/
Although the modification was made in build/, this directory is temporary and is not tracked directly in Git. When you modify build/, a watcher automatically updates the corresponding files in kits/. You can verify this by running git status inside build/.

The watcher automatically updates the corresponding files in ../kits
Testing
Everything looks good, so let's run tests. Testing is done from orchestrator using commands such as composer kits:pint and composer kits:check -- --livewire.
cd ../orchestrator
# Source formatting
composer kits:pint
# PHP tests (Livewire variants only)
composer kits:check -- --livewire

Running kit:check while kit:run is still active results in an error
While kits:pint is fine, due to Maestro's design, you must stop kit:run before running kits:check. Therefore, terminate the terminal session where composer kit:run is running.

The tests take quite a while, but eventually complete
That completes the Livewire modification. As shown earlier, Git now reports:
% git status
On branch main
Your branch is up to date with 'origin/main'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ../kits/Livewire/Fortify/resources/views/pages/auth/login.blade.php
no changes added to commit (use "git add" and/or "git commit -a")
Since the changes have been propagated into kits/, that is the directory whose contents should be committed, pushed, and submitted in a PR.
Rewriting the Remaining 7 Variants
At this point, only Livewire/Fortify has been rewritten. The remaining 7 variants must also be updated, otherwise the PR is unlikely to be accepted.
Of course, you could build them one by one, rewrite them one by one, and let the changes be reflected in kits/. However, since that requires considerable effort, it is also acceptable to modify kits/ directly. For example, you could ask an AI to rewrite them like this:
# Prompt for AI
Check the git diff for the changes made to:
kits/Livewire/Fortify/resources/views/pages/auth/login.blade.php
Based on those changes, update the remaining Fortify and Fortify + Teams variants for:
- Livewire
- React
- Svelte
- Vue
so that the `Log in` button is changed to `Log on` throughout the `kits/` directory.
Even when modifying kits/ directly, it is safer to stop kit:run. Use it only when working inside build/.
The following files were edited:
- kits/Inertia/Fortify/React/resources/js/pages/auth/login.tsx
- kits/Inertia/Fortify/Svelte/resources/js/pages/auth/Login.svelte
- kits/Inertia/Fortify/Vue/resources/js/pages/auth/Login.vue
- kits/Inertia/Teams/Fortify/React/resources/js/pages/auth/login.tsx
- kits/Inertia/Teams/Fortify/Svelte/resources/js/pages/auth/Login.svelte
- kits/Inertia/Teams/Fortify/Vue/resources/js/pages/auth/Login.vue
- kits/Livewire/Fortify/resources/views/pages/auth/login.blade.php
- kits/Livewire/Teams/Fortify/resources/views/pages/auth/login.blade.php
Actually Run One Variant and Test It in the Browser
Here, we will verify React + Fortify + No Teams. In reality, after changing all eight files, you should manually test all eight variants in a browser. However, due to space constraints, only this one is shown.
Since changes made directly in kits/ require rebuilding, move to orchestrator/ and run:
php artisan build --kit=react

Providing --kit disables interactive mode.
After that, run composer kit:run, and the application will be accessible at localhost:8000. Let's take a look.

The React version has also been updated.
Proceed with testing the variants one by one in this manner.
Final Testing
Again, stop kit:run. Then move to ./orchestrator and run:
composer kits:pint
composer kits:check
composer kits:lint
This executes all tests and linting. Note that linting is for JavaScript.

Since it runs 21 combinations, the testing process takes quite a while.

kits:check shows 21 passed. It takes an extremely long time.
Additionally, kits:lint runs 15 checks, which also takes a very long time.

kits:lint shows 15 passed. This is also lengthy.
In any case, only after reaching this point are you ready to commit.
Preparing the Commit and PR
In this example, the changes involve these eight files. As mentioned earlier, only kits/ should ever be included in the commit. Nothing else should be touched.
git add kits/
git commit
The username shown here is arbitrary, but in practice you should create commits appropriately for the project. After that, simply follow the project's OSS contribution process and submit the PR.
Example: Making a Shared Backend Change
Previously, we modified eight frontend files. As a final example, let's learn how to make a backend change. This time, we'll build with React + Fortify.
cd orchestrator
php artisan build --kit=react
composer kit:run
Then work inside the build/ directory.
First, Set Up the Authentication Database
Inside build/, run:
php artisan migrate:fresh --seed --force

The database was recreated and seeded.
You can now log in using test@example.com / password. Log in, navigate to Settings, click the profile update button, and observe the toast message.
Let's change that text. Search for it using rg.
It turns out that simply modifying app/Http/Controllers/Settings/ProfileController.php is sufficient. For example, change it to Profile changed.
Then:
% git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: ../kits/Inertia/Base/app/Http/Controllers/Settings/ProfileController.php
As shown above, the watcher detects the change and updates kits/Inertia/Base/app/Http/Controllers/Settings/ProfileController.php.
Updating Livewire
Notice the focus on kits/Inertia. At this stage, all Inertia backend changes have been completed. However, the Livewire side has not yet been updated, so we must make the corresponding Livewire changes.
Stop kit:run, then in orchestrator/ run:
php artisan build --kit=livewire
composer kit:run
Once it starts, move to build/ and run:
php artisan migrate:fresh --seed --force
Then log in and navigate to the relevant page.

The style is actually a little different from React and the others, but functionally it's equivalent.
Livewire Does Not Handle This Through a Controller
Searching with rg reveals that this is handled in resources/views/pages/settings/profile.blade.php.

Handled in a view file rather than a controller.
This can be confusing if you're not familiar with Livewire (the author isn't particularly experienced with it either), but since it is just a simple text replacement, let's make the change.
Then:
As shown, kits/Livewire/Fortify/resources/views/pages/settings/profile.blade.php is now modified as well.
For this change, these two files will be committed. The remaining steps are the same: stop kit:run, then run:
composer kits:pint
composer kits:check
# composer kits:lint # Not necessary this time
Since this is not a frontend change, kits:lint is unnecessary. If the tests pass, proceed with the same commit, push, and PR workflow.









Top comments (0)