Using Framer Alongside a Development Workflow, Not Instead of One
TIL that building the Clonify Framer plugin taught me Framer is a genuinely different target than a normal web build — its plugin runtime and code-component model don't map 1:1 onto React conventions.
The Problem #
Building the Clonify Framer plugin meant writing code that runs inside Framer's own plugin environment — not a standalone React app — and I initially approached it like any other Vite + React + TypeScript project, assuming the differences would be minor.
Context #
Framer plugins run inside Framer's canvas, with access to a plugin API for reading/writing the user's project (layers, components, assets) that has no equivalent in a normal web app's runtime.
What I Tried #
Wrote the plugin's core logic assuming standard DOM APIs and browser storage would behave the same way inside Framer's plugin iframe as they would in a regular page.
What Went Wrong #
Some browser APIs I reached for out of habit (certain storage and window-level APIs) behaved differently or were restricted inside the plugin's sandboxed iframe context, which meant code that worked in isolated local testing didn't behave identically once actually running inside Framer.
The Solution #
Built and tested directly against Framer's plugin API and its documented constraints from early on, rather than developing the logic standalone and porting it in afterward — treating "does this work inside the actual plugin runtime" as a first-class check on every feature, not a final integration step.
// Framer's plugin API, not a generic browser API —
// this is the actual interface the plugin runtime provides
import { framer } from "framer-plugin";
const selection = await framer.getSelection();Why It Works #
A plugin runtime is a distinct execution environment with its own capabilities and restrictions, similar in spirit to a browser extension's isolated world — code that "should" work because it's valid JavaScript can still behave differently because the surrounding sandbox is different. Developing directly against the real API surface catches that gap immediately instead of at integration time.
Lessons Learned #
"It's just React and TypeScript" undersold how much the actual runtime environment matters. The language is the same; the platform contract underneath it isn't, and that's where the real learning curve was.
What I Would Do Differently #
I'd prototype the riskiest platform-specific pieces (storage, selection APIs) against the real Framer plugin runtime before investing in the rest of the plugin's architecture, rather than assuming standard web patterns would transfer cleanly.
Related Concepts #
Plugin sandbox environments, platform-specific API surfaces, developing against the real runtime vs. a standalone approximation.
Related content
Designing a Dispatch Management Dashboard for Real Operations, Not a Generic Admin Panel
TIL that a dispatch dashboard built around 'show all the order fields' overwhelmed the person actually using it — the redesign that worked started from what a dispatcher needs to decide, not what data exists.
Turning Figma Designs Into Reusable React Components
TIL that building each screen straight from its Figma frame produced pixel-accurate but unreusable components — the fix was designing the component API from the design system's tokens, not from any one screen.
useDebounce hook
A small generic hook for debouncing a fast-changing value — search inputs, resize handlers, anything you don't want firing on every keystroke.