---
title: "v1.4.0"
description: "CMS pages default to static generation with on-demand revalidation, and Payload gains a form builder with an email system and automatic upload optimization."
requested_language: cs
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/cs/changelog/v1.4.0
version: changelog
docs_index: https://docs.systhema.app/cs/llms.txt
---
> This page isn't translated yet. Showing English.


## ⚠️ Breaking & Behavioral Changes

### Minimum Dependency Versions

`@systhemaui/payload` now requires **Next.js 15.4.11** or higher on version 15 (or **16.2.0+** once it exits canary). The minimum **PayloadCMS** version is bumped to **3.79.0** across all `@payloadcms/*` peer dependencies.

### Static Generation & Revalidation Overhaul

CMS pages now default to **static site generation (SSG)** instead of server-side rendering. This is a significant performance improvement but requires changes to your project's route files.

The sitemap route switches from `force-dynamic` to hourly ISR. A new `/sys/revalidate` POST route with Bearer auth enables on-demand revalidation with wildcard support. The home page now also generates proper metadata via `generatePageMetadata`.

The revalidation endpoint requires the `SYSTHEMA_API_SECRET` environment variable to be set — it's used as the Bearer token for authentication.

#### Required project changes

**`src/app/(site)/(systhema)/[...path]/page.tsx`** — Remove `export const dynamic = 'auto'` and add a long `revalidate` value:

```diff
- export const dynamic = 'auto'
  export const revalidate = 2629746 // 1 month
```

**`src/app/(site)/(systhema)/page.tsx`** — Add `revalidate`, import `generatePageMetadata`, and export a `generateMetadata` function so the home page gets proper SEO metadata:

```diff
+ import type { Metadata } from 'next'
- import { RootPage } from '@systhemaui/payload/next'
+ import { RootPage, generatePageMetadata } from '@systhemaui/payload/next'

+ export const revalidate = 2629746 // 1 month

  export default async function Page({ params }: { params: Promise<{ path: string }> }) {
    const payload = await getPayload({ config: configPromise })
    return <RootPage params={params} payloadInstance={Promise.resolve(payload)} />
  }

+ export async function generateMetadata(): Promise<Metadata | null> {
+   return generatePageMetadata({
+     params: Promise.resolve({ path: undefined }),
+     payloadInstance: getPayload({ config: configPromise }),
+   })
+ }
```

**`src/app/(site)/(systhema)/(sitemaps)/pages-sitemap.xml/route.ts`** — Replace `force-dynamic` with hourly ISR:

```diff
- export const dynamic = 'force-dynamic'
  export const revalidate = 3600 // Revalidate every hour
```

**`src/app/(site)/(systhema)/sys/[route]/route.ts`** — Add a `POST` handler for the new revalidation API:

```diff
+ export const POST = (
+   request: NextRequest,
+   { params }: { params: Promise<{ route: string }> },
+ ): Promise<Response> =>
+   systhemaApiRoutes(request, { params }, getPayload({ config: configPromise }))
```

**`src/app/robots.ts`** — Disallow the `/api/` route:

```diff
- disallow: ['/sys/', '/admin/'],
+ disallow: ['/api/', '/sys/', '/admin/'],
```

## ✨ New Feature Highlights

### Form Builder & Email System

A full-featured form builder powered by `@payloadcms/plugin-form-builder` is now integrated into Systhema. Create forms directly in the admin panel with a drawer-based UX, unique field IDs, customizable labels, and multi-column layouts via `<FormRow>`. New field types include file upload with split-button design, date/datetime with validation presets, and hidden fields with IP/geo template variables. Forms can also be rendered standalone via the new `<RenderForm>` component.

Hidden fields support template variables like `{{ip}}`, `{{geo}}`, `{{url}}`, `{{timestamp}}`, URL query parameters via `{{param:utm_source}}`, and browser metadata. These are resolved automatically on form load and included in submissions. Existing form handler integrations may need to account for the new hidden field values.

All outgoing emails are centralized through a single processing pipeline with support for file attachments, Lexical rich text editing, inline CSS styles resolved from design tokens, and humanized wildcard field names. A built-in branded email template handles layout and dark mode out of the box, but can be fully replaced with a custom `react-email` template.

#### Configuration

Forms are enabled by default in `withSysthema()`. To send form notification emails, a PayloadCMS email adapter must be configured in the base Payload config. This example uses `@payloadcms/email-resend`, but any PayloadCMS-compatible email adapter works (e.g. `@payloadcms/email-nodemailer`). If no email adapter is configured, forms still work but email features are automatically disabled.

```ts
// payload.config.ts
import { buildConfig } from 'payload'
import { withSysthema, type SysthemaPayloadPluginOptions } from '@systhemaui/payload'
import { resendAdapter } from '@payloadcms/email-resend'

const userSysthemaConfig: SysthemaPayloadPluginOptions = {
  forms: {
    enabled: true,
    redirectRelationships: ['pages'],
    fields: {
      text: true,      // text input with placeholder and max length
      email: true,     // email input with validation
      number: true,    // number input with min/max/step
      textarea: true,  // multi-line text with placeholder and max length
      select: true,    // dropdown select
      radio: true,     // radio button group
      checkbox: true,  // multi-option checkbox group
      date: true,      // date or datetime-local input with validation presets
      file: true,      // file upload with type/size restrictions
      message: true,   // instructional rich text content (not a form input)
      country: false,  // country selector (disabled by default)
      state: false,    // state/province selector (disabled by default)
    },
    formOverrides: {
      // standard PayloadCMS collection overrides
    },
    formSubmissionOverrides: {
      // standard PayloadCMS collection overrides
    },
  },
}

export default buildConfig(
  withSysthema(
    {
      secret: process.env.PAYLOAD_SECRET || '',
      db: yourDatabaseAdapter({ /* ... */ }),
      email: resendAdapter({
        defaultFromAddress: process.env.SYSTHEMA_EMAIL_FROM ?? 'Company <noreply@mail.example.com>',
        defaultFromName: 'Company',
        apiKey: process.env.RESEND_API_KEY || '',
      }),
    },
    userSysthemaConfig,
  ),
)
```

All fields support a `width` percentage for multi-column layouts via `<FormRow>`. The values shown above are the defaults — you only need to specify fields you want to change.

#### Environment variables

| Variable                       | Purpose                                        | Example                                |
| ------------------------------ | ---------------------------------------------- | -------------------------------------- |
| `SYSTHEMA_EMAIL_FROM`          | Default "from" address for all emails          | `"Company <noreply@mail.example.com>"` |
| `SYSTHEMA_EMAIL_REPLY_TO`      | Default reply-to address                       | `support@example.com`                  |
| `SYSTHEMA_EMAIL_ADMIN_ADDRESS` | Fallback recipient when form has no addressee  | `admin@example.com`                    |
| `RESEND_API_KEY`               | Resend API key (or your provider's equivalent) | `re_xxxxxxxxxxxx`                      |
| `NEXT_PUBLIC_SERVER_URL`       | Server URL for logo/attachment resolution      | `https://example.com`                  |

Email settings can also be managed from the admin panel via the **Emails** global (from name, from address, reply-to, logo). The precedence is: form-level settings > Emails global > environment variables.

#### Custom email template with react-email

The built-in template works out of the box, but you can replace it entirely with a `react-email` template. The `emailTemplate` option receives the pre-processed HTML body (with inline styles already applied) and an options object with `logoUrl` and `serverUrl`.

Create your template components:

```ts
// src/emails/EmailLayout.tsx
import { Html, Head, Body, Container, Img } from '@react-email/components'
import { Tailwind } from '@react-email/tailwind'
import { getEmailTailwindConfig } from '@systhemaui/core'

export function EmailLayout({
  children,
  logoUrl,
}: {
  children: React.ReactNode
  logoUrl?: string
}) {
  return (
    <Html>
      <Head />
      <Tailwind config={getEmailTailwindConfig()}>
        <Body>
          <Container style={{ maxWidth: '512px' }}>
            {logoUrl && <Img src={logoUrl} alt="Logo" height={48} />}
            {children}
          </Container>
        </Body>
      </Tailwind>
    </Html>
  )
}
```

```ts
// src/emails/FormNotificationEmail.tsx
import { EmailLayout } from './EmailLayout'

export function FormNotificationEmail({
  content,
  logoUrl,
}: {
  content: string
  logoUrl?: string
  serverUrl?: string
}) {
  return (
    <EmailLayout logoUrl={logoUrl}>
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </EmailLayout>
  )
}
```

```ts
// src/emails/renderer.ts
import { render } from '@react-email/components'
import { FormNotificationEmail } from './FormNotificationEmail'

export async function renderEmailTemplate(
  html: string,
  options: { logoUrl?: string; serverUrl?: string },
): Promise<string> {
  return render(
    FormNotificationEmail({
      content: html,
      logoUrl: options.logoUrl,
      serverUrl: options.serverUrl,
    }),
  )
}
```

Then wire it up in your payload config:

```ts
// payload.config.ts
import { renderEmailTemplate } from './emails/renderer'

const userSysthemaConfig: SysthemaPayloadPluginOptions = {
  emailTemplate: renderEmailTemplate,
}
```

### Upload Optimization

Uploaded images are now automatically optimized before storage. By default, images wider than 2560px are resized, JPEGs are compressed to 60% quality, and opaque PNGs (without actual transparency) are converted to JPEG. A 100MB file size limit is enforced both client-side (with UI feedback) and server-side.

All defaults can be customized via the `uploads` option:

```ts
const userSysthemaConfig: SysthemaPayloadPluginOptions = {
  uploads: {
    maxFileSize: 52428800, // 50MB
    imageOptimization: {
      maxWidth: 1920,          // resize to max 1920px wide (default: 2560)
      jpegQuality: 80,         // JPEG quality 1-100 (default: 60)
      convertPngToJpeg: false, // keep PNGs as-is (default: true)
      pngQuality: 80,          // PNG quality 1-100 (default: 60)
    },
  },
}
```

Set `imageOptimization: false` to disable all processing entirely.

## List of all changes

### 🚀 Features

#### core

- feat(core): add email Tailwind config with typography and color primitives (44bbc16)

#### core,react

- feat(core,react): added width="" attribute to all form field components, so we can correctly render them inside a <FormRow> next to each other, and also added placeholder styles to the select input (3f7ff94)

#### core,react,payload

- feat(core,react,payload): add FileField component with split-button design (1206612)

#### react,next

- feat(react,next): add DateField component and set NumberField inputMode to decimal (baf2457)

#### payload

- !feat(payload): add hidden fields, keyField, and IP/geo template variables to forms (breaking) (ab0f60d)
- feat(payload): add form builder integration with @payloadcms/plugin-form-builder (adc3f35)
- feat(payload): add RenderForm component for standalone form rendering (2ab9017)
- feat(payload): add file upload field to form builder (ff19d81)
- feat(payload): add configurable max file size to form file upload field (998d420)
- feat(payload): add configurable upload optimization and file size validation (7324fad)
- feat(payload): improve form builder admin UX with drawers, icons, and global email settings (689b3f6)
- feat(payload): add date/datetime form field with validation and step presets (07f51e2)
- feat(payload): add branded email template, file attachments, and case-insensitive field resolution (3932159)
- feat(payload): improve form builder UX with clearer labels, unique IDs, and simplified options (367fa68)
- feat(payload): render message field with Lexical richtext, simplify options display, remove date min/max/step (d81f040)
- feat(payload): add programmatic payload type generation script (1c7a459)
- feat(payload): integrate type generation into build and dev scripts (4fd4266)
- feat(payload): add /sys/revalidate route with Bearer auth and wildcard support (d69c0ce)
- feat(payload): hide collections from admin nav based on user roles (7c8a4e1)
- feat(payload): hide globals from admin nav for non-editor users and export hidden helpers (cdb2c13)
- feat(payload): add email lexical editor and inline email styles (998cbc1)
- feat(payload): resolve all typography properties in email inline styles (023e492)
- feat(payload): humanize field names in email wildcard output (b027a93)

#### templates/payload

- feat(templates/payload): add react-email template example with emailTemplate plugin option (9aa4e83)
- feat(payload-template): wire up Systhema email Tailwind config and auto-width logo (885f199)

### ♻️ Refactors

#### payload

- refactor(payload): remove date/datetime min, max, step types until custom implementation (c95089e)

#### general

- refactor: gate email features behind adapter check and refine Emails global (2a4ef81)

### 🐛 Bug fixes

#### core

- fix(core): flatten media-overlay CSS selectors and use pseudo-element for icon mask (a1cf89d)

#### core,payload

- fix(core,payload): append px unit to bare numeric token values in email styles (39af1ea)

#### react

- fix(react): disable Swiper loop when not enough slides for loop mode (c03c716)
- fix(react): stop passing required to individual checkboxes in CheckboxField (a4b7cf4)

#### react,next

- fix(react,next): suppress hydration warnings for config-dependent class names (15fcab1)
- fix(react,next,payload): added missing preload="auto" to \<video> tags (2772883)

#### payload

- fix(payload): removed unnecessary margins and paddings from inline blocks that have a systhema style renderer in them (2c73d64)
- fix(payload): correct FormBlock width alignment detection and hide empty drawer (24f372a)
- fix(payload): remove file size limit constraint on form file upload field (91d5e0b)
- fix(payload): improve video block controls and parallax logic (316641a)
- fix(payload): add playsInline to video elements with poster images (3a79728)
- fix(payload): make sure UploadFeature's jsx converter is also using the Image component from `@systhemaui/next` (c40a5a6)
- fix(payload): coerce non-string defaultValues in buildInitialFormState (0ae021c)
- fix(payload): correct private IP range check for 172.16-31.x.x in geo route (9eb90dd)
- fix(payload): prevent redundant API calls in async hidden field resolution (05cd6a2)
- fix(payload): harden form-upload endpoint with server-side validation (f3ae591)
- fix(payload): replace lite-youtube embed with standard YouTube iframe (d028cd3)
- fix(payload): enforce 16/9 aspect ratio on YouTube iframe embeds (d4d0344)
- fix(payload): disallow api route in robots.txt (89424a9)
- fix(payload): revert admin.hidden access control changes (c89ee81)
- fix(payload): restrict global update access to editor role (b39532b)
- fix(payload): enable SSG for CMS pages in catch-all route (94982eb)
- fix(payload): replace force-dynamic with hourly ISR on sitemap route (08b6a4e)
- fix(payload): fix queryPageByPath cache key and add home page metadata (059a138)
- fix(payload): use empty string check for homepage path in revalidateDelete (3265bdb)
- fix(payload): Next.js version constraints in correct place of the package.json file (f68bccb)
- fix(payload): default video and YouTube block aspect ratio to 16/9 (81f96a4)
- fix(payload): use object form for typescript.declare config option (8cc6ae2)
- fix(payload,next): add viewport meta, robots noindex for admin, and Safari iOS zoom prevention (7331470)
- fix(payload): fix type mismatches exposed by auto-generated payload types (33650c1)
- fix(payload): centralize email processing for all outgoing emails (8a0188b)
- fix(payload): parse defaultFromAddress format and change email logo to upload field (e533bba)
- fix(payload): fix form field ID generation, button borders, and form builder UX (e1d9151)
- fix(payload): update addClassNamesToElement import for richtext-lexical 3.79.0 (2f522f0)
- fix(payload): preserve custom ID field values on page reload (b4a2567)
- fix(payload): resolve key field lock checkbox path for nested fields (3fa20f7)
- fix(payload): use custom select placeholder and default to first option (166f8d8)
- fix(payload): simplify default email template to plain layout (1265fcf)
- fix(payload): remove explicit colors from email templates for dark mode (b58c9ab)
- fix(payload): fetch file content as buffer for email attachments (d5e6a1f)
- fix(payload): use auto width for email logo and restrict upload to jpg/png (aa7daaa)

#### templates/payload

- fix(templates/payload): use valid EditorName value in custom block config (5947098)
- fix(templates/payload): include optimized version of the @tailwindcss/postcss package instead of the default messed up one (1bdb2d2)

#### general

- fix: apply aspect ratio to video blocks with poster images (1da30a3)
- fix: remove useField from drawer component to fix form field creation crash (26717be)

### ⚡ Performance

#### payload

- perf(payload): optimize revalidation hooks and richText field processing (bfc861e)

### 💄 Style

#### payload

- style(payload): format auto-generated payload types (3b6a756)
- style(payload): center email template container and remove side padding (6204f5f)

### 📚 Docs

#### payload

- docs(payload): update form types comment to reference auto-generation (7aab781)

### 🧹 Chores

#### deps

- chore(deps): update dependencies and bump peer dependency ranges (51a5695)
- chore(deps): update dependencies across all packages and templates (f7dbe27)
- chore(deps): updated dependencies on all packages and templates to latest versions (cc6b55e)
- chore(deps): updated next packages to safest 15.4.11 version, still waiting for 16.2.0 (034616d)

#### payload

- chore(payload): sync auto-generated payload types (f5314c3)
- chore(payload): update Next.js version constraint in package.json for compatibility and security (1cb0520)

#### general

- chore: publish internal packages under internal dist-tag (86ce792)
- chore: renamed GITHUB*TOKEN to have a SYSTHEMA* prefix, so i won't make conflicts with other applications, like the `gh` cli (5db8f5a)
