npm package: @latte-macchiat-io/latte-payload
Description: Reusable Payload CMS collections, utilities, and components for Latte projects
Author: Latte Macchiat.io
Latest version: 1.0.10

# @latte-macchiat-io/latte-payload

Reusable Payload CMS collections, utilities, and components for Latte projects. This package provides a solid foundation for building Payload CMS applications with pre-configured collections, access control, email queuing, and more.

## Features

- **5 Pre-built Collections**: Users, Media, ContactMessages, EmailTemplates, QueuedEmails
- **2 Global Configs**: PrivacyPolicy, TermsOfUse
- **8 Access Control Functions**: Ready-to-use access control patterns
- **Email System**: Template-based emails with queue and retry logic
- **Background Jobs**: Async email processing with exponential backoff
- **Admin UI Components**: Custom components for better UX
- **Fully Typed**: Complete TypeScript support
- **Configurable**: All collections and utilities accept configuration options

## Installation

```bash
pnpm add @latte-macchiat-io/latte-payload
```

## Peer Dependencies

```bash
pnpm add payload@3.64.0 \
  @payloadcms/db-postgres@3.64.0 \
  @payloadcms/next@3.64.0 \
  @payloadcms/richtext-lexical@3.64.0 \
  @payloadcms/ui@3.64.0 \
  react@19.0.0 \
  react-dom@19.0.0 \
  next@15.0.0 \
  sharp@0.34.0
```

### Optional Dependencies

```bash
# For email functionality
pnpm add @payloadcms/email-resend@3.64.0  # or @payloadcms/email-nodemailer@3.64.0

# For S3 storage
pnpm add @payloadcms/storage-s3@3.64.0
```

## ⚠️ Important: Payload Version Alignment

**All `@payloadcms/*` packages and `payload` must be at the exact same version (minimum 3.64.0).**

Payload CMS enforces strict version matching across all its packages. If any package has a different version, you'll encounter this error:

```
Error: Mismatching "payload" dependency versions found
```

### Ensuring Version Alignment

Add the following to your project's `package.json` to enforce version alignment:

```json
{
  "pnpm": {
    "overrides": {
      "@payloadcms/db-postgres": "3.64.0",
      "@payloadcms/email-nodemailer": "3.64.0",
      "@payloadcms/email-resend": "3.64.0",
      "@payloadcms/next": "3.64.0",
      "@payloadcms/richtext-lexical": "3.64.0",
      "@payloadcms/storage-s3": "3.64.0",
      "@payloadcms/ui": "3.64.0",
      "payload": "3.64.0"
    }
  }
}
```

**For npm users:**
```json
{
  "overrides": {
    "@payloadcms/db-postgres": "3.64.0",
    "@payloadcms/email-nodemailer": "3.64.0",
    "@payloadcms/email-resend": "3.64.0",
    "@payloadcms/next": "3.64.0",
    "@payloadcms/richtext-lexical": "3.64.0",
    "@payloadcms/storage-s3": "3.64.0",
    "@payloadcms/ui": "3.64.0",
    "payload": "3.64.0"
  }
}
```

**For yarn users:**
```json
{
  "resolutions": {
    "@payloadcms/db-postgres": "3.64.0",
    "@payloadcms/email-nodemailer": "3.64.0",
    "@payloadcms/email-resend": "3.64.0",
    "@payloadcms/next": "3.64.0",
    "@payloadcms/richtext-lexical": "3.64.0",
    "@payloadcms/storage-s3": "3.64.0",
    "@payloadcms/ui": "3.64.0",
    "payload": "3.64.0"
  }
}
```

### Why This Matters

Payload CMS validates that all its packages share the same version to prevent compatibility issues. Even if transitive dependencies (packages installed by other packages) try to pull in different versions, the overrides above will ensure version consistency.

## Quick Start

### Basic Usage

```typescript
// payload.config.ts
import { buildConfig } from 'payload';
import { postgresAdapter } from '@payloadcms/db-postgres';
import {
  Users,
  Media,
  EmailTemplates,
  QueuedEmails,
  PrivacyPolicy,
  TermsOfUse,
  processEmailQueueTaskConfig
} from '@latte-macchiat-io/latte-payload';

export default buildConfig({
  collections: [
    Users,
    Media,
    EmailTemplates,
    QueuedEmails,
    // Add your custom collections here
  ],
  globals: [
    PrivacyPolicy,
    TermsOfUse,
  ],
  jobs: {
    tasks: [processEmailQueueTaskConfig],
  },
  db: postgresAdapter({
    pool: {
      connectionString: process.env.DATABASE_URI,
    },
  }),
  // ... other config
});
```

## Collections

### Users

Complete authentication system with email verification and password reset.

```typescript
import { createUsersCollection } from '@latte-macchiat-io/latte-payload';

// Customize the Users collection
const CustomUsers = createUsersCollection({
  roles: [
    { label: { fr: 'Admin', nl: 'Admin', en: 'Admin' }, value: 'admin' },
    { label: { fr: 'Éditeur', nl: 'Redacteur', en: 'Editor' }, value: 'editor' },
    { label: { fr: 'Utilisateur', nl: 'Gebruiker', en: 'User' }, value: 'user' },
  ],
  defaultLocale: 'fr',
  tokenExpiration: 60 * 60 * 24 * 7, // 1 week
  verifyEmailConfig: {
    generateVerifyUrl: async (token, user) => {
      return `https://yourdomain.com/verify?token=${token}`;
    },
  },
});
```

### Media

File upload collection with S3 support.

```typescript
import { Media } from '@latte-macchiat-io/latte-payload';
import { s3Storage } from '@payloadcms/storage-s3';

export default buildConfig({
  collections: [Media],
  plugins: [
    s3Storage({
      collections: {
        media: true,
      },
      bucket: process.env.S3_BUCKET,
      config: {
        credentials: {
          accessKeyId: process.env.S3_ACCESS_KEY_ID,
          secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
        },
        region: process.env.S3_REGION,
      },
    }),
  ],
});
```

### EmailTemplates

Manage email templates in the CMS.

```typescript
import { createEmailTemplatesCollection } from '@latte-macchiat-io/latte-payload';

const CustomEmailTemplates = createEmailTemplatesCollection({
  emailCodes: [
    { label: 'verify-email', value: 'verify-email' },
    { label: 'password-lost', value: 'password-lost' },
    { label: 'welcome', value: 'welcome' },
    { label: 'order-confirmation', value: 'order-confirmation' },
  ],
});
```

### QueuedEmails

Email queue with retry logic and admin UI.

```typescript
import { QueuedEmails } from '@latte-macchiat-io/latte-payload';
```

### ContactMessages

Store contact form submissions.

```typescript
import { createContactMessagesCollection } from '@latte-macchiat-io/latte-payload';

const CustomContactMessages = createContactMessagesCollection({
  locales: ['en', 'fr', 'nl', 'de'],
  emailNotificationConfig: {
    contactEmail: 'support@yourdomain.com',
    subject: 'New Contact Form Submission',
  },
});
```

## Globals

### PrivacyPolicy & TermsOfUse

Localized legal content with Next.js cache revalidation.

```typescript
import { PrivacyPolicy, TermsOfUse } from '@latte-macchiat-io/latte-payload';
```

## Access Control

8 pre-built access control patterns:

```typescript
import {
  anyone,                         // Public access
  authenticated,                  // Authenticated users only
  authenticatedFieldLevel,        // Field-level authenticated access
  admins,                         // Admin users only
  adminsFieldLevel,               // Field-level admin access
  publicReadAuthenticatedAccess,  // Public read, authenticated write
  authenticatedAccessOnly,        // All operations require auth
  adminAccessOnly,                // All operations require admin
} from '@latte-macchiat-io/latte-payload';

// Use in your custom collections
export const MyCollection: CollectionConfig = {
  slug: 'my-collection',
  access: publicReadAuthenticatedAccess,
  fields: [
    {
      name: 'sensitiveField',
      type: 'text',
      access: {
        read: adminsFieldLevel,
        update: adminsFieldLevel,
      },
    },
  ],
};
```

## Utilities

### Queue Email

```typescript
import { queueEmail } from '@latte-macchiat-io/latte-payload';

await queueEmail({
  payload,
  to: 'user@example.com',
  emailCode: 'welcome',
  variables: {
    name: 'John Doe',
    url: 'https://yourdomain.com',
  },
  locale: 'en',
  idempotencyKey: `welcome-${userId}`, // Prevent duplicates
});
```

### Generate Email HTML

```typescript
import { generateEmailHtmlFromTemplate } from '@latte-macchiat-io/latte-payload';

const emailContent = await generateEmailHtmlFromTemplate({
  payload,
  emailCode: 'order-confirmation',
  locale: 'fr',
  variables: {
    orderNumber: '#12345',
    customerName: 'Jane Doe',
  },
});
```

### Get Payload Client

```typescript
import { getPayloadClient } from '@latte-macchiat-io/latte-payload';
import config from './payload.config';

const payload = await getPayloadClient(config);
```

## Background Tasks

### Process Email Queue

Automatically process queued emails with retry logic.

```typescript
import { createProcessEmailQueueTask } from '@latte-macchiat-io/latte-payload';
import * as Sentry from '@sentry/nextjs';

const emailTask = createProcessEmailQueueTask({
  timezone: 'America/New_York',
  maxEmailsPerRun: 50,
  delayBetweenEmails: 500, // 0.5 seconds
  defaultFromAddress: 'noreply@yourdomain.com',
  errorReporter: {
    captureException: Sentry.captureException,
  },
});

export default buildConfig({
  jobs: {
    tasks: [emailTask],
  },
});
```

## Email HTML Template

Create an HTML email template at `./emails/template.html`:

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>{{ emailSubject }}</title>
</head>
<body>
  <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
    {{ emailContent }}
  </div>
</body>
</html>
```

The `{{ emailContent }}` placeholder will be replaced with your email template content.

## Environment Variables

```bash
# Required
DATABASE_URI=postgresql://user:password@localhost:5432/dbname
PAYLOAD_SECRET=your-secret-key

# Email (choose one)
ENABLE_EMAILS_SENDING=true
RESEND_API_KEY=re_xxx          # For production (Resend)
# OR
SMTP_HOST=localhost             # For development (Mailpit/Nodemailer)
SMTP_PORT=1025
SMTP_FROM_ADDRESS=noreply@yourdomain.com

# Contact form
EMAIL_DEFAULT_CONTACT_ADDRESS=contact@yourdomain.com

# Dashboard widget
NEXT_PUBLIC_CLIENT_DASHBOARD_URL=https://dashboard.latte-macchiat.io/your-project

# S3 Storage (optional)
S3_ENDPOINT=https://s3.amazonaws.com
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=xxx
S3_SECRET_ACCESS_KEY=xxx
S3_BUCKET=your-bucket-name
```

## Admin UI Components

### ClientSpaceButton

A dashboard widget that links to your Latte Macchiat.io dashboard for support, feature requests, and issue tracking.

```typescript
// payload.config.ts
import { buildConfig } from 'payload';

export default buildConfig({
  admin: {
    components: {
      afterDashboard: [
        '@latte-macchiat-io/latte-payload/components#ClientSpaceButton',
      ],
    },
  },
  // ... rest of config
});
```

**Environment Variable:**

```bash
# URL to your Latte Macchiat.io dashboard
NEXT_PUBLIC_CLIENT_DASHBOARD_URL=https://dashboard.latte-macchiat.io/your-project
```

**Features:**
- Opens dashboard in a new tab with proper security attributes
- Hidden in production when `CLIENT_DASHBOARD_URL` is not set
- Shows warning in development mode when URL is missing
- Uses Payload's native Button and Banner components
- Automatically adapts to Payload's theme (light/dark mode)

## TypeScript Types

```typescript
import type {
  Locale,
  EmailTemplateCode,
  QueuedEmailStatus,
  UsersCollectionConfig,
  ContactMessagesConfig,
  EmailTemplatesConfig,
  ProcessEmailQueueConfig,
} from '@latte-macchiat-io/latte-payload';
```

## Version Compatibility

- **Payload CMS**: >=3.64.0 <4.0.0 (all @payloadcms/* packages must match)
- **React**: ^19.0.0
- **Next.js**: ^15.0.0
- **Sharp**: ^0.34.0
- **Node.js**: >=22.x
- **pnpm**: ^10.0.0

**Important:** All `@payloadcms/*` packages must be at the same version. Use package manager overrides (see above) to enforce this.

## Contributing

This package is maintained by Latte Macchiat.io for internal use across projects. If you find bugs or have suggestions, please open an issue.

## License

MIT