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.

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.

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 webflow folder

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.

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

@webflow/webflow-cli v1.19.0 or later is the documented floor for export; this page uses 2.15.0, which needs Node.js 22.13.0 or later.

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 WEBFLOW_API_TOKEN for CI. A Site token covers one site, and a Workspace token covers every site in the workspace.

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.

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.

flowchart TD DESIGN["Designer: Components, Style Variants, props, slots, variables, interactions"] --> RUN["webflow devlink export"] CONFIG["webflow.json: siteId and devlink-export rules"] --> RUN AUTH["Browser login or WEBFLOW_API_TOKEN"] --> RUN RUN --> FETCH["Fetch components and styles of the site"] FETCH --> FILTER["Filter with components and componentGroups regular expressions"] FILTER --> RENAME["Sanitize names: PascalCase components, camelCase props"] RENAME --> GEN["Generate React files, stylesheets and engine modules"] GEN --> OUT["webflow folder: components, css, webflow_modules, DevLinkProvider"] GEN --> WARN["Warnings and @warning tags for filtered attributes and unsupported actions"] OUT --> APP["Application: import global.css, wrap the root in DevLinkProvider, use components"] APP --> BUILD["Build and deploy the application on its own hosting"] DESIGN -. "change in Webflow" .-> RUN

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.

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

PlantCard

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

EventBanner

A strip announcing the next guided walk

Text props for title and date, a Link prop for tickets

SiteHeader

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).

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

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

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 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": "/"
  }
}
Option Meaning Default

siteId

The Webflow site ID. Top level, not inside devlink-export.

WEBFLOW_SITE_ID from the environment

rootDir

The directory the components are exported into.

./webflow

cssScopes

Wraps component styles in CSS @scope so Webflow styles do not leak into the application.

true

ts

Generates TypeScript files. When it is false, the files are emitted as .jsx instead of .tsx.

Auto-detected from tsconfig.json and .ts or .tsx files on the first export

components

A regular expression that selects the Components to export.

.*

componentGroups

A regular expression that selects the Component groups to export.

.*

relativeHrefRoot

How relative href values are rewritten in exported Link and Image components.

/

camelCaseVariantNames

Converts Style Variant names to camelCase for the variant prop.

false

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"
  }
}

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 #fragment and CMS page links become /<slug>. This is the default when the CLI writes the configuration.

A base URL string such as https://fernbrook.example

The base URL is prepended to internal paths, after a trailing slash is stripped.

false

Internal links are not rewritten and render as href="#" placeholders. Use it when the application supplies its own link rendering through a custom renderLink passed to DevLinkProvider.

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.

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.

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 <token>

API token used for the export.

WEBFLOW_API_TOKEN from .env or the environment, or the OAuth session of a browser login

-s, --site <site-id>

Site ID to export from.

siteId in webflow.json, then WEBFLOW_SITE_ID from .env

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

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

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_CLASS through CSS @scope;

  • nested elements, rendered as JSX built from primitives such as Block, Link and Image;

  • 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}
    />
  );
}

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-icons and the w-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.

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>
  );
}

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/*"]
    }
  }
}

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.ts defines DEVLINK_SCOPE_CLASS;

  • the root element of each component is wrapped in a div that carries that class;

  • the category stylesheets are wrapped in @scope blocks 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.

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 UnknownComponent prefix, so 123 becomes UnknownComponent123 and class becomes UnknownComponentClass. An empty name becomes UnknownComponent. Duplicates get numeric suffixes, as in MyComponent and MyComponent2.

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 unknownProp prefix. A reserved word gets a Prop suffix, so class becomes classProp and for becomes forProp. Duplicates get numeric suffixes. data-* and aria-* names are preserved. An empty name becomes unknownProp.

Variant names

By default they match the Webflow names exactly. With camelCaseVariantNames they become camelCase; a reserved word gets a Variant suffix, a name that starts with a number becomes unknownVariant and duplicates get numeric suffixes.

Custom attributes

HTML5 names are converted to React casing, so tabindex becomes tabIndex and maxlength becomes maxLength, and React equivalents are substituted, so class becomes className and for becomes htmlFor. Event handlers such as onClick, attributes that start with a number, incomplete data- or aria- attributes, empty names, names with invalid characters and other invalid HTML5 attributes are filtered out.

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}
    />
  );
}

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,
      }}
    />
  );
}

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.

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} />}
    />
  );
}

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" />
    </>
  );
}

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
        },
      }}
    />
  );
}

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>
  );
}

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.

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.

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}
            />
          ))}
        </>
      }
    />
  );
}

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>

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" />

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 DevLinkProvider and the global CSS in app/layout.tsx, as in the layout above. The Pages Router does the same in pages/_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 place DevLinkProvider in 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 renderLink and renderImage props of DevLinkProvider, 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, ~/webflow or a relative path, avoid duplicate CSS imports with the framework’s recommended approach, and include the directory’s TypeScript files in tsconfig.json.

  • Other frameworks. Remix imports global.css in app/root.tsx and passes loader data as props; Gatsby adds DevLinkProvider to both gatsby-browser.tsx and gatsby-ssr.tsx and guards interactive components for hydration.

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>
  );
}

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:

  1. The designer edits the Component in the Designer and publishes the site if the team’s practice requires it.

  2. The developer runs webflow devlink export and the folder is refreshed. The quickstart states that the run refreshes the files in webflow and leaves application code outside that folder untouched.

  3. The developer reads the CLI warnings and the @warning tags, then reviews the diff of the generated files.

  4. The change is committed together with any application change it forces, such as a renamed prop or variant.

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

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

@webflow/webflow-cli v1.19.0 or later

Node.js

Earlier versions

v22 or higher

API token variable

WEBFLOW_SITE_API_TOKEN

WEBFLOW_API_TOKEN

Workspace token variable

WEBFLOW_WORKSPACE_API_TOKEN

Accepted but deprecated; rename it

Site ID

WEBFLOW_SITE_ID environment variable

Top-level siteId in webflow.json; the variable is still supported

Configuration block

devlink

devlink-export

Output folder

./devlink

./webflow

Style isolation

cssModules, CSS Modules

cssScopes, CSS @scope

TypeScript

Not a listed option

ts

Component selection

Not a listed option

components and componentGroups

Removed options

skipTagSelectors, envVariables, overwriteModule, fileExtensions

No replacement

Kept option

relativeHrefRoot

relativeHrefRoot, unchanged

Path alias

devlink/*

webflow/*

Slots

DevLink Slots

The native Slot element

Props

Runtime Props

The Attributes prop

Steps

  1. 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.

  2. Update the environment. Rename WEBFLOW_SITE_API_TOKEN to WEBFLOW_API_TOKEN, and rename the workspace token variable too, since it still works but triggers a deprecation warning. WEBFLOW_SITE_ID still works, but the canonical place is the top-level siteId of webflow.json. If authentication still fails, run webflow auth login --force, and if the failure mentions the site, check that the site ID belongs to a site the token can access.

  3. Update webflow.json. Either run webflow devlink export and follow the prompts, or edit by hand: add a top-level siteId, replace the devlink block with a devlink-export block and delete the old devlink block manually. The CLI ignores the old block but does not remove it.

  4. Update the path alias. If the default root directory was used, change the alias in tsconfig.json from devlink/* to webflow/*.

  5. Update the imports. Change imports and CSS references from devlink/... to webflow/..., including the CSS file and DevLinkProvider.

  6. Re-run the export. webflow devlink export regenerates the webflow folder from the live components.

  7. 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.

  8. Verify. Build the application and confirm that the components keep their styles, fonts and interactions.

A before-and-after of the Fernbrook kiosk configuration shows the shape of step 3:

{
  "devlink": {
    "rootDir": "./devlink",
    "cssModules": true,
    "relativeHrefRoot": "/"
  }
}

becomes

{
  "siteId": "REPLACE_WITH_SITE_ID",
  "devlink-export": {
    "rootDir": "./webflow",
    "cssScopes": true,
    "relativeHrefRoot": "/"
  }
}

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

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-export do not match it, or it uses an unsupported element.

  • Missing styles. css/global.css is not imported, DevLinkProvider or DevLinkFontTags is 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.json files. 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_TOKEN and never commit a .env file. 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.

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.