Skip to content

Configuration

Plainwright is configured entirely with environment variables. This page lists all of them: the application settings in backend/app/core/config.py, the few variables read straight from the environment, and the ones only deployment tooling uses. The frontend has no settings of its own: it calls the API at the relative path /api.

Use this page to look up a setting. To set up a feature end to end, follow its guide, for example Sign-in and email or Object storage.

  • Three services read them: the backend (API), the worker (runs tests) and the scheduler (cron runs and the stuck-run reaper). The Read by column says which services need each variable. With Docker Compose all three get the same environment, so there’s nothing extra to do. On Render, Railway or Fly.io, give every app service the same values.
  • Names aren’t case-sensitive, and unknown names are ignored. A misspelled variable doesn’t stop anything from starting; the setting just keeps its default.
  • Booleans accept true/false, 1/0, yes/no and on/off. An empty value for a number or boolean doesn’t parse and the service fails to start, so delete the line instead of leaving it empty.
  • Values are read once, at start. After editing .env, recreate the containers with docker compose up -d. docker compose restart keeps the old environment.

Compose uses .env twice: for ${…} substitution in docker-compose.yml, and as an optional env_file that hands every variable in it to the backend, worker and scheduler. A few values are fixed by the compose file and win over .env, so the in-network addresses stay correct:

Fixed by docker-compose.yml Value
DATABASE_URL postgresql://testai:testai_secret@postgres:5432/testai_db
REDIS_URL redis://redis:6379
PLAYWRIGHT_MCP_URL, PLAYWRIGHT_MCP_URL_FIREFOX, PLAYWRIGHT_MCP_URL_WEBKIT http://playwright-mcp:8931/mcp, http://playwright-mcp-firefox:8931/mcp, http://playwright-mcp-webkit:8931/mcp
SCREENSHOTS_DIR /app/screenshots (the screenshots volume)
TEST_FILES_DIR /test-files (the ./test-files folder)

The compose file also passes PLAINWRIGHT_ENV (fallback development), SECRET_KEY, APP_BASE_URL, ANTHROPIC_API_KEY and the storage, observability and billing settings with explicit fallbacks. An empty or missing SECRET_KEY falls back to the insecure built-in default, supersecretkey123, so a first local start works. With PLAINWRIGHT_ENV=production the backend refuses to start on that fallback; see Production mode.

These are the fields of the Settings class in backend/app/core/config.py, with the code’s defaults. .env.example uses the same values with two exceptions: it leaves SECRET_KEY empty for you to fill in, and its DATABASE_URL points at localhost:5433, where Compose publishes Postgres, for running the backend directly on the host.

Variable Default Read by What it does
PLAINWRIGHT_ENV development backend production (or prod) makes the backend refuse to start while SECRET_KEY is missing, a known default or shorter than 32 characters, and hides /metrics unless METRICS_TOKEN is set. Any other value is development mode: the backend starts, logs the problem at CRITICAL, and shows it to project owners and admins as a banner. Set production on every install other people use.
SECRET_KEY supersecretkey123 backend · worker · scheduler Signs sign-in tokens and OAuth state, and derives the key that encrypts provider API keys, vault secrets, saved logins and integration credentials. Set a random value of at least 32 characters and use the same one on every service. Don’t edit it in place: changing it by hand signs everyone out and makes stored secrets unreadable. Change it with the rotation command, which re-encrypts them.
ACCESS_TOKEN_EXPIRE_MINUTES 10080 backend How long a sign-in token stays valid (10080 = 7 days). Tokens can’t be revoked server-side, so a shorter lifetime limits a leaked token.
ALGORITHM HS256 backend JWT signing algorithm for sign-in tokens and OAuth state. Leave it as HS256.
CORS_ORIGINS http://localhost:8080,http://localhost:3000,http://localhost:5173 backend Comma-separated browser origins allowed to call the API cross-origin. The app is same-origin behind nginx, so this only matters for a separately served frontend (e.g. the Vite dev server). * allows any origin; credentials are never allowed.
APP_BASE_URL http://localhost:8080 backend · worker Public URL people open, e.g. https://plainwright.example.com. Used for links in invitation, password-reset and alert emails, run links in Slack/Discord/webhook alerts, Stripe return URLs, and the OAuth callback when OAUTH_REDIRECT_BASE is empty.
Variable Default Read by What it does
DATABASE_URL postgresql://testai:testai_secret@localhost:5432/testai_db backend · worker · scheduler Postgres connection string (SQLAlchemy, postgresql:// scheme). Every service must reach the same database.
REDIS_URL redis://localhost:6379 backend · worker · scheduler Redis for the test_runs job queue and live run and crawl events. The rq worker command takes its own --url (or RQ_REDIS_URL).
Variable Default Read by What it does
SCREENSHOTS_DIR /app/screenshots backend · worker Folder for screenshots, visual diffs and evidence when STORAGE_BACKEND=local. The backend serves it at /screenshots/.
TEST_FILES_DIR /test-files backend · worker Per-project uploaded test files, stored as <dir>/<project_id>/<file>. The backend writes uploads (25 MB per file); workers list the files into test prompts. The browser must see the same folder at /test-files.
PLAYWRIGHT_MCP_URL http://playwright-mcp:8931/mcp backend · worker Chromium Playwright MCP server. Workers use it for browser runs; the backend uses it for the AI Crawler. Only the host and port matter: the client opens /sse on them.
PLAYWRIGHT_MCP_URL_FIREFOX http://playwright-mcp-firefox:8931/mcp worker Firefox MCP server, used for runs with browser firefox.
PLAYWRIGHT_MCP_URL_WEBKIT http://playwright-mcp-webkit:8931/mcp worker WebKit MCP server, used for runs with browser webkit.
Variable Default Read by What it does
ANTHROPIC_API_KEY (empty) backend · worker Server-wide fallback model (Anthropic claude-haiku-5-5), used when neither the project nor its owner has an AI provider. Leave empty to require every user to add a provider.
MAX_TRIALS 10 backend · worker · scheduler Most repeats a run may make for a pass-rate test. Never more than 10, whatever this says.
MAX_EVAL_ROWS_PER_RUN 500 worker Most dataset rows scored by one eval or RAG run. Each row costs a target call plus a judge call.
MAX_CONVERSATION_TURNS 12 worker Most turns a simulated user may take in a conversation or red-team test.
JUDGE_MAX_TOKENS 800 worker Minimum output-token budget for a judge reply. The budget grows with the number of criteria or metrics (about 120 tokens each, up to 4,000).
A2A_TIMEOUT_S 60 — Reserved. Not read by the current code: A2A tests use the test’s own timeout_s (default 60 s, 1 to 300 s).
MCP_TARGET_TIMEOUT_S 60 worker Per-request timeout in seconds for an MCP server under test, when the test doesn’t set its own.
Variable Default Read by What it does
CRAWLER_MAX_PAGES 5 backend Most pages the AI Crawler reads from a URL (PDFs: up to 4 times this many pages).
CRAWLER_MAX_CONTENT_CHARS 8000 backend Most characters of crawled content sent to the model, to bound token cost.
Variable Default Read by What it does
ALLOW_INTERNAL_TARGETS false backend · worker Lets server-side requests to user-supplied URLs reach cloud-metadata addresses and the internal hosts postgres, redis, backend, worker and scheduler. Leave false unless you trust every user on a single-user install. See Security model.
Variable Default Read by What it does
SMTP_HOST (empty) backend · worker SMTP server. Email is on when this and SMTP_FROM are set: invitations, password reset (backend) and failure alerts (worker).
SMTP_PORT 587 backend · worker SMTP port. Use 587 with SMTP_TLS=true. Implicit TLS on port 465 isn’t supported.
SMTP_USER (empty) backend · worker SMTP username. Leave empty for a relay that needs no login.
SMTP_PASSWORD (empty) backend · worker SMTP password or app password.
SMTP_FROM (empty) backend · worker From address, e.g. Plainwright <noreply@example.com>.
SMTP_TLS true backend · worker Upgrade the connection with STARTTLS before logging in.
EMAIL_ALERTS_ENABLED true backend · worker Send failure and flaky-test alert emails to each project’s alert addresses. Needs SMTP; false turns off alert emails only.
Variable Default Read by What it does
GOOGLE_CLIENT_ID (empty) backend Google OAuth client id. The Google button appears when this and the secret are set.
GOOGLE_CLIENT_SECRET (empty) backend Google OAuth client secret.
GITHUB_CLIENT_ID (empty) backend GitHub OAuth App client id. The GitHub button appears when this and the secret are set.
GITHUB_CLIENT_SECRET (empty) backend GitHub OAuth App client secret.
OAUTH_REDIRECT_BASE (empty) backend Public origin for OAuth callbacks (<base>/api/oauth/<provider>/callback). Empty uses APP_BASE_URL. When the resulting base starts with https://, the sign-in cookie is also marked Secure.
PASSWORD_RESET_TTL_MINUTES 60 backend How long a password-reset link stays valid.
Variable Default Read by What it does
STORAGE_BACKEND local backend · worker local (the SCREENSHOTS_DIR folder) or s3 for any S3-compatible store (r2 and minio are aliases). s3 without S3_BUCKET falls back to local with a warning.
S3_ENDPOINT_URL (empty) backend · worker S3 API endpoint for R2, MinIO and other non-AWS stores. Leave empty for AWS S3. When set, path-style addressing is used.
S3_BUCKET (empty) backend · worker Bucket name. Objects are written under screenshots/.
S3_REGION auto backend · worker Region: auto for R2, the bucket’s region for AWS (e.g. eu-west-1), us-east-1 for MinIO.
S3_ACCESS_KEY_ID (empty) backend · worker Access key with read and write access to the bucket. Empty uses boto3’s default credential chain (e.g. an IAM role).
S3_SECRET_ACCESS_KEY (empty) backend · worker Secret for S3_ACCESS_KEY_ID.
S3_PUBLIC_BASE_URL (empty) backend · worker Public bucket or CDN URL. When set, artifacts link straight to <url>/screenshots/<key>; when empty, /api/files/<key> redirects to a presigned URL valid for one hour.
Variable Default Read by What it does
SENTRY_DSN (empty) backend · worker · scheduler Sentry DSN. When set, each service reports errors tagged with its service name. No PII or request bodies are sent.
LOG_FORMAT text backend · worker · scheduler text (readable) or json (one JSON object per line on stdout, uvicorn and RQ logs included).
METRICS_ENABLED true backend Serve Prometheus metrics at /metrics on the backend port. false makes it answer 404.
METRICS_TOKEN (empty) backend When set, /metrics requires Authorization: Bearer <token>. When empty, production mode answers /metrics with 404, and development mode answers only clients on loopback or a private network (the compose network, the host). Set it whenever a scraper reaches the backend from elsewhere. See Monitoring.
Variable Default Read by What it does
BILLING_ENABLED false backend · worker Turn on plans, usage limits and Stripe. Leave false for a self-hosted install. Set it on the backend and workers (workers enforce the quota).
STRIPE_SECRET_KEY (empty) backend Stripe secret key, used for Checkout and the customer portal.
STRIPE_WEBHOOK_SECRET (empty) backend Signing secret of the Stripe webhook endpoint /api/billing/webhook.
STRIPE_PRICE_PRO (empty) backend Stripe price id for the Pro plan.
STRIPE_PRICE_TEAM (empty) backend Stripe price id for the Team plan.

These are read with os.environ rather than the settings class. Set them the same way: in .env (Compose passes them through) or in your platform’s environment.

Variable Default Read by What it does
SSRF_DNS_CHECK true backend · worker Also resolve host names in user-supplied URLs, and refuse them when any address is a cloud-metadata or link-local one. Lookups time out after 2 s and are cached for 60 s. Set false only where DNS lookups aren’t possible.
SENTRY_ENVIRONMENT production backend · worker · scheduler Sentry environment tag.
SENTRY_TRACES_SAMPLE_RATE 0 backend · worker · scheduler Share of requests sent to Sentry as performance traces, from 0 to 1. 0 sends errors only.

The application never reads these. Docker Compose, the rq command, uvicorn or your hosting platform do.

Variable Default Used by What it does
WORKER_REPLICAS 2 docker-compose.yml Number of worker containers, which is how many tests run at the same time. See Scaling workers.
POSTGRES_HOST_PORT 5433 docker-compose.yml Host port for Postgres, published on 127.0.0.1 only. Containers always use postgres:5432.
RQ_REDIS_URL (none) rq worker Redis URL for the worker command when no --url is passed. The Render blueprint sets it.
FORWARDED_ALLOW_IPS 127.0.0.1 uvicorn Proxy addresses whose X-Forwarded-* headers uvicorn trusts. The Render blueprint sets *.
PORT (none) Render Port Render routes traffic to. The blueprint sets 8000.

The Postgres container’s own credentials are written in docker-compose.yml (POSTGRES_USER=testai, POSTGRES_PASSWORD=testai_secret, POSTGRES_DB=testai_db). The database is reachable only on the compose network and the host’s loopback address. The internal names stay testai so existing volumes keep working. Postgres applies POSTGRES_PASSWORD only when it first creates the volume, so to change the password of an existing database, run ALTER USER testai PASSWORD '…' and update both the compose file and DATABASE_URL.

PLAINWRIGHT_ENV tells the backend whether other people depend on this install. It checks SECRET_KEY once at start, before touching the database:

SECRET_KEY is… PLAINWRIGHT_ENV=development (default) PLAINWRIGHT_ENV=production
A random value of 32+ characters Starts normally. Starts normally.
Empty, or a publicly known default such as supersecretkey123 Starts, logs a CRITICAL line starting SECURITY:, and shows owners and admins a banner. Refuses to start: “Refusing to start with PLAINWRIGHT_ENV=production: SECRET_KEY is …”
Shorter than 32 characters Same as above. Refuses to start.

The banner comes from GET /api/system/warnings, which answers project owners and admins with {"warnings": [{"code", "severity", "message", "docs"}]} (codes default_secret_key and weak_secret_key) and everyone else with an empty list. Production mode also hides /metrics unless METRICS_TOKEN is set.

The Render blueprint sets PLAINWRIGHT_ENV=production. Elsewhere, set it yourself once SECRET_KEY is in place.

Start from .env.example and change at least these values. Everything else can keep its default.

.env
# Random, at least 32 characters, the same on every service (generate one below).
SECRET_KEY=replace-with-a-long-random-value
# Refuse to start with a missing, default or short SECRET_KEY.
PLAINWRIGHT_ENV=production
# The URL people open.
APP_BASE_URL=https://plainwright.example.com
CORS_ORIGINS=https://plainwright.example.com
# Optional: the model runs use when a user hasn't added an AI provider.
ANTHROPIC_API_KEY=
# Recommended: email for invitations, password reset and alerts.
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=plainwright@example.com
SMTP_PASSWORD=app-password-here
SMTP_FROM=Plainwright <plainwright@example.com>
# Recommended when a log shipper reads stdout.
LOG_FORMAT=json

Generate a SECRET_KEY. Each command prints a 64-character key:

Terminal window
python3 -c "import secrets; print(secrets.token_urlsafe(48))"
# or: openssl rand -base64 48

An install that already holds data has to rotate the key instead of swapping it: see Rotate SECRET_KEY.