From webflow-pack
Configures Webflow Data API v2 for dev, staging, prod environments with per-env tokens, site IDs, and Vault/AWS/GCP secret management. Includes TypeScript config loader.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin webflow-packThis skill is limited to using the following tools:
Configure Webflow Data API v2 integrations across development, staging, and production
Deploys Webflow Data API integrations to Vercel, Fly.io, and Google Cloud Run with secrets management, webhook handling, and platform configs.
Configures Vercel projects for development, preview, and production environments with scoped secrets using CLI and REST API.
Initializes, builds, and deploys Webflow Cloud applications via CLI with non-interactive flags, mount path validation, and package manager detection. Use for Webflow project creation and deployment.
Share bugs, ideas, or general feedback.
Configure Webflow Data API v2 integrations across development, staging, and production with separate API tokens, site IDs, and secret management. Each environment targets a different Webflow site for complete isolation.
| Environment | Webflow Site | API Token | CMS Data | Purpose |
|---|---|---|---|---|
| Development | Dev site | Dev token | Test/seed data | Local development |
| Staging | Staging site | Staging token | Copy of prod data | Pre-prod validation |
| Production | Production site | Prod token | Real data | Live traffic |
// src/config/webflow.ts
type Environment = "development" | "staging" | "production";
interface WebflowEnvConfig {
accessToken: string;
siteId: string;
maxRetries: number;
webhookSecret: string;
debug: boolean;
}
function detectEnvironment(): Environment {
const env = process.env.NODE_ENV || "development";
const valid: Environment[] = ["development", "staging", "production"];
return valid.includes(env as Environment) ? (env as Environment) : "development";
}
export function getWebflowConfig(): WebflowEnvConfig {
const env = detectEnvironment();
// Each environment has its own token and site
const configs: Record<Environment, () => WebflowEnvConfig> = {
development: () => ({
accessToken: requireEnv("WEBFLOW_API_TOKEN"),
siteId: requireEnv("WEBFLOW_SITE_ID"),
maxRetries: 1,
webhookSecret: process.env.WEBFLOW_WEBHOOK_SECRET || "",
debug: true,
}),
staging: () => ({
accessToken: requireEnv("WEBFLOW_API_TOKEN_STAGING"),
siteId: requireEnv("WEBFLOW_SITE_ID_STAGING"),
maxRetries: 2,
webhookSecret: requireEnv("WEBFLOW_WEBHOOK_SECRET_STAGING"),
debug: false,
}),
production: () => ({
accessToken: requireEnv("WEBFLOW_API_TOKEN_PROD"),
siteId: requireEnv("WEBFLOW_SITE_ID_PROD"),
maxRetries: 3,
webhookSecret: requireEnv("WEBFLOW_WEBHOOK_SECRET_PROD"),
debug: false,
}),
};
const config = configs[env]();
console.log(`[webflow] Environment: ${env}, Site: ${config.siteId.substring(0, 8)}...`);
return config;
}
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required env var: ${name}`);
return value;
}
# .env.development (local, git-ignored)
WEBFLOW_API_TOKEN=dev-token-here
WEBFLOW_SITE_ID=dev-site-id-here
WEBFLOW_WEBHOOK_SECRET=dev-webhook-secret
# .env.staging (stored in CI/deployment platform, never committed)
WEBFLOW_API_TOKEN_STAGING=staging-token-here
WEBFLOW_SITE_ID_STAGING=staging-site-id-here
WEBFLOW_WEBHOOK_SECRET_STAGING=staging-webhook-secret
# .env.production (stored in vault/secret manager, never committed)
WEBFLOW_API_TOKEN_PROD=prod-token-here
WEBFLOW_SITE_ID_PROD=prod-site-id-here
WEBFLOW_WEBHOOK_SECRET_PROD=prod-webhook-secret
# .env.example (committed to repo, no real values)
WEBFLOW_API_TOKEN=your-token-here
WEBFLOW_SITE_ID=your-site-id-here
WEBFLOW_WEBHOOK_SECRET=your-webhook-secret
# Store secrets
aws secretsmanager create-secret \
--name webflow/production \
--secret-string '{
"WEBFLOW_API_TOKEN": "prod-token",
"WEBFLOW_SITE_ID": "prod-site-id",
"WEBFLOW_WEBHOOK_SECRET": "prod-webhook-secret"
}'
# Retrieve in application
aws secretsmanager get-secret-value \
--secret-id webflow/production \
--query SecretString --output text | jq .
// Load from AWS at startup
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
async function loadWebflowSecrets(env: string) {
const client = new SecretsManagerClient({});
const command = new GetSecretValueCommand({
SecretId: `webflow/${env}`,
});
const response = await client.send(command);
const secrets = JSON.parse(response.SecretString!);
process.env.WEBFLOW_API_TOKEN_PROD = secrets.WEBFLOW_API_TOKEN;
process.env.WEBFLOW_SITE_ID_PROD = secrets.WEBFLOW_SITE_ID;
}
# Store secrets
echo -n "prod-token" | gcloud secrets create webflow-api-token-prod --data-file=-
echo -n "prod-site-id" | gcloud secrets create webflow-site-id-prod --data-file=-
# Use in Cloud Run
gcloud run deploy webflow-service \
--set-secrets="WEBFLOW_API_TOKEN_PROD=webflow-api-token-prod:latest,WEBFLOW_SITE_ID_PROD=webflow-site-id-prod:latest"
# Store secrets
vault kv put secret/webflow/production \
api_token="prod-token" \
site_id="prod-site-id" \
webhook_secret="prod-webhook-secret"
# Retrieve
vault kv get -field=api_token secret/webflow/production
Prevent destructive operations in wrong environment:
function requireProduction(operation: string): void {
const config = getWebflowConfig();
if (config.debug) {
throw new Error(
`${operation} is production-only. Current: ${detectEnvironment()}`
);
}
}
function blockProduction(operation: string): void {
const env = detectEnvironment();
if (env === "production") {
throw new Error(
`${operation} is blocked in production. Use staging for testing.`
);
}
}
// Usage
async function publishSite(siteId: string) {
requireProduction("publishSite"); // Only in production
await webflow.sites.publish(siteId, { publishToWebflowSubdomain: true });
}
async function deleteAllItems(collectionId: string) {
blockProduction("deleteAllItems"); // Never in production
const { items } = await webflow.collections.items.listItems(collectionId);
const ids = items!.map(i => i.id!);
await webflow.collections.items.deleteItemsBulk(collectionId, { itemIds: ids });
}
# .github/workflows/webflow-deploy.yml
name: Deploy Webflow Integration
on:
push:
branches: [main, staging]
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- branch: staging
env: staging
token_secret: WEBFLOW_API_TOKEN_STAGING
site_secret: WEBFLOW_SITE_ID_STAGING
- branch: main
env: production
token_secret: WEBFLOW_API_TOKEN_PROD
site_secret: WEBFLOW_SITE_ID_PROD
if: github.ref == format('refs/heads/{0}', matrix.branch)
environment: ${{ matrix.env }}
env:
NODE_ENV: ${{ matrix.env }}
WEBFLOW_API_TOKEN: ${{ secrets[matrix.token_secret] }}
WEBFLOW_SITE_ID: ${{ secrets[matrix.site_secret] }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npm test
- name: Verify Webflow connectivity
run: |
curl -sf -H "Authorization: Bearer $WEBFLOW_API_TOKEN" \
https://api.webflow.com/v2/sites/$WEBFLOW_SITE_ID > /dev/null
- run: npm run deploy
async function environmentReport() {
for (const env of ["development", "staging", "production"]) {
try {
process.env.NODE_ENV = env;
const config = getWebflowConfig();
const webflow = new WebflowClient({ accessToken: config.accessToken });
const site = await webflow.sites.get(config.siteId);
console.log(`[${env}] ${site.displayName} — last published: ${site.lastPublished}`);
} catch (error: any) {
console.log(`[${env}] ERROR: ${error.message}`);
}
}
}
| Issue | Cause | Solution |
|---|---|---|
| Wrong site in production | Token/site mismatch | Verify WEBFLOW_SITE_ID matches expected site |
| Secret not found | Wrong path/name | Check secret manager path matches env |
| Guard triggered | Running prod operation in dev | Set NODE_ENV correctly |
| Missing env var | .env not loaded | dotenv.config({ path: ".env.development" }) |
For observability setup, see webflow-observability.