Skip to content
LibxaFrame
Packages 12 August 2026 5 min read

libxa new my-app

One command that asks which database you want, installs the skeleton, configures .env, generates the key and makes the first commit. A self-contained executable with no dependencies, because the first thing someone runs is the worst place for an install to fail.

Starting a LibxaFrame project used to mean remembering a Composer incantation, then copying .env.example, then generating a key, then editing three lines to point at the database you actually use. Now:

libxa new my-app

What it does

It asks which database you want, runs composer create-project libxa/libxa, and then does the parts everyone does by hand afterwards: .env configured for the driver you chose, the application key generated, front-end dependencies installed if you want them, a git repository with a first commit, and a list of what to run next.

  LibxaFrame installer 1.0.0

  Which database will it use?
    1. SQLite (no server required) (default)
    2. MySQL
    3. MariaDB
    4. PostgreSQL
  › 2

  Creating the application…
  Configured .env for mysql.
  Initialised a git repository on main.

   DONE  Your application is ready.

Flags for everything, so it fits a script as readily as a terminal:

libxa new my-app --database=mysql --git --npm
libxa new my-app --github=public --organization=my-team
libxa new my-app --dev
libxa new .

Only three lines of .env change

This one is worth explaining, because getting it wrong is invisible until it matters.

composer create-project runs the skeleton's post-install scripts, and one of them is php libxa key:generate, which writes APP_KEY into the .env it has just copied. If the installer then wrote a fresh .env for your chosen database, that key would be gone. Nothing would complain. The application would boot, serve pages, and fail the first time it touched an encrypted cookie or a session.

So only DB_DRIVER, DB_PORT and DB_DATABASE are rewritten, in place, and everything else in the file is left exactly as it was. There is a test whose only job is to assert that the generated key is still there afterwards.

Installed, not pip-installed

The installer is a single self-contained executable. It needs nothing on your machine to run: no Python, no runtime, nothing to resolve.

irm https://raw.githubusercontent.com/libxa-framework/libxa-installer/main/scripts/install.ps1 | iex
curl -fsSL https://raw.githubusercontent.com/libxa-framework/libxa-installer/main/scripts/install.sh | sh

Both scripts install for the current user and add the command to your PATH. Neither asks for administrator rights or sudo, and neither writes outside your home directory: the program goes in your local application directory and the PATH entry is the per-user one, never the machine-wide one that everything else depends on.

Downloads land in a temporary file and are moved into place only once complete. An interrupted download replacing a working install with a truncated one is the kind of failure that surfaces later as something that looks nothing like a network problem.

No dependencies, on purpose

The installer has none at all.

It is the first thing someone runs, on a machine nobody has tested, in whatever state that machine happens to be in. Every dependency is another way for that first step to fail, and a failure there is a failure to start at all rather than a failure in something you were already invested in.

It also means the whole tool freezes cleanly into one executable with nothing to resolve at install time.

And a skill for the framework

Also published: an agent skill for LibxaFrame.

It exists because LibxaFrame looks enough like Laravel that an assistant will write Laravel confidently and produce code that parses and then fails at runtime. So the skill is mostly the differences:

  • src/app/ and src/public/, not app/ and public/
  • php libxa, not php artisan
  • Response::getStatus(), not getStatusCode()
  • @extends takes exactly one argument; a second is a parse error, not an ignored argument
  • {{ }} is inert inside a @php block
  • routes match in registration order, with no specificity sorting, so /posts/{slug} registered first makes /posts/new unreachable
  • a migration whose class name does not match its filename is skipped in silence
  • and the one that catches everyone: the home page loads, every other route 404s, and only once deployed

Every item in it caused a real bug while this ecosystem was being built. That is the test for whether something belongs there: not that it is a difference, but that the difference has cost someone an afternoon.

Keep reading