Skip to main content

Funnels Controller

Path: internal/api/v1/funnels/controllers/funnels.controller.js
Service: funnelsService
Module: Funnels


Overview

The Funnels controller is the core controller for managing multi-step marketing funnels. It handles funnel CRUD operations, step management, component versioning, form submissions, preview generation, and Cloudflare edge caching integration.

Key Capabilities

  • Funnel Management: Create, read, update, delete funnels
  • Step Management: Add, update, reorder, delete funnel steps
  • Component System: Save/retrieve builder components for each step
  • Version Control: Backup and rollback system for step content
  • Page Settings: SEO, tracking codes, custom CSS per step
  • Form Submissions: Collect and export form data from funnels
  • Domain Integration: Connect custom domains to funnels
  • Preview System: Generate preview links with URL shortening
  • Cloudflare Edge: Edge-cached content delivery with hash verification
  • Cache Management: Purge Cloudflare cache on content updates

Methods

getFunnels()

Lists all funnels for an account with filtering, search, and pagination.

Route: GET /v1/funnels
Auth: Required (account-scoped)

Request Query Parameters

{
limit?: number; // Results per page (default: 10)
page?: number; // Page number
search?: string; // Search funnel names
status?: string; // Filter by status (active/inactive)
tags?: string; // Filter by tag IDs (comma-separated)
domain_connected?: string; // Filter by domain connection status
}

Response

{
"success": true,
"message": "SUCCESS",
"data": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"name": "Lead Generation Funnel",
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"base_path": "/landing",
"status": "active",
"favicon": "https://cdn.example.com/favicon.ico",
"tags": ["60d5ec49f1b2c72e8c8e4b1c"],
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Home Page",
"path": "/",
"order": 1
}
],
"header_tracking_code": "<script>...</script>",
"footer_tracking_code": "<script>...</script>",
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"created_at": "2024-01-15T10:30:00Z"
}
],
"pagination": {
"total": 45,
"page": 1,
"limit": 10,
"pages": 5
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfind with filtersQuery funnels by account, status, tags
funnel_tagslookupJoin tag names
funnel_domainslookupJoin domain info

getFunnelsFilters()

Retrieves available filter options for funnels (status, tags).

Route: GET /v1/funnels/filters
Auth: Required (account-scoped)

Request Query Parameters

{
status?: string; // Include status options
tags?: string; // Include tag options
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"statuses": ["active", "inactive", "draft"],
"tags": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1c",
"name": "E-commerce",
"count": 12
},
{
"_id": "60d5ec49f1b2c72e8c8e4b1e",
"name": "Lead Gen",
"count": 8
}
]
}
}

getFunnel()

Retrieves a single funnel by ID with complete details.

Route: GET /v1/funnels/:id
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID (MongoDB ObjectId)
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"name": "Lead Generation Funnel",
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"domain": {
"_id": "60d5ec49f1b2c72e8c8e4b1b",
"name": "example.com",
"ssl_enabled": true
},
"base_path": "/landing",
"status": "active",
"favicon": "https://cdn.example.com/favicon.ico",
"tags": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1c",
"name": "E-commerce"
}
],
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Home Page",
"path": "/",
"type": "page",
"order": 1,
"seo": {
"title": "Welcome - Best Products",
"description": "Shop the best products online",
"image": "https://cdn.example.com/og-image.jpg"
}
},
{
"_id": "60d5ec49f1b2c72e8c8e4b1f",
"name": "Thank You",
"path": "/thank-you",
"type": "page",
"order": 2
}
],
"header_tracking_code": "<script>gtag('config', 'GA-XXX');</script>",
"footer_tracking_code": "<script>fbq('track', 'PageView');</script>",
"live_chat_widget": {
"enabled": true,
"provider": "intercom",
"config": {}
},
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-20T14:22:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneRetrieve funnel by ID and account
funnel_domainslookupPopulate domain details
funnel_tagslookupPopulate tag details

validatePath()

Validates if a funnel path is available (no conflicts with existing funnels on the same domain).

Route: POST /v1/funnels/validate-path
Auth: Required

Request Body

{
"path": "/new-landing-page",
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a" // Optional: exclude current funnel
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"available": true,
"path": "/new-landing-page"
}
}

Conflict Response

{
"success": false,
"message": "Path already in use",
"data": {
"available": false,
"path": "/new-landing-page",
"conflicting_funnel": {
"_id": "60d5ec49f1b2c72e8c8e4b20",
"name": "Existing Funnel"
}
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindCheck for path conflicts on domain

createFunnel()

Creates a new funnel with initial configuration.

Route: POST /v1/funnels
Auth: Required (account-scoped)

Request Body

{
"name": "Product Launch Funnel",
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"base_path": "/launch",
"status": "active",
"favicon": "https://cdn.example.com/favicon.ico",
"tags": ["60d5ec49f1b2c72e8c8e4b1c"],
"steps": [
{
"name": "Landing Page",
"path": "/",
"type": "page",
"order": 1
}
],
"header_tracking_code": "<script>gtag('config', 'GA-XXX');</script>",
"footer_tracking_code": "<script>fbq('track', 'PageView');</script>"
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b21",
"name": "Product Launch Funnel",
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"base_path": "/launch",
"status": "active",
"favicon": "https://cdn.example.com/favicon.ico",
"tags": ["60d5ec49f1b2c72e8c8e4b1c"],
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b22",
"name": "Landing Page",
"path": "/",
"type": "page",
"order": 1
}
],
"header_tracking_code": "<script>gtag('config', 'GA-XXX');</script>",
"footer_tracking_code": "<script>fbq('track', 'PageView');</script>",
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"created_at": "2024-01-22T09:15:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsinsertOneCreate new funnel document
funnel_stepsinsertManyCreate initial steps

duplicateFunnel()

Duplicates an existing funnel with all steps and settings.

Route: POST /v1/funnels/:id/duplicate
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID to duplicate
}

Request Query Parameters

{
subaccountid?: string; // Optional: duplicate to subaccount
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b23",
"name": "Lead Generation Funnel (Copy)",
"domain_id": null,
"base_path": "/landing-copy",
"status": "inactive",
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b24",
"name": "Home Page (Copy)",
"path": "/",
"order": 1
}
],
"account_id": "60d5ec49f1b2c72e8c8e4b19",
"created_at": "2024-01-22T10:00:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneRetrieve source funnel
funnelsinsertOneCreate duplicate funnel
funnel_stepsfindRetrieve source steps
funnel_stepsinsertManyCreate duplicate steps
funnel_step_componentsfindCopy builder components
funnel_step_componentsinsertManyCreate duplicate components

Business Logic

  • Duplicates entire funnel structure including all steps
  • Copies all builder components and settings
  • Appends " (Copy)" to funnel name
  • Sets status to "inactive" for safety
  • Clears domain connection (must be reconnected manually)
  • If subaccountid provided, duplicates to different account

duplicateFunnelStep()

Duplicates a single step within a funnel.

Route: POST /v1/funnels/:id/steps/:step_id/duplicate
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID to duplicate
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b25",
"name": "Home Page (Copy)",
"path": "/home-copy",
"type": "page",
"order": 2,
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"created_at": "2024-01-22T10:15:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsfindOneRetrieve source step
funnel_stepsinsertOneCreate duplicate step
funnel_step_componentsfindOneCopy builder components
funnel_step_componentsinsertOneCreate duplicate components

updateFunnel()

Updates funnel settings and metadata.

Route: PUT /v1/funnels/:id
Auth: Required (account-scoped)
Special: Admin check required if updating category (templates)

Request Parameters

{
id: string; // Funnel ID
}

Request Body

{
"name": "Updated Funnel Name",
"status": "active",
"favicon": "https://cdn.example.com/new-favicon.ico",
"tags": ["60d5ec49f1b2c72e8c8e4b1c", "60d5ec49f1b2c72e8c8e4b1e"],
"header_tracking_code": "<script>gtag('config', 'GA-NEW');</script>",
"footer_tracking_code": "<script>fbq('track', 'PageView');</script>",
"live_chat_widget": {
"enabled": true,
"provider": "intercom",
"config": {
"app_id": "abc123"
}
},
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"conflicts": false,
"category": "60d5ec49f1b2c72e8c8e4b26"
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"name": "Updated Funnel Name",
"status": "active",
"favicon": "https://cdn.example.com/new-favicon.ico",
"tags": ["60d5ec49f1b2c72e8c8e4b1c", "60d5ec49f1b2c72e8c8e4b1e"],
"header_tracking_code": "<script>gtag('config', 'GA-NEW');</script>",
"footer_tracking_code": "<script>fbq('track', 'PageView');</script>",
"live_chat_widget": {
"enabled": true,
"provider": "intercom",
"config": {
"app_id": "abc123"
}
},
"domain_id": "60d5ec49f1b2c72e8c8e4b1b",
"category": "60d5ec49f1b2c72e8c8e4b26",
"updated_at": "2024-01-22T11:00:00Z",
"updated_by": "60d5ec49f1b2c72e8c8e4b27"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneAndUpdateUpdate funnel document
admin_permissionsfindOneVerify admin permissions if category update

Authorization

  • Standard updates: Account ownership check
  • Category updates: Requires admin permission check via adminAppCheck('funnel', 'templates', 'edit', true)
  • Records user_id in updated_by field for audit trail

updateFunnelStepOrders()

Reorders steps within a funnel (drag-and-drop support).

Route: PUT /v1/funnels/:id/steps/orders
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Request Body

{
"orders": [
{
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"order": 2
},
{
"step_id": "60d5ec49f1b2c72e8c8e4b1f",
"order": 1
}
]
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1f",
"name": "Thank You",
"order": 1
},
{
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Home Page",
"order": 2
}
]
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsbulkWriteUpdate multiple step orders atomically

getFunnelStep()

Retrieves the HTML content and configuration for a specific funnel step.

Route: GET /v1/funnels/:id/steps/:step_id
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Home Page",
"path": "/",
"type": "page",
"order": 1,
"html": "<!DOCTYPE html><html>...</html>",
"seo": {
"title": "Welcome - Best Products",
"description": "Shop the best products online"
},
"tracking_code": {
"header": "<script>...</script>",
"footer": "<script>...</script>"
},
"css": ".custom-class { color: red; }",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsfindOneRetrieve step with HTML content

createFunnelStep()

Creates a new step in a funnel.

Route: POST /v1/funnels/:id/steps
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Request Body

{
"stepInfo": {
"name": "Checkout Page",
"path": "/checkout",
"type": "page",
"order": 3
}
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b28",
"name": "Checkout Page",
"path": "/checkout",
"type": "page",
"order": 3,
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"created_at": "2024-01-22T12:00:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsinsertOneCreate new step
funnelsfindOneAndUpdateAdd step reference to funnel

updateFunnelStep()

Updates a funnel step's configuration, HTML, and builder components.

Route: PUT /v1/funnels/:id/steps/:step_id
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID
}

Request Body

{
"stepInfo": {
"name": "Updated Page Name",
"path": "/updated-path"
},
"builderComponents": {
"version": "1.0",
"components": [
{
"id": "header-1",
"type": "header",
"content": "<h1>Welcome</h1>"
}
]
},
"externalFonts": ["https://fonts.googleapis.com/css2?family=Roboto:wght@400;700"]
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Updated Page Name",
"path": "/updated-path",
"updated_at": "2024-01-22T12:30:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsfindOneAndUpdateUpdate step metadata
funnel_step_componentsfindOneAndUpdateUpdate builder components

saveStepComponents()

Saves builder components for a step (used by drag-and-drop builder).

Route: POST /v1/funnels/:id/steps/:step_id/components
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID
}

Request Body

{
"builderComponents": {
"version": "1.0",
"components": [
{
"id": "hero-section",
"type": "hero",
"settings": {
"background": "#ffffff",
"padding": "60px"
},
"content": {
"heading": "Welcome to Our Site",
"subheading": "The best products online"
}
},
{
"id": "form-1",
"type": "form",
"settings": {
"action": "/submit",
"method": "POST"
},
"fields": [
{
"type": "email",
"name": "email",
"required": true
}
]
}
]
}
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b29",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"components": {
/* builder components */
},
"saved_at": "2024-01-22T13:00:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_step_componentsfindOneAndUpdate (upsert)Save/update builder components
funnel_step_backupsinsertOneCreate backup before save

Business Logic

  • Automatically creates backup before saving new version
  • Implements versioning system for rollback capability
  • Maintains last 10 backups per step (configurable)

getStepComponents()

Retrieves builder components for a step.

Route: GET /v1/funnels/:id/steps/:step_id/components
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b29",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"components": {
"version": "1.0",
"components": [
{
"id": "hero-section",
"type": "hero",
"settings": {
/* ... */
},
"content": {
/* ... */
}
}
]
},
"saved_at": "2024-01-22T13:00:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_step_componentsfindOneRetrieve current components

getFunnelStepBackups()

Lists all backup versions for a funnel step.

Route: GET /v1/funnels/:id/steps/:step_id/backups
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID
}

Response

{
"success": true,
"message": "SUCCESS",
"data": [
{
"_id": "60d5ec49f1b2c72e8c8e4b2a",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"backup_date": "2024-01-22T12:45:00Z",
"description": "Before homepage redesign",
"components_checksum": "abc123def456"
},
{
"_id": "60d5ec49f1b2c72e8c8e4b2b",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"backup_date": "2024-01-20T09:30:00Z",
"description": "Before form changes",
"components_checksum": "def456abc789"
}
]
}

MongoDB Collections

CollectionOperationPurpose
funnel_step_backupsfindList backups sorted by date descending

getRollBackVersion()

Retrieves a specific backup version for rollback.

Route: GET /v1/funnels/rollback/:rollback_id
Auth: Required (account-scoped)

Request Parameters

{
rollback_id: string; // Backup ID
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b2a",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"backup_date": "2024-01-22T12:45:00Z",
"components": {
"version": "1.0",
"components": [
/* full backup data */
]
}
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_step_backupsfindOneRetrieve backup by ID

updatePageSettings()

Updates step-level page settings (SEO, tracking, CSS).

Route: PUT /v1/funnels/steps/:step_id/settings
Auth: Required (account-scoped)

Request Parameters

{
step_id: string; // Step ID
}

Request Body

{
"details": {
"name": "Updated Landing Page",
"path": "/new-path"
},
"seo": {
"title": "Best Products Online | Shop Now",
"description": "Discover amazing products with fast shipping",
"keywords": "products, online shopping, deals",
"image": "https://cdn.example.com/og-image.jpg",
"robots": "index,follow",
"canonical": "https://example.com/new-path"
},
"tracking_code": {
"header": "<script async src='https://www.googletagmanager.com/gtag/js?id=GA-XXX'></script>",
"footer": "<script>fbq('track', 'PageView');</script>"
},
"css": ".hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }"
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Updated Landing Page",
"path": "/new-path",
"seo": {
"title": "Best Products Online | Shop Now",
"description": "Discover amazing products with fast shipping",
"keywords": "products, online shopping, deals",
"image": "https://cdn.example.com/og-image.jpg",
"robots": "index,follow",
"canonical": "https://example.com/new-path"
},
"tracking_code": {
"header": "<script async src='https://www.googletagmanager.com/gtag/js?id=GA-XXX'></script>",
"footer": "<script>fbq('track', 'PageView');</script>"
},
"css": ".hero { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }",
"updated_at": "2024-01-22T14:00:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsfindOneAndUpdateUpdate page settings

deleteFunnel()

Soft deletes a funnel (marks as deleted, preserves data).

Route: DELETE /v1/funnels/:id
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"deleted": true,
"deleted_at": "2024-01-22T14:30:00Z"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneAndUpdateMark as deleted

Business Logic

  • Soft delete: Sets deleted: true flag
  • Preserves all data for potential recovery
  • Steps remain but are no longer accessible

deleteFunnelStep()

Deletes a step from a funnel.

Route: DELETE /v1/funnels/:id/steps/:step_id
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
step_id: string; // Step ID to delete
}

Response

{
"success": true,
"message": "SUCCESS"
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsdeleteOneRemove step document
funnelsfindOneAndUpdateRemove step reference from funnel
funnel_step_componentsdeleteOneRemove step components
funnel_step_backupsdeleteManyRemove step backups

Business Logic

  • Hard delete: Permanently removes step and associated data
  • Cleans up all related collections (components, backups)
  • Reorders remaining steps automatically

getForms()

Retrieves form submissions for a funnel with filtering and pagination.

Route: GET /v1/funnels/:id/forms
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Request Query Parameters

{
search?: string; // Search form submissions
limit?: number; // Results per page (default: 10)
page?: number; // Page number
start_date?: string; // Filter from date (ISO 8601)
end_date?: string; // Filter to date (ISO 8601)
}

Response

{
"success": true,
"message": "SUCCESS",
"data": [
{
"_id": "60d5ec49f1b2c72e8c8e4b2c",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"form_id": "contact-form",
"fields": {
"name": "John Doe",
"email": "john@example.com",
"phone": "+1 555-0123",
"message": "Interested in your products"
},
"visitor_id": "vis_abc123",
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0...",
"submitted_at": "2024-01-22T10:45:00Z"
}
],
"pagination": {
"total": 234,
"page": 1,
"limit": 10,
"pages": 24
}
}

MongoDB Collections

CollectionOperationPurpose
form_submissionsfind with date rangeQuery submissions by funnel
funnel_stepslookupJoin step names

shortenUrl()

Generates a shortened URL for funnel sharing/preview.

Route: POST /v1/funnels/shorten
Auth: Required (account-scoped)

Request Query Parameters

{
funnel_id: string; // Funnel ID to shorten
}

Response

{
"success": true,
"message": "SUCCESS",
"data": "https://short.domain/a3bCdE"
}

MongoDB Collections

CollectionOperationPurpose
short_urlsinsertOneCreate short URL mapping

Business Logic

  • Generates unique 6-character code
  • Maps to full funnel URL
  • Uses process.env.SHORT_DOMAIN as base
  • Tracks analytics on short URL clicks

exportForms()

Exports form submissions as CSV file.

Route: GET /v1/funnels/:id/forms/export
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Request Query Parameters

{
start_date?: string; // Filter from date (ISO 8601)
end_date?: string; // Filter to date (ISO 8601)
}

Response

  • Content-Type: text/csv
  • Filename: Forms-2024-01-22-14-30-00.csv

CSV Format

Submission Date,Name,Email,Phone,Message,IP Address
2024-01-22 10:45:00,John Doe,john@example.com,+1 555-0123,Interested in your products,203.0.113.42
2024-01-22 11:30:00,Jane Smith,jane@example.com,+1 555-0124,Need more info,203.0.113.43

MongoDB Collections

CollectionOperationPurpose
form_submissionsfind with date rangeQuery all submissions for export

funnelPreview()

Generates preview data for a funnel (used in preview mode).

Route: GET /v1/funnels/preview
Auth: Optional (public preview)

Request Query Parameters

{
funnel_id: string; // Funnel ID (must be valid MongoDB ObjectId)
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"_id": "60d5ec49f1b2c72e8c8e4b1a",
"name": "Lead Generation Funnel",
"base_path": "/landing",
"favicon": "https://cdn.example.com/favicon.ico",
"steps": [
{
"_id": "60d5ec49f1b2c72e8c8e4b1d",
"name": "Home Page",
"path": "/",
"order": 1
}
],
"header_tracking_code": "",
"footer_tracking_code": "",
"preview_mode": true
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneRetrieve funnel without account check

Business Logic

  • Does NOT require authentication (public preview)
  • Validates funnel_id is valid ObjectId
  • Returns 404 if funnel not found
  • Strips sensitive data (tracking codes in preview)

getfunnelStepDataCloudflare()

Retrieves funnel step HTML content for Cloudflare edge workers (edge-cached delivery).

Route: GET /v1/funnels/cloudflare/step
Auth: Custom signature-based authentication

Request Query Parameters

{
hostname: string; // Domain hostname
pathname: string; // URL path
funnel_id?: string; // Optional: specific funnel ID
isPreview?: string; // Preview mode flag
_sig: string; // Request signature
_ts: number; // Timestamp
}

Response

{
"success": true,
"message": "SUCCESS",
"data": {
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"funnel_id": "60d5ec49f1b2c72e8c8e4b1a",
"html": "<!DOCTYPE html><html>...</html>",
"checksum": "abc123def456",
"seo": {
"title": "Welcome - Best Products",
"description": "Shop the best products online"
},
"tracking_code": {
"header": "<script>...</script>",
"footer": "<script>...</script>"
},
"css": ".custom-class { color: red; }"
}
}

MongoDB Collections

CollectionOperationPurpose
funnelsfindOneFind funnel by domain/path
funnel_stepsfindOneRetrieve step HTML

Authorization

  • Uses custom signature verification via _sig and _ts parameters
  • Designed for Cloudflare edge worker authentication
  • No standard JWT authentication required

updatefunnelStepDataCloudflare()

Updates funnel step HTML with hash verification (Cloudflare edge sync).

Route: POST /v1/funnels/cloudflare/step
Auth: Custom signature-based authentication

Request Body

{
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"html": "<!DOCTYPE html><html>...</html>",
"newHash": "def456abc789",
"currentHash": "abc123def456",
"skipIfUnchanged": true,
"_sig": "signature_string",
"_ts": 1705920000
}

Response (Updated)

{
"success": true,
"message": "UPDATED",
"data": {
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"updated": true,
"reason": "UPDATED",
"checksum": "def456abc789"
}
}

Response (No Change)

{
"success": true,
"message": "NO_CHANGE",
"data": {
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"updated": false,
"reason": "NO_CHANGE",
"checksum": "abc123def456"
}
}

Response (Not Found)

{
"success": false,
"message": "NOT_FOUND",
"data": {
"step_id": "60d5ec49f1b2c72e8c8e4b1d",
"updated": false,
"reason": "NOT_FOUND",
"checksum": "def456abc789"
}
}

MongoDB Collections

CollectionOperationPurpose
funnel_stepsupdateOne with hash matchUpdate HTML with concurrent modification check

Business Logic

  • Optimistic Concurrency: Uses currentHash to prevent conflicting updates
  • Checksum Verification: Only updates if currentHash matches current DB value
  • Skip Unchanged: If skipIfUnchanged=true and hash matches, skips update
  • Cloudflare Integration: Designed for edge worker cache synchronization

purgeCache()

Purges Cloudflare cache for funnel or specific step.

Route: DELETE /v1/funnels/:id/cache
Auth: Required (account-scoped)

Request Parameters

{
id: string; // Funnel ID
}

Request Query Parameters

{
step_id?: string; // Optional: specific step to purge
all?: boolean; // Purge all steps
}

Response

{
"success": true,
"message": "SUCCESS"
}

Business Logic

  • Triggers Cloudflare cache purge API call
  • If step_id provided: Purges specific step only
  • If all=true: Purges all steps in funnel
  • Otherwise: Purges funnel-level cache

Data Models

Funnel Document

{
_id: ObjectId;
name: string;
domain_id?: ObjectId; // Optional: custom domain
base_path: string; // Base URL path
status: 'active' | 'inactive' | 'draft';
favicon?: string; // Favicon URL
tags: ObjectId[]; // Tag references
steps: {
_id: ObjectId;
name: string;
path: string;
type: 'page' | 'redirect';
order: number;
}[];
header_tracking_code?: string; // Global tracking code
footer_tracking_code?: string; // Global tracking code
live_chat_widget?: {
enabled: boolean;
provider: string;
config: object;
};
category?: ObjectId; // Template category (if template)
account_id: ObjectId;
deleted?: boolean;
created_at: Date;
updated_at: Date;
updated_by?: ObjectId; // User who made last update
}

Funnel Step Document

{
_id: ObjectId;
funnel_id: ObjectId;
name: string;
path: string;
type: 'page' | 'redirect';
order: number;
html?: string; // Rendered HTML
seo?: {
title?: string;
description?: string;
keywords?: string;
image?: string;
robots?: string;
canonical?: string;
};
tracking_code?: {
header?: string;
footer?: string;
};
css?: string; // Custom CSS
created_at: Date;
updated_at: Date;
}

Funnel Step Components Document

{
_id: ObjectId;
step_id: ObjectId;
funnel_id: ObjectId;
components: {
version: string;
components: Array<{
id: string;
type: string;
settings: object;
content: object;
}>;
};
checksum?: string; // Hash for Cloudflare sync
saved_at: Date;
}

Form Submission Document

{
_id: ObjectId;
funnel_id: ObjectId;
step_id: ObjectId;
form_id: string;
fields: Record<string, any>;
visitor_id?: string;
ip_address?: string;
user_agent?: string;
submitted_at: Date;
}

Integration Points

Cloudflare Edge Workers

  • Purpose: Edge-cached funnel delivery for maximum performance
  • Authentication: Custom signature-based (_sig, _ts)
  • Endpoints: getfunnelStepDataCloudflare, updatefunnelStepDataCloudflare, purgeCache
  • Hash Verification: Prevents concurrent modification conflicts

URL Shortener Service

  • Purpose: Generate short preview URLs for funnels
  • Integration: shortenUrl() method
  • Domain: Uses process.env.SHORT_DOMAIN

Domain Service

  • Purpose: Custom domain management for funnels
  • Integration: Domain ID references, SSL configuration
  • Path Conflicts: validatePath() prevents URL conflicts

Error Handling

Common Errors

// Funnel not found
{
"success": false,
"message": "Funnel not found",
"statusCode": 404
}

// Path conflict
{
"success": false,
"message": "Path already in use",
"data": {
"conflicting_funnel": { /* funnel details */ }
},
"statusCode": 409
}

// Invalid ObjectId
{
"success": false,
"message": "Invalid funnel ID",
"statusCode": 400
}

// Unauthorized
{
"success": false,
"message": "Unauthorized",
"statusCode": 401
}

// Admin permission required
{
"success": false,
"message": "Admin permission required for template category updates",
"statusCode": 403
}

Business Logic & Workflows

Funnel Creation Flow

sequenceDiagram
participant Client
participant Controller
participant Service
participant Database
participant Cloudflare

Client->>Controller: POST /v1/funnels (createFunnel)
Controller->>Service: createFunnel()
Service->>Database: Check path conflicts
alt Path Available
Service->>Database: Insert funnel document
Service->>Database: Insert initial steps
Service->>Cloudflare: Prepare edge cache
Database-->>Service: Created funnel
Service-->>Controller: Funnel data
Controller-->>Client: 200 OK + funnel
else Path Conflict
Database-->>Service: Conflicting funnel
Service-->>Controller: Conflict error
Controller-->>Client: 409 Conflict
end

Step Update Flow with Versioning

sequenceDiagram
participant Client
participant Controller
participant Service
participant Database
participant Cloudflare

Client->>Controller: PUT /v1/funnels/:id/steps/:step_id
Controller->>Service: updateFunnel()
Service->>Database: Retrieve current components
Service->>Database: Create backup version
Service->>Database: Update step + components
Service->>Database: Limit backups (keep last 10)
Service->>Cloudflare: Purge cache for step
Database-->>Service: Updated data
Service-->>Controller: Success
Controller-->>Client: 200 OK + updated step

Cloudflare Edge Sync Flow

sequenceDiagram
participant Edge as Cloudflare Edge
participant API as Funnels API
participant Database

Edge->>API: GET /cloudflare/step?hostname&pathname
API->>Database: Find funnel by domain/path
Database-->>API: Step HTML + checksum
API-->>Edge: HTML + checksum

Note over Edge: User edits content

Edge->>API: POST /cloudflare/step (update)
API->>Database: Update with hash verification
alt Hash Matches
Database-->>API: Updated
API-->>Edge: SUCCESS: UPDATED
else Hash Mismatch
Database-->>API: No match
API-->>Edge: SUCCESS: NO_CHANGE
end

Performance Considerations

Pagination

  • Default limit: 10 results
  • Implemented for: getFunnels, getForms
  • Uses skip/limit pattern for efficiency

Caching Strategy

  • Cloudflare edge caching for funnel HTML
  • Cache purging on content updates
  • Hash-based verification prevents stale data

Query Optimization

  • Indexed fields: account_id, domain_id, status, tags
  • Step lookups use funnel_id index
  • Form submissions indexed by funnel_id + submitted_at

Backup Management

  • Automatic cleanup: Retains last 10 backups per step
  • Async backup creation to avoid blocking updates
  • Checksums enable fast comparison

Security

Multi-Tenant Isolation

  • All queries filtered by account_id
  • Steps verified against funnel ownership
  • Form submissions scoped to account funnels

Authorization Patterns

  • Standard routes: JWT authentication with req.auth.account_id
  • Template category updates: Requires admin permission via adminAppCheck
  • Cloudflare endpoints: Custom signature verification (_sig, _ts)

Input Validation

  • Path validation prevents directory traversal
  • ObjectId validation for all ID parameters
  • HTML sanitization for user-provided content (handled in service layer)

Best Practices

When to Use This Controller

  • ✅ Creating/managing marketing funnels
  • ✅ Building drag-and-drop page builders
  • ✅ Implementing funnel analytics dashboards
  • ✅ Custom domain funnel hosting
  • ✅ Form submission collection systems

Common Patterns

  1. Always validate paths: Use validatePath() before creating/updating
  2. Backup before updates: saveStepComponents() auto-creates backups
  3. Purge cache after updates: Call purgeCache() after content changes
  4. Use rollback for recovery: getFunnelStepBackups() + getRollBackVersion()

Edge Cases

  • Domain conflicts: Multiple funnels cannot use same domain + path combination
  • Circular redirects: Step redirects should be validated to prevent loops
  • Orphaned steps: Deleting funnel should cascade delete all steps
  • Backup limits: Only last 10 backups retained per step

  • Analytics Controller: Conversion tracking and metrics
  • Domains Controller: Custom domain configuration
  • Tags Controller: Funnel categorization
  • Blocks Controller: Reusable UI components
  • Global Templates Controller: Template marketplace

Last Updated: January 2025
API Version: v1
Maintained By: DashClicks Engineering

💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM