Simulator
Note: The simulator supports Node and Python edge apps, screen apps, and web apps (see the note under Running Apps). Any project scaffolded with
phy app initis ready to run against the simulator out of the box.
The local device simulator simulates the on-device environment and runtime locally, letting you test application logic, settings behavior, and more. You can also test messaging between multiple apps running on your computer and connected to the same simulator instance. The simulator runs in offline mode and does not fully replicate the real device environment, so always verify your app on a real device before publishing.
The simulator ships as its own package, separate from the PhyStack CLI:
npm i -g @phystack/device-simulatorThis provides the phy-simulator binary. App templates already integrate with it out of the box — their dev script runs the app against the simulator for you.
Project Structure Requirements
Section titled “Project Structure Requirements”The simulator expects your project to follow a specific structure. Projects scaffolded with phy app init meet these requirements automatically.
Required for all apps
Section titled “Required for all apps”package.jsonat the project root with:"application-type": used for auto-detection. You can also pass--typeto override this."name": used to identify the app in the simulator
Screen apps
Section titled “Screen apps”- A
"start"script inpackage.json(e.g.,"start": "vite"), this is used as the launch command - Settings in
src/settings/index.json(optional, defaults are generated fromschema.tsif the file doesn’t exist)
Edge apps
Section titled “Edge apps”- With Dockerfile (recommended): a
Dockerfileat the project root. The simulator builds the image and runs the container with environment variables injected. Container configuration is read fromsettings.jsonat the project root. An explicit--dev-commandskips the Docker build and runs your source directly — this is what the templates do for the fastest feedback loop. - Without Dockerfile: falls back to the
"start"script inpackage.json, similar to screen apps - Settings in
src/settings/index.json(optional, same behavior as screen apps)
If
package.jsonis missing, the simulator exits with an error. Ifapplication-typeis missing and--typeis not passed, it will fail with “Cannot detect app type.”
Starting the Simulator
Section titled “Starting the Simulator”The simulator is a standalone server that runs in the background. Start it in a separate terminal before running any apps:
phy-simulator startThis can be run from any directory, the simulator listens on port 55000 and serves all apps launched against it.
| Option | Description | Default |
|---|---|---|
-p, --port <port> | Port to listen on | 55000 |
Note:
phy-simulator runalways connects to port 55000, so keep the default port unless you have a conflict you can resolve another way.
Running Apps
Section titled “Running Apps”Once the simulator is running, launch an app against it. If you scaffolded from a template, just use the wired-up script:
bun run devwhich invokes the simulator under the hood, e.g. phy-simulator run . --dev-command 'bun src/app.ts'. You can also run it directly:
phy-simulator run <path>To run the app in the current directory:
phy-simulator run .| Option | Description | Default |
|---|---|---|
--type <type> | Override app type (screen, edge, or web) | Auto-detected from application-type in package.json |
--dev-command <command> | Override launch command | The start script from package.json (edge apps with a Dockerfile build and run the container instead) |
--settings-dir <path> | Path to the directory containing index.json with local settings | src/settings |
Web apps: run with
phy-simulator run . --type web(or set"application-type": "web"inpackage.json). The simulator writespublic/boot.jsonpointing your app at the local simulator instance, then starts the dev server. Openhttp://localhost:3000/#code=dev— any#code=value works against the simulator, and3000is the template’s Vite port.
Managing Simulated Devices
Section titled “Managing Simulated Devices”The simulator persists a twin identity per app, so your app keeps a stable instance ID across restarts.
How It Works
Section titled “How It Works”The simulator injects the PHYSTACK_SIMULATOR_URL environment variable, which instructs the PhyStack SDK in your app to connect to the local simulator instance instead of the edge runtime on the device. It also creates a local simulated twin and provides the mandatory TWIN_ID environment variable so your app can connect when running locally.
For screen apps, the TWIN_ID must be passed to the browser as a URL hash parameter (/#instanceId=<twin-id>). This mirrors how the production screen bootloader delivers the instance ID to screen apps running inside iframes. The Vite config in the screen app template handles this automatically by opening the browser with the correct hash when the simulator is active.
For this reason, your launch script should ensure these environment variables are propagated to your app on start. Projects scaffolded with phy app init configure this for you out of the box.
When you run phy-simulator run, it performs the following steps:
- App type detection, reads
application-typefrompackage.json, or uses the--typeflag if provided - Twin and settings setup, creates a virtual simulated twin identity for the app (reusing the persisted instance ID on subsequent runs) and uses your local settings from
src/settings/index.json(generates defaults fromschema.tsif the file doesn’t exist) - Launch:
- Screen apps runs the resolved launch command (your custom
--dev-command, or thestartscript) - Edge apps with a
--dev-commandruns your source directly — no Docker required - Edge apps with Dockerfile builds a Docker image and runs the container with Docker config from
settings.json - Edge apps without Dockerfile falls back to the
startscript
Troubleshooting
Section titled “Troubleshooting”“No package.json found”
The simulator requires a package.json at the project root with "application-type": "edge" (or "screen"), this is how it identifies and launches the app, regardless of the runtime inside. Both Node and Python edge templates scaffold this file alongside their Dockerfile. You’ll see this error if you ran phy-simulator run from a directory without a package.json or pointed it at the wrong path. Make sure you run it from your project root.
“Cannot detect app type”
Your package.json is missing the "application-type" field. Add "application-type": "screen", "edge", or "web", or pass --type screen, --type edge, or --type web.
“Simulator is not running”
Start the simulator first with phy-simulator start in a separate terminal. The simulator server must be running before you can launch apps against it.
“No Dockerfile or start script found”
Your edge app has no Dockerfile at the project root and no "start" script in package.json. Add one or the other. For Docker-based edge apps, the Dockerfile should be at the project root (not in a subdirectory).
Settings not loading
The simulator looks for settings in src/settings/index.json by default. If your settings are elsewhere, pass --settings-dir <path>. The JSON file must contain settings nested under app.gridApp.settings.
An extra “unknown” instance appears in the simulator log
A browser tab from a previously run app is still connected to the shared simulator. The simulator only knows the names of apps launched in the current session, so stale connections show as unknown. Close the old tab and restart the simulator.