Skip to content

Conversation

@r1tsuu
Copy link
Member

@r1tsuu r1tsuu commented Dec 11, 2024

Adds KV storage support to Payload Local API with an adapter pattern, includes 3 adapters. We'll use it for the Realtime API.
You can access it via payload.kv:

interface KVAdapter { /**  * Clears all entries in the store.  * @returns A promise that resolves once the store is cleared.  */ clear(): Promise<void> /**  * Deletes a value from the store by its key.  * @param key - The key to delete.  * @returns A promise that resolves once the key is deleted.  */ delete(key: string): Promise<void> /**  * Retrieves a value from the store by its key.  * @param key - The key to look up.  * @returns A promise that resolves to the value, or `null` if not found.  */ get(key: string): Promise<KVStoreValue | null> /**  * Checks if a key exists in the store.  * @param key - The key to check.  * @returns A promise that resolves to `true` if the key exists, otherwise `false`.  */ has(key: string): Promise<boolean> /**  * Retrieves all the keys in the store.  * @returns A promise that resolves to an array of keys.  */ keys(): Promise<string[]> /**  * Sets a value in the store with the given key.  * @param key - The key to associate with the value.  * @param value - The value to store.  * @returns A promise that resolves once the value is stored.  */ set(key: string, value: KVStoreValue): Promise<void> }

To configure the adapter you can use the kv property of the root config.

buildConfig({ kv: adapter() })

Database KV adapter (default)

No need to configure, as Payload uses it by default. It generates new collection payload-kv and uses the current database adapter to access it. The collection is hidden in the admin panel and access locked.
If you want to override the generated collection:

import { databaseKVAdapter } from 'payload' buildConfig({ kv: databaseKVAdapter({ kvCollectionOverrides: { slug: 'custom-kv', ...(process.env.DEBUG === 'true' && { admin: { hidden: false }, access: {}, }), }, }), })

In Memory KV adapter

Simple and very fast storage using memory. Don't use it on Vercel / multiple instances or if you need data persistence.

import { inMemoryKVAdapter } from 'payload' buildConfig({ kv: inMemoryKVAdapter(), })

Redis KV Adapter

Uses Redis. Probably the best option as it's faster than database, persistent and works with Vercel / multiple instances, but requires additional setup

pnpm add @payloadcms/kv-redis
import { redisKVAdapter } from '@payloadcms/kv-redis' buildConfig({ kv: redisKVAdapter({ keyPrefix: "custom-prefix:", // defaults to 'payload-kv:' redisURL: "redis://127.0.0.1:6379" // defaults to process.env.REDIS_URL (Vercel generates this variable for you if you connect a project to Redis) }), })
@r1tsuu r1tsuu requested a review from denolfe as a code owner December 11, 2024 21:43
@r1tsuu r1tsuu changed the title feat: KV storage adapters feat: kv storage adapters Dec 11, 2024
@r1tsuu r1tsuu changed the title feat: kv storage adapters feat: add KV storage adapters Dec 11, 2024
@r1tsuu r1tsuu force-pushed the feat/kv branch 2 times, most recently from 084d143 to 5f29665 Compare December 11, 2024 23:12
r1tsuu added a commit that referenced this pull request Dec 12, 2024
…ating existing ones (#9916) ### What? `payload.db.updateOne` (and so `payload.db.upsert`) with drizzle adapters used incoming `where` incorrectly and worked properly only either if you passed `id` or some where query path required table joins (like `where: { 'array.title'`) which is also the reason why `upsert` _worked_ with user preferences specifically, because we need to join the `preferences_rels` table to query by `user.relationTo` and `user.value` Fixes #9915 This was found here - #9913, the database KV adapter uses `upsert` with `where` by unique fields.
@r1tsuu r1tsuu force-pushed the feat/kv branch 2 times, most recently from 16f614e to 2f146f0 Compare December 17, 2024 12:23
kendelljoseph pushed a commit that referenced this pull request Feb 21, 2025
…ating existing ones (#9916) ### What? `payload.db.updateOne` (and so `payload.db.upsert`) with drizzle adapters used incoming `where` incorrectly and worked properly only either if you passed `id` or some where query path required table joins (like `where: { 'array.title'`) which is also the reason why `upsert` _worked_ with user preferences specifically, because we need to join the `preferences_rels` table to query by `user.relationTo` and `user.value` Fixes #9915 This was found here - #9913, the database KV adapter uses `upsert` with `where` by unique fields.
@r1tsuu r1tsuu requested a review from AlessioGr as a code owner October 23, 2025 11:43
@github-actions
Copy link
Contributor

github-actions bot commented Oct 23, 2025

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 755.41 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.22 MB ⚠️ +1.80 KB (+0.1%)
packages/payload/meta_shared.json esbuild/exports/shared.js 162.94 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 279.48 KB ✅ No change
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.15 MB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 14.39 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████ }}}$ 80.2%, 601.84 KB
dist/views/Version ${{\color{Goldenrod}{ █▋ }}}$ 6.6%, 49.56 KB
dist/views/Document ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 15.43 KB
dist/views/List ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 10.46 KB
dist/views/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 8.70 KB
dist/views/API ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 5.98 KB
dist/views/Versions ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 5.96 KB
dist/elements/Nav ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 5.53 KB
dist/views/Account ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 5.32 KB
dist/elements/DocumentHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.81 KB
dist/views/Login ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 4.39 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 3.69 KB
dist/views/ForgotPassword ${{\color{Goldenrod}{ }}}$ 0.4%, 3.09 KB
dist/layouts/Root ${{\color{Goldenrod}{ }}}$ 0.4%, 2.91 KB
dist/templates/Default ${{\color{Goldenrod}{ }}}$ 0.4%, 2.83 KB
dist/views/CreateFirstUser ${{\color{Goldenrod}{ }}}$ 0.4%, 2.76 KB
dist/views/BrowseByFolder ${{\color{Goldenrod}{ }}}$ 0.3%, 2.60 KB
dist/views/CollectionFolders ${{\color{Goldenrod}{ }}}$ 0.3%, 2.46 KB
dist/views/ResetPassword ${{\color{Goldenrod}{ }}}$ 0.3%, 2.41 KB
dist/views/Logout ${{\color{Goldenrod}{ }}}$ 0.3%, 1.91 KB
(other) ${{\color{Goldenrod}{ ████▉ }}}$ 19.8%, 148.89 KB

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.1%, 841.07 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 41.94 KB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.0%, 37.03 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.26 KB
dist/queues/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 12.04 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.91 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.57 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 11.54 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.21 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.31 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.07 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.88 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.74 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.56 KB
dist/collections/endpoints ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.94 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 6.27 KB
dist/auth/strategies ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 5.50 KB
dist/config/sanitize.js ${{\color{Goldenrod}{ }}}$ 0.4%, 5.44 KB
dist/auth/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 5.41 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ }}}$ 0.4%, 5.31 KB
(other) ${{\color{Goldenrod}{ ███████▋ }}}$ 30.9%, 376.17 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.5%, 126.93 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▌ }}}$ 6.4%, 10.21 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▍ }}}$ 1.6%, 2.48 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.28 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 943 B
dist/folders/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 916 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.4%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.4%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.4%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.3%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.3%, 413 B
dist/utilities/formatLabels.js ${{\color{Goldenrod}{ }}}$ 0.2%, 380 B
dist/utilities/appendUploadSelectFields.js ${{\color{Goldenrod}{ }}}$ 0.2%, 360 B
dist/utilities/transformColumnPreferences.js ${{\color{Goldenrod}{ }}}$ 0.2%, 348 B
(other) ${{\color{Goldenrod}{ █████▏ }}}$ 20.5%, 32.70 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▏ }}}$ 12.6%, 34.80 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.5%, 31.68 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▏ }}}$ 8.8%, 24.36 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▏ }}}$ 8.6%, 23.70 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▋ }}}$ 6.9%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.5%, 18.04 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.4%, 17.75 KB
dist/features/upload ${{\color{Goldenrod}{ █▎ }}}$ 5.0%, 13.69 KB
dist/features/textState ${{\color{Goldenrod}{ █ }}}$ 4.0%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 8.96 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.08 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 7.39 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 7.12 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.04 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.17 KB
dist/lexical/theme ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.62 KB
dist/features/indent ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.50 KB
(other) ${{\color{Goldenrod}{ █████████████████████▊ }}}$ 87.4%, 241.42 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████▌ }}}$ 50.3%, 572.86 KB
dist/elements/FolderView ${{\color{Goldenrod}{ ▋ }}}$ 2.6%, 29.17 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 26.93 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 16.70 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 16.04 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 15.40 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.29 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 15.12 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 12.87 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 11.49 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 8.74 KB
dist/providers/Folders ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.48 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.46 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 8.38 KB
dist/elements/ListHeader ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.83 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.81 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 7.56 KB
dist/views/CollectionFolder ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.36 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.96 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 6.91 KB
(other) ${{\color{Goldenrod}{ ████████████▍ }}}$ 49.7%, 566.52 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ █████▋ }}}$ 22.6%, 3.12 KB
../../node_modules ${{\color{Goldenrod}{ ████▊ }}}$ 19.2%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▊ }}}$ 11.0%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ ██▍ }}}$ 9.6%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 862 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █▍ }}}$ 5.9%, 814 B
dist/utilities/api.js ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 756 B
dist/elements/Translation ${{\color{Goldenrod}{ ▉ }}}$ 3.6%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 440 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 339 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 168 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 159 B
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 147 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 146 B
dist/utilities/hasSavePermission.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 136 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 129 B
dist/utilities/findLocaleFromCode.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 84 B
dist/utilities/sanitizeID.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 77 B
dist/utilities/isEditing.js ${{\color{Goldenrod}{ }}}$ 0.4%, 59 B
(other) ${{\color{Goldenrod}{ ███████████████████▎ }}}$ 77.4%, 10.68 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.
@DanRibbens DanRibbens requested a review from Copilot October 23, 2025 16:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds a Key-Value (KV) storage capability to Payload using an adapter pattern, supporting database, in-memory, and Redis backends for data storage needs.

  • Introduces KVAdapter interface and three implementations: database, in-memory, and Redis adapters
  • Adds configuration support via the kv property in root config
  • Includes comprehensive test coverage and GitHub Actions CI integration for Redis

Reviewed Changes

Copilot reviewed 28 out of 31 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/payload/src/kv/index.ts Defines the KVAdapter interface and type definitions
packages/payload/src/kv/adapters/DatabaseKVAdapter.ts Implements KV storage using Payload's database
packages/payload/src/kv/adapters/InMemoryKVAdapter.ts Implements in-memory KV storage
packages/kv-redis/src/index.ts Implements Redis-based KV storage adapter
packages/payload/src/config/defaults.ts Adds default KV adapter configuration
packages/payload/src/index.ts Exports KV types and initializes KV adapter
test/kv/int.spec.ts Integration tests for all KV adapters
.github/workflows/main.yml Adds Redis service to CI pipeline
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/payload/src/config/types.ts:1

  • The function name in the documentation example is createDatabaseKVAdapter, but the actual exported function is databaseKVAdapter.
import type { 

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link
Contributor

@DanRibbens DanRibbens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need one more approval for the new package from @denolfe

Copy link
Member

@denolfe denolfe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

We'll want to get an initial version published, so it can go out in the next release.

@r1tsuu r1tsuu merged commit ffb9a2e into main Oct 30, 2025
91 checks passed
@r1tsuu r1tsuu deleted the feat/kv branch October 30, 2025 15:05
@github-actions
Copy link
Contributor

🚀 This is included in version v3.62.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment