MidstreamDocs
Configuring your project

midstream.json

The file that tells Midstream how to install, start, and test your app.

To open an instance, Midstream has to run your app the way you would run it yourself. You tell it how in midstream.json, a file in the root of your repository. Nothing is guessed. If the file is missing, the deploy stops before anything else happens.

The file

{
  "install": "npm ci",
  "start": "npm start",
  "test": "npx playwright test {{file}} -g \"{{grep}}\""
}
KeyRequiredWhat it does
installYesRuns once, before the app starts. Install dependencies, build, migrate, seed.
startYesStarts your app and keeps running.
testNoRuns one Playwright test. Defaults to npx playwright test {{file}} -g {{grep}}.

They run in that order: install, then start, then — as soon as your app answers on its port — the test that gets the app to the scene.

install and start must both be present and must both be non-empty strings. Anything else in the file is ignored.

Every command runs from the repository root

Midstream clones your repository, and runs all three commands with the repository root as the working directory. There is no way to point a command at a subdirectory, so your app has to be installable and startable from the root.

That is worth checking before anything else if you are in a monorepo. A root package.json whose start script delegates to the right workspace will work:

{
  "install": "pnpm install",
  "start": "pnpm --filter web start"
}

A repository with no root manifest at all, where the app only builds from apps/web, cannot be configured yet.

You will be able to give each command its own directory, so an app that lives in a subdirectory needs no root wrapper:

{
  "install": "pnpm install",
  "installDir": ".",
  "start": "pnpm start",
  "startDir": "apps/web"
}

The test file path is recorded from the repository root, so any testDir works — testDir: "./tests" registers tests/checkout.spec.ts and the deploy finds it under the clone. What the root rule does still cover is the Playwright config: the test command runs from the repository root, so keep the config there and point testDir at wherever the tests actually live.

The two placeholders

The test command needs to know which test to run. Two placeholders are filled in for you:

  • {{file}} — the path of the test file that contains the scene call.
  • {{grep}} — the test's full title: every enclosing describe title and the test name, joined with spaces.

For a scene marked inside this test:

test.describe("Checkout", () => {
  test("shows the empty cart", async ({ page }) => {
    // ...
  });
});

{{grep}} is Checkout shows the empty cart.

Two things to know if you write your own test command:

Quote {{grep}}. The command runs through a shell, and test titles contain spaces. -g "{{grep}}" matches the one test you meant; -g {{grep}} hands the shell a fistful of words.

Use each placeholder at most once. Only the first occurrence of each is replaced.

Everything else about the command is yours. If your tests need a particular Playwright project, or a different runner, say so:

{
  "test": "npx playwright test --project=chromium {{file}} -g \"{{grep}}\""
}

A worked example

A Next.js app with a Postgres database, where the database runs as a container alongside the app:

{
  "install": "docker compose up -d db && npm ci && npm run build && npm run db:migrate && npm run db:seed",
  "start": "npm start",
  "test": "npx playwright test {{file}} -g \"{{grep}}\""
}

Two things make this work. The slow parts — the build, the migration, the seed — are all in install, which has fifteen minutes to finish, while start has ninety seconds. And the data the test needs is created by install, because a deploy starts with nothing but your repository. See Making your app run inside a deploy.

When it goes wrong

Two errors come from this file, and both stop the deploy before your app runs.

SANDBOX_CONFIG_MISSING — there is no midstream.json in the repository root at the commit being deployed. A common version of this: the file is on main but not on the branch that produced the capture.

SANDBOX_CONFIG_MALFORMED — the file is not valid JSON, or install or start is missing or empty. It has to be plain JSON: no comments, no trailing commas. Paste it into a JSON validator if you are not sure.

Both are reported on the deploy itself, with the reason. See Why a deploy failed.