Payload CMS 3.89.0: критические исправления Next.js, безопаснее Jobs и большие загрузки без съедания RAM

15 сентября 2026 г.Александр Андреев

10 сентября вышел Payload CMS 3.89.0. По номеру версии это обычный минорный релиз ветки 3.x, но пропускать его не стоит: внутри обновление Next.js с исправлением двух критических RCE, изменение доступа к Jobs, оптимизация загрузки больших файлов и несколько неприятных исправлений на уровне PostgreSQL, MongoDB и админки.

Разберём, что изменилось и на что обратить внимание перед обновлением.

Next.js обновлён до 16.3.3 — и это не просто dependency bump

В Payload 3.89.0 Next.js и связанные @next/* пакеты в core и официальных шаблонах обновлены с 16.3.0 до 16.3.3.

Причина — security release Next.js от 25 августа. В 16.3.3 закрыты две уязвимости критического уровня.

Первая позволяет получить unauthenticated remote code execution через Image Optimization API при обработке специально подготовленного AVIF-файла. Проблема находится в используемой для обработки изображений библиотеке libheif.

Вторая тоже позволяет выполнить код без аутентификации, но касается Next.js-серверов под Windows, одновременно использующих Pages Router и App Router без Cache Components. Linux и macOS этой конкретной проблеме не подвержены.

Для Payload это особенно актуально: начиная с третьей версии CMS фактически живёт внутри Next.js-приложения.

Если проект использует Payload 3.x, стоит проверить не только версию payload, но и прямую зависимость next в своём package.json. Обновление пакетов Payload само по себе не гарантирует, что закреплённая в приложении версия Next.js автоматически станет 16.3.3.

Jobs теперь закрыты по умолчанию

Это единственное изменение в 3.89.0, которое авторы прямо пометили как breaking change.

Внутренняя коллекция payload-jobs раньше могла быть доступна через обычные CRUD-операции. Теперь generic-доступ к ней по REST, GraphQL и Local API с overrideAccess: false запрещён по умолчанию.

Изменение логичное. В Job можно хранить входные данные, результаты выполнения, ошибки и логи. Это не та коллекция, которую разумно случайно выставлять наружу.

При этом нормальная работа очереди не меняется. Специализированные методы вроде payload.jobs.queue(), payload.jobs.run() и payload.jobs.cancel() продолжают использовать отдельные правила доступа Jobs Config.

Проблема возникнет у проектов, которые напрямую читали или изменяли payload-jobs.

Например, если вы выводили очередь Jobs в собственной админке через обычный Collection API, после обновления такой код может получить отказ в доступе.

Разрешения теперь нужно задавать явно через jobsCollectionOverrides. Например, только для чтения администраторами:

import { buildConfig } from 'payload'

export default buildConfig({
  jobs: {
    jobsCollectionOverrides: ({ defaultJobsCollection }) => ({
      ...defaultJobsCollection,

      access: {
        ...defaultJobsCollection.access,
        read: ({ req }) => Boolean(
          req.user?.roles?.includes('admin')
        ),
      },

      admin: {
        ...defaultJobsCollection.admin,
        hidden: false,
      },
    }),
  },
})

Это хороший пример изменения, которое формально ломает обратную совместимость, но делает поведение Payload безопаснее по умолчанию. В актуальной документации Payload generic create/read/update/delete для payload-jobs уже описаны именно как закрытые по умолчанию.

Большие файлы больше не должны съедать всю память сервера

Ещё одно важное изменение касается client uploads.

При прямой загрузке файла из браузера в storage сам файл обходит Payload-сервер. Но после загрузки Payload всё равно может понадобиться прочитать его — например, чтобы определить размеры изображения, проверить MIME type или сохранить локальную копию.

До 3.89.0 сервер в некоторых таких сценариях скачивал весь файл целиком в память.

Для фотографии на несколько мегабайт это незаметно. Для видео или другого объекта размером в несколько гигабайт — уже потенциальный OOM.

Особенно проблема проявилась после появления поддержки Azure client uploads размером более 5 ГБ. Один достаточно большой файл мог оказаться больше всей доступной серверу RAM.

Теперь Payload сначала определяет, сколько данных ему действительно нужно. Если хватает переданных клиентом метаданных — файл вообще не скачивается. Для определения размеров изображения может быть прочитан только небольшой диапазон байтов. Если нужен весь файл, Payload стримит его во временный файл на диск вместо огромного буфера в оперативной памяти.

Для проектов с S3/Azure-подобным storage и крупными пользовательскими загрузками это, пожалуй, одно из самых полезных изменений релиза.

PostgreSQL: Payload начал предупреждать о слишком длинных именах

Есть хороший фикс для пользователей Drizzle/PostgreSQL.

PostgreSQL ограничивает длину identifier 63 символами. Payload генерирует имена колонок, индексов и foreign keys из путей полей, поэтому при сильно вложенной структуре схемы легко получить имя длиннее лимита.

PostgreSQL молча его обрезает. В результате реальная схема БД и схема, которую ожидает Payload, могут начать расходиться. Один из симптомов — повторяющийся при каждом запуске dev-режима вопрос вида is <column> created or renamed?.

В 3.89.0 Payload проверяет такие идентификаторы при инициализации PostgreSQL.

Если имя просто длиннее 63 символов, в development появится предупреждение. Если после обрезания два разных идентификатора превращаются в одно и то же имя, Payload выбросит ошибку — такую схему PostgreSQL всё равно не сможет корректно создать.

Это небольшое изменение, но диагностировать подобные рассинхронизации раньше было крайне неприятно.

Исправлена потенциальная потеря hasMany-данных в PostgreSQL

Другой фикс Drizzle уже касается непосредственно данных.

При низкоуровневом db.updateOne частичное обновление документа могло затронуть hasMany select, даже если это поле вообще отсутствовало в update.

При определённых условиях Payload создавал для пропущенного поля пустую служебную структуру, после чего воспринимал её как команду удалить существующие связи.

Теперь отсутствие поля означает «не изменять существующее значение». Явный select: [], как и раньше, очищает связи.

Если проект использует низкоуровневые database operations поверх PostgreSQL, обновиться особенно разумно.

MongoDB избавили от лишних aggregation

Для MongoDB исправили менее опасную, но полезную проблему.

find, findOne и queryDrafts могли переходить на aggregation pipeline только потому, что в коллекции присутствует join field — даже когда конкретный запрос этот join не выбирает и реальных $lookup в pipeline нет.

В таком случае Payload уходил с обычного Model.findOne / Model.paginate на Model.aggregate без какой-либо пользы.

Теперь при пустом join pipeline используется обычный путь выполнения запроса. Особенно это может быть заметно на коллекциях с joins, где API активно использует select для выборки ограниченного набора полей.

Админка стала устойчивее

В релиз вошла целая группа менее масштабных UI-фиксов.

Например, быстрое изменение поля, используемого как useAsTitle, могло вызвать каскад React-обновлений и закончиться ошибкой Maximum update depth exceeded. Теперь частые изменения объединяются, а заголовок обновляется не чаще одного раза за animation frame.

Исправлен и неприятный баг Versions: если между версиями документа заменялся Block, просмотр diff мог падать с ошибкой No client field found for .... Особенно это проявлялось с историческими полями внутри row, collapsible или tabs.

Rich Text внутри tabs теперь корректно становится read-only для удалённых и заблокированных документов. Раньше остальные поля могли быть заблокированы, а Lexical внутри вкладки оставался редактируемым.

Кроме того, компонент Banner получил новый тип warning. Заодно разработчики починили давно неработавшие interactive states баннеров и поправили контрастность состояний hover/active.

Появилась документация по UI-компонентам Payload

Ещё одно полезное изменение не в runtime, а в документации.

Команда Payload наконец добавила отдельный раздел с компонентами @payloadcms/ui: примеры использования, props, рекомендации и accessibility notes. Первая версия документации охватывает наиболее часто используемые визуальные компоненты.

Для разработчиков кастомной админки это полезнее, чем выглядит на первый взгляд. Раньше для многих компонентов проще было идти в исходники Payload и смотреть реальное использование там.

Стоит ли обновляться до 3.89.0?

Да.

Payload 3.89.0 — не релиз с большой пользовательской фичей, но это хороший эксплуатационный апдейт. Здесь закрываются проблемы сразу на нескольких уровнях: security в Next.js, доступ к внутренним Jobs, потребление RAM при больших upload, целостность данных в Drizzle и несколько падений админки.

Перед обновлением стоит проверить только один действительно важный момент — использует ли проект прямой CRUD-доступ к payload-jobs.

Сам Payload и все установленные @payloadcms/* пакеты должны иметь одинаковую точную версию. Это официальная рекомендация проекта: смешивание разных версий Payload-пакетов может приводить к трудно диагностируемым runtime-ошибкам.

После обновления я бы проверил: версию next не ниже 16.3.3 для ветки Next 16.3; одинаковую версию всех payload / @payloadcms/*; работу Jobs, если есть кастомный интерфейс или прямой API к payload-jobs; загрузку файлов; просмотр Version Diff; и миграции/push, если используется PostgreSQL.

Полный changelog Payload CMS 3.89.0 опубликован на GitHub.

Назад в блог
Payload CMS 3.89.0: критические исправления Next.js, безопаснее Jobs и большие загрузки без съедания RAM | Payload по-русски