diff --git a/package-lock.json b/package-lock.json
index b5dec3f8..bfdf7c0b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11559,9 +11559,9 @@
}
},
"node_modules/vite-plugin-framer": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/vite-plugin-framer/-/vite-plugin-framer-1.0.6.tgz",
- "integrity": "sha512-Y6lI/TPdvJ4fBBgq5O/9s3VpIHIO+9fAUl3K/GdGUm3og8Xgrm0xQfLSIeieI7PYSLuQM/fH5WJ2KDfHCI6Z4Q==",
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/vite-plugin-framer/-/vite-plugin-framer-1.0.7.tgz",
+ "integrity": "sha512-aUf7p6mbGOR1DlTfejSrT4yKmSmg0yr3IIAFi/V9SwWJSDI5GmVQUVStUQORFo+q9yxtfZh7kVKS4NdslxHnSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -14899,7 +14899,7 @@
"name": "cms-starter",
"version": "0.0.0",
"dependencies": {
- "framer-plugin": "^3.0.0",
+ "framer-plugin": "^3.1.0-beta.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
@@ -14919,7 +14919,7 @@
"typescript": "^5.7.3",
"typescript-eslint": "^8.25.0",
"vite": "^6.2.4",
- "vite-plugin-framer": "^1.0.6",
+ "vite-plugin-framer": "^1.0.7",
"vite-plugin-mkcert": "^1.17.7"
}
},
@@ -15114,9 +15114,9 @@
}
},
"starters/cms/node_modules/framer-plugin": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.0.0.tgz",
- "integrity": "sha512-JRHSSa/uf+LTPRfk1CQfzpU2Al0/gW5OP5CTlpotHUqyak3gGyjmVw9tmr9xE1tLXqPpkHl4mN5QEHwHPNzp4g==",
+ "version": "3.1.0-beta.3",
+ "resolved": "https://registry.npmjs.org/framer-plugin/-/framer-plugin-3.1.0-beta.3.tgz",
+ "integrity": "sha512-JIxc3pNNP4JLQc2bXRwI9nISUBgxEyDSeDVvHK/FVtom51mSACNUD7xmL8pJ4lXRkUFSge3+sbo6zCzARY2Gfg==",
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
diff --git a/starters/cms/package.json b/starters/cms/package.json
index f82f216b..a6091c0a 100644
--- a/starters/cms/package.json
+++ b/starters/cms/package.json
@@ -14,7 +14,7 @@
"typecheck": "tsc"
},
"dependencies": {
- "framer-plugin": "^3.0.0",
+ "framer-plugin": "^3.1.0-beta.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
@@ -34,7 +34,7 @@
"typescript": "^5.7.3",
"typescript-eslint": "^8.25.0",
"vite": "^6.2.4",
- "vite-plugin-framer": "^1.0.6",
+ "vite-plugin-framer": "^1.0.7",
"vite-plugin-mkcert": "^1.17.7"
}
}
diff --git a/starters/cms/src/FieldMapping.tsx b/starters/cms/src/FieldMapping.tsx
index ec1a9597..c001c893 100644
--- a/starters/cms/src/FieldMapping.tsx
+++ b/starters/cms/src/FieldMapping.tsx
@@ -1,6 +1,6 @@
-import { type ManagedCollectionFieldInput, framer, type ManagedCollection } from "framer-plugin"
+import { type ManagedCollectionFieldInput, framer, type ManagedCollection, useIsAllowedTo } from "framer-plugin"
import { useEffect, useState } from "react"
-import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection } from "./data"
+import { type DataSource, dataSourceOptions, mergeFieldsWithExistingFields, syncCollection, syncMethods } from "./data"
interface FieldMappingRowProps {
field: ManagedCollectionFieldInput
@@ -8,6 +8,7 @@ interface FieldMappingRowProps {
isIgnored: boolean
onToggleDisabled: (fieldId: string) => void
onNameChange: (fieldId: string, name: string) => void
+ disabled: boolean
}
function FieldMappingRow({
@@ -16,6 +17,7 @@ function FieldMappingRow({
isIgnored,
onToggleDisabled,
onNameChange,
+ disabled,
}: FieldMappingRowProps) {
return (
<>
@@ -23,6 +25,7 @@ function FieldMappingRow({
type="button"
className={`source-field ${isIgnored ? "ignored" : ""}`}
onClick={() => onToggleDisabled(field.id)}
+ disabled={disabled}
>
{originalFieldName ?? field.id}
@@ -39,7 +42,7 @@ function FieldMappingRow({
onNameChange(field.id, event.target.value)}
@@ -135,6 +138,8 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
})
}
+ const isAllowedToManage = useIsAllowedTo("ManagedCollection.setFields", ...syncMethods)
+
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault()
@@ -149,8 +154,11 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
try {
setStatus("syncing-collection")
- const fieldsToSync = fields.filter(field => !ignoredFieldIds.has(field.id))
+ const fieldsToSync = fields
+ .filter(field => !ignoredFieldIds.has(field.id))
+ .map(field => ({ ...field, name: field.name.trim() || field.id }))
+ await collection.setFields(fieldsToSync)
await syncCollection(collection, dataSource, fieldsToSync, selectedSlugField)
await framer.closePlugin("Synchronization successful", { variant: "success" })
} catch (error) {
@@ -188,6 +196,7 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
if (!selectedField) return
setSelectedSlugField(selectedField)
}}
+ disabled={!isAllowedToManage}
>
{possibleSlugFields.map(possibleSlugField => {
return (
@@ -210,13 +219,17 @@ export function FieldMapping({ collection, dataSource, initialSlugFieldId }: Fie
isIgnored={ignoredFieldIds.has(field.id)}
onToggleDisabled={toggleFieldDisabledState}
onNameChange={changeFieldName}
+ disabled={!isAllowedToManage}
/>
))}
diff --git a/starters/cms/src/data.ts b/starters/cms/src/data.ts
index 24daa29e..8d806931 100644
--- a/starters/cms/src/data.ts
+++ b/starters/cms/src/data.ts
@@ -4,6 +4,7 @@ import {
framer,
type ManagedCollection,
type ManagedCollectionItemInput,
+ type ProtectedMethod,
} from "framer-plugin"
export const PLUGIN_KEYS = {
@@ -101,11 +102,6 @@ export async function syncCollection(
fields: readonly ManagedCollectionFieldInput[],
slugField: ManagedCollectionFieldInput
) {
- const sanitizedFields = fields.map(field => ({
- ...field,
- name: field.name.trim() || field.id,
- }))
-
const items: ManagedCollectionItemInput[] = []
const unsyncedItems = new Set(await collection.getItemIds())
@@ -123,7 +119,7 @@ export async function syncCollection(
const fieldData: FieldDataInput = {}
for (const [fieldName, value] of Object.entries(item)) {
- const field = sanitizedFields.find(field => field.id === fieldName)
+ const field = fields.find(field => field.id === fieldName)
// Field is in the data but skipped based on selected fields.
if (!field) continue
@@ -141,7 +137,6 @@ export async function syncCollection(
})
}
- await collection.setFields(sanitizedFields)
await collection.removeItems(Array.from(unsyncedItems))
await collection.addItems(items)
@@ -149,6 +144,12 @@ export async function syncCollection(
await collection.setPluginData(PLUGIN_KEYS.SLUG_FIELD_ID, slugField.id)
}
+export const syncMethods = [
+ "ManagedCollection.removeItems",
+ "ManagedCollection.addItems",
+ "ManagedCollection.setPluginData",
+] as const satisfies ProtectedMethod[]
+
export async function syncExistingCollection(
collection: ManagedCollection,
previousDataSourceId: string | null,
@@ -162,6 +163,10 @@ export async function syncExistingCollection(
return { didSync: false }
}
+ if (!framer.isAllowedTo(...syncMethods)) {
+ return { didSync: false }
+ }
+
try {
const dataSource = await getDataSource(previousDataSourceId)
const existingFields = await collection.getFields()