payload: Public PAYLOAD_PUBLIC environment variables don't work in production builds

Bug Report

I want to use a public environment variable for the S3 URL of my app. I have this in my .env file:

PAYLOAD_PUBLIC_S3_URL=https://my-bucket.s3.eu-central-1.amazonaws.com

…and this in my src/collections/Media.ts file:

adminThumbnail: ({ doc }) => {
	let media = doc as unknown as MediaType;
	return `${process.env.PAYLOAD_PUBLIC_S3_URL}${media.sizes.thumb.url}`;
},

Everything works great when I npm run dev. The served URLs in the panel are these:

https://my-bucket.s3.eu-central-1.amazonaws.com/media/my-image-350x233.jpg

But when I make a production build with npm run build and then open the app with npm run serve, I get these URLs:

undefined/media/my-image-350x233.jpg

When I search for my bucket URL in the resulting build folder, it’s nowhere to be found.

Other Details

Payload version 1.3.0

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 4
  • Comments: 27 (24 by maintainers)

Most upvoted comments

OK! So, you need to add the following at the top of your payload config file.

import path from 'path';
import dotenv from 'dotenv';

dotenv.config({
  path: path.resolve(__dirname, '../.env'),
});

If you were fetching your env variables from a remote source you would need to wire up a custom build script that uses payloads build script after fetching your remote variables, something like this:

// customAdminBuild.js
const path = require('path');

// Tell Payload where to find your Payload config - can do it either here, or in package.json script
process.env.PAYLOAD_CONFIG_PATH = path.resolve(__dirname, 'src/payload.config.ts');

const { build } = require('payload/dist/bin/build');

const buildPayloadAdmin = async () => {
// Fetch your environment variables here
// And then run `build`

build();
}

buildPayloadAdmin();

And you would use that in your build command instead of payload build 👍

@hdodov wow I never responded - my bad, I totally thought I did.

The reason it must be declared in both places is because:

  • when you serve, your project needs access to env in the admin panel
  • when you build, your server code needs access to the env

I am not sure there is a single solution to allow for both to be done without declaring it in both.

@CallMeLaNN Yes I think that the example should be updated. I think we should add the import statements for dotenv in both places. Would you like to contribute with a simple PR to the 3 src/templates/[template-name]/src/payload.config.ts files? (see them here)

examaple (blog template):

import { buildConfig } from 'payload/config';
import path from 'path';
import Categories from './collections/Categories';
import Posts from './collections/Posts';
import Tags from './collections/Tags';
import Users from './collections/Users';
import dotenv from 'dotenv';

dotenv.config();

export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || 'http://localhost:3000',
  admin: {
    user: Users.slug,
  },
  collections: [
    Categories,
    Posts,
    Tags,
    Users,
  ],
  typescript: {
    outputFile: path.resolve(__dirname, 'payload-types.ts')
  },
  graphQL: {
    schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
  },
});

@CallMeLaNN What I mean by ‘changed at startup’ is when calling ‘yarn serve’, either locally or in a docker container, the values passed will be ignored and instead the values set in the .env file will be used.

@JarrodMFlesch I’m doing this in server.ts:

require("dotenv").config();

…and this in payload.config.ts:

import dotenv from "dotenv";
dotenv.config();

…and it works alright. My main concern is this:

I am using it in a few projects, declaring the dotenv import in both places and it has been working for me

The user shouldn’t have to include dotenv in both places. That was my point - the docs I quoted suggest loading dotenv in server.ts, but doing so (without also loading it in payload.config.ts) won’t work.

It’d be great DX if PAYLOAD_PUBLIC variables can work without having to load dotenv twice.

Thank you, @JarrodMFlesch. I tried this out, and it is working now. In case others in the future get hung up on this, I should specify that in order to get this to work, I added the following to the top of server.ts:

import dotenv from 'dotenv';

dotenv.config();

and this to the top of payload.config.ts:

import path from 'path';
import dotenv from 'dotenv';

dotenv.config({
  path: path.resolve(__dirname, '../.env'),
});

@brachypelma can you try using import instead of using require statement? I do get the blank screen error when using require to import dotenv.

import path from 'path';
import dotenv from 'dotenv';

dotenv.config({
  path: path.resolve(__dirname, '../.env'),
});

With this I am able to run dev and build, and read env vars in both. Let me know!

@JarrodMFlesch , than you for posting your solution to this problem. Unfortunately, I cannot get this to work. This may just be a misunderstanding on my part. Please let me know if it is.

My goal is to have separate .env files, one on my local computer for local development and one on my server for production use. I want to be able to have different serverURL and CORS config options in my payload.config for local and production development, so I would like to read these payload.config values from the .env files so the same config file will work in each environment.

Here is an example app I put together following your guidelines above.

https://github.com/brachypelma/payload-env-test

My steps to create this:

  1. I ran npx create-payload-app with TS and blank template options
  2. I added the following line to my .env file: PAYLOAD_PUBLIC_SERVER_URL=http://localhost:3000
  3. I added the following to the top of my payload.config file:
import * as dotenv from 'dotenv'

dotenv.config({
  path: path.resolve(__dirname, '../.env'),
})
  1. I changed the buildConfig as follows:
export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL,
  ...
});

When I save this and run npm run dev and then load http://localhost:3000/admin in my browser, I get a blank screen and this in the console:

Uncaught TypeError: dotenv__WEBPACK_IMPORTED_MODULE_2__.config is not a function ts payload.config.ts:7

I’m not sure why dotenv.config is not recognized as a function, especially since it is called in server.ts

FWIW, I tried removing the dotenv.config call from payload.config and added the specified option to the dotenv.config call in server.ts, i.e.:

require('dotenv').config({
  path: path.resolve(__dirname, '../.env'),
});

However, when I do that, .env variables loaded in payload.config are undefined when I build the app.

Can you offer any help? I’m not really sure what I am doing wrong here. Apologies again if I just misunderstood your solution.