Files

791 lines
16 KiB
Markdown

# PayloadCMS Skill for QwenClaw
## Overview
This skill provides **PayloadCMS expertise** to QwenClaw, enabling it to build, configure, and extend Payload CMS projects. Payload is an open-source, fullstack Next.js framework that gives you instant backend superpowers.
**Source:** https://github.com/payloadcms/payload
---
## What is PayloadCMS?
**Payload** is a Next.js native CMS that can be installed directly in your existing `/app` folder. It provides:
- Full TypeScript backend and admin panel instantly
- Server components to extend Payload UI
- Direct database queries in server components (no REST/GraphQL needed)
- Automatic TypeScript types for your data
### Key Features
- **Next.js Native** - Runs inside your `/app` folder
- **TypeScript First** - Automatic type generation
- **Authentication** - Built-in auth out of the box
- **Versions & Drafts** - Content versioning support
- **Localization** - Multi-language content
- **Block-Based Layout** - Visual page builder
- **Customizable Admin** - React-based admin panel
- **Lexical Editor** - Modern rich text editor
- **Access Control** - Granular permissions
- **Hooks** - Document and field-level hooks
- **High Performance** - Optimized API
- **Security** - HTTP-only cookies, CSRF protection
---
## Installation
### Create New Project
```bash
# Basic installation
pnpx create-payload-app@latest
# With website template (recommended)
pnpx create-payload-app@latest -t website
# From example
npx create-payload-app --example example_name
```
### Project Structure
```
my-payload-app/
├── app/
│ ├── (payload)/
│ │ ├── admin/
│ │ ├── api/
│ │ └── layout.tsx
│ └── (frontend)/
│ ├── page.tsx
│ └── layout.tsx
├── collections/
│ ├── Users.ts
│ └── Posts.ts
├── payload.config.ts
├── payload-types.ts
└── package.json
```
---
## Collections & Schemas
### Basic Collection
```typescript
// collections/Posts.ts
import type { CollectionConfig } from 'payload';
export const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
required: true,
},
{
name: 'publishedAt',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayAndTime',
},
},
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
],
defaultValue: 'draft',
},
],
};
```
### Collection with Relationships
```typescript
// collections/Authors.ts
export const Authors: CollectionConfig = {
slug: 'authors',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'email',
type: 'email',
required: true,
},
{
name: 'avatar',
type: 'upload',
relationTo: 'media',
},
],
};
// In Posts collection
{
name: 'author',
type: 'relationship',
relationTo: 'authors',
}
```
### Blocks Field (Page Builder)
```typescript
{
name: 'layout',
type: 'blocks',
blocks: [
{
slug: 'hero',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'subtitle',
type: 'text',
},
{
name: 'backgroundImage',
type: 'upload',
relationTo: 'media',
},
],
},
{
slug: 'content',
fields: [
{
name: 'content',
type: 'richText',
},
],
},
{
slug: 'cta',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'buttonText',
type: 'text',
},
{
name: 'buttonUrl',
type: 'text',
},
],
},
],
}
```
---
## Access Control
### Field-Level Access
```typescript
{
name: 'isFeatured',
type: 'checkbox',
access: {
read: () => true,
update: ({ req }) => req.user?.role === 'admin',
},
}
```
### Collection Access
```typescript
access: {
read: () => true,
create: ({ req }) => req.user?.role === 'admin',
update: ({ req }) => req.user?.role === 'admin',
delete: ({ req }) => req.user?.role === 'admin',
}
```
---
## Hooks
### Before Change Hook
```typescript
{
name: 'slug',
type: 'text',
hooks: {
beforeChange: [
({ value, data }) => {
if (!value && data.title) {
return data.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
return value;
},
],
},
}
```
### After Change Hook
```typescript
hooks: {
afterChange: [
async ({ doc, req, operation }) => {
if (operation === 'create') {
// Send welcome email
await sendWelcomeEmail(doc.email);
}
return doc;
},
],
}
```
---
## Localization
```typescript
const config: Config = {
localization: {
locales: [
{ code: 'en', label: 'English' },
{ code: 'es', label: 'Spanish' },
{ code: 'fr', label: 'French' },
],
defaultLocale: 'en',
},
// ...
};
```
---
## Using Payload in Server Components
### Query Collection
```typescript
import { getPayload } from 'payload';
import config from '@payload-config';
export default async function PostsPage() {
const payload = await getPayload({ config });
const posts = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
},
sort: '-publishedAt',
limit: 10,
});
return (
<div>
{posts.docs.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
</article>
))}
</div>
);
}
```
### Query by ID
```typescript
const post = await payload.findByID({
collection: 'posts',
id: postId,
depth: 2, // Populate relationships
});
```
---
## VPS Landing Page with PayloadCMS
### Collections for VPS Site
```typescript
// collections/VpsPlans.ts
export const VpsPlans: CollectionConfig = {
slug: 'vps-plans',
admin: {
useAsTitle: 'name',
},
access: {
read: () => true,
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'price',
type: 'number',
required: true,
},
{
name: 'billingPeriod',
type: 'select',
options: ['monthly', 'yearly'],
defaultValue: 'monthly',
},
{
name: 'vcpuCores',
type: 'number',
required: true,
},
{
name: 'ram',
type: 'number',
label: 'RAM (GB)',
required: true,
},
{
name: 'storage',
type: 'number',
label: 'Storage (GB)',
required: true,
},
{
name: 'bandwidth',
type: 'text',
label: 'Bandwidth',
required: true,
},
{
name: 'features',
type: 'array',
fields: [
{
name: 'feature',
type: 'text',
},
],
},
{
name: 'isPopular',
type: 'checkbox',
defaultValue: false,
},
{
name: 'ctaText',
type: 'text',
defaultValue: 'Get Started',
},
],
};
// collections/Features.ts
export const Features: CollectionConfig = {
slug: 'features',
admin: {
useAsTitle: 'title',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'description',
type: 'richText',
required: true,
},
{
name: 'icon',
type: 'text',
label: 'Icon Name (Bootstrap Icons)',
},
{
name: 'order',
type: 'number',
admin: {
position: 'sidebar',
},
},
],
};
// collections/Testimonials.ts
export const Testimonials: CollectionConfig = {
slug: 'testimonials',
admin: {
useAsTitle: 'authorName',
},
access: {
read: () => true,
},
fields: [
{
name: 'authorName',
type: 'text',
required: true,
},
{
name: 'authorTitle',
type: 'text',
},
{
name: 'company',
type: 'text',
},
{
name: 'quote',
type: 'richText',
required: true,
},
{
name: 'avatar',
type: 'upload',
relationTo: 'media',
},
{
name: 'rating',
type: 'number',
min: 1,
max: 5,
},
],
};
```
### Frontend Page Component
```typescript
// app/(frontend)/page.tsx
import { getPayload } from 'payload';
import config from '@payload-config';
import VpsPricing from './components/VpsPricing';
import FeaturesGrid from './components/FeaturesGrid';
export default async function HomePage() {
const payload = await getPayload({ config });
const plans = await payload.find({
collection: 'vps-plans',
sort: 'price',
});
const features = await payload.find({
collection: 'features',
sort: 'order',
});
return (
<main>
<HeroSection />
<FeaturesGrid features={features.docs} />
<VpsPricing plans={plans.docs} />
<CtaSection />
</main>
);
}
```
### Pricing Component
```typescript
// app/(frontend)/components/VpsPricing.tsx
import type { VpsPlan } from '@payload-types';
interface VpsPricingProps {
plans: VpsPlan[];
}
export default function VpsPricing({ plans }: VpsPricingProps) {
return (
<section className="py-20 bg-dark">
<div className="container">
<div className="text-center mb-16">
<h2 className="text-4xl font-bold mb-4">
Simple, Transparent Pricing
</h2>
<p className="text-gray-400 max-w-2xl mx-auto">
No hidden fees. Pay only for what you use.
</p>
</div>
<div className="grid md:grid-cols-3 gap-8">
{plans.map((plan) => (
<div
key={plan.id}
className={`p-8 rounded-2xl ${
plan.isPopular
? 'bg-dark-light border-primary border-2'
: 'bg-dark-light border border-gray-800'
}`}
>
{plan.isPopular && (
<span className="bg-primary text-white px-3 py-1 rounded-full text-sm font-medium">
Most Popular
</span>
)}
<h3 className="text-xl font-semibold mt-4 mb-2">
{plan.name}
</h3>
<div className="text-4xl font-bold mb-2">
${plan.price}
<span className="text-sm text-gray-400 font-normal">
/{plan.billingPeriod}
</span>
</div>
<ul className="space-y-3 my-6">
<li className="flex items-center gap-2">
<i className="bi bi-check-circle-fill text-primary" />
{plan.vcpuCores} vCPU Cores
</li>
<li className="flex items-center gap-2">
<i className="bi bi-check-circle-fill text-primary" />
{plan.ram} GB RAM
</li>
<li className="flex items-center gap-2">
<i className="bi bi-check-circle-fill text-primary" />
{plan.storage} GB NVMe Storage
</li>
<li className="flex items-center gap-2">
<i className="bi bi-check-circle-fill text-primary" />
{plan.bandwidth} Bandwidth
</li>
{plan.features?.map((feature, idx) => (
<li key={idx} className="flex items-center gap-2">
<i className="bi bi-check-circle-fill text-primary" />
{feature.feature}
</li>
))}
</ul>
<button
className={`w-full py-3 rounded-lg font-semibold ${
plan.isPopular
? 'bg-primary text-white'
: 'border-2 border-gray-600 text-white'
}`}
>
{plan.ctaText}
</button>
</div>
))}
</div>
</div>
</section>
);
}
```
---
## Payload Config Example
```typescript
// payload.config.ts
import { buildConfig } from 'payload';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
import { mongooseAdapter } from '@payloadcms/db-mongodb';
import { nodemailerAdapter } from '@payloadcms/email-nodemailer';
import path from 'path';
import { fileURLToPath } from 'url';
import { Users } from './collections/Users';
import { VpsPlans } from './collections/VpsPlans';
import { Features } from './collections/Features';
import { Media } from './collections/Media';
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export default buildConfig({
admin: {
user: Users.slug,
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [
Users,
VpsPlans,
Features,
Media,
],
editor: lexicalEditor(),
db: mongooseAdapter({
url: process.env.DATABASE_URI || '',
}),
email: nodemailerAdapter({
defaultFromAddress: 'noreply@example.com',
defaultFromName: 'CloudVPS',
}),
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
});
```
---
## Usage in QwenClaw
### Basic Payload Project Creation
```
Use the payloadcms-cms skill to create a new PayloadCMS project for a VPS hosting landing page with:
- VPS plans collection (name, price, specs, features)
- Features collection (title, description, icon)
- Testimonials collection
- Media library for images
```
### Query Payload Data
```
Use payloadcms-cms to query all VPS plans sorted by price and display them in a pricing table
```
### Create Collection
```
Use payloadcms-cms skill to create a new collection for data centers with:
- name (text)
- location (text)
- region (select: US, EU, Asia)
- features (array: DDoS protection, NVMe, 10Gbps)
- latitude/longitude for map
```
---
## Best Practices
### 1. Type Safety
```typescript
// Always import generated types
import type { Post, User } from '@payload-types';
```
### 2. Depth for Relationships
```typescript
// Use depth to populate relationships
const post = await payload.findByID({
collection: 'posts',
id: postId,
depth: 2,
});
```
### 3. Access Control
```typescript
// Always define access control
access: {
read: () => true,
create: ({ req }) => !!req.user,
update: ({ req }) => !!req.user,
}
```
### 4. Validation
```typescript
// Add validation to fields
{
name: 'email',
type: 'email',
required: true,
validate: (value) => {
if (value && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return 'Invalid email format';
}
return true;
},
}
```
---
## Resources
### Official
- **Website**: https://payloadcms.com/
- **GitHub**: https://github.com/payloadcms/payload
- **Documentation**: https://payloadcms.com/docs
- **Examples**: https://github.com/payloadcms/payload/tree/main/examples
### Templates
- **Website**: `pnpx create-payload-app@latest -t website`
- **E-commerce**: `pnpx create-payload-app@latest -t ecommerce`
- **Blank**: `pnpx create-payload-app@latest -t blank`
---
## Skill Metadata
```yaml
name: payloadcms-cms
version: 1.0.0
category: development
description: PayloadCMS project creation, configuration, and development
author: PayloadCMS Team (https://github.com/payloadcms)
license: MIT
tags:
- cms
- nextjs
- typescript
- react
- backend
- admin-panel
```
---
**Skill ready for QwenClaw integration!** 🚀