Skip to content
Back to skills

Bootstrap App

CSecurity

Provision a Mendix project in a repo that has none — interview, `mxcli new`, session hook, project brief, first commit, boot and quality baseline. Use when the repository is empty or has no .mpr yet, typically from the empty-repo seed prompt.

  • 128 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 26, 2026
ai-agentsrustgoshellbashsqldockergitdatabasefrontendbackend

Works with

  • claude code
  • cli

Security analysis

C68/100
  • criticalAccesses sensitive system or user directories
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned October 4, 2026

npx -y skills add mendixlabs/mxcli --skill bootstrap-app --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Bootstrap App?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Bootstrap App
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/mendixlabs-bootstrap-app/badge)](https://www.skillsdirectory.com/skills/mendixlabs-bootstrap-app)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: bootstrap-app
description: "Provision a Mendix project in a repo that has none — interview, `mxcli new`, session hook, project brief, first commit, boot and quality baseline. Use when the repository is empty or has no .mpr yet, typically from the empty-repo seed prompt."
---

# Bootstrap a Mendix App in an Empty Repo

## When to Use This Skill

Use this when a repo has **no Mendix project yet** and you have been asked to
provision one with mxcli — typically from the empty-repo seed prompt, which does
nothing but install mxcli, run `mxcli init --sync-skills`, and send you here.

Everything the seed prompt used to spell out lives here instead, so the prompt stays
short enough to paste from a phone and this procedure can be fixed by shipping a new
mxcli rather than by re-pasting a longer prompt.

If an `.mpr` already exists, this is the wrong skill: run `mxcli init` in the app
folder and go straight to the work.

Related skills: `run-local` (the warm dev loop this ends in), `mdl-entities` and
`create-page` (building the model you propose at the end),
`migrate-design-prototype` (when a design was handed to you), `assess-quality`
(reading the `report` this takes a baseline with), `test-microflows` and `test-app`
(the two gates that prove behaviour rather than syntax).

---

## Step 0 — interview, and WAIT for the answers

Ask **all** of these in ONE message, numbered, each with the default you would pick,
so the user can reply "defaults" or answer only what they care about. **Do not
provision anything until they reply.**

The interview comes first for a reason: the app name becomes the `.mpr` file name, the
Studio Pro app name and the path baked into the SessionStart hook, so it is far
cheaper to ask than to rename afterwards. The rest of the answers are the brief — they
get written into the repo, so the session that resumes after an idle reap knows what
it is building.

1. **One app, or a solution of several?** One Mendix app is the default. "Solution"
   means several apps in one repo — e.g. a backend that owns the data and publishes
   OData/REST, and a frontend that consumes it. If so, ask for each app's name and one
   line on what it owns, and follow "If this is a solution" below.
2. **App name.** Becomes the `.mpr` file name, the app name in Studio Pro, and the
   path in the session hook, so it is awkward to change later. One PascalCase word,
   letters and digits only — `OrderPortal`, `FieldService`, `ClubAdmin`. Propose one
   derived from the answer to Q3.
3. **What is the app for?** One or two sentences: who uses it, and what it lets them
   do. If the answer is vague ("a tool for work"), ask one follow-up — everything
   below is derived from this.
4. **What does it keep track of?** Three to six nouns that will become entities, and a
   word on how they relate (e.g. "a Job has many Visits; each Visit has Photos"). For
   a solution, also ask which app owns each noun. Ask two follow-ups here, because
   both change the model rather than decorate it: does anything go through **steps
   someone has to act on** (approval, hand-off, review, a deadline), and is there a
   **number or count across records** anyone needs to see. The first is a workflow and
   the second a view entity — see "Two choices to make deliberately" below.
5. **Who logs in?** The user roles, and roughly what each may do (e.g. "Requester
   creates and sees their own; Approver sees everything and approves").
6. **Look and feel.** One of the bundled themes: `signal` (light, high contrast),
   `ledger` (light, dense, data-heavy), `console` (dark), or `none` for stock Atlas.
   Default `signal`.
7. **Mendix version.** Default to whatever the session environment already provides —
   see below — and otherwise the newest version on the CDN (today `11.14.0`).
8. **Do you have requirements to work from?** A specification document, a
   prototype, a wireframe, a long description — anything that is the source of
   truth but is not in this repo. **Default: yes, record them.** If they say yes,
   ask them to paste or point at it; if they say "just build it", say in one line
   that you will record the slices you derive as you go, and carry on.

   This one is not cosmetic. Requirements that live in a Word document, a Figma
   file or a chat window leave **no trace in git**: not in an issue, not in a
   commit message. A session that resumes after an idle reap has no idea what it
   was building towards, and neither does the next person. Recording them costs
   a minute now and is unrecoverable later.

If the user says "defaults" or ignores a question, choose something sensible for it,
say what you chose in one line, and keep going — **do not block on them twice**.

### Choosing the Mendix version

**Prefer a version the environment already has.** A Claude Code session image may bake
in an MxBuild, and using it turns a multi-hundred-MB download into no download at all.
There is no environment variable for this — the cache directory is the signal:

```bash
ls ~/.mxcli/mxbuild/ 2>/dev/null | sort -V | tail -1   # e.g. 11.13.0, or empty
```

If that names a version, use it and say so in one line ("using 11.13.0, already cached
in this environment"). If the user asked for a specific version, they win — check it on
the CDN as below and accept the download.

**Otherwise take the newest version on the CDN.** Everything mxcli does starts with
downloading MxBuild, so "supported" means "on the CDN", and `run --local` needs the
runtime tarball as well. At the time of writing the newest is **11.14.0** (11.15.0 is
not published). Do not trust that number — it ages. Confirm the one you land on, and
walk backwards a minor if it 404s:

```bash
V=11.14.0
curl -sI -o /dev/null -w '%{http_code}\n' https://cdn.mendix.com/runtime/mxbuild-$V.tar.gz
curl -sI -o /dev/null -w '%{http_code}\n' https://cdn.mendix.com/runtime/mendix-$V.tar.gz
```

Both have to answer `200`. Run the same check for any version the user names.

In a solution, give every app the **same** version: they share the `~/.mxcli/mxbuild`
cache, and a mismatch means a second multi-hundred-MB download and two runtimes to
keep straight — which also means the cached-version rule applies to the solution as a
whole, not per app.

---

## Provision

Substitute the answers for `<AppName>`, `<version>` and `<theme>` throughout. For a
solution, do steps 1–3 once per app and read "If this is a solution" first. The
commands below say `./mxcli` because that is where the seed prompt puts the binary —
drop the `./` if it came pre-installed on `PATH`.

1. **Create the app at the repo root** — that is where `.claude/` and the `./mxcli`
   binary have to live for future sessions to self-bootstrap. `mxcli new` refuses to
   write into a directory that is not empty, and a git repo always has `.git`, so
   create it in a subfolder and move it up:

   ```bash
   ./mxcli new <AppName> --version <version> --theme <theme>
   rm -f <AppName>/mxcli        # a hardlink to the ./mxcli you just ran; mv would
                                # refuse it as "the same file". Still needed when
                                # mxcli is already on PATH — `new` hardlinks it
                                # into the project either way.
   rm -rf .ai-context           # the seed prompt's own `mxcli init --sync-skills`
                                # wrote this; `mxcli new` writes its own copy from
                                # the same binary, so the one here is a stale
                                # duplicate and `mv` fails with
                                #   mv: cannot overwrite './.ai-context': Directory not empty
   shopt -s dotglob && mv <AppName>/* . && rmdir <AppName>
   ```

   `mxcli new` also runs `mxcli init`, which writes `.claude/settings.json` with a
   SessionStart hook plus the `.claude/bootstrap-mxcli.sh` it runs — **check that the
   `.mpr` named in the script is right after the move.**
2. **Confirm the Claude tooling:** `./mxcli init --tool claude`. Idempotent — it is
   what step 1 already ran, and re-running it is the cheapest way to be sure the hook,
   skills and commands are in place.
3. **Bring prerequisites up:** `./mxcli run --local --setup --ensure-db -p <AppName>.mpr`
   (caches MxBuild + runtime, starts Postgres, creates the app database).
4. **Write the brief to `README.md`** at the repo root: the app name(s), the answers
   to Q3–Q5 **in the user's words**, and the theme and Mendix version used. For a
   solution, say which app owns what and how they talk to each other. Say that the
   project's MDL scripts are `mdl 1`: each one starts with `mdl 1;`. This is what
   tells the next session — after an idle reap, with none of this conversation — what
   it is building. Keep it short enough that it stays true.
5. **Start a `FINDINGS.md`** at the repo root and keep appending to it as you work.
   Log anything surprising or broken: an mxcli command that errored, a workaround you
   applied, a `mxcli check` that passed but a real `mx check` later flagged. Note the
   Mendix + mxcli versions, the MDL language version the scripts use (`mdl 1`), and
   how each finding was verified. This is durable context
   for the next session, and the most useful thing to share back to improve mxcli.
6. **Record the plan in the brain** — unless the user opted out at Q8:

   ```bash
   ./mxcli brain init -p <AppName>.mpr
   ./mxcli brain capture "<one requirement, in the user's words>" \
     --slice 01-<first-slice> -a @<AppName>Module.<WhatWillImplementIt> \
     -p <AppName>.mpr
   ./mxcli brain staged -p <AppName>.mpr    # then promote each
   ```

   Split the requirements into **slices you could deliver one at a time**, named
   with a numeric prefix so they sort into a roadmap: `01-accounts`,
   `02-approvals`. Anchor each requirement at **what will implement it** — the
   entity, microflow or page you are about to create. The anchor points forward,
   so naming something that does not exist yet is correct here, and it is what
   makes `./mxcli brain plan` a real progress report instead of a checklist.

   Read `.ai-context/skills/project-brain/SKILL.md` before doing this.

   Two things to keep straight, because they are easy to conflate:

   - `README.md` is the **brief** — what the app is, in the user's words, for a
     human landing on the repo. One page, written once.
   - `docs/brain/plan/` is the **scope** — the individual requirements, anchored
     and countable, appended to as they emerge.

   **Never write a status or a tick-box next to a requirement.** Whether it is
   built is computed by `./mxcli brain plan` from the model; a hand-maintained
   status is wrong the moment anyone builds anything, and nothing will tell you.

7. **COMMIT everything now** — `<AppName>.mpr`, `.devcontainer/`, `.claude/` (the
   SessionStart hook **and** `.claude/bootstrap-mxcli.sh`), `README.md`,
   `FINDINGS.md` and `docs/brain/`. This step is mandatory, not housekeeping: the seed prompt is a
   *one-time* seed, and committing its output is what makes every later session
   bootstrap from files instead of from a re-paste. The `mxcli` binary itself stays
   git-ignored (~85 MB); the bootstrap script is what fetches it back into a fresh
   clone, so committing the script is what makes the hook survive a reap.
8. **Boot and verify:** `./mxcli run --local -p <AppName>.mpr` in the background, then
   confirm the app answers HTTP 200 at http://localhost:8080/ and report.
9. **Take the quality baseline** — run the two gates that score the project, on the
   blank app, before any of your own work is in it:

   ```bash
   ./mxcli lint -p <AppName>.mpr
   ./mxcli report -p <AppName>.mpr --format markdown
   ```

   Report the warning count and the six category scores, and put them in
   `FINDINGS.md` with the date. This is the only moment the numbers mean "what the
   template ships with" — afterwards every figure is yours plus the template's, and
   there is nothing to subtract. A blank app is **not** expected to score zero
   warnings; knowing which ones it starts with is what stops you chasing them later.
   Read `.ai-context/skills/assess-quality/SKILL.md` before interpreting the report —
   it covers what each category means and which findings are worth acting on.
10. **(Optional) browser preview from a cloud session:**
    `./mxcli run --hub https://hub.mxcli.org -p <AppName>.mpr`, and report the preview
    URL it prints. Needs `MXCLI_HUB_KEY` on the environment; without it, continue as a
    normal local run. `--hub` ships in the **Linux** build only (a cloud session is a
    Linux container, so it works there); on a native Windows/macOS mxcli it fails with
    an explanatory message — continue as a normal local run.

---

## If this is a solution (several apps in one repo)

Each app is a full Mendix project — one `.mpr`, one runtime, one database. Same steps,
with these deltas:

- **Layout.** One subfolder per app, nothing at the repo root but `README.md`,
  `FINDINGS.md` and `.claude/`. Run `mxcli new <AppName> --version <version> --theme
  <theme>` once per app and leave each where it lands; do not move anything up.
- **Ports.** Every app defaults to 8080/8090/6543 and they will collide. Give the
  first app the defaults and the second `--app-port 8180 --admin-port 8190
  --serve-port 6643`. Avoid 8081/8091/6544 — `mxcli test --local` uses those.
- **Give each app its own hostname**, not just its own port. Cookies are keyed on
  host name and **ignore the port**, so two apps on `localhost:8080` and
  `localhost:8180` share one cookie jar: logging into one can silently replace the
  other's `XASSESSIONID`. Two hostnames give two jars, and the differing ports do no
  harm. Add them to `/etc/hosts` —

  ```
  127.0.0.1  backend.local frontend.local
  ```

  — and browse `http://backend.local:8080/` and `http://frontend.local:8180/`. The
  runtime binds `127.0.0.1` and serves any `Host` you send it, and the client uses
  relative URLs, so it works under any name that resolves to loopback. (`*.nip.io`
  works too if you would rather not touch `/etc/hosts`; prefer `/etc/hosts` in a
  locked-down container, where public wildcard DNS may not resolve — `localtest.me`
  resolves to `::1` in some of them.)

  Then record the name in each app's own configuration, so the runtime knows the URL
  it is reached at and generates absolute URLs — OIDC/SAML redirect URIs, deep links —
  against the host name rather than the listen address:

  ```sql
  mdl 1;
  alter settings configuration 'Default' (
    ApplicationRootUrl: 'http://backend.local:8080/'
  );
  ```

  `run --local` picks that up at boot and prints which configuration it came from.
  A blank app ships `http://localhost:8080/` there, and that stock loopback value is
  deliberately ignored — otherwise every project would start advertising a URL, and
  the wrong port under `--app-port`. Only a real host name is passed through.
- **Databases** need no action: the name is derived from the `.mpr` file name, so
  differently-named apps get different databases.
- **The session hook.** `mxcli init` writes `.claude/settings.json` inside each app
  folder, but Claude Code reads the one at the **repo root** — and it will not add a
  second entry for you (it dedupes on the command, not on the project). Write the root
  one yourself, one line per app, e.g.
  `test -x backend/mxcli && (cd backend && ./mxcli run --local --setup --ensure-db -p Backend.mpr) || true`.
  Verify it by checking that a fresh shell can boot each app.
- **Previews.** Pass `--hub-solution <SolutionName>` to every `run --hub` so the apps
  appear grouped in the hub overview instead of as unrelated previews.

**Wire the integration in dependency order — the producer must be running first.**
`CREATE CONSUMED ODATA SERVICE` fetches the `$metadata` at the moment you create it and caches
it in the model; if the URL is unreachable it warns and leaves the client unvalidated,
with no external entities to import. So: publish on the producer
(`CREATE PUBLISHED ODATA SERVICE … publish entity …`), boot it (`run --local`), and only then,
on the consumer, `CREATE CONSUMED ODATA SERVICE … MetadataUrl: 'http://backend.local:8080/odata/…/$metadata'`
followed by `CREATE EXTERNAL ENTITIES FROM …`. Use the hostname here too, so the
cached contract and the constant below agree with what the browser sees. Point
`ServiceUrl` at a **constant** (`ServiceUrl: @Module.SvcUrl`) so the address can be
changed per environment without touching the model — it will not stay `localhost`.
`mxcli syntax odata.publish` and `mxcli syntax odata.consume` have the full syntax;
business events (`mxcli syntax business-events`) are the alternative when the link
should be asynchronous.

---

## Then propose the model — do not build it yet

The blank template ships a `MyFirstModule`; the app's own work belongs in a module
named after it. From the brief, propose in chat:

- a module name, and the entities from Q4 with their attributes and associations
- the user roles from Q5 and what each may read/write
- the handful of pages that make it usable
- for a solution: which app owns each entity, and what crosses the boundary — publish
  only what the other app actually needs

### Two choices to make deliberately — the lazy answer is wrong both times

Both of these are first-class in Mendix and both are easy to reinvent in microflows,
because the microflow version *works*: it passes `check`, it builds, and nothing
flags it. The cost lands later, on someone else.

- **A business process with human steps is a `WORKFLOW`**, not a status attribute and
  a handful of microflows. Approvals, hand-offs, "someone has to look at this",
  anything with a due date or a timer, anything that can sit waiting for days. You
  get the state machine, the user-task inbox (`System.WorkflowUserTask`), assignment
  and targeting, timers and boundary events, and a definition the business can read.
  Rebuild it from status attributes and every one of those is yours to write and
  maintain, and the process stops being inspectable — nobody can answer "where is
  this request" except by reading microflows. `create workflow`; see
  `mxcli syntax workflow` and the `write-workflows` skill.
- **An aggregation is a `VIEW ENTITY`**, not a microflow that retrieves the rows and
  counts them. Totals, counts per group, a figure on a dashboard, a report, anything
  joined across entities: a view entity is OQL the **database** executes — joins,
  `GROUP BY`, `SUM`/`COUNT` — returning rows a page binds to directly. The microflow
  version pulls every object into memory to produce one number, and it gets slower
  exactly as the app succeeds, which is the worst possible failure curve. Needs
  **Mendix 10.18+** (`list features` confirms it). `create view entity Mod.Name (…)
  as ( select … )` — see `mxcli syntax view-entity` for the shape, its `oql` and
  `association` subtopics for the rules that bite (every column needs an `AS`
  alias; `ORDER BY` needs a `LIMIT`; selecting an id under an alias makes an
  *association*, not an attribute), and the `write-oql-queries` skill for worked
  queries.

Name which of the two you are using **in the proposal**, with one line on why. Both
are cheap to choose now and expensive to retrofit: the pages, security rules and
tests all bind to whichever you picked.

Show it as **MDL the user can read**, and wait for their go-ahead before executing it.
Write it as a script in `mdlsource/` that **starts with `mdl 1;`** — this one and every
script after it. A freshly bootstrapped project has no headerless (`mdl 0`) scripts, so
there is nothing to upgrade and no dialect to mix; keep it that way (if you ever inherit
a headerless file, the `choose-edit-mode` skill says how to upgrade it first). Use
`create or modify` so it can be re-run. Run `check`, `exec`, then `exec` the same script
again: the second run must write nothing (everything reported unchanged).
Name the elements the same way the plan's anchors do — if a requirement is anchored
`@<AppName>Module.ACT_Approve`, propose that name — so `./mxcli brain plan` starts
counting the moment the work lands, without anyone editing the plan.
If a design was handed to you, it is the source of truth for the model and the pages —
see `migrate-design-prototype`.

---

## After bootstrap — the inner loop

```bash
./mxcli run --local -p <AppName>.mpr --watch --screenshot   # warm dev loop + screenshots
./mxcli exec change.mdl -p <AppName>.mpr                     # edit the model; the loop hot-applies
```

### The gates — the same list the project's CLAUDE.md publishes

`mxcli init` wrote these into the project's `CLAUDE.md`, so every later session has
them in context. They are the **definition of done**, not a menu: run them in order,
stop at the first that fails, and say what each one reported.

Run them **once per change, not per edit** — a change being a coherent unit of work,
not a single statement and not a file write. Iterate with `exec` until the script is
right, then run the gates once over the result. The whole list after every edit costs
~55s and five calls each time and proves nothing the one run at the end does not.

```bash
./mxcli check change.mdl -p <AppName>.mpr --references   # syntax + references (~2s)
./mxcli exec change.mdl -p <AppName>.mpr                 # apply
./mxcli lint -p <AppName>.mpr                            # rules (~3s)
./mxcli report -p <AppName>.mpr                          # scored quality report
./mxcli docker check -p <AppName>.mpr                    # mxbuild, the slow one (~25s)
./mxcli test tests/ -p <AppName>.mpr --local             # microflow tests (~30s cold, ~2s warm)
./mxcli run --local --watch -p <AppName>.mpr             # the app, hot-reloading
```

Two of them are easy to mistake for optional and are not:

- **`report` is the quality report**, and its six category scores are what the
  baseline in step 9 exists to be compared against. A score that fell is a finding,
  not a detail. `assess-quality` covers how to read it.
- **`test` needs a suite to run.** Write the first one with the **first microflow you
  build** — not "later", because later is after the code is written and the expected
  values have stopped being obvious. One `tests/<Slice>.test.mdl` per slice is the
  shape that keeps up; `test-microflows` has the annotations, and `--local` needs no
  Docker daemon. For pages and rendering, `test-app` drives a real browser: a page can
  serialize correctly, pass `check`, build clean and still render wrong.

Keep the plan current as you go — it is the only record of scope that outlives the
conversation:

```bash
./mxcli brain plan -p <AppName>.mpr        # what is outstanding, counted from the model
./mxcli brain capture "<new requirement>" --slice <slice> -a @Mod.Thing -p <AppName>.mpr
```

Requirements arrive mid-build — the user says "and it should also…". Capture that
when it is said, not later. Do **not** tick anything off: `brain plan` derives what is
built from the model, so finishing the work is what moves the number.

In a solution, run one loop per app from its own folder, with the second app on the
alternate ports, and start the producer first so the consumer's external entities
resolve:

```bash
(cd backend  && ./mxcli run --local -p Backend.mpr --watch)
(cd frontend && ./mxcli run --local -p Frontend.mpr --watch \
                  --app-port 8180 --admin-port 8190 --serve-port 6643)
```

See `run-local` for the warm loop, `--watch`, `--ensure-db`, and the screenshot
flags.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…