agentsclimarketplace

Webflow reference architecture

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/webflow-pack/skills/webflow-reference-architecture

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill webflow-reference-architecture

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

Implement Webflow reference architecture \u2014 layered project structure,\ \ client wrapper, CMS sync service, webhook handlers, and caching layer for production\ \ integrations. Trigger with phrases like "webflow architecture", "webflow project\ \ structure", "how to organize webflow", "webflow integration design", "\ webflow best practices".

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

12.7 KB, as published. Nobody here has run it

Webflow Reference Architecture

Overview

Production-ready architecture for Webflow Data API v2 integrations. Layered design separating API access, business logic, caching, and webhook handling.

Prerequisites

  • TypeScript 5+ project
  • webflow-api SDK (v3.x)
  • Understanding of service-oriented architecture
  • Redis (optional, for distributed caching)

Project Structure

my-webflow-project/
├── src/
│   ├── webflow/                     # Webflow API layer
│   │   ├── client.ts                # WebflowClient singleton
│   │   ├── types.ts                 # TypeScript types for Webflow resources
│   │   ├── errors.ts                # Custom error classes
│   │   └── cache.ts                 # Response caching (LRU/Redis)
│   ├── services/                    # Business logic layer
│   │   ├── cms.service.ts           # CMS content management
│   │   ├── ecommerce.service.ts     # Products, orders, inventory
│   │   ├── forms.service.ts         # Form submission processing
│   │   └── sync.service.ts          # External data sync
│   ├── webhooks/                    # Event handling layer
│   │   ├── router.ts                # Event type routing
│   │   ├── handlers/
│   │   │   ├── form-submission.ts
│   │   │   ├── cms-item-changed.ts
│   │   │   └── ecomm-new-order.ts
│   │   └── middleware.ts            # Signature verification
│   ├── api/                         # HTTP endpoints
│   │   ├── health.ts
│   │   ├── webhooks.ts
│   │   └── content.ts
│   └── config/
│       └── webflow.ts               # Environment-aware config
├── tests/
│   ├── unit/
│   │   ├── services/
│   │   └── webhooks/
│   └── integration/
│       └── webflow.integration.test.ts
├── .env.example
├── tsconfig.json
└── package.json

Layer Architecture

┌──────────────────────────────────────────────────┐
│                  API Layer                        │
│   Express routes, webhook endpoints, health      │
├──────────────────────────────────────────────────┤
│               Service Layer                       │
│   CMS sync, ecommerce, form processing           │
│   (Business logic, orchestration)                 │
├──────────────────────────────────────────────────┤
│             Webflow Client Layer                  │
│   WebflowClient wrapper, error handling, types    │
├──────────────────────────────────────────────────┤
│           Infrastructure Layer                    │
│   Cache (LRU/Redis), queue (p-queue), monitoring  │
└──────────────────────────────────────────────────┘

Instructions

Layer 1: Webflow Client

// src/webflow/client.ts
import { WebflowClient } from "webflow-api";
import { getConfig } from "../config/webflow.js";

let client: WebflowClient | null = null;

export function getClient(): WebflowClient {
  if (!client) {
    const config = getConfig();
    client = new WebflowClient({
      accessToken: config.accessToken,
      maxRetries: config.maxRetries,
    });
  }
  return client;
}

export function resetClient(): void {
  client = null;
}
// src/webflow/errors.ts
export class WebflowServiceError extends Error {
  constructor(
    message: string,
    public readonly statusCode: number,
    public readonly retryable: boolean,
    public readonly originalError?: unknown
  ) {
    super(message);
    this.name = "WebflowServiceError";
  }

  static fromApiError(error: any): WebflowServiceError {
    const status = error.statusCode || error.status || 500;
    const retryable = status === 429 || status >= 500;

    return new WebflowServiceError(
      error.message || "Unknown Webflow error",
      status,
      retryable,
      error
    );
  }
}
// src/webflow/types.ts
export interface WebflowSite {
  id: string;
  displayName: string;
  shortName: string;
  lastPublished: string | null;
  customDomains?: Array<{ url: string }>;
}

export interface WebflowCollection {
  id: string;
  displayName: string;
  slug: string;
  itemCount: number;
  fields: WebflowField[];
}

export interface WebflowField {
  slug: string;
  displayName: string;
  type: string;
  isRequired: boolean;
}

export interface WebflowItem {
  id: string;
  isDraft: boolean;
  isArchived: boolean;
  createdOn: string;
  lastUpdated: string;
  fieldData: Record<string, any>;
}

Layer 2: Service Layer

// src/services/cms.service.ts
import { getClient } from "../webflow/client.js";
import { WebflowServiceError } from "../webflow/errors.js";
import { cachedFetch, invalidateCache } from "../webflow/cache.js";
import type { WebflowItem } from "../webflow/types.js";

export class CmsService {
  private webflow = getClient();

  async getCollections(siteId: string) {
    return cachedFetch(
      `collections:${siteId}`,
      () => this.webflow.collections.list(siteId).then(r => r.collections!),
      30 * 60 * 1000 // 30 min — schemas change rarely
    );
  }

  async getPublishedItems(collectionId: string): Promise<WebflowItem[]> {
    // CDN-cached — no rate limit
    return cachedFetch(
      `items:live:${collectionId}`,
      () => this.webflow.collections.items.listItemsLive(collectionId, { limit: 100 })
        .then(r => r.items as WebflowItem[]),
      60 * 1000 // 1 min
    );
  }

  async createItems(
    collectionId: string,
    items: Array<{ fieldData: Record<string, any> }>
  ): Promise<string[]> {
    try {
      const result = await this.webflow.collections.items.createItemsBulk(
        collectionId,
        { items: items.map(i => ({ ...i, isDraft: false })) }
      );
      // Invalidate cache after write
      invalidateCache(`items:live:${collectionId}`);
      return result.items!.map(i => i.id!);
    } catch (error) {
      throw WebflowServiceError.fromApiError(error);
    }
  }

  async publishItems(collectionId: string, itemIds: string[]): Promise<void> {
    await this.webflow.collections.items.publishItem(collectionId, { itemIds });
    invalidateCache(`items:live:${collectionId}`);
  }
}
// src/services/sync.service.ts
import { CmsService } from "./cms.service.js";

export class SyncService {
  constructor(private cms: CmsService) {}

  async syncFromExternal(
    collectionId: string,
    externalData: Array<{ title: string; body: string; slug: string }>
  ) {
    // Get existing items to avoid duplicates
    const existing = await this.cms.getPublishedItems(collectionId);
    const existingSlugs = new Set(existing.map(i => i.fieldData?.slug));

    // Filter new items
    const newItems = externalData
      .filter(d => !existingSlugs.has(d.slug))
      .map(d => ({
        fieldData: {
          name: d.title,
          slug: d.slug,
          "post-body": d.body,
        },
      }));

    if (newItems.length === 0) return { synced: 0 };

    // Bulk create (100 at a time)
    const createdIds = await this.cms.createItems(collectionId, newItems.slice(0, 100));

    // Publish new items
    await this.cms.publishItems(collectionId, createdIds);

    return { synced: createdIds.length };
  }
}

Layer 3: Webhook Handling

// src/webhooks/router.ts
import { handleFormSubmission } from "./handlers/form-submission.js";
import { handleCmsItemChanged } from "./handlers/cms-item-changed.js";
import { handleNewOrder } from "./handlers/ecomm-new-order.js";

type Handler = (payload: any) => Promise<void>;

const handlers: Record<string, Handler> = {
  form_submission: handleFormSubmission,
  collection_item_created: handleCmsItemChanged,
  collection_item_changed: handleCmsItemChanged,
  ecomm_new_order: handleNewOrder,
};

export async function routeWebhookEvent(
  triggerType: string,
  payload: any
): Promise<void> {
  const handler = handlers[triggerType];
  if (!handler) {
    console.log(`No handler for: ${triggerType}`);
    return;
  }
  await handler(payload);
}
// src/webhooks/handlers/cms-item-changed.ts
import { invalidateCache } from "../../webflow/cache.js";

export async function handleCmsItemChanged(payload: any): Promise<void> {
  const { collectionId, itemId } = payload;

  // Invalidate cache for this collection
  invalidateCache(`items:live:${collectionId}`);
  invalidateCache(`items:staged:${collectionId}`);

  // Trigger downstream updates (search index, external DB, etc.)
  console.log(`CMS item changed: ${itemId} in collection ${collectionId}`);
}

Layer 4: Configuration

// src/config/webflow.ts
interface WebflowConfig {
  accessToken: string;
  siteId: string;
  maxRetries: number;
  environment: "development" | "staging" | "production";
  webhookSecret: string;
}

export function getConfig(): WebflowConfig {
  const env = (process.env.NODE_ENV || "development") as WebflowConfig["environment"];

  return {
    accessToken: requireEnv("WEBFLOW_API_TOKEN"),
    siteId: requireEnv("WEBFLOW_SITE_ID"),
    maxRetries: env === "production" ? 3 : 1,
    environment: env,
    webhookSecret: process.env.WEBFLOW_WEBHOOK_SECRET || "",
  };
}

function requireEnv(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`${name} environment variable required`);
  return value;
}

Data Flow

External Data Source
       │
       ▼
┌─────────────────┐     ┌─────────────┐
│  Sync Service   │────▶│  CMS Service │
│  (orchestration)│     │  (CRUD ops)  │
└─────────────────┘     └──────┬───────┘
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
            ┌──────────────┐    ┌────────────────┐
            │ Cache (LRU)  │    │ Webflow Client │
            │ or Redis     │    │ (webflow-api)  │
            └──────────────┘    └───────┬────────┘
                                        │
                                        ▼
                               ┌────────────────┐
                               │ Webflow API v2 │
                               │ api.webflow.com│
                               └────────────────┘

Output

  • Layered project structure with clear boundaries
  • WebflowClient wrapper with singleton and error handling
  • CMS service with caching and bulk operations
  • Webhook event router with typed handlers
  • Environment-aware configuration
  • Sync service for external data integration

Error Handling

IssueCauseSolution
Circular importsWrong layer dependenciesServices depend on client, not reverse
Cache inconsistencyMissing invalidationInvalidate on writes and webhook events
Config missingEnvironment not setrequireEnv() fails fast with clear message
Type mismatchesAPI shape changesUpdate types.ts from collection schema

Resources

Next Steps

For multi-environment setup, see webflow-multi-env-setup.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.