Any other stack
Next release
The stacks midcode recognises by their files, the command it runs for each, the form it shows when it can't tell, and the .midcode/server.json format.
This ships with the next release of midcode. The version you can download today (1.1.2) doesn’t have it yet.
midcode opens any folder. When the project isn’t one of the frameworks it starts with its own plugin, it works out how the site is served from the files it finds, runs that command and shows the result on the canvas. When it can’t tell, it asks you, and keeps your answer in .midcode/server.json. Of the projects on this page, midcode 1.1.2 and earlier open only the ones with a dev script in package.json or an index.html, and only to view and comment.
At a glance
| Any other stack | |
|---|---|
| Detected by | Files at the project’s root, a script in package.json, or your answer in .midcode/server.json |
| Runs with | The stack’s own command, in your login shell, on a free port |
| Elements are marked by | Matching each loaded page against the project’s templates, when it has any |
| Editing | Text, classes, attributes and structure where an element is found in a template. View and comment otherwise |
| Pages | The stack’s routes where midcode can read them. Any path can be typed |
| Styles | mid: utilities. Where midcode.css goes depends on the stack |
| Tried with | Laravel, PHP, Django, Flask, FastAPI, Hugo, WordPress, Shopify and Angular. Rails and Jekyll through server.json |
What midcode tries, in order
Your answer. A
.midcode/server.jsonwith a command or an address comes before everything else.A framework it starts itself: Next.js, React Router, Remix, Astro, SvelteKit, Nuxt or Vite in
package.json. Each has its own page.A stack it knows by its files: the table below.
A
devscript inpackage.json, run with your package manager. midcode reads the address from what it prints.An
index.htmlat the root: plain HTML, served by midcode.Another script: the first of
start,serve,developanddocs:dev.Nothing fits: the canvas asks “How does this site run?”
At the top of a repository that keeps its site in a subfolder, midcode opens that site instead: see Monorepos.
Stacks midcode recognises
The first row that fits wins.
| Stack | Recognised by | Command |
|---|---|---|
| Laravel | artisan | php artisan serve --host=127.0.0.1 --port=$PORT |
| Django | manage.py | python manage.py runserver 127.0.0.1:$PORT |
| Rails | bin/rails or config/application.rb | bin/rails server -b 127.0.0.1 -p $PORT |
| Shopify theme | layout/theme.liquid | shopify theme dev --path $THEME --host 127.0.0.1 --port $PORT, plus --store <store> once it’s known |
| Hugo | hugo.toml, hugo.yaml, hugo.yml or hugo.json; or config.toml / config.yaml with content/ and layouts/ or themes/ | hugo server --bind 127.0.0.1 --port $PORT |
| Jekyll | _config.yml or _config.toml | jekyll serve --host 127.0.0.1 --port $PORT, through bundle exec when there’s a Gemfile |
| Zola | config.toml with templates/ and content/ | zola serve --interface 127.0.0.1 --port $PORT |
| Phoenix | A mix.exs that names :phoenix | mix phx.server |
| ASP.NET | A .csproj, .fsproj or .vbproj at the top, with Pages/, Views/ or wwwroot/ | dotnet watch run --urls http://127.0.0.1:$PORT |
| Flask | flask among the Python dependencies, and a module that makes Flask(…) | python -m flask --app <module> run --debug --host 127.0.0.1 --port $PORT |
| FastAPI | fastapi among the Python dependencies, and a module with app = FastAPI(…) | python -m uvicorn <module>:<app> --reload --host 127.0.0.1 --port $PORT |
| MkDocs | mkdocs.yml or mkdocs.yaml | python -m mkdocs serve -a 127.0.0.1:$PORT |
| WordPress | wp-load.php, or a theme folder inside a WordPress | php -d auto_prepend_file=$WP_PREPEND -S 127.0.0.1:$PORT |
| Angular | angular.json | NG_CLI_ANALYTICS=false ./node_modules/.bin/ng serve --host 127.0.0.1 --port $PORT |
| PHP | index.php at the root, or in public/, web/ or webroot/ | php -S 127.0.0.1:$PORT, with -t <folder> when it’s not the root |
| Deno | No package.json, and a dev, start or serve task in deno.json or deno.jsonc | deno task <task> |
python is the project’s own: .venv/bin/python, venv/bin/python or env/bin/python when one exists, else uv run python, poetry run python or pipenv run python by the lockfile it finds, else python3.
Some of these do more than the table says, and have their own page:
Laravel: midcode serves the app with PHP’s own server when it can, so Blade views are marked as they compile, and shows a site already running at its
APP_URL.WordPress: what
$WP_PREPENDis, and opening a theme folder.PHP and Twig, Django, Flask and FastAPI, Ruby on Rails, Hugo, Jekyll and Eleventy: templates, pages and styles for each.
Shopify themes: what
$THEMEis, and what the canvas asks for instead of the terminal.Angular: templates, external and inline.
How $PORT works
Before it runs a command, midcode picks a free port, starting at 4310, and puts it wherever the command or the address says $PORT (or ${PORT}). The same number is in the PORT environment variable, which many tools read by themselves. Some don’t, and would take their usual port: when midcode runs a script of your package.json that is only a call to one of those (eleventy, rsbuild, rspack, vue-cli-service, webpack, webpack-dev-server, docusaurus, gatsby, parcel, vitepress, vuepress) and names no port, it adds --port with that number.
The command runs in your login shell, from the project’s folder, so it finds what your terminal finds. midcode also sets BROWSER=none, so a tool doesn’t open a browser tab of its own.
Then midcode waits, up to two minutes, for something to answer at:
the address you gave, if you gave one;
a local address the command prints (its “Local:” line when it has one, else the first
localhostor127.0.0.1address);http://localhost:$PORTandhttp://127.0.0.1:$PORT, when no address was given.
A stack that may already be running is asked first. A Laravel app that answers at its APP_URL, or a WordPress at the WP_HOME of its wp-config.php, is shown as it is, and the log says so: Already running at http://shop.test: midcode shows it.
When midcode asks
When nothing fits, the canvas shows a form instead of the site:
How does this site run? “Give midcode the command that starts it, the address where it’s already running, or both.”
Command, with the example
bin/rails server -p $PORT.Address, with the example
http://localhost:$PORT.A note under them: “$PORT is a free port midcode picks. With no command, midcode only shows the address. Saved in .midcode/server.json.”
Start saves your answer and starts the site.
The same form is behind Change how it runs, the link under the card “The server didn’t start”. There it opens filled in with your saved answer, or with the stack midcode recognised (“It looks like Rails. Change the command if yours is another.”). Reset (“Forget this and let midcode work it out”) clears a saved answer.
.midcode/server.json
The form writes one small file in your project. You can write it yourself too:
| Field | What it is |
|---|---|
command | What starts the site, as you’d type it in a terminal at the project’s root. May be empty |
url | Where the site answers. Without http://, midcode adds it. Only the origin counts: a path after it is dropped. May be empty |
With both empty, or with a file that isn’t valid JSON, midcode goes back to working it out by itself.
A command. midcode finds the address in what the command prints, or at the port it handed over:
{
"command": "php -S 127.0.0.1:$PORT -t public_html",
"url": ""
}A command and an address. Here the stack is one midcode knows, but with a Ruby that isn’t on your PATH:
{
"command": "/opt/homebrew/opt/ruby/bin/bundle exec jekyll serve --host 127.0.0.1 --port $PORT",
"url": "http://127.0.0.1:$PORT"
}If the command chooses its own port, write that address instead (http://localhost:8080).
Only an address. The site already runs somewhere (Herd, Valet, Docker, another terminal), and midcode starts nothing:
{
"command": "",
"url": "https://shop.test"
}midcode waits 20 seconds for it to answer, then says Nothing answers at https://shop.test. Start the site, then try again. Over https, a certificate the server made for itself is accepted on localhost, 127.0.0.1 and *.localhost. Any other host needs a certificate macOS trusts.
After changing the file by hand, open the Dev server log in the top bar and press Restart. Whether to commit the file is up to you: it’s right for everyone who runs the project the same way. What midcode adds to your project lists it with the rest of .midcode/.
What you can edit
For the stacks on this page, that depends on what the folder holds:
Templates a server fills in (
.html,.php,.blade.php,.erb,.ejs,.twig,.njk,.liquid,.hbsand the like): when a page loads, midcode looks each element up in those files and marks the ones it’s sure about. Text, classes, attributes and structure are then written into the template. On sites built from Markdown (Hugo, Jekyll, Eleventy, Zola, MkDocs), the words of the content’s.mdfiles are found and edited the same way. Sites a server renders explains the matching and its limits.JSX or Vue built with webpack or Rspack: marked as the site builds. See Create React App, Vue CLI, Gatsby, Docusaurus.
Neither: nothing is marked. You can check every breakpoint, use the site in Preview (P) and leave comments for your agent.
Pages
Where midcode can read a stack’s routes, they’re the page list: routes/web.php in Laravel, config/routes.rb in Rails, urls.py in Django, the @app.route lines of Flask, Angular’s route files, the content files of Hugo, Jekyll and Eleventy, and a Shopify theme’s templates. Anywhere else the list has the home page alone, and any path can be typed in the page menu in the top bar (“Search, or type a path and press Enter”).
Styles
Without Tailwind 4, midcode writes its own mid: utilities and keeps their CSS in midcode.css (Without Tailwind). Where midcode knows which folder a stack serves as it is (static/ in Hugo, public/ in Rails, Laravel and Angular, the document root in PHP), the file goes there and every layout gets a <link> to it. For a stack it doesn’t know, midcode may not find where to link it. It creates midcode.css, tells you it couldn’t tell where to import it, and you link the file from your layout yourself.
Limits
Phoenix, ASP.NET, Zola, MkDocs and Deno are recognised by their files, but were never tried.
There’s no terminal behind the command. One that stops to ask a question can’t be answered.
midcode starts one process. If the site needs a second one (a database, an API, a queue), start it yourself.
The free canvas is for Next.js projects on the App Router.
Pages are added from midcode in Hugo, Jekyll, Eleventy and Laravel only, and Site settings needs a layout that closes a
<head>: see New pages and Site settings.
Troubleshooting
“… isn’t installed on this Mac (or not on your shell’s PATH).” The shell didn’t find the command’s first word. If it works in your terminal, write its full path in the command, as in the Jekyll example above.
“The server stopped (code 1)”. The command ran and quit. View log on the card shows what it printed.
“Nothing answers at …” or “The server did not answer in time”. The command is running, but not where midcode is looking. Either it ignores $PORT (give the address it really uses), or it takes longer than two minutes to come up.
The wrong site shows. Something else was already answering at that address. Stop it, or give the command a $PORT of its own.