Skip to main content

Environment Status

A super_admin-only page (/system/environment) that reports which of the platform's integrations are actually configured — across the backend and both Next.js frontends — without ever exposing a secret value. This was undocumented until now; the feature was built in a later session than the rest of this admin doc set.

Deliberately stricter than the VIEW_ANALYTICS-style gate used by the dashboard resolvers: this surface enumerates the platform's entire integration inventory (which providers are wired up, which credentials are absent, which regions/account IDs are in play), which is a reconnaissance map even though no secret value is ever returned — so every query and mutation here requires super_admin, not just a permission flag.

Implementation checklist

  • Report the backend's own configuration (adminGetEnvironmentStatus) — reads constants/env-catalog.js (18 groups, 109 variables) against process.env and returns, per variable: whether it's set, its length, its resolved requirement (required/recommended/optional, with some variables only required when another var equals a given value — e.g. BigQuery vars when ANALYTICS_DB_TYPE is bigquery), and — for variables the catalog explicitly marks secret: false (region names, public IDs, feature flags, public URLs) — the real value. Everything else reports presence and length only, never the value.
  • Report a deployed frontend's own configuration (adminGetFrontendEnvironmentStatus(app: WEB | ADMIN)) — proxied server-to-server: the backend fetches /api/env-status on the target app's own deployed URL (FRONTEND_URL for WEB, ADMIN_FRONTEND_URL for ADMIN) since it can't otherwise introspect a separate Next.js process's environment. Each frontend catalogs its own NEXT_PUBLIC_* vars (frontend-nextjs: 19; frontend-admin: 2) in a local src/lib/env-catalog.ts and serves them, unauthenticated, from src/app/api/env-status/route.ts — safe because anything NEXT_PUBLIC_* is already readable from the deployed JS bundle via view-source, so there's nothing to protect. If the fetch fails (app unreachable, timed out, or its *_URL env var isn't configured on the backend), the group comes back with an empty variables array and a fetchError message instead of throwing.
  • Run a live connectivity check per group (adminTestEnvironmentGroup(groupId)) — a mutation (not a query) since it reaches third parties and must never be batched/cached/prefetched. 9 of the 18 backend groups declare a test (database, redis, storage, email, analytics, push, payments, ads, livekit); the rest have no connectivity test and the mutation reports that rather than erroring. ads added this session: when GAM_NETWORK_CODE/GAM_SERVICE_ACCOUNT_KEY are configured it now probes GAM connectivity via gam-report.service.js#testConnection — authenticates and resolves the creator custom-targeting key without running an actual report. Every test races an 8s timeout. Successful/failed runs are logged with the calling admin's id for audit purposes, since they exercise production credentials against live providers.
  • Frontend UI (/system/environment) — three tabs (Backend / Admin / Web), each group rendered as an accordion closed by default, with a green "all good" check when every required variable in that group is set (a group can show green while still having unconfigured optional variables — the bar is "nothing required is missing," not "100% configured"). Each variable row shows a three-state icon (set / missing-and-required / missing-and-optional — a missing optional variable is deliberately not rendered as a problem) plus, when expanded, its requirement tier, a docs link when the catalog has one, and (for secret: false variables) the real value. Skeleton-shaped loading state, not a spinner, matching the rest of the admin panel.

A structural decision worth calling out

The backend catalog (constants/env-catalog.js) holds only structure — key, secret, requirement, requiredIf, docsUrl — no label, description, or "how to obtain this" copy. All of that text lives in apps/frontend-admin/src/messages/{en,es}.json under system.environment.groups.<id> and system.environment.variables.<KEY>, looked up by the id/key the catalog defines and rendered through next-intl. This keeps the page from ever being English-only in one locale (see the repo's CLAUDE.md i18n rule) and is enforced by a backend test (tests/unit-test/env-catalog.unit.test.js) that fails the build if a catalog entry is missing its matching messages in either language.

Technical reference

query AdminGetEnvironmentStatus {
adminGetEnvironmentStatus {
nodeEnv
checkedAt
totalMissingRequired
groups {
id
testable
missingRequired
configured
total
fetchError
variables {
key
isSecret
isSet
value
valueLength
requirement
isMissing
docsUrl
}
}
}
}

query AdminGetFrontendEnvironmentStatus($app: AdminFrontendApp!) {
adminGetFrontendEnvironmentStatus(app: $app) {
id testable missingRequired configured total fetchError
variables { key isSecret isSet value valueLength requirement isMissing docsUrl }
}
}

mutation AdminTestEnvironmentGroup($groupId: String!) {
adminTestEnvironmentGroup(groupId: $groupId) { groupId ok message durationMs testedAt }
}

Backend: graphql/types/admin/env-status.type.js, graphql/resolvers/admin/env-status.resolver.js, managers/admin-managers/env-status.manager.js, constants/env-catalog.js. Frontend: apps/frontend-admin/src/app/system/environment/page.tsx, each app's own src/lib/env-catalog.ts + src/app/api/env-status/route.ts.

The separate /system page (system health metrics + analytics export) is a different feature — see Analytics Dashboard for adminGetSystemHealth and adminExportAnalytics.