From 0b17a0ab4e5e0c3f3721aaa10abedc9bd1691fb6 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Mon, 16 Mar 2026 15:26:47 -0400 Subject: [PATCH 1/4] Get updated version of API --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1140d69..bc81fbc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@types/node": "^22.13.5", "@types/prismjs": "^1.26.3", "@vitejs/plugin-react": "^4.0.0", - "@webflow/designer-extension-typings": "^2.0.25", + "@webflow/designer-extension-typings": "^2.0.29", "@webflow/webflow-cli": "^1.8.6", "@xatom/wf-app-hot-reload": "^1.0.5", "concurrently": "^6.3.0", @@ -4450,9 +4450,9 @@ "dev": true }, "node_modules/@webflow/designer-extension-typings": { - "version": "2.0.25", - "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.25.tgz", - "integrity": "sha512-nehI7TnpWzwdTAa0ZceV/7TEeBkjN8JMARHQAyDZXQQm6HmQJaaJaXsUYm4WNpuFjNMMxMM21MH0muZ4tJDUZQ==", + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@webflow/designer-extension-typings/-/designer-extension-typings-2.0.29.tgz", + "integrity": "sha512-gq3nGE4yv3W/kHi8/9mpw5OFJT7w5GV5E/ycwQ3ltViQzCzzDJC5ee4iRRg+Bg+D5PT1Z+MOzGkrTVx7XGeuwg==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 43860f1..cc93b01 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@types/node": "^22.13.5", "@types/prismjs": "^1.26.3", "@vitejs/plugin-react": "^4.0.0", - "@webflow/designer-extension-typings": "^2.0.25", + "@webflow/designer-extension-typings": "^2.0.29", "@webflow/webflow-cli": "^1.8.6", "@xatom/wf-app-hot-reload": "^1.0.5", "concurrently": "^6.3.0", From 1a4153491b67aff5957d41891522c663f08e8005 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Mon, 16 Mar 2026 15:27:07 -0400 Subject: [PATCH 2/4] Add example of creating a component without root --- src/components/PermissionsMap.tsx | 1 + src/designer-extension-typings/api.d.ts | 25 +++++++++++++++++++++++++ src/examples/components.ts | 10 ++++++++++ 3 files changed, 36 insertions(+) diff --git a/src/components/PermissionsMap.tsx b/src/components/PermissionsMap.tsx index 837beba..6f9b3f1 100644 --- a/src/components/PermissionsMap.tsx +++ b/src/components/PermissionsMap.tsx @@ -49,6 +49,7 @@ export const permissionsMap: PermissionsMap = { setName: { permissions: ['canModifyComponents'] }, getRootElement: { permissions: ['canAccessCanvas'] }, registerComponent: { permissions: ['canCreateComponents'] }, + createComponentWithoutRoot: { permissions: ['canCreateComponents'] }, unregisterComponent: { permissions: ['canCreateComponents'] }, getAllComponents: { permissions: ['canAccessCanvas'] }, enterComponent: { permissions: ['canModifyComponents'] }, diff --git a/src/designer-extension-typings/api.d.ts b/src/designer-extension-typings/api.d.ts index 0e1c2ce..d8466f6 100644 --- a/src/designer-extension-typings/api.d.ts +++ b/src/designer-extension-typings/api.d.ts @@ -138,6 +138,31 @@ interface WebflowApi { name: string, root: AnyElement | ElementPreset | Component ): Promise; + + /** + * Create a component that is not inside any element. + * @param options - The options for the component + * @param options.name - The name of the component (required) + * @param options.group - The group/folder to place the component in (optional) + * @param options.description - A description for the component (optional) + * @returns A Promise resolving to an object containing the newly created Component - with the id property. + * @example + * ```ts + * // Create a hero component in the Sections group that is not within an existing element + * const hero = await webflow.registerComponent({ + * name: 'Hero Section', + * group: 'Sections', + * description: 'A reusable hero section with heading and CTA', + * }); + * + * // Example Response + * {id: '204d04de-bf48-5b5b-0ca8-6ec4c5364fd2'} + * ``` + */ + createComponentWithoutRoot( + options: ComponentOptions + ): Promise; + /** * Delete a component from the Designer. If there are any instances of the Component within the site, they will * be converted to regular Elements. diff --git a/src/examples/components.ts b/src/examples/components.ts index 6a64903..078b280 100644 --- a/src/examples/components.ts +++ b/src/examples/components.ts @@ -70,6 +70,16 @@ export const Components = { } }, + createComponentWithoutRoot: async () => { + // Create a hero component in the Sections group that is not within an existing element + const hero = await webflow.registerComponent({ + name: 'Hero Section', + group: 'Sections', + description: 'A reusable hero section with heading and CTA', + }); + console.log(`Component registered with ID: ${hero.id}`) + }, + deleteComponent: async () => { // Get selected element const selectedElement = await webflow.getSelectedElement() From 4db1d476e4e88a550e61fcf91b3ecd73218f1f49 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Wed, 18 Mar 2026 09:33:22 -0400 Subject: [PATCH 3/4] DEVREL-2686: Convert elements or duplicate components --- src/components/PermissionsMap.tsx | 2 ++ src/designer-extension-typings/api.d.ts | 36 +++++++++---------- .../components.d.ts | 11 ++++++ src/examples/components.ts | 21 +++++++++++ 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/components/PermissionsMap.tsx b/src/components/PermissionsMap.tsx index 6f9b3f1..a4fb974 100644 --- a/src/components/PermissionsMap.tsx +++ b/src/components/PermissionsMap.tsx @@ -50,6 +50,8 @@ export const permissionsMap: PermissionsMap = { getRootElement: { permissions: ['canAccessCanvas'] }, registerComponent: { permissions: ['canCreateComponents'] }, createComponentWithoutRoot: { permissions: ['canCreateComponents'] }, + createComponentFromElement: { permissions: ['canCreateComponents'] }, + duplicateComponent: { permissions: ['canCreateComponents'] }, unregisterComponent: { permissions: ['canCreateComponents'] }, getAllComponents: { permissions: ['canAccessCanvas'] }, enterComponent: { permissions: ['canModifyComponents'] }, diff --git a/src/designer-extension-typings/api.d.ts b/src/designer-extension-typings/api.d.ts index d8466f6..a78569b 100644 --- a/src/designer-extension-typings/api.d.ts +++ b/src/designer-extension-typings/api.d.ts @@ -123,7 +123,7 @@ interface WebflowApi { /** * Create a component by promoting a Root Element. * @param name - The name of the component. - * @param rootElement - An Element that will become the Root Element of the Component. + * @param root - An Element that will become the Root Element of the Component. * @returns A Promise resolving to an object containing the newly created Component - with the id property. * @example * ```ts @@ -136,31 +136,31 @@ interface WebflowApi { */ registerComponent( name: string, - root: AnyElement | ElementPreset | Component + root?: AnyElement | ElementPreset | Component | ComponentId ): Promise; /** - * Create a component that is not inside any element. - * @param options - The options for the component - * @param options.name - The name of the component (required) - * @param options.group - The group/folder to place the component in (optional) - * @param options.description - A description for the component (optional) + * Create a component by converting an element into a component or duplicating a component. + * @param options - The options for the new component. + * @param root - An Element that will become the Root Element of the Component. * @returns A Promise resolving to an object containing the newly created Component - with the id property. * @example * ```ts - * // Create a hero component in the Sections group that is not within an existing element - * const hero = await webflow.registerComponent({ - * name: 'Hero Section', - * group: 'Sections', - * description: 'A reusable hero section with heading and CTA', - * }); - * - * // Example Response - * {id: '204d04de-bf48-5b5b-0ca8-6ec4c5364fd2'} + * // Convert an existing element into a component + * const selectedElement = await webflow.getSelectedElement() + * const heroComponent = await webflow.registerComponent( + * { + * name: 'Hero Section', + * group: 'Sections', + * description: 'Main hero with heading and CTA' + * }, + * selectedElement + * ) * ``` */ - createComponentWithoutRoot( - options: ComponentOptions + registerComponent( + options: ComponentOptions, + root?: AnyElement | ElementPreset | Component | ComponentId ): Promise; /** diff --git a/src/designer-extension-typings/components.d.ts b/src/designer-extension-typings/components.d.ts index ffd5917..e2657c2 100644 --- a/src/designer-extension-typings/components.d.ts +++ b/src/designer-extension-typings/components.d.ts @@ -35,3 +35,14 @@ interface Component { } type ComponentId = string; + +interface ComponentOptions { + /** The name of the component (required) */ + name: string; + /** The group/folder to place the component in (optional) */ + group?: string; + /** A description for the component (optional) */ + description?: string; + /** Whether to replace the existing element (optional) */ + replace?: Boolean; +} diff --git a/src/examples/components.ts b/src/examples/components.ts index 078b280..ad35f63 100644 --- a/src/examples/components.ts +++ b/src/examples/components.ts @@ -80,6 +80,27 @@ export const Components = { console.log(`Component registered with ID: ${hero.id}`) }, + createComponentFromElement: async () => { + // Convert an existing element into a component + const selectedElement = await webflow.getSelectedElement() + if (selectedElement) { + const heroComponent = await webflow.registerComponent( + { + name: 'Hero Section', + group: 'Sections', + description: 'Main hero with heading and CTA' + }, + selectedElement + ) + } + }, + + duplicateComponent: async () => { + // Duplicate a component + const [original] = await webflow.getAllComponents() + const copy = await webflow.registerComponent({ name: 'Card Copy' }, original) + }, + deleteComponent: async () => { // Get selected element const selectedElement = await webflow.getSelectedElement() From 68ec4f89833527b094474b3b7f1aaef176d8bcd5 Mon Sep 17 00:00:00 2001 From: Tim McMackin Date: Wed, 18 Mar 2026 15:36:29 -0400 Subject: [PATCH 4/4] Note about replacement --- src/examples/components.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/examples/components.ts b/src/examples/components.ts index ad35f63..f8fc2e9 100644 --- a/src/examples/components.ts +++ b/src/examples/components.ts @@ -81,7 +81,7 @@ export const Components = { }, createComponentFromElement: async () => { - // Convert an existing element into a component + // Convert an existing element into a component, by default replacing the element with the new component const selectedElement = await webflow.getSelectedElement() if (selectedElement) { const heroComponent = await webflow.registerComponent(