loor.json
The single declarative manifest behind every Loor project — services, ports, domains, databases, and mobile apps. Studio and Vibe Chat keep it in sync, but it's plain JSON you can read, diff, and edit.
What it is
Every Loor project has one loor.json at its root. It's the source of truth for
what your project is made of: each app (service), the ports they run on, the
domains they answer to, the databases they need, and — for mobile — how they get built and
signed. The same file drives local dev, branch previews, and production. You rarely write it by
hand: Studio writes it as you click, and Vibe Chat edits it when you ask ("add Redis"). But it's
yours — version-controlled, reviewable, and editable.
The shape
{
"version": "1",
"project": {
"name": "my-app",
"type": "fullstack", // fullstack · api · client · monorepo
"defaultPreview": "client" // which service the web preview opens
},
"services": {
"client": { /* … */ },
"api": { /* … */ },
"mobile": { /* … */ } // expo / react-native (see below)
},
"resources": {
"mongodb": { "enabled": true },
"redis": { "enabled": false }
},
"scripts": { /* optional named commands */ }
}
Top level: version, a project block, a services map, and
optional resources (databases) and scripts. Everything else lives
under a service.
project
project.name is required. project.type is a hint
(fullstack, api, client, monorepo).
project.defaultPreview names the service the web preview opens by default — it must
match a service key.
services
Each service is keyed by name and points at a folder. A service is one runnable thing — a web
app, an API, a worker, or a mobile app. Only type is required; Loor fills in sensible
defaults and auto-detects the type from your repo when you leave it off.
"api": {
"type": "nodejs", // pin it, or let Loor detect from the repo
"root": "apps/api", // folder, relative to the project root
"port": 5001, // dev / preview port
"dev": "pnpm dev", // command Studio runs for live dev
"build": {
"command": "pnpm build",
"output": "dist", // static output dir (for static/SPA types)
"installCommand": "pnpm install"
},
"start": { "command": "node dist/index.js" }, // production entrypoint
"domain": "api.example.com", // string, or the structured form below
"env": { "NODE_ENV": "production" },
"secrets": ["JWT_SECRET"] // names only — values live encrypted in Studio
} Service types
The recognized type values:
static · react · astro · nextjs · vue · angular
nodejs · nestjs · python · go · turbo · docker
custom · worker · expo · react-native
Web/static types (react, astro, nextjs, vue,
angular, static) build to an output dir and serve over a domain. Server
types (nodejs, nestjs, python, go,
docker, custom) run a long-lived process via start.command.
worker runs with no inbound domain. expo / react-native are
mobile — they build to native artifacts on a Mac host instead of serving HTTP
(see below).
Ports & domains
port is the dev/preview port. domain attaches a hostname in production —
either a plain string, or a structured object when you want Loor to manage the DNS record and TLS
on a Cloudflare zone.
// simple — a plain hostname
"domain": "app.example.com"
// structured — let Loor manage DNS + TLS on a Cloudflare zone
"domain": {
"mode": "auto", // auto = Loor creates the DNS record
"subdomain": "app",
"zoneId": "<cloudflare-zone-id>",
"proxied": true // orange-cloud / WAF
} Env & secrets
Non-sensitive config goes in services.<name>.env as a flat map.
secrets lists the env keys that are sensitive — their values are
set in the Studio sidebar (or the CLI) and stored encrypted at rest. Never paste a real secret
value into loor.json; it's committed to git, and Studio masks anything listed in
secrets. For the env vars themselves — service URLs, managed-DB connections, generated
secrets — see .env.example.loor.
resources — databases & cache
Backing services are toggles under resources. Flip one on and Loor provisions it,
manages it, and injects its connection string (MONGODB_URL, REDIS_URL)
into your services. No host, port, or password to wire by hand.
"resources": {
"mongodb": { "enabled": true }, // provisioned + MONGODB_URL injected
"redis": { "enabled": true } // provisioned + REDIS_URL injected
} See Databases & cache for versions and backups.
scripts
Named one-off commands you can run from Studio — seeds, migrations, maintenance tasks. Each has a
command, an optional service it runs inside, and an optional
description.
"scripts": {
"seed": { "command": "pnpm db:seed", "service": "api", "description": "Seed dev data" },
"migrate": { "command": "pnpm migrate" }
} Mobile apps
An expo (or react-native) service is a mobile app. Mobile services are
domainless — no domain, no HTTP port; they use metroPort
for the live Metro dev server and build to native .ipa / .apk /
.aab artifacts on a Mac host. The optional inner mobile block describes
bundle identifiers and per-profile build settings — the profiles mirror EAS so migrating off it is
mechanical.
"mobile": {
"type": "expo", // or "react-native"
"root": "apps/mobile",
"metroPort": 8081, // live Metro dev server (no domain — mobile is domainless)
"dev": "pnpm dev",
"mobile": {
"platforms": ["ios", "android"],
"ios": { "bundleIdentifier": "com.acme.app", "deploymentTarget": "16.0", "appleTeamId": "ABCDE12345" },
"android": { "package": "com.acme.app", "minSdkVersion": 24, "targetSdkVersion": 34 },
"build": {
"profiles": {
"development": { "ios": { "buildConfiguration": "Debug", "simulator": true }, "android": { "buildType": "apk" } },
"preview": { "ios": { "buildConfiguration": "Release", "distribution": "internal" }, "android": { "buildType": "apk" } },
"production": { "ios": { "buildConfiguration": "Release", "distribution": "store" }, "android": { "buildType": "aab" } }
}
}
}
}
The whole mobile block is optional — with just { "type": "expo", "root": "apps/mobile" }
Loor defaults to both platforms and the three standard profiles. Signing material (App Store
Connect API keys, certificates, Android keystores) is not in loor.json
— it's stored encrypted in Studio. The full walkthrough — simulator preview, build profiles, and
signing — is in Mobile apps.
How Studio uses it
Studio reads loor.json to build the project sidebar, decide what to provision, run
your dev servers, plan the deploy, and — for mobile — drive builds on the Mac host. When Vibe Chat
changes the project ("add MongoDB", "add a mobile app"), it edits this file and shows you the diff
before applying. Config is validated on every change, so a typo'd service type or a domain on a
mobile service is caught before it ships.