{"_id":"@ace-ams/payload-syncer","name":"@ace-ams/payload-syncer","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.2":{"name":"@ace-ams/payload-syncer","version":"0.1.2","description":"A Payload CMS plugin to sync database content and S3 media across environments","license":"MIT","type":"module","repository":{"type":"git","url":"git+https://github.com/ace-ams/payload-plugin-syncer.git"},"bugs":{"url":"https://github.com/ace-ams/payload-plugin-syncer/issues"},"homepage":"https://github.com/ace-ams/payload-plugin-syncer#readme","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts","default":"./dist/index.js"},"./client":{"import":"./dist/exports/client.js","types":"./dist/exports/client.d.ts","default":"./dist/exports/client.js"}},"main":"./dist/index.js","types":"./dist/index.d.ts","scripts":{"build":"npm run copyfiles && npm run build:types && npm run build:swc","prepublishOnly":"npm run clean && npm run build","build:swc":"swc ./src -d ./dist --config-file .swcrc --strip-leading-paths","build:types":"tsc --outDir dist --rootDir ./src","clean":"rimraf {dist,*.tsbuildinfo}","copyfiles":"copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/","dev":"next dev dev --turbo --no-server-fast-refresh","dev:generate-importmap":"npm run dev:payload generate:importmap","dev:generate-types":"npm run dev:payload generate:types","dev:payload":"cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload","generate:importmap":"npm run dev:generate-importmap","generate:types":"npm run dev:generate-types","lint":"eslint","lint:fix":"eslint ./src --fix","test":"npm run test:int && npm run test:e2e","test:e2e":"playwright test","test:int":"vitest"},"devDependencies":{"@eslint/eslintrc":"^3.2.0","@payloadcms/db-mongodb":"^3.37.0","@payloadcms/db-postgres":"^3.37.0","@payloadcms/db-sqlite":"^3.37.0","@payloadcms/eslint-config":"^3.9.0","@payloadcms/next":"^3.37.0","@payloadcms/richtext-lexical":"^3.37.0","@playwright/test":"^1.58.2","@swc-node/register":"^1.10.9","@swc/cli":"^0.6.0","@types/node":"^22.19.9","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","copyfiles":"^2.4.1","cross-env":"^7.0.3","eslint":"^9.23.0","eslint-config-next":"^16.2.1","graphql":"^16.8.1","mongodb-memory-server":"^10.1.4","next":"^16.2.1","open":"^10.1.0","payload":"^3.37.0","prettier":"^3.4.2","qs-esm":"^8.0.1","react":"^19.2.4","react-dom":"^19.2.4","rimraf":"^3.0.2","sharp":"^0.34.2","sort-package-json":"^2.10.0","typescript":"^5.7.3","vite-tsconfig-paths":"^6.0.5","vitest":"^4.0.18"},"peerDependencies":{"@payloadcms/ui":">=3.0.0","mongodb":">=5.0.0","payload":"^3.37.0"},"engines":{"node":"^24.0.0"},"publishConfig":{"access":"public"},"registry":"https://registry.npmjs.org/","dependencies":{"@aws-sdk/client-s3":"^3.1022.0"},"gitHead":"321b98a0030eb7460126bd6c1b5e99cce2b9fa7d","_id":"@ace-ams/payload-syncer@0.1.2","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-WtHMaUBxch8sKhWi4nUBlOXDSw14AdmQ1m8V6tJ1DuCZcGFE6fyNBuN62D3QuveAVgzpNbWGrhdKx0R7o1sLUQ==","shasum":"904309b6940815e6685d34dca5c73a1e4c252ba1","tarball":"https://registry.npmjs.org/@ace-ams/payload-syncer/-/payload-syncer-0.1.2.tgz","fileCount":17,"unpackedSize":48837,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ace-ams%2fpayload-syncer@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGsLYtpLuzUCzQ0RUEXxksCgXuv2WjtiVmCl6YNKKrZAAiEAt0kNBB/TBoGjkhCfEf8aZ1OrlNhPkGYBYHJih4szlB4="}]},"_npmUser":{"name":"codename-niels","email":"nielsjlwijers@gmail.com"},"directories":{},"maintainers":[{"name":"rens-born05","email":"rens@born05.com"},{"name":"tazio","email":"personal@tazio.nl"},{"name":"codename-niels","email":"nielsjlwijers@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/payload-syncer_0.1.2_1776175335861_0.13426039818661062"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-14T14:02:15.739Z","0.1.2":"2026-04-14T14:02:16.018Z","modified":"2026-04-14T14:02:16.505Z"},"maintainers":[{"name":"rens-born05","email":"rens@born05.com"},{"name":"tazio","email":"personal@tazio.nl"},{"name":"codename-niels","email":"nielsjlwijers@gmail.com"}],"description":"A Payload CMS plugin to sync database content and S3 media across environments","homepage":"https://github.com/ace-ams/payload-plugin-syncer#readme","repository":{"type":"git","url":"git+https://github.com/ace-ams/payload-plugin-syncer.git"},"bugs":{"url":"https://github.com/ace-ams/payload-plugin-syncer/issues"},"license":"MIT","readme":"# Payload Syncer\n\nA Payload CMS plugin that lets admins sync database content and S3 media from one environment (production or acceptance) down to a lower environment.\n\nA sync button will appear in the admin dashboard for users that pass the access check.\n\n---\n\n## Installation\n\n```sh\nnpm i @ace-ams/payload-syncer\n```\n\n---\n\n## Setup\n\nAdd the plugin to your `payload.config.ts`:\n\n```ts\nimport { enviromentSyncing } from '@ace-ams/payload-syncer'\n\nexport default buildConfig({\n  plugins: [\n    enviromentSyncing({\n      currentEnv: process.env.APP_ENV as 'development' | 'acceptance' | 'production',\n      databaseUrls: {\n        development: process.env.DATABASE_URL!,\n        acceptance: process.env.DATABASE_URL_ACC!,\n        production: process.env.DATABASE_URL_PROD!,\n      },\n      exceptCollections: ['users'],\n      s3: {\n        bucket: process.env.S3_BUCKET!,\n        endpoint: process.env.S3_ENDPOINT,\n        region: process.env.S3_REGION,\n        accessKeyId: process.env.S3_ACCESS_KEY_ID!,\n        secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,\n      },\n    }),\n  ],\n})\n```\n\n### Options\n\n| Option | Type | Required | Description |\n|---|---|---|---|\n| `databaseUrls` | `Record<Environment, string>` | Yes | MongoDB connection strings per environment. |\n| `currentEnv` | `'development' \\| 'acceptance' \\| 'production'` | No | The environment this deployment is running in. Falls back to `APP_ENV`, then `NODE_ENV`. |\n| `access` | `(req: PayloadRequest) => boolean \\| Promise<boolean>` | No | Custom access control for the sync endpoint. Replaces the default `adminRole` check when provided. |\n| `adminRole` | `{ field: string, value: string }` | No | The field and value that identifies an admin user. Defaults to `{ field: 'role', value: 'admin' }`. Ignored when `access` is set. |\n| `exceptCollections` | `CollectionSlug[]` | No | Collections to skip during sync. Always include sensitive collections such as `'users'`. |\n| `enviromentLabels` | `Partial<Record<Environment, string>>` | No | Display labels shown in the sync button. Defaults to `DEV`, `ACC`, `PROD`. |\n| `mediaCollection` | `string` | No | The slug of your upload collection. Defaults to `'media'`. |\n| `s3` | `S3Config` | No | S3 credentials and bucket. Required if you store media in S3. |\n| `disabled` | `boolean` | No | Disable the plugin without uninstalling it. |\n\n---\n\n## Access control\n\nBy default the sync button and endpoint are restricted to users whose `role` field equals `'admin'`. There are two ways to configure this.\n\n### Option A — `adminRole` (simple field/value check)\n\nUse this when your users collection has a single role field:\n\n```ts\nenviromentSyncing({\n  adminRole: { field: 'permissions', value: 'superadmin' },\n  // ...\n})\n```\n\nAdd the corresponding field to your users collection:\n\n```ts\n{\n  slug: 'users',\n  auth: true,\n  fields: [\n    {\n      name: 'role',\n      type: 'select',\n      defaultValue: 'user',\n      required: true,\n      options: [\n        { label: 'Admin', value: 'admin' },\n        { label: 'User', value: 'user' },\n      ],\n    },\n  ],\n}\n```\n\n### Option B — `access` function (custom logic)\n\nUse this for multi-role setups, JWT claims, or any custom authorization logic. When `access` is provided it fully replaces the `adminRole` check.\n\n```ts\nenviromentSyncing({\n  access: (req) => req.user?.role === 'superadmin',\n  // ...\n})\n```\n\nThe function receives the full `PayloadRequest` and must return `true` (or a promise resolving to `true`) to allow the sync.\n\n> **Note:** The access check is enforced server-side on the `/sync` endpoint. The sync button also hides itself client-side for non-admin users, but the server-side check is the authoritative gate.\n\n---\n\n## Environment setup\n\n1. Set `APP_ENV` (or `NEXT_PUBLIC_APP_ENV` for client-side env detection) to `development`, `acceptance`, or `production` on each server.\n2. Set `DATABASE_URL`, `DATABASE_URL_ACC`, and `DATABASE_URL_PROD` to the MongoDB connection strings for each environment. Use `mongodb+srv://` URIs with TLS enabled for remote databases.\n3. Set `S3_BUCKET`, `S3_ENDPOINT`, `S3_REGION`, `S3_ACCESS_KEY_ID`, and `S3_SECRET_ACCESS_KEY` on every environment that needs to sync media.\n4. Ensure the user performing the sync has the correct admin role value in the database.\n\n---\n\n## How it works\n\n### What gets synced\n\n- **Database** — all MongoDB collections except `payload-*` system collections and any slugs listed in `exceptCollections`.\n- **Media** — if `s3` is configured, objects are copied between environment prefixes inside the same bucket (e.g. `production/` → `development/`). The `prefix` field on all media documents is updated accordingly.\n\n### What is never synced\n\n- Collections in `exceptCollections` are skipped entirely. Always add sensitive collections such as `users` or `admins` to this list.\n- Internal Payload collections prefixed with `payload-` (migrations, preferences, jobs, etc.) are always skipped.\n\n### Safety behaviours\n\n| Behaviour | Details |\n|---|---|\n| **Production writes blocked** | The sync endpoint only runs when the target environment is `development` or `acceptance`. Syncing to `production` is never allowed. |\n| **Same-environment guard** | Syncing an environment to itself is rejected with a `400` error. |\n| **Concurrency lock** | Only one sync can run at a time. Concurrent requests receive a `409` response until the active sync completes. |\n| **Batched copy** | Collections are read via a cursor and inserted in batches of 500 documents, preventing out-of-memory errors on large datasets. |\n\n---\n\n## Security notes\n\n- **Database connection strings** contain credentials — always load them from environment variables, never hardcode them.\n- **Use TLS** on all remote MongoDB connections (`mongodb+srv://` or `tls=true` in the connection string).\n- **`exceptCollections`** is your responsibility — always exclude collections that contain user credentials, sessions, or other sensitive data.\n","readmeFilename":"README.md","_rev":"1-d27707db2b6556580901a825aa9f9bf5"}