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
| Collection | Operation | Purpose |
|---|---|---|
funnels | find with filters | Query funnels by account, status, tags |
funnel_tags | lookup | Join tag names |
funnel_domains | lookup | Join 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOne | Retrieve funnel by ID and account |
funnel_domains | lookup | Populate domain details |
funnel_tags | lookup | Populate 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | find | Check 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | insertOne | Create new funnel document |
funnel_steps | insertMany | Create 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOne | Retrieve source funnel |
funnels | insertOne | Create duplicate funnel |
funnel_steps | find | Retrieve source steps |
funnel_steps | insertMany | Create duplicate steps |
funnel_step_components | find | Copy builder components |
funnel_step_components | insertMany | Create 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
subaccountidprovided, 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | findOne | Retrieve source step |
funnel_steps | insertOne | Create duplicate step |
funnel_step_components | findOne | Copy builder components |
funnel_step_components | insertOne | Create 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOneAndUpdate | Update funnel document |
admin_permissions | findOne | Verify 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_idinupdated_byfield 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | bulkWrite | Update 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | findOne | Retrieve 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | insertOne | Create new step |
funnels | findOneAndUpdate | Add 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | findOneAndUpdate | Update step metadata |
funnel_step_components | findOneAndUpdate | Update 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_step_components | findOneAndUpdate (upsert) | Save/update builder components |
funnel_step_backups | insertOne | Create 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_step_components | findOne | Retrieve 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_step_backups | find | List 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_step_backups | findOne | Retrieve 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | findOneAndUpdate | Update 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOneAndUpdate | Mark as deleted |
Business Logic
- Soft delete: Sets
deleted: trueflag - 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | deleteOne | Remove step document |
funnels | findOneAndUpdate | Remove step reference from funnel |
funnel_step_components | deleteOne | Remove step components |
funnel_step_backups | deleteMany | Remove 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
| Collection | Operation | Purpose |
|---|---|---|
form_submissions | find with date range | Query submissions by funnel |
funnel_steps | lookup | Join 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
| Collection | Operation | Purpose |
|---|---|---|
short_urls | insertOne | Create short URL mapping |
Business Logic
- Generates unique 6-character code
- Maps to full funnel URL
- Uses
process.env.SHORT_DOMAINas 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
| Collection | Operation | Purpose |
|---|---|---|
form_submissions | find with date range | Query 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOne | Retrieve 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
| Collection | Operation | Purpose |
|---|---|---|
funnels | findOne | Find funnel by domain/path |
funnel_steps | findOne | Retrieve step HTML |
Authorization
- Uses custom signature verification via
_sigand_tsparameters - 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
| Collection | Operation | Purpose |
|---|---|---|
funnel_steps | updateOne with hash match | Update HTML with concurrent modification check |
Business Logic
- Optimistic Concurrency: Uses
currentHashto prevent conflicting updates - Checksum Verification: Only updates if
currentHashmatches current DB value - Skip Unchanged: If
skipIfUnchanged=trueand 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_idprovided: 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_idindex - 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
- Always validate paths: Use
validatePath()before creating/updating - Backup before updates:
saveStepComponents()auto-creates backups - Purge cache after updates: Call
purgeCache()after content changes - 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
Related Documentation
- 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