so i get an error by doing npm run generate:types :
PS C:\Users\hacke\complete_fullstack_digital_marketplace_app> npm run generate:types
> complete_fullstack_digital_marketplace_app@0.1.0 generate:types
> cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types
C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:52
throw new _errors.InvalidFieldRelationship(field, relationship);
^
InvalidFieldRelationship: Field Product file(s) has invalid relationship 'product_files'.
at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:52:31
at Array.forEach (<anonymous>)
at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:50:31
at Array.map (<anonymous>)
at sanitizeFields (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\fields\config\sanitize.js:25:19)
at sanitizeCollection (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\collections\config\sanitize.js:135:53)
at C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\sanitize.js:81:85
at Array.map (<anonymous>)
at sanitizeConfig (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\sanitize.js:81:45)
at buildConfig (C:\Users\hacke\complete_fullstack_digital_marketplace_app\node_modules\payload\dist\config\build.js:21:41) {
data: null,
isOperational: true,
isPublic: false,
status: 500
}
Node.js v20.10.0.
i do not know what is the issue becasue everything worked till this point and i need to generate typscript types based on configuration in src/payload.config.ts recognise collection.
github repo: https://github.com/RenePung/complete_fullstack_digital_marketplace_app
package.json :
{
"name": "complete_fullstack_digital_marketplace_app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts nodemon",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^3.3.2",
"@payloadcms/bundler-webpack": "^1.0.5",
"@payloadcms/db-mongodb": "^1.0.8",
"@payloadcms/richtext-slate": "^1.2.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.44.1",
"@trpc/next": "^10.44.1",
"@trpc/react-query": "^10.44.1",
"@trpc/server": "^10.44.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cross-env": "^7.0.3",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"lucide-react": "^0.292.0",
"next": "14.0.3",
"nodemailer": "^6.9.7",
"payload": "^2.2.2",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.48.2",
"sonner": "^1.2.4",
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20",
"@types/nodemailer": "^6.4.14",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.3",
"nodemon": "^3.0.1",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
Products.ts file :
import { PRODUCT_CATEGORIES } from "../../config";
import { CollectionConfig } from "payload/types";
export const Products: CollectionConfig = {
slug: "products",
admin: {
useAsTitle: "name"
},
access: {},
fields: [
{
name: "user",
type: "relationship",
relationTo: "users",
required: true,
hasMany: false,
admin: {
condition: () => false
},
},
{
name: "name",
label: "Name",
type: "text",
required: true,
},
{
name: "description",
type: "textarea",
label: "Product details",
},
{
name: "price",
label: "Price in USD",
min: 0,
max: 1000,
type: "number",
required: true,
},
{
name: "category",
label: "Category",
type: "select",
options: PRODUCT_CATEGORIES.map(({ label, value }) => ({ label, value })
),
required: true,
},
{
name: "product_files",
label: "Product file(s)",
type: "relationship",
required: true,
relationTo: "product_files",
hasMany: false,
},
{
name: "approvedForSale",
label: "Product Status",
type: "select",
defaultValue: "pending",
access: {
create: ({ req }) => req.user.role === "admin",
read: ({ req }) => req.user.role === "admin",
update: ({ req }) => req.user.role === "admin",
},
options: [
{
label: "Pending verification",
value: "pending",
},
{
label: "Approved",
value: "approved",
},
{
label: "Denied",
value: "denied",
},
],
},
{
name: "priceId",
access: {
create: () => false,
read: () => false,
update: () => false,
},
type: "text",
admin: {
hidden: true,
},
},
{
name: "stripeId",
access: {
create: () => false,
read: () => false,
update: () => false,
},
type: "text",
admin: {
hidden: true,
},
},
{
name: "images",
type: "array",
label: "Product images",
minRows: 1,
maxRows: 4,
required: true,
labels: {
singular: "Image",
plural: "Images",
},
fields: [
{
name: "image",
type: "upload",
relationTo: "media",
required: true,
},
],
},
],
}
ProductFile.ts file :
import { User } from "../payload-types";
import { BeforeChangeHook } from "payload/dist/collections/config/types";
import { Access, CollectionConfig } from "payload/types";
const addUser: BeforeChangeHook = ({ req, data }) => {
const user = req.user as User | null
return {...data, user: user?.id}
}
const yourOwnAndPurchased: Access = async ({ req }) => {
const user = req.user as User | null
if( user?.role === "admin" ) return true
if( !user ) return false
const {} = await req.payload.find({
collection: "products"
})
}
export const ProductFiles: CollectionConfig = {
slug: "product_files",
admin: {
hidden: ({ user }) => user.role !== "admin",
},
hooks: {
beforeChange: [addUser]
},
access: {
read: yourOwnAndPurchased
},
upload: {
staticURL: "/product_files",
staticDir: "product_files",
mimeTypes: ["image/*", "font/*", "application/postscript"],
},
fields: [
{
name: "user",
type: "relationship",
relationTo: "users",
admin: {
condition: () => false
},
hasMany: false,
required: true,
},
],
}
payload.config.ts file :
import { mongooseAdapter } from "@payloadcms/db-mongodb";
import { slateEditor } from "@payloadcms/richtext-slate";
import { buildConfig } from "payload/config";
import { webpackBundler } from "@payloadcms/bundler-webpack";
import path from "path";
import { Users } from "./collections/Users";
import dotenv from "dotenv";
import { Products } from "./collections/Products/Products";
import { Media } from "./collections/Media";
//*****************************IMPORTS*****************************************************
dotenv.config({
path: path.resolve(__dirname, "../.env"),
})
export default buildConfig({
serverURL: process.env.NEXT_PUBLIC_SERVER_URL || '',
collections: [Users, Products, Media],
routes: {
admin: '/sell'
},
admin: {
user: "users",
bundler: webpackBundler(),
meta: {
titleSuffix: "- HippoHub",
favicon: "/favicon.ico",
ogImage: "/thumbnail.jpg",
},
},
rateLimit: {
max: 2000,
},
editor: slateEditor({}),
db: mongooseAdapter({
url: process.env.MONGODB_URL!,
}),
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
})
You just need to import and use the ProductFiles collection in your payload config file.