Environment variables in SvelteKit

Published: Tuesday, 21 Sep 2021

Updated: Tuesday, 30 Jan 2024

What are environment variables?

Environment variables are named variables which change the way our application runs. This enables us to have multiple environments, with different databases etc, without requiring any code changes.

At the end of this article I’ve included example environment variable setups for:

  • Supabase browser client
  • Supabase admin client
  • Stripe Checkout

Why & when should I use environment variables?

Use environment variables to differentiate between environments (product, preview, dev, etc.) for things such as:

  • database credentials and urls
  • publishable keys (e.g. with supabase, the anon and url)
  • secrets (e.g. with stripe, the secret key)

For example, we generally have a separate database for production and development. Our environment variable for DATABASE_URL locally could be DATABASE_URL=dbdev.ourapp.com while our deployed application would have DATABASE_URL=dbprod.ourapp.com.

Where do environment variables live?

They are stored in a .env file, or some variation of that such as .env.local, etc. My preference is to use .env.local.

.env files must not be added to version control - ensure they are in .gitignore.

While we want to keep sensitive details out of version control, we still need to know what environment variables are required to run the project. To achieve this, it is common to include an .env.example file with blank values in your projects’ repository.

The below .gitignore will exclude all flavours of .env EXCEPT for .env.example.

// .gitignore

.env
.env.*
!.env.example

What does a .env file look like?

An environment variable file is plaintext.

Below is a mockup of .env and .env.example for a simple application using Supabase and Stripe.

// .env.local

PUBLIC_SUPABASE_URL=https://abcdefg.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJdfdnuu3e3ej3Wjew.eyJew
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiABCzD1NiXsInR3c
PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_24mk3szSd4233
STRIPE_SECRET_KEY=sk_test_24mfdsk3E4r
// .env.example

PUBLIC_SUPABASE_URL=https://xxxxxx.supabase.co
PUBLIC_SUPABASE_ANON_KEY=<jwt_token>
SUPABASE_SERVICE_ROLE_KEY=<service_role_key>
PUBLIC_STRIPE_PUBLISHABLE_KEY=<stripe_publishable_key>
STRIPE_SECRET_KEY=<stripe_secret_key>

OK, so what’s different about them with SvelteKit?

SvelteKit projects import environment variables via the following:

  • For environment variables read at runtime:
    • import { env } from '$env/dynamic/private'; for secrets (official docs) and then use them in your code as env.KEY_NAME
    • import { env } from '$env/dynamic/public'; (official docs) and then use them in your code as env.KEY_NAME
  • For environment variables read at build time only:
    • import { KEY_NAME } from '$env/static/private'; for secrets (official docs)
    • import { KEY_NAME } from '$env/static/public'; (official docs)

SvelteKit helps prevent leaking secrets in two ways:

  1. Using the above imports will check for prefixes in the environment variables. A PUBLIC_ prefix is understood by SvelteKit to be used anywhere, while no prefix is assumed to indicate that the environment variable must be kept secret. You can configure the prefix behaviour, learn more here.

  2. It will error if you try to import secrets (private environment variables) in client-side code.

Some quick examples

Supabase browser client

Setting up a Supabase client to make use of their Auth offering would look like:

// src/lib/utils/supabaseBrowser.js

import { createClient } from '@supabase/supabase-js';
import {
  PUBLIC_SUPABASE_URL,
  PUBLIC_SUPABASE_ANON_KEY
} from '$env/static/public';

export const supabase = createClient(
  PUBLIC_SUPABASE_URL,
  PUBLIC_SUPABASE_ANON_KEY
);
export const auth = supabase.auth;

Supabase’s URL and ANON_KEY provide very little access and are intended to be included in client-side code. They also will not change during application runtime, so can safely be imported once at build time. As such they should be imported via $env/static/public. Refer to Supabase’s docs for more on their API keys.

Supabase admin client

The SERVICE_ROLE_KEY key provides admin-like access to Supabase and as such must only ever be used on server-side code. Refer to Supabase’s docs for more on their API keys.

Setting up the admin client would look like:

// src/lib/utils/supabaseAdmin.js

import { createClient } from '@supabase/supabase-js';
import {
  PUBLIC_SUPABASE_URL,
  SUPABASE_SERVICE_ROLE_KEY
} from '$env/static/private';

export const supabaseAdmin = createClient(
  PUBLIC_SUPABASE_URL,
  SUPABASE_SERVICE_ROLE_KEY
);

Stripe Checkout

This is an example for Stripe-hosted Checkout.

I generally asynchronously load and defer the initialisation of @stripe/stripe-js for performance reasons. Client side would look like:

// src/routes/route_name_here/+page.svelte

<script>
  import { PUBLIC_STRIPE_PUBLISHABLE_KEY } from '$env/static/public';

  onMount(async () => {
      const { loadStripe } = await import('@stripe/stripe-js');
      stripe = await loadStripe(PUBLIC_STRIPE_PUBLISHABLE_KEY);
    });
</script>

For demonstration purposes, we will use a runtime secret on your server to create the checkout session:

// src/lib/utils/stripe.js

import Stripe from 'stripe';
import { env } from '$env/dynamic/private';

export const stripe = new Stripe(env.STRIPE_SECRET_KEY);

Learn more about Stripe API keys.