Environment variables

Environment variables

A site hosted by Val Build reads its configuration and credentials when it runs. You set them in Val Build, and a change is live within a minute: nothing is rebuilt or published. This page is about the TanStack Start sites Val Build hosts; a site hosted anywhere else sets them with its host, by that framework's rules.

Where to set them

On the project's Variables page in Val Build, or in the Studio under Settings › Variables. Owners and developers can change them; everyone in the project can see the variables and the names of the secrets.

A project on its own hosting sets them in its host's settings instead, and in a .env file for local development.

Variables and secrets

  • Variables are configuration: an API's address, a feature flag. Their values are shown and edited in place. A name starting with PUBLIC_ reaches the browser; every other variable stays on the server.

  • Secrets are credentials: API keys and tokens. A secret is encrypted in the browser it is typed into, before it is sent, and is never shown again — only your site's server receives it. Because it is only ever available on the server, a secret cannot have a PUBLIC_ name.

Reading them

On the server every variable and secret is process.env.NAME. In the browser only PUBLIC_ variables exist, as import.meta.env.NAME. Never read process.env in a component: in vite dev the browser has no process, and the page breaks when it hydrates. Read a secret where code runs on the server only: a createServerFn handler, or a server route.

import { createServerFn } from "@tanstack/react-start";

// In the browser and on the server: a PUBLIC_ variable.
const apiUrl = import.meta.env.PUBLIC_API_URL;

// On the server only: a secret.
export const getOrders = createServerFn().handler(async () => {
  const response = await fetch(`${apiUrl}/orders`, {
    headers: { authorization: `Bearer ${process.env.API_TOKEN}` },
  });
  if (!response.ok) {
    throw new Error(`The orders could not be loaded: ${response.status}`);
  }
  // Return what the page needs, never the token.
  return response.json();
});

Keeping a secret secret

Whether a value reaches the browser is decided by its name, and a secret never does. But code on the server can still hand a secret to the page, and anything a page contains is public, so:

  • Never render a secret. It is in the HTML for anyone to see, even if it is hidden.

  • Never return one from a route loader. What a loader returns is written into the page so the browser can pick up where the server left off — it is in the page source even when nothing shows it. A loader also runs in the browser when someone navigates, where the secret is undefined.

  • Never put a credential in a PUBLIC_ variable. Val Build refuses a secret by such a name, but a variable is sent to every visitor whatever it holds.

vite.config.ts

For local development, put them in a .env file, and keep it out of git. Vite only lets VITE_ names through to the browser by default; let PUBLIC_ through instead, so vite dev behaves like the hosted site.

export default defineConfig({
  envPrefix: "PUBLIC_",
  // ...
});

Good to know

  • Your site and its proposals share one set of variables and secrets. Anyone who can put code in a proposal can therefore read the secrets, so only let people you would trust with them change code.

  • Names are capital letters, digits and underscores, do not start with a digit, and are at most 128 characters. A few names are set by Val Build and cannot be used (VAL_SECRET, VAL_PROJECT, NODE_ENV and the like), nor any starting with PLATFORM_. A project has at most 100 variables and secrets together, and a value is at most 5 KB.

  • Values set in Val Build are never part of a build, so going back to an earlier version of the site keeps today's variables and secrets.

  • On another host, with the envPrefix above, vite build writes PUBLIC_ values into the bundle when it builds, so changing one there means building again. On Val Build they are read when the site runs.