Payload Setup: Install It and Understand the Config
Set up the Payload CMS config from start to finish.
So you've got a Next.js project running, and now you want a real backend behind it. Payload CMS gives you somewhere to manage your content, an admin panel a client can actually use, and an API you didn't have to build yourself. To install it, run npx create-payload-app for a new project, or add payload, @payloadcms/next, and graphql to the Next.js app you already have. The config file only needs two options to run, secret and db.
This post covers two things: how to get Payload installed, both in a brand new project and in one you already have, and then how to read the config file that runs it. If you're new to Payload, start with What Is Payload? first.
Everything here is Payload 3, specifically Payload 3.88.0. At the time of writing, Payload 4 is on
What do you need before installing Payload?
Before we install anything, make sure you've got:
- Node 20.9 or higher.
- A supported Next.js version. The ranges are a little wild. There are four supported windows:
- Next 15.2: 15.2.9 or newer
- Next 15.3: 15.3.9 or newer
- Next 15.4: 15.4.11 or newer
- Next 16: 16.2.6 or newer
- A database. Payload works with MongoDB, Postgres, or SQLite, and it doesn't care whether that's running on your machine or hosted somewhere else. Which one you should pick is something I'll cover in a future tutorial.
Anything outside those four Next.js windows isn't supported, and that includes all of 15.5 and 15.6. So if you're on 15.4.2, you're on a supported minor but not a supported patch, and you'll need to update before any of this works.
How to start a brand new Payload project
If you're starting from nothing, setup takes about a minute. Open up your terminal, go to wherever you keep your projects, and run:
1npx create-payload-app
Payload walks you through a few prompts. Pick a template, pick your database, name the project, and you're done.
When that finishes, change into the folder and start it up:
1pnpm dev
Then open up http://localhost:3000/admin. You should get a screen asking you to create your first user.
And that's it. This next part is for everyone adding Payload to a project that already exists.
How to add Payload to an existing Next.js app
Adding Payload to an existing project has more steps, but it's still fairly simple.
Install the packages
First, the packages Payload needs. That's payload itself, the Next adapter, and graphql.
1pnpm i payload @payloadcms/next graphql
graphql doesn't install with Payload. Payload lists it as a peer dependency, so you'll get a warning if you don't install it manually.
Then you'll probably want @payloadcms/richtext-lexical if you want rich text fields, and sharp if you want Payload's image processing tools.
1pnpm i @payloadcms/richtext-lexical sharp
And then one database adapter. You can't use more than one, so I'll choose MongoDB:
1pnpm i @payloadcms/db-mongodb
Copy in Payload's files
Next you'll need Payload's own files inside your app folder. You can get those from the blank template. Don't write them yourself.
Here's a link to the (payload) files from the blank template: https://github.com/payloadcms/payload/tree/main/templates/blank/src/app
Payload's files belong in a route group called (payload), and your own site needs to live in a separate route group, something like (frontend). They can't share a route group, and if you place Payload's routes in next to your existing pages, they'll collide.
Wrap your Next config
Now open up your Next config. Import withPayload and wrap your nextConfig export in it:
1import { withPayload } from '@payloadcms/next/withPayload'23export default withPayload(nextConfig)
Be sure to check that your project is running ESM. Either add "type": "module" to your package.json, or rename the Next config to use a .mjs extension. And if anything in your project still uses require(), that has to become an import.
Add the config path to tsconfig
Last, open up your tsconfig.json and add a path so @payload-config points at your config file. It goes under compilerOptions, in the paths option, which could already exist:
1{2 "compilerOptions": {3 "paths": {4 "@payload-config": ["./payload.config.ts"]5 }6 }7}
Your config may be in a different location, so be sure to put the correct path for you. Payload uses this alias everywhere, so make sure it's there before you start the server.
What does the Payload config file require?
Now let's open up payload.config.ts and see what's actually in it. Out of everything in this file, only two options are required, and without them Payload doesn't start.
The first is secret. Payload uses it for password hashing and encryption, so it needs to be long and random, and it belongs in your environment file, not in your code. Instead of making one up on your own, generate it using:
1openssl rand -base64 32
The second is db, which is the adapter you installed earlier, plus your connection string.
Here's the smallest config you can run:
1import { buildConfig } from 'payload'2import { mongooseAdapter } from '@payloadcms/db-mongodb'34export default buildConfig({5 secret: process.env.PAYLOAD_SECRET,6 db: mongooseAdapter({ url: process.env.DATABASE_URL }),7})
That's all you need, and even collections aren't required. Payload starts, you get an admin panel, but there'll be nothing in it. It's a working CMS, albeit not a very useful one. Everything after this is about how to make it useful.
Four common config options
There are a lot of options you can add in this file. Here are four common ones.
collections
collections is an array, and it's where your content types go. A pages collection, a posts collection, a media collection. You can define them right here inside the config, but you shouldn't. Put each one in its own file and import it, or this file gets unreadable by about your third collection. I cover collections in more detail in the next post.
editor
editor sets your rich text editor. Payload uses Lexical, and you need to import it and pass it in:
1import { lexicalEditor } from '@payloadcms/richtext-lexical'23editor: lexicalEditor(),
admin
admin controls the admin panel itself. The option you'll definitely set here is user, which tells Payload which collection people log in from. You'll need it when you want a separate authors collection that isn't the same collection as your admin users. You can also set dateFormat to any date-fns pattern, so if you'd rather see a different date format than Payload's default, that's where you change it.
1admin: {2 user: 'users',3 dateFormat: 'MM/dd/yyyy',4},
typescript
typescript tells Payload where to write your generated types.
1typescript: {2 outputFile: path.resolve(dirname, 'payload-types.ts'),3},
That generated file is what gives you types for every collection you build, so when you get to querying your own data, your code editor knows what the collection looks like.
Four options that break things when they're missing
These next four won't stop your project from starting, but setting them can make your life easier.
sharp
The first is sharp. Payload runs fine without it. What doesn't run is Payload's image handling, so no resizing, no cropping, no focal points, no converting formats on upload. You need it if you add a media collection and you don't already have a different solution.
serverURL
The second is serverURL, which is your app's absolute URL. Protocol and domain, optionally a port, and no path. Payload builds one from the incoming request when you leave this out, so most of the time you won't notice it missing. What it can't do is build one when there is no request, like when a password reset is triggered through the Local API. There's nothing to read a URL from, so the link in that email goes nowhere. Set it from an environment variable and you never think about it again.
Third is your email adapter. Without one, every password reset gets logged to your console and sent to nobody. While you're developing, I'd recommend you use Mailpit. You get a real inbox running locally, so you can look at a real looking email. I'll go into email more in a future tutorial, but you can use classic SMTP using nodemailer, or Resend using an API key.
defaultDepth
And lastly, defaultDepth, which controls how far Payload populates your relationships when you query your data. We'll come back to this when we get to relationships.
There are plenty of other options in this file, and we'll cover some of them in their own tutorials, like globals, localization, and plugins.
Conclusion
For now, that's all there is to it! You've got Payload running at localhost:3000/admin, you've got a user, and you can read every line of the config file that put it there.
If you'd like a sequential walkthrough of a full Payload setup that goes deeper than this post, check out my course, Payload Essentials.