DevLink Export
|
This content was generated with the assistance of AI and should be verified against the official Webflow documentation before being relied on in production. This sub-section’s Webflow bibliography lists the reference material consulted while preparing these pages. |
This page describes DevLink component export as it was on 2026-10-28, the date on which its facts were checked against
the DevLink overview, the component export quickstart, installation, "what’s exported", component architecture, props
and slots, element settings, interactions and migration pages of the documentation on developers.webflow.com, and
against the command reference of the Webflow CLI. Component export needs a paid Workspace plan or a CMS, Business or
Ecommerce Site plan, so the examples assume a site on one of those tiers; plans are named by tier only, never by price.
The pinned tool versions are @webflow/webflow-cli 2.15.0, which requires Node.js 22.13.0 or later, and, for the
exported code, React 18 or later. The CLI itself is described in Webflow CLI, and
the product map in Developer Platform Overview.
The running example is Fernbrook Gardens, a fictitious botanical garden. Its designer has built a PlantCard
Component with two Style Variants, compact and feature, an EventBanner and a SiteHeader with a Slot, all
documented in Components, Props, Slots and Variants. The
garden’s developer now needs the same PlantCard in a Next.js visitor kiosk that shows plants on a touch screen in
the greenhouse. The kiosk is a separate application with its own routing, its own data and its own hosting, and the
designer must remain the owner of how a plant card looks. DevLink export is the tool for exactly that split.
What DevLink export is
DevLink is described in the documentation as the bridge between design and code for teams working in Webflow. It covers three jobs, and this page covers only one of them:
-
Export: converting Webflow Components, with their styles, variables and interactions, into React components inside an external codebase. This is the topic of this page.
-
Import: bringing React components that developers wrote into Webflow, where they appear on the canvas as code components. That direction is covered in Code Components.
-
Deploy: hosting Next.js and Astro applications on Webflow Cloud, covered in Webflow Cloud.
Export is a one-way flow from Webflow to code. The components are designed visually in the Designer; a command in the terminal turns them into plain React files; the application imports those files like any other module. Nothing flows back: editing an exported file never changes the Webflow site, and the next export overwrites the edit. The design system stays in Webflow, and the application owns everything else.
The documented use cases are prototyping in Webflow and exporting to React for further development, keeping an application visually consistent with a Webflow site, and smoother hand-offs between designers and developers.
Source: DevLink: Overview
How it differs from the other ways of leaving Webflow
Several mechanisms move design out of Webflow, and they are easy to confuse.
| Mechanism | Unit | Result | Continues after the first run |
|---|---|---|---|
Code export (ZIP) |
The whole site |
Static HTML, CSS and JavaScript |
No, a one-off snapshot |
DevLink export |
Components, styles and variables |
React components, CSS and a small runtime |
Yes, every export refreshes the |
Headless (Data API, Content Delivery API) |
Content, not design |
JSON that an application renders itself |
Yes, live |
Code components (DevLink import) |
React components from code |
Components usable on the Webflow canvas |
Yes, by importing again |
Code export is described in Code Export and Portability, the headless route in Content Delivery API and Headless, and the choice between them in Comparison and Decision Guide. DevLink export and a headless reader are often combined, as in the kiosk of this page: DevLink supplies the look of a plant card and the Content Delivery API or the Data API supplies the plants.
Source: DevLink: Component export
Requirements
| Requirement | Detail |
|---|---|
Plan |
A paid Workspace plan, or a CMS, Business or Ecommerce Site plan. A free Developer workspace site is enough to learn the Designer but not to run an export. |
Permission |
Site Admin or Owner permission on the site that is exported. |
CLI |
|
Node.js |
v22 or higher. |
React |
React 18 or higher in the application that uses the exported components. |
Credentials |
A browser login for a person at a workstation, or |
Source: DevLink export setup
The exported output is plain React, so it is not tied to Next.js. The quickstart uses Next.js and states that any React-based framework works. The framework guides cover the App Router and the Pages Router of Next.js, Remix and Gatsby, and a Vite or Create React App project follows the same pattern of importing one stylesheet and wrapping the root in one provider.
Source: DevLink export quickstart
The export pipeline
The export is a pipeline with a fixed order. The CLI reads the configuration, authenticates, fetches the site’s
components and styles from Webflow, filters them with the components and componentGroups rules, generates React
files, stylesheets and engine modules into the root directory, and prints warnings for anything that could not be
exported as written.
The dotted edge is the maintenance loop. Whenever the designer changes a Component, the developer or a CI job runs the
export again, reviews the diff of the webflow folder and commits it. Because the folder is generated, the review
looks at what changed in the design, not at hand-written code.
The documented workflow in five steps is: design the styles, variables and Components in Webflow; configure the connection between the project and the React application; export with the CLI; import the global styles and the generated components; add functionality such as logic, state and interactivity in the application.
Source: DevLink: Component export
Preparing the Components in Webflow
DevLink exports Components, not arbitrary canvas elements. A selection that is not a Component does not appear in the export, and the quickstart’s troubleshooting list starts with that check. To convert elements, select an element or a group of elements, right-click, choose Create Component and give it a descriptive name, because the name becomes the React component name.
For Fernbrook Gardens the designer prepares the following Components in the Designer, using the techniques of Building a Design System:
| Component | Purpose in the kiosk | Design decisions |
|---|---|---|
|
Shows one plant: name, zone link, endangered badge, image and short care notes |
Style Variants compact and feature; a Text prop for the plant name, a Link prop for the zone, a Visibility prop for the endangered badge, a Rich text prop for the care notes, an Attributes prop on the root and a Slot for the image |
|
A strip announcing the next guided walk |
Text props for title and date, a Link prop for tickets |
|
The logo, the language links and the opening-hours line |
A Slot for the navigation so the kiosk can place its own buttons |
The rules of the guidelines that matter most at this stage are these. Keep a Component self-contained: everything it needs for its look must live inside it, because page-level and site-level custom code is not exported. Expose every value the application must change as a prop instead of relying on text edited in the application. Name Components, props and variants with plain words, because the names are sanitized on export (see Name-change rules and @warning tags).
Source: DevLink export quickstart
Setting up the kiosk and running the first export
The kiosk is a new Next.js application. The quickstart creates it with create-next-app and installs the CLI as a
development dependency so that every contributor and every CI run uses the version recorded in package.json.
npx create-next-app@latest fernbrook-kiosk
cd fernbrook-kiosk
npm install -D @webflow/webflow-cli@2.15.0
Source: DevLink export quickstart
The documentation installs the latest tag; pinning an exact version is the safer choice for a repository, in line with
the guidance in Webflow CLI, because a pipeline that silently changes its tool
version is hard to debug. The CLI can also be installed globally so that webflow works outside any project.
Then, from the root of the React project, run the export:
npx webflow devlink export
Source: DevLink export quickstart
On the first run the CLI opens the browser to authenticate, then walks through the setup questions: which site, which
root directory, which components and groups, whether to generate TypeScript and whether to use CSS scopes. Accepting
the defaults exports every component to ./webflow with style isolation enabled. The answers are saved to
webflow.json, so later runs do not prompt again. The result is a populated webflow folder with the React
components, the generated stylesheets, a DevLinkProvider and the supporting modules the components depend on.
After the run, check the output for @warning tags. They mark an identifier that DevLink could not export as written
(see Name-change rules and @warning tags).
The devlink-export configuration
The configuration lives in webflow.json in the project root. The siteId is at the top level and every other option
is under a devlink-export object. The older .webflowrc.js file is deprecated, and existing projects should move to
webflow.json.
{
"siteId": "REPLACE_WITH_SITE_ID",
"devlink-export": {
"rootDir": "./webflow",
"components": ".*",
"componentGroups": ".*",
"ts": true,
"cssScopes": true,
"relativeHrefRoot": "/"
}
}
Source: DevLink export setup
| Option | Meaning | Default |
|---|---|---|
|
The Webflow site ID. Top level, not inside |
|
|
The directory the components are exported into. |
|
|
Wraps component styles in CSS |
|
|
Generates TypeScript files. When it is |
Auto-detected from |
|
A regular expression that selects the Components to export. |
|
|
A regular expression that selects the Component groups to export. |
|
|
How relative |
|
|
Converts Style Variant names to camelCase for the |
|
Source: DevLink export setup
Selecting what is exported
components and componentGroups are regular expressions, so an application can take a subset. The kiosk needs only
the plant-related Components and the header, so its configuration restricts the export:
{
"siteId": "REPLACE_WITH_SITE_ID",
"devlink-export": {
"rootDir": "./webflow",
"components": "^(PlantCard|SiteHeader)$",
"componentGroups": ".*",
"ts": true,
"cssScopes": true,
"relativeHrefRoot": "https://fernbrook.example"
}
}
Source: DevLink export setup
A narrow filter keeps the diff of each export small and prevents a designer’s experimental Component from appearing in
a production application by accident. When a Component that should be exported is missing, the first two checks are
that the element is a Component in Webflow and that the regular expressions in devlink-export match its name.
relativeHrefRoot
The setting controls how internal links inside exported Components are rewritten. External URLs, mailto:, tel: and
asset links are exported unchanged whatever the value.
| Value | Result |
|---|---|
|
Page links become root-relative paths, section links become a |
A base URL string such as |
The base URL is prepended to internal paths, after a trailing slash is stripped. |
|
Internal links are not rewritten and render as |
For the kiosk the choice matters. A card shown on the garden’s own website can use root-relative paths, but a kiosk on the greenhouse network usually wants absolute links to the public site, so the zone link of a card points to the visitor website and not to a route the kiosk does not have.
Source: DevLink export setup
camelCaseVariantNames
By default a variant value keeps its Webflow name, with spaces and capitals, for example "Papaya With Whip". With
camelCaseVariantNames set to true it becomes "papayaWithWhip". Names that normalize to the same value get a
numeric suffix, so Foo-Bar and Foo Bar become fooBar and fooBar2, and names that would be invalid, such as a
bare number or a reserved word like class, fall back to a safe placeholder. Setting the option changes the strings
the application passes, so changing it in an existing project is a breaking change for every call site that uses a
variant.
Source: DevLink export setup
Authentication and site selection
The command resolves its credentials and its site in a fixed order. This is the same model as the rest of the CLI, described in Webflow CLI.
| Option | Description | Fallback |
|---|---|---|
|
API token used for the export. |
|
|
Site ID to export from. |
|
Source: Webflow CLI: Command reference
For daily work the browser login is enough. For a script or a pipeline use a token and keep it out of the command line, because a flag value shows in the process list and in shell history:
export WEBFLOW_API_TOKEN="REPLACE_WITH_WORKSPACE_API_TOKEN"
npx webflow devlink export --no-input
Source: DevLink export quickstart
When authentication fails, the documentation lists these checks: the token has the right permissions; the token can
access the site ID in webflow.json (a Site token covers one site, a Workspace token covers every site in the
workspace); the environment variables are loaded; webflow.json is in the application’s root directory. A forced fresh
login is webflow auth login --force.
The generated folder
The export writes React files into the root directory, by default a webflow folder at the project root. The
structure, as documented for the React output, is the following.
webflow/
DevLinkProvider.tsx context provider for Classic and GSAP interactions
ix3-interactions.tsx provider and hook for Interactions with GSAP
devlinkScope.ts DEVLINK_SCOPE_CLASS, when cssScopes is enabled
PlantCard.tsx one file per exported Component
SiteHeader.tsx
css/
global.css imports the other stylesheets
normalize.css
defaults.css
variables.css
tags.css
classes.css
fonts.css @font-face rules for custom fonts
webflow_modules/
devlink.js engine for Classic Interactions
devlink.d.ts
interactions.tsx
devlink-ix3.js engine for Interactions with GSAP
devlink-ix3.d.ts
devlink-gsap.js per-export GSAP bundle
DevLinkFontTags.tsx loads external fonts on the server
types.ts
utils.ts
fonts.manifest.json
useInjectFonts.ts
Basic/ category folders with components and helpers
Source: DevLink export reference
Components that belong to groups or libraries are exported into group and library folders, which can nest groups. The folder names in the listing above are the ones the documentation names; the exact file list depends on the site, for example the IX3 engine files are lightweight stubs on a site that has no GSAP interactions, so that bundlers still resolve their imports.
What a component file contains
Each exported component carries the following:
-
typed props, grouped and sanitized from the props defined in Webflow;
-
scoped classes, wrapped in
DEVLINK_SCOPE_CLASSthrough CSS@scope; -
nested elements, rendered as JSX built from primitives such as
Block,LinkandImage; -
assets and media, with images, dimensions, alt text and link attributes carried over;
-
static text;
-
conditional visibility, compiled to React for the Boolean, Enum, Number, Text and Style Variant types.
The Card file in the documentation shows the shape of the output: a generated-file header comment, an import of
DEVLINK_SCOPE_CLASS and of primitives from webflow_modules, an exported props type, and a function component that
returns a root div with the scope class and display: contents, so the wrapper does not take part in layout.
Do not edit generated components manually. A re-export overwrites every change, and the header of each file says so. Make the change in the Designer, or wrap the component in the application:
// kiosk/components/KioskPlantCard.tsx - hand-written, never overwritten
import { PlantCard } from "webflow/PlantCard";
type KioskPlantCardProps = {
plantName: string;
zoneLabel: string;
endangered: boolean;
};
export function KioskPlantCard({ plantName, zoneLabel, endangered }: KioskPlantCardProps) {
return (
<PlantCard
plantName={plantName}
zoneLabel={zoneLabel}
showEndangered={endangered}
/>
);
}
Source: DevLink export reference
The wrapper keeps application concerns, such as mapping a kiosk record to card props, in a file the export never touches.
Styles: global.css, variables and breakpoints
css/global.css is the single stylesheet the application imports, once, at the root. It uses @import and @layer to
pull in the category files, so the order and the cascade are the same on every export. It contains:
-
a normalize and reset layer for cross-browser consistency;
-
base element defaults for
html,body, headings, lists and images; -
the core utility classes with the
w-prefix; -
the component frameworks for sliders, tabs, navigation, dropdowns, lightbox and background video;
-
the icon font
webflow-iconsand thew-icon-glyph selectors; -
responsive breakpoints at 991px, 767px and 479px;
-
CSS variables and theme defaults.
variables.css holds the CSS custom properties that come from the site’s Webflow variables, plus the base typography
and link styles. This is why a variable collection such as Fernbrook’s brand collection, described in
Variables and Modes, is a design-token source for code as well: the kiosk
can read the exported custom properties in its own stylesheets and stay in step with the website.
The breakpoints are the Webflow defaults, with the cascade of Responsive Design and Breakpoints: styles set at the base breakpoint apply to smaller screens until a smaller breakpoint overrides them. A kiosk is a fixed touch screen, so most of its layout comes from the application, and the components should be tested at the breakpoint that matches the screen’s width.
Source: DevLink export reference
DevLinkProvider and DevLinkFontTags
DevLinkProvider is the runtime wiring. It sets up both interaction engines, Classic Interactions (IX2) and
Interactions with GSAP (IX3), so there is no separate provider for GSAP. The quickstart wraps the app in it whenever
the components use interactions. ix3-interactions.tsx exposes the useIX3Interactions hook and an IX3Provider,
and DevLinkProvider nests that provider automatically, so the application never imports the file directly.
DevLinkFontTags is a server-rendered component that loads the external fonts. Placed in the document head it avoids a
brief flash of a fallback font. If the site uses no external fonts it returns null, at no cost. Without it, fonts
still load through client-side injection, but the fallback font may flash.
The root layout of the kiosk, in the Next.js App Router, imports the global stylesheet, renders the font tags and wraps the children in the provider:
// app/layout.tsx
import "./globals.css";
import "webflow/css/global.css";
import { DevLinkProvider } from "webflow/DevLinkProvider";
import { DevLinkFontTags } from "webflow/webflow_modules/DevLinkFontTags";
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<head>
<DevLinkFontTags />
</head>
<body>
<DevLinkProvider>{children}</DevLinkProvider>
</body>
</html>
);
}
Source: DevLink export quickstart
The import of DevLinkFontTags follows the documented location in webflow_modules; the quickstart’s own listing uses
a relative path, so the path depends on where the layout sits relative to the export folder. The path alias below makes
the import independent of the file’s depth. The lang attribute stays an application decision; the kiosk’s Spanish
and German variants are handled by the application, not by the export.
The alias goes into the paths section of tsconfig.json:
{
"compilerOptions": {
"paths": {
"webflow/*": ["./webflow/*"]
}
}
}
Source: DevLink export quickstart
With the alias, import { PlantCard } from "webflow/PlantCard" resolves to ./webflow/PlantCard from anywhere in the
application. A Next.js project created with the default template already has an alias for its own src folder, and the
webflow/* entry is added beside it; make sure the include list of tsconfig.json covers the export directory.
CSS scopes
Webflow’s generated CSS is global by nature: classes such as w-nav and base rules for body and headings apply
everywhere. Dropping them into an application would restyle the application’s own pages. CSS scopes prevent that.
When cssScopes is enabled, which is the default and the recommended setting:
-
devlinkScope.tsdefinesDEVLINK_SCOPE_CLASS; -
the root element of each component is wrapped in a
divthat carries that class; -
the category stylesheets are wrapped in
@scopeblocks keyed by a generated class of the form.wf-devlink-<hash>, so Webflow’s styles apply only inside DevLink components.
The practical consequences are the following. A kiosk header written by hand next to an exported SiteHeader keeps its
own typography. A Tailwind or CSS Modules stylesheet in the application does not collide with the w- utility
classes. Because the wrapper is display: contents, it does not break flex or grid layouts in the application, but it
does appear in the DOM, so a CSS selector in the application that depends on direct children, such as
.grid > *, now sees the wrapper instead of the component’s root. If cssScopes is false, Webflow rules apply
globally and the only protection is discipline, so it is only advisable for an application that is entirely made of
exported components.
Source: DevLink export reference
The older sync-based DevLink used CSS Modules for the same purpose, through a cssModules option; export replaced it
with cssScopes. See Migrating from devlink sync.
Name-change rules and @warning tags
Names in Webflow are free text, and names in React are not. The export sanitizes them, so the identifiers an application uses are sometimes different from the ones a designer typed.
| Kind | Rules |
|---|---|
Components (PascalCase) |
Spaces and special characters, including emojis and leading underscores, are removed and the words are joined in
PascalCase. A leading number or a reserved word gets an |
Props (camelCase) |
Dashes and spaces are removed and the words are joined in camelCase; special characters and leading underscores are
removed. A leading number gets an |
Variant names |
By default they match the Webflow names exactly. With |
Custom attributes |
HTML5 names are converted to React casing, so |
Source: DevLink export reference
When an attribute is filtered, except for an empty name, the CLI prints a warning and the generated component gets a
JSDoc @warning block. The block says the component could not be fully exported and lists each removed attribute, for
example an invalid onClick. The attribute is removed from the rendered JSX. This is why the quickstart tells the
developer to look for @warning tags after every export: a custom attribute on an element can silently disappear from
the output.
For Fernbrook Gardens the designer follows three habits that keep names stable: Components get plain two-word names
such as PlantCard; props get short names such as plantName and zoneLabel; and event handlers are never added as
custom attributes in the Designer, because they would be filtered. Handlers are passed from the application through an
Attributes prop instead (see the next section).
Props, slots and attributes
DevLink converts the properties of a Webflow Component into typed React props, with the defaults and rules set in Webflow. The Type setting of a property controls which values it accepts and how it appears in the Designer.
Basic props
Export supports these basic prop types: Text, Video, Link, Rich text, Number and Visibility. A Text prop in the
documentation’s generated Card component is typed as React.ReactNode, so it accepts strings and elements. A
Visibility prop is a flag that shows or hides an element.
For the PlantCard the designer defines:
| Property in Webflow | Type | Use in the kiosk |
|---|---|---|
Plant name |
Text |
The common name shown as the card heading |
Zone label |
Text |
The garden zone shown under the name |
Zone link |
Link |
Points to the zone page of the public site |
Care notes |
Rich text |
Short care text; the kiosk passes already-sanitized content |
Show endangered |
Visibility |
Shows the endangered badge |
Card attributes |
Attributes |
Receives analytics identifiers and the touch handler |
After export the card is used like any other React component:
import { PlantCard } from "webflow/PlantCard";
export function PlantTile() {
return (
<PlantCard
plantName="Japanese maple"
zoneLabel="Zone 4, Woodland Walk"
showEndangered={false}
/>
);
}
Source: DevLink: Props and slots
The prop names above follow the camelCase rules of the previous section, and the exact set depends on how the Component was configured in the Designer. The generated props type in the file is the authority: the editor of the kiosk project shows it, and a mismatch is a compile error and not a runtime surprise.
Attributes props
An Attributes prop forwards arbitrary HTML attributes to the DOM node of the element it is bound to. This includes
data-*, aria-*, id, role, event handlers and refs. DevLink types the prop as Record<string, unknown> and
spreads it on the bound element. It can be created in two ways: bind the Custom attributes list of an element in the
Settings tab to a new prop, in which case the static attributes become the default value, or add a property in the
props panel, choose Attributes and bind it through the element’s Custom attributes section.
import { PlantCard } from "webflow/PlantCard";
export function KioskCard({ onPick }: { onPick: () => void }) {
return (
<PlantCard
plantName="Japanese maple"
cardAttributes={{
"data-analytics-id": "kiosk-plant-card",
"aria-describedby": "kiosk-help",
onClick: onPick,
}}
/>
);
}
Source: DevLink: Props and slots
Attributes props are the supported way to attach behaviour. They are also the answer to the filtering of event handlers that was described above: a handler typed into the Designer’s attribute list is removed, whereas a handler passed through the Attributes prop at runtime is spread onto the element.
Two warnings from the element-settings page apply here. Built-in IDs on elements such as Grid or Quick Stack are
styling hooks, and replacing them dynamically in React breaks Webflow’s generated selectors, so keep the original ID and
pass dynamic identifiers as custom attributes such as data-category. Custom IDs set in the Designer are converted to
namespaced selectors of the form ComponentName_custom-id__unique-identifier, so a script that looks up a custom ID by
its literal name will not find it.
Source: DevLink: Element settings
Slots
A Slot is a Webflow element that lets a Component accept other content. In React the Slot becomes a prop of type
React.ReactNode that receives anything renderable. Slots are the way to embed what Webflow cannot produce: a chart, a
modal, a media player or a component from a third-party library.
Fernbrook’s PlantCard has a Slot for the plant image. The kiosk passes the framework’s optimised image component
into it:
import Image from "next/image";
import { PlantCard } from "webflow/PlantCard";
export function PlantCardWithPhoto({ photoUrl, alt }: { photoUrl: string; alt: string }) {
return (
<PlantCard
plantName="Japanese maple"
cardImage={<Image src={photoUrl} alt={alt} width={640} height={420} />}
/>
);
}
Source: DevLink: Props and slots
The alt text arrives with the data and is passed explicitly, which keeps the accessibility rules of Accessibility in the application’s control. Configure the image host of the application when the photos come from Webflow’s CDN, as the Next.js framework guide describes.
Slots replace two older mechanisms. The legacy DevLink Slots, deprecated in the Designer, show warnings and should be replaced with Webflow’s native Slot element, which the in-canvas Convert action does for existing components. Legacy Runtime Props are replaced by the Attributes prop, and the documentation says existing Runtime Props will be converted automatically in the future.
Style Variants
Style Variants, the compact and feature versions of PlantCard, are exported as a variant prop whose values are
the variant names. Conditional visibility rules that are driven by a variant compile to React as well.
import { PlantCard } from "webflow/PlantCard";
export function GreenhouseList() {
return (
<>
<PlantCard plantName="Maidenhair fern" variant="compact" />
<PlantCard plantName="Giant water lily" variant="feature" />
</>
);
}
Source: DevLink export setup
The strings are exactly the Webflow variant names unless camelCaseVariantNames is on. A designer who renames a
variant therefore changes an API: the application must be updated in the same pull request that commits the new
export. Treat variant names like exported function names.
Forms
Forms follow two build paths in the guidelines. A full Webflow form can be exported as one Component, and the
application attaches handlers through an Attributes prop. The exported form is uncontrolled: the handler prevents the
default submission, reads the values with FormData and posts them to the application’s endpoint. If the handler
throws, the form shows its Error state; otherwise it shows its Success state. Alternatively, individual inputs and
buttons are exported and composed inside a hand-written form element, which gives full control over validation,
error messages and submission state.
The kiosk has a small "tell us about this plant" form. The designer builds it as a Component named PlantFeedbackForm
with an Attributes prop on the form element:
import { PlantFeedbackForm } from "webflow/PlantFeedbackForm";
export function KioskFeedback() {
return (
<PlantFeedbackForm
formAttributes={{
onSubmit: async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const res = await fetch("/api/feedback", {
method: "POST",
body: new FormData(e.currentTarget),
});
if (!res.ok) throw new Error("Submit failed"); // shows the Error state
},
}}
/>
);
}
Source: DevLink: Component architecture
Note that exported forms do not submit to Webflow. The submission handling, spam protection and storage that
Forms describes for a published Webflow site belong to Webflow’s hosting; in the exported
component the application is responsible for them, so the /api/feedback route in the example is the application’s own
code.
Interactions: support and limits
DevLink export supports both interaction engines. Classic Interactions (IX2) are the original engine, described in
Classic Interactions. Interactions with GSAP (IX3) are the GSAP-powered
engine with timelines and plugins such as ScrollTrigger, SplitText and CustomEase, described in
Interactions with GSAP. To enable either, wrap the application in
DevLinkProvider.
import { DevLinkProvider } from "webflow/DevLinkProvider";
import "webflow/css/global.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<DevLinkProvider>{children}</DevLinkProvider>
</body>
</html>
);
}
Source: DevLink: Interactions
How IX3 is generated
For each component that has IX3 interactions, export generates registration code that runs through the
useIX3Interactions hook, webflow_modules/devlink-ix3.js, which is the runtime that initialises GSAP and registers
the timeline data, and webflow_modules/devlink-gsap.js, a per-export GSAP bundle that contains only the plugins the
site uses. The selection is automatic: there are no CLI flags and no webflow.json settings, and dependencies resolve
by themselves, so CustomBounce brings CustomEase and ScrollSmoother brings ScrollTrigger. Both bundles are lazy
loaded, fetched only after a component that uses useIX3Interactions mounts, so a page without animated components
adds no runtime cost.
Limits
| Limit | Consequence |
|---|---|
IX2 page triggers |
Only the first page interaction of each component is exported. If a component has page interactions on several pages, the others are dropped. This applies to IX2 only. |
IX3 page-level interactions |
Page-level IX3 interactions are not exported. IX3 interactions must be scoped to the Component: in the Designer attach the trigger to an element inside the Component, not to the page. |
Lottie, Spline and Rive actions |
Not yet supported. Export shows a warning and continues; the component exports without those actions and its remaining triggers and timelines run as designed. |
Source: DevLink: Interactions
For Fernbrook Gardens this means the "bloom reveal" of Scroll and Page
Animations can travel with the PlantCard only if the scroll trigger is attached to an element inside the Component.
The pinned seasonal timeline and the hide-on-scroll header are page-level and stay on the website, and the kiosk, which
does not scroll like a web page, uses a simple hover or tap trigger that is attached inside the card. A Lottie
animation on the card is replaced by a plain image in the exported version, with a warning in the CLI output that the
developer should read once and then ignore deliberately. Triggers and animations themselves are explained in the
Webflow Help Center.
Design guidelines for exportable components
The component architecture guidelines distinguish two approaches, which can be mixed. Complex components are
self-contained sections or features that should look and behave the same in Webflow and in the application, such as
navigation bars, pricing tables, footers, heroes or feature blocks. Atomic components are small building blocks of a
design system, such as buttons with variants, headings of several sizes, links with different styles or cards with
different layouts. Fernbrook uses atomic components for PlantCard and the badge inside it, and a complex component
for SiteHeader.
Supported and unsupported elements
| Group | Supported elements |
|---|---|
Layout |
Container, Div Block, Section, Grid, Columns, List and List Item, Map, Navbar, Slider, Tabs, Iframe |
Interactive |
Button, Link Block, form elements, Dropdown, Search |
Media and content |
Image, Video, Icon, Background Video, Code Embed, Heading, Paragraph, Text Link, Rich Text, Block Quote, Inline Code, Code Block and social embeds for Facebook, X and YouTube |
Ecommerce-specific elements and CMS-specific elements are unsupported. This is the guideline that shapes the whole
design: a Collection List, which is the CMS element of Collection Lists, is not
exported. A component that is meant to be exported is therefore a presentation component. It receives the plant’s
values as props, and the application loops over its own data and renders one PlantCard per plant.
Source: DevLink: Component architecture
Repeatable lists
For repeating content the guidelines split the markup into two components: a container Component that defines the list
structure and exposes a Slot, and an item Component that defines how one item renders. Fernbrook’s equivalent is
PlantGrid, with a Slot named gridContent, and PlantCard as the item:
import { PlantGrid } from "webflow/PlantGrid";
import { PlantCard } from "webflow/PlantCard";
type Plant = { id: string; name: string; zone: string; endangered: boolean };
export function PlantList({ plants }: { plants: Plant[] }) {
return (
<PlantGrid
gridContent={
<>
{plants.map((plant) => (
<PlantCard
key={plant.id}
plantName={plant.name}
zoneLabel={plant.zone}
showEndangered={plant.endangered}
/>
))}
</>
}
/>
);
}
Source: DevLink: Component architecture
The data comes from wherever the application prefers; the Journal and the Plants Collection can be read with the Content Delivery API described in Content Delivery API and Headless or with the Data API of Data API: CMS.
Custom code
DevLink does not export page-level or site-level custom code, such as code in the head or at the end of the body of the
site settings. It also does not keep plain class selectors, because every component gets its own namespaces. To add
custom CSS or JavaScript to a component, put a Code Embed element inside it. For CSS, wrap the rules in style tags and
target the namespaced classes with an attribute selector:
<style>
[class*="PlantCard_badge__"] {
letter-spacing: 0.04em;
}
</style>
Source: DevLink: Component architecture
A Code Embed may also hold a script element, which should be used only for logic specific to that component; general
interaction logic belongs in the application. Custom code in general, and its limits on a Webflow-hosted site, is the
subject of Custom Code: Head, Footer and Embeds.
Accessibility
Exported components inherit the accessibility features of the Webflow elements they are built from, so the semantic choices made in the Designer, such as heading tags, landmark elements, link text and alt text, travel with them. The Heading element has a Tag setting that can be bound to a prop, so one component can serve different levels of the document outline:
<Heading headingTag="h2" />
Source: DevLink: Element settings
A card used inside a list under an h1 should render its title as an h2 or h3, and a bound tag prop is how the
application chooses. Follow the guidance of Accessibility as well, since the
guidelines state that meeting accessibility standards still depends on following Webflow’s accessibility guidelines.
Using the components in Next.js
The quickstart’s structure for a Next.js app is: export into webflow, add the path alias, import global.css and
wrap the root in DevLinkProvider, then use the components in pages. The framework guides add details that matter in
production.
-
App Router. Set up
DevLinkProviderand the global CSS inapp/layout.tsx, as in the layout above. The Pages Router does the same inpages/_app.tsx. -
Server and client components. Components support server-side rendering. Where React Server Components are in use, use exported components inside client components marked with
"use client"and placeDevLinkProviderin a client context such as a client layout. -
Links and images. DevLink renders its own Link and Image primitives. To use the framework’s router link and image optimisation, replace them through the
renderLinkandrenderImageprops ofDevLinkProvider, and configure the remote image patterns of the application for Webflow’s asset host. -
Imports. Make sure import paths resolve to the export directory, whether
webflow,~/webflowor a relative path, avoid duplicate CSS imports with the framework’s recommended approach, and include the directory’s TypeScript files intsconfig.json. -
Other frameworks. Remix imports
global.cssinapp/root.tsxand passes loader data as props; Gatsby addsDevLinkProviderto bothgatsby-browser.tsxandgatsby-ssr.tsxand guards interactive components for hydration.
Source: DevLink: Framework guides
A kiosk page for Fernbrook Gardens then reads as follows, with the plants loaded on the server and the cards rendered by exported components:
// app/greenhouse/page.tsx
import { PlantGrid } from "webflow/PlantGrid";
import { PlantCard } from "webflow/PlantCard";
async function loadPlants() {
const res = await fetch("https://fernbrook.example/api/plants?zone=greenhouse", {
next: { revalidate: 300 },
});
return (await res.json()) as { id: string; name: string; zone: string }[];
}
export default async function GreenhousePage() {
const plants = await loadPlants();
return (
<main>
<h1>In the greenhouse today</h1>
<PlantGrid
gridContent={plants.map((p) => (
<PlantCard key={p.id} plantName={p.name} zoneLabel={p.zone} variant="compact" />
))}
/>
</main>
);
}
Source: DevLink export quickstart
The endpoint in the example is the garden’s own placeholder API and not a Webflow endpoint. Because interactions and the provider rely on the browser, check that the page’s tree still hydrates cleanly after adding exported components.
Keeping the export up to date
The designer changes a Component and the application must follow. The loop is short and deliberately mechanical:
-
The designer edits the Component in the Designer and publishes the site if the team’s practice requires it.
-
The developer runs
webflow devlink exportand the folder is refreshed. The quickstart states that the run refreshes the files inwebflowand leaves application code outside that folder untouched. -
The developer reads the CLI warnings and the
@warningtags, then reviews the diff of the generated files. -
The change is committed together with any application change it forces, such as a renamed prop or variant.
Source: DevLink export quickstart
Committing the webflow folder is the common choice: builds are then reproducible without Webflow credentials, a
reviewer sees every design change as a diff and a rollback is a git revert. The alternative is to run the export in the
build, which always yields the latest design but makes every build depend on the Webflow API and on a token, and means
a designer’s edit can change a production build without a code review. Fernbrook commits the folder and refreshes it
through a scheduled pull request, as the next section shows.
Exporting in CI
Pass WEBFLOW_API_TOKEN in the environment and add --no-input, which disables every prompt for CI. The CLI also turns
prompts off when it detects a non-interactive environment, and --no-input makes the intention explicit. The token is a
Workspace or Site API token stored as a repository secret.
The following GitHub Actions workflow refreshes the export every weekday morning and opens a pull request when the
generated folder changed. The siteId comes from the committed webflow.json.
name: Refresh DevLink export
on:
schedule:
- cron: "0 6 * * 1-5"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- name: Export components
env:
WEBFLOW_API_TOKEN: ${{ secrets.WEBFLOW_API_TOKEN }}
run: npx webflow devlink export --no-input
- name: Open a pull request when the export changed
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ -n "$(git status --porcelain webflow)" ]; then
git config user.name "devlink-export"
git config user.email "devlink-export@users.noreply.github.com"
git checkout -b devlink/refresh-$(date +%Y%m%d)
git add webflow
git commit -m "Refresh DevLink export"
git push origin HEAD
gh pr create --fill
fi
Source: DevLink export quickstart
Three details make the job dependable. The CLI is installed from package.json, so the version is the pinned one and
npm ci is reproducible. The token is read from the environment and never appears in the command, so it is masked in
the log. The job opens a pull request instead of committing to the main branch, so a designer’s change is reviewed
together with its effect on the application’s tests, and a failed export, for example from an expired token, makes the
job fail visibly. The general CI guidance, such as secrets and exit codes, is in
Webflow CLI, and a broader pipeline that also covers tests and other Webflow
deployments is in Testing, CI and DevOps.
Migrating from devlink sync
The earlier DevLink workflow used webflow devlink sync with a devlink block in the configuration. The command is
deprecated and was removed from the CLI, and devlink export replaces it. The migration is mechanical, and the
documentation lists the steps.
Differences
| Area | DevLink sync (legacy) | DevLink export |
|---|---|---|
CLI version |
Older CLI |
|
Node.js |
Earlier versions |
v22 or higher |
API token variable |
|
|
Workspace token variable |
|
Accepted but deprecated; rename it |
Site ID |
|
Top-level |
Configuration block |
|
|
Output folder |
|
|
Style isolation |
|
|
TypeScript |
Not a listed option |
|
Component selection |
Not a listed option |
|
Removed options |
|
No replacement |
Kept option |
|
|
Path alias |
|
|
Slots |
DevLink Slots |
The native Slot element |
Props |
Runtime Props |
The Attributes prop |
Source: DevLink: Migrate from sync
Steps
-
Raise the CLI and Node.js. Install the current CLI as a development dependency and make sure Node.js is 22 or newer, locally and on CI runners.
-
Update the environment. Rename
WEBFLOW_SITE_API_TOKENtoWEBFLOW_API_TOKEN, and rename the workspace token variable too, since it still works but triggers a deprecation warning.WEBFLOW_SITE_IDstill works, but the canonical place is the top-levelsiteIdofwebflow.json. If authentication still fails, runwebflow auth login --force, and if the failure mentions the site, check that the site ID belongs to a site the token can access. -
Update
webflow.json. Either runwebflow devlink exportand follow the prompts, or edit by hand: add a top-levelsiteId, replace thedevlinkblock with adevlink-exportblock and delete the olddevlinkblock manually. The CLI ignores the old block but does not remove it. -
Update the path alias. If the default root directory was used, change the alias in
tsconfig.jsonfromdevlink/*towebflow/*. -
Update the imports. Change imports and CSS references from
devlink/...towebflow/..., including the CSS file andDevLinkProvider. -
Re-run the export.
webflow devlink exportregenerates thewebflowfolder from the live components. -
Replace deprecated Designer patterns. They do not break the export but show warnings: replace DevLink Slots with the native Slot element, using the Convert action in the canvas, and replace Runtime Props with the Attributes prop.
-
Verify. Build the application and confirm that the components keep their styles, fonts and interactions.
Source: DevLink: Migrate from sync
A before-and-after of the Fernbrook kiosk configuration shows the shape of step 3:
{
"devlink": {
"rootDir": "./devlink",
"cssModules": true,
"relativeHrefRoot": "/"
}
}
Source: DevLink: Migrate from sync
becomes
{
"siteId": "REPLACE_WITH_SITE_ID",
"devlink-export": {
"rootDir": "./webflow",
"cssScopes": true,
"relativeHrefRoot": "/"
}
}
Source: DevLink export setup
The import rewrite of step 5 is a plain search and replace. A one-off command on macOS or Linux, run from the application root after the export folder has been renamed, does it for TypeScript and CSS files; review the diff before committing:
grep -rlE "devlink/" src app --include="*.ts" --include="*.tsx" --include="*.css" \
| xargs perl -pi -e 's#devlink/#webflow/#g'
git diff --stat
Source: DevLink: Migrate from sync
Corrections and complements to older material
Older books and tutorials, including the book Webflow by Example that this sub-section uses as a reference point for
the classic workflow, were written before component export and describe the Designer, the CMS and Classic
interactions, not DevLink. Tutorials found online that use webflow devlink sync, a devlink block, a ./devlink
folder or WEBFLOW_SITE_API_TOKEN describe the removed workflow, and following them on a current CLI fails or
produces a configuration that is silently ignored. The list of such outdated advice is collected in
Where Older Books and Tutorials Are Outdated, and
the dated changes in Product Changes and Deprecations.
Pitfalls
-
Editing generated files. Every change is overwritten by the next export. Wrap the component in the application, or change the Component in the Designer.
-
Missing Component. The element is not a Component, the regular expressions in
devlink-exportdo not match it, or it uses an unsupported element. -
Missing styles.
css/global.cssis not imported,DevLinkProviderorDevLinkFontTagsis not set up, or another stylesheet of the application conflicts. -
Interactions do nothing. The application is not wrapped in
DevLinkProvider, the trigger is attached to the page and not to an element in the Component, or the action is Lottie, Spline or Rive. -
CMS data on the card. Collection Lists and other CMS elements are not exported, so a card that was built on Collection fields has to be redesigned with props.
-
Custom code lost. Site-level and page-level custom code is not exported; only Code Embed elements inside the Component are.
-
Dynamic IDs. Replacing built-in IDs in React breaks the generated selectors; use custom attributes.
-
Renamed variants and props. A rename in the Designer is a breaking change for the application; the compiler catches it for props, but a variant string passed from data is not checked.
-
Several
webflow.jsonfiles. The CLI reads the file in the current directory; run the command from the application root or point to the right file with--manifest. -
Filtered attributes. Event handlers added as custom attributes in the Designer are dropped; use an Attributes prop.
-
Out-of-date generated code. An application that is not refreshed keeps showing the old design, so a schedule or a notification from the Webflow side is part of the process.
Security notes
-
The API token is a credential: store it as a CI secret, pass it through
WEBFLOW_API_TOKENand never commit a.envfile. Prefer a Site token for a single-site export when possible, since a Workspace token covers every site in the workspace. -
Review the generated diff like any third-party code. Rich text and Code Embed content come from the Webflow site, so a script in a Code Embed inside an exported component runs in the application’s origin. Restrict who can edit exported Components accordingly.
-
Rich text props accept markup. Pass only sanitized content, never raw user input from the kiosk’s feedback form.
-
Run the export from a trusted CI job with the least permission needed: it needs read access to the repository, write access to open a pull request and the Webflow token, and nothing else.
-
The session file of a browser login is a credential too, described in Webflow CLI.
Choosing DevLink export
Use DevLink export when a design system lives in Webflow and an application must render the same components with its own routing and data: a kiosk, a customer portal, a documentation site, or a Next.js application that mixes marketing components with product screens. Prefer code export for a static snapshot of a whole site, a headless approach with the Content Delivery API when only the content is needed and the design is built in code, and code components when the direction is the opposite, React written by developers and used by designers on the canvas. The related pages are Code Export and Portability, Hybrid Apps, Code Components and Reverse Proxy and Custom Hosting, which covers serving an application next to a Webflow site on one domain. Webflow also offers a DevLink template in its Made in Webflow gallery that comes with components and styles already prepared for export.