v1.0

API Reference & Security

Extend QuikForms with custom plugins and secure your forms with comprehensive security features including Cloudflare Turnstile, honeypot protection, rate limiting, and XSS prevention

Ctrl+K

API Reference

This guide explains how to extend QuikForms with custom functionality using the Plugin Framework.

Plugin Framework Overview

The QuikForms Plugin Framework enables you to:

  • Create custom field types (signature pads, address lookup, rich text editors)
  • Perform external integrations (CRM sync, payment gateways, webhooks)
  • Implement custom validation logic
  • Execute business logic before/after form submission
  • Work with both low-code (Flow) and pro-code (Apex/JavaScript) approaches

Architecture

The framework consists of four layers:

  1. Apex Interfaces - Server-side plugin logic
  2. JavaScript API - Client-side rendering and interactions
  3. Platform Events - Async integrations
  4. Custom Metadata - Plugin registration and configuration

Quick Start Example

Follow these steps to create a custom field type plugin.

Step 1: Create the Apex Class

global class MyCustomPlugin implements IQuikFormsPlugin, IQuikFormsCalloutHandler {

    global QuikFormsPluginInfo getPluginInfo() {
        return new QuikFormsPluginInfo(
            'my-custom-plugin',
            '1.0.0',
            'My Custom Plugin',
            'Description of what this plugin does'
        );
    }

    global void initialize(Map<String, Object> configuration) {
        // Initialize plugin with configuration
    }

    global Boolean isReady() {
        return true;
    }

    global QuikFormsCalloutResponse executeCallout(QuikFormsCalloutRequest request) {
        // Handle server-side callouts from JavaScript
        QuikFormsCalloutResponse response = new QuikFormsCalloutResponse();
        response.success = true;
        response.data = new Map<String, Object>{'result' => 'success'};
        return response;
    }
}

Step 2: Create the JavaScript Static Resource

Create a Static Resource named MyCustomPlugin with this content:

(function(global) {
    'use strict';

    global.initMyCustomPlugin = function(QuikFormsPlugins) {
        QuikFormsPlugins.register('my-custom-plugin', {
            name: 'my-custom-plugin',
            version: '1.0.0',

            fieldTypes: {
                'my-field-type': {
                    render: function(field, context) {
                        var config = field.pluginConfiguration || {};
                        return '<div class="my-custom-field">' +
                               '  <input type="text" id="field-' + field.id + '" />' +
                               '</div>';
                    },

                    initialize: function(fieldId, container) {
                        // Set up event listeners, third-party libraries, etc.
                        var input = container.querySelector('#field-' + fieldId);
                        if (input) {
                            input.addEventListener('change', function() {
                                // Handle changes
                            });
                        }
                    },

                    getValue: function(fieldId) {
                        var input = document.getElementById('field-' + fieldId);
                        return input ? input.value : '';
                    },

                    setValue: function(fieldId, value) {
                        var input = document.getElementById('field-' + fieldId);
                        if (input) input.value = value;
                    },

                    validate: function(fieldId, field) {
                        var value = this.getValue(fieldId);
                        if (field.isRequired && !value) {
                            return {
                                isValid: false,
                                errors: ['This field is required']
                            };
                        }
                        return { isValid: true, errors: [] };
                    }
                }
            },

            hooks: {
                onFormLoad: function(context) {
                    console.log('Form loaded:', context.formConfigId);
                },

                onFieldChange: function(context) {
                    console.log('Field changed:', context.fieldId, context.value);
                },

                onBeforeSubmit: async function(context) {
                    // Return { abort: true, errors: ['message'] } to stop submission
                    return { abort: false, errors: [] };
                },

                onAfterSubmit: async function(context) {
                    console.log('Form submitted, record ID:', context.recordId);
                }
            }
        });
    };
})(window);

Step 3: Register the Plugin in Custom Metadata

Create a QuikForms_Plugin__mdt record:

Field Value
Label My Custom Plugin
DeveloperName MyCustomPlugin
Plugin_Type__c FieldType
Apex_Class_Name__c MyCustomPlugin
JS_Static_Resource__c MyCustomPlugin
JS_Entry_Point__c initMyCustomPlugin
Is_Active__c true
Execution_Order__c 10
Error_Behavior__c Fatal
Validation_Mode__c Sync

Create a QuikForms_Custom_Field_Type__mdt record:

Field Value
Label My Field Type
DeveloperName MyFieldType
Plugin__c MyCustomPlugin
Field_Type_Name__c my-field-type
Display_Label__c My Custom Field
Icon__c utility:custom_apps
Default_Configuration__c {"setting1": "value1"}

Step 4: Use in a Form

Set a FormField__c record's Plugin_Field_Type__c to my-field-type and optionally provide Plugin_Configuration__c as JSON.

Apex Interfaces

IQuikFormsPlugin (Required)

Base interface all plugins must implement:

global interface IQuikFormsPlugin {
    QuikFormsPluginInfo getPluginInfo();
    void initialize(Map<String, Object> configuration);
    Boolean isReady();
}

IQuikFormsFieldValidator

For custom validation logic:

global interface IQuikFormsFieldValidator extends IQuikFormsPlugin {
    QuikFormsValidationResult validateSync(QuikFormsValidationContext context);
    QuikFormsValidationResult validateAsync(QuikFormsValidationContext context);
    QuikFormsValidationMode getValidationMode();
}

Validation Modes

  • SYNC_ONLY - Validate immediately on client
  • ASYNC_ONLY - Validate on server only
  • SYNC_THEN_ASYNC - Client validation first, then server
  • ASYNC_ON_BLUR - Server validation when field loses focus

IQuikFormsSubmissionHandler

For before/after submission logic:

global interface IQuikFormsSubmissionHandler extends IQuikFormsPlugin {
    QuikFormsSubmissionResult onBeforeSubmit(QuikFormsSubmissionContext context);
    QuikFormsSubmissionResult onAfterSubmit(QuikFormsSubmissionContext context);
}

IQuikFormsCalloutHandler

For server-side HTTP callouts:

global interface IQuikFormsCalloutHandler extends IQuikFormsPlugin {
    QuikFormsCalloutResponse executeCallout(QuikFormsCalloutRequest request);
}

JavaScript API

Registering a Plugin

QuikFormsPlugins.register('plugin-name', {
    name: 'plugin-name',
    version: '1.0.0',
    fieldTypes: { /* ... */ },
    hooks: { /* ... */ }
});

Available Hooks

Hook When Called Context Properties
onFormLoad After form data loaded formConfigId, locale, fields
onFieldChange Field value changes fieldId, value, previousValue
onFieldBlur Field loses focus fieldId, value
onFieldRender After field rendered fieldId, container
onValidate Before submission validation fields, values
onBeforeSubmit Before form submission fields, values
onAfterSubmit After successful submission recordId, formConfigId
onFormReset When form is reset formConfigId
onFileUpload When file is uploaded fieldId, file, base64

Making Server Callouts

const response = await QuikFormsPlugins.callout({
    pluginName: 'my-plugin',
    action: 'validateAddress',
    data: { address: '123 Main St' }
});

if (response.success) {
    console.log('Result:', response.data);
} else {
    console.error('Error:', response.errorMessage);
}

Field Type Interface

fieldTypes: {
    'field-type-name': {
        // Required: Return HTML for the field
        render: function(field, context) {
            return '<input type="text" id="field-' + field.id + '" />';
        },

        // Optional: Called after render to set up the field
        initialize: function(fieldId, container) {
            // Set up event listeners, initialize libraries
        },

        // Required: Get the current field value
        getValue: function(fieldId) {
            return document.getElementById('field-' + fieldId).value;
        },

        // Optional: Set the field value
        setValue: function(fieldId, value) {
            document.getElementById('field-' + fieldId).value = value;
        },

        // Optional: Validate the field
        validate: function(fieldId, field) {
            return { isValid: true, errors: [] };
        }
    }
}

Data Classes

QuikFormsPluginInfo

global class QuikFormsPluginInfo {
    global String name;
    global String version;
    global String displayName;
    global String description;
}

QuikFormsValidationContext

global class QuikFormsValidationContext {
    global String fieldId;
    global String fieldApiName;
    global Object fieldValue;
    global String formConfigId;
    global Map<String, Object> allFieldValues;
    global String locale;
}

QuikFormsValidationResult

global class QuikFormsValidationResult {
    global Boolean isValid;
    global List<String> errors;
    global List<String> warnings;
}

QuikFormsSubmissionContext

global class QuikFormsSubmissionContext {
    global String formConfigId;
    global String targetObject;
    global Id recordId;
    global Map<String, Object> fieldValues;
    global String locale;
    global String submitterIp;
    global Boolean isUpdate;
}

QuikFormsSubmissionResult

global class QuikFormsSubmissionResult {
    global Boolean abort;
    global List<String> errors;
    global Map<String, Object> modifiedFieldValues;
}

QuikFormsCalloutRequest

global class QuikFormsCalloutRequest {
    global String pluginName;
    global String action;
    global Map<String, Object> data;
    global String formConfigId;
    global String locale;
}

QuikFormsCalloutResponse

global class QuikFormsCalloutResponse {
    global Boolean success;
    global Map<String, Object> data;
    global String errorMessage;
    global Integer statusCode;
}

Custom Metadata Configuration

QuikForms_Plugin__mdt

Register and configure plugins using this custom metadata type:

Field Description
Plugin_Type__c Type of plugin: FieldType, Validator, SubmissionHandler, CalloutHandler
Apex_Class_Name__c Name of the Apex class implementing the plugin interface
JS_Static_Resource__c Name of the JavaScript Static Resource
JS_Entry_Point__c JavaScript function name to initialize the plugin
Is_Active__c Whether the plugin is enabled
Execution_Order__c Order in which plugins are loaded (lower numbers first)
Error_Behavior__c How errors are handled: Fatal, Warn, Silent
Validation_Mode__c When validation runs: Sync, Async, SyncThenAsync, AsyncOnBlur
Supports_Callout__c Whether the plugin can make server callouts

QuikForms_Custom_Field_Type__mdt

Define custom field types that can be used in forms:

Field Description
Plugin__c Reference to the QuikForms_Plugin__mdt record
Field_Type_Name__c Unique identifier for the field type (e.g., 'signature-pad')
Display_Label__c User-friendly name shown in the form builder
Icon__c SLDS icon name (e.g., 'utility:signature')
Default_Configuration__c JSON string of default configuration options

Flow Integration (Low-Code)

For admins who prefer Flow over Apex, use Flow Hooks:

Step 1: Create a Flow

Create an Autolaunched Flow with these input variables:

  • hookName (Text) - The lifecycle hook name
  • formConfigId (Text) - The form configuration ID
  • recordId (Text) - The created record ID (onAfterSubmit only)
  • fieldValuesJson (Text) - JSON string of field values
  • locale (Text) - Form locale

And these output variables:

  • shouldContinue (Boolean) - Whether to continue processing
  • errorMessage (Text) - Error message if shouldContinue is false
  • modifiedValuesJson (Text) - JSON of any modified field values

Step 2: Register the Flow Hook

Create a QuikForms_Flow_Hook__mdt record:

Field Value
Hook_Name__c onAfterSubmit
Flow_API_Name__c MyQuikFormsHook
Form_Config_Filter__c (empty for all forms, or comma-separated IDs)
Is_Active__c true
Is_Blocking__c false
Execution_Order__c 10

Platform Events (Async Integration)

Subscribe to these Platform Events for decoupled async processing:

QuikForms_Submission_Complete__e

Published after successful form submission:

Field Description
Form_Config_Id__c The form configuration ID
Record_Id__c The created/updated record ID
Object_API_Name__c Target object API name
Field_Values_JSON__c Submitted values as JSON
Submission_Timestamp__c When the form was submitted
User_IP__c Submitter's IP address
Locale__c Form locale

QuikForms_Validation_Failed__e

Published when validation fails:

Field Description
Form_Config_Id__c The form configuration ID
Field_Id__c The field that failed validation
Validation_Errors_JSON__c Error details as JSON
Submitted_Value__c The value that failed validation

Security Considerations

Important Security Guidelines

  1. Apex Class Validation: Only classes that implement IQuikFormsPlugin and pass namespace validation can be instantiated.
  2. Callout Security: Use Named Credentials for external API calls. Never store credentials in code or configuration.
  3. XSS Prevention: Always escape user input when rendering HTML in JavaScript plugins.
  4. Input Validation: Validate all input on both client and server side.

Example: Signature Capture Plugin

See the included sample plugin for a complete implementation:

  • Apex: SignatureCapturePlugin.cls
  • JavaScript: QuikFormsSignaturePlugin.resource
  • Custom Metadata: QuikForms_Plugin.SignatureCapture.md-meta.xml

This plugin demonstrates:

  • Custom field type rendering (canvas-based signature pad)
  • Client-side validation
  • Server-side callout handling
  • Hook integration

Troubleshooting

Plugin Not Loading

  1. Check QuikForms_Plugin__mdt.Is_Active__c is true
  2. Verify Static Resource name matches JS_Static_Resource__c
  3. Check browser console for JavaScript errors
  4. Ensure entry point function name matches JS_Entry_Point__c

Callout Failing

  1. Verify Supports_Callout__c is true on the plugin
  2. Check Apex class implements IQuikFormsCalloutHandler
  3. Review debug logs for exceptions

Validation Not Running

  1. Check Validation_Mode__c setting
  2. Ensure validate function returns correct format
  3. Verify field is marked as plugin field type

QuikForms implements a comprehensive security framework to protect your forms and data. This section covers authentication, authorization, rate limiting, and protection against common web vulnerabilities.

Security Best Practice: Always follow the principle of least privilege when configuring guest user permissions. Grant only the minimum access required for forms to function.

Security Overview

QuikForms security is built on multiple layers of defense:

Security Layer Purpose Implementation
Guest User Permissions Control object and field access for public forms Salesforce Permission Sets and Profile settings
Cloudflare Turnstile Prevent bot submissions and abuse Privacy-friendly CAPTCHA with Named Credentials
Honeypot Protection Catch automated bots transparently Hidden field technique, always active
Rate Limiting Prevent DoS attacks and spam IP-based throttling with Platform Cache
XSS Prevention Protect against cross-site scripting Output escaping and HTML sanitization
Plugin Security Validate and sandbox custom plugins Class validation and configuration sanitization
Defense in Depth: QuikForms uses multiple overlapping security controls to ensure that if one layer fails, others continue to protect your data.

Turnstile & Honeypot Configuration

QuikForms uses a dual-layer approach to bot protection: Cloudflare Turnstile for CAPTCHA verification and honeypot fields for transparent bot detection.

Bot Protection Overview

QuikForms provides two complementary layers of bot protection:

Protection Layer How It Works User Impact
Cloudflare Turnstile Privacy-friendly CAPTCHA that validates users non-intrusively. Runs silently in most cases; only presents a challenge when suspicious activity is detected. Minimal - no "I'm not a robot" checkbox in most cases
Honeypot Hidden form field invisible to real users. Automated bots that fill it in are detected and blocked. Zero - completely transparent to users
Always-On Protection: Honeypot protection is always active on all forms, even when CAPTCHA is disabled. This provides a baseline layer of defense against simple bots.

Getting Turnstile Keys

Before configuring QuikForms, you'll need to obtain API keys from Cloudflare.

Step 1: Create a Turnstile Widget

  1. Go to the Cloudflare Turnstile Dashboard
  2. Sign in with your Cloudflare account (free account is sufficient)
  3. Click Add Site

Step 2: Configure Widget Settings

  1. Site Name: Enter a descriptive name (e.g., "QuikForms Production")
  2. Domain: Add your Salesforce Site domains:
    • yourorg.my.salesforce-sites.com
    • yourorg.my.site.com
    • Any custom domains
  3. Widget Mode: Select Managed (recommended) — Cloudflare will automatically choose the best challenge type
  4. Click Create

Step 3: Save Your Keys

After creation, Cloudflare provides two keys:

Key Type Usage Visibility
Site Key Public key used to render the Turnstile widget (client-side) Public - visible in page source
Secret Key Private key used for server-side token verification Private - stored in Named Credential
Security: Never expose your Secret Key in client-side code or version control. Always store it securely in Salesforce Named Credentials.

Copy both keys - you'll need them for the next steps.

Named Credential Setup

QuikForms uses Salesforce Named Credentials to securely store and access your Turnstile Secret Key. QuikForms includes pre-configured Named Credential and External Credential metadata — you only need to add your Secret Key.

Package Configuration: QuikForms ships with the Turnstile Named Credential and Turnstile_External_Credential already configured. You only need to add the principal with your Secret Key.

Configure External Credential

  1. Navigate to External Credentials
    • Setup → Security → Named Credentials
    • Click the External Credentials tab
    • Find Turnstile External Credential
  2. Add Principal
    • In the Principals section, click New
    • Parameter Name: Default
    • Authentication Protocol: Password Authentication
    • Username: turnstile (can be any value)
    • Password: Paste your Secret Key from Cloudflare
    • Click Save
  3. Grant Guest User Access
    • In the Permission Set Mappings section, click New
    • Permission Set: Select your Site's Guest User permission set
    • Principal: Default
    • Click Save
Critical: The Guest User must have access to the External Credential principal. Without this, Turnstile verification will fail on public forms.
Named Credential Metadata (pre-configured)
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
    <allowMergeFieldsInBody>true</allowMergeFieldsInBody>
    <allowMergeFieldsInHeader>true</allowMergeFieldsInHeader>
    <calloutStatus>Enabled</calloutStatus>
    <generateAuthorizationHeader>false</generateAuthorizationHeader>
    <label>Turnstile</label>
    <namedCredentialParameters>
        <parameterName>Url</parameterName>
        <parameterType>Url</parameterType>
        <parameterValue>https://challenges.cloudflare.com/turnstile/v0</parameterValue>
    </namedCredentialParameters>
    <namedCredentialType>SecuredEndpoint</namedCredentialType>
</NamedCredential>

Update QuikForms Settings

After configuring Named Credentials, add your Site Key to QuikForms Custom Metadata.

  1. Navigate to Custom Metadata Types
    • Setup → Custom Code → Custom Metadata Types
    • Click Manage Records next to QuikForms Setting
  2. Edit Default Settings
    • Click Edit next to the default record
    • Find Turnstile Site Key
    • Paste your Site Key (public key from Cloudflare)
    • Click Save
Setup Assistant: You can also use the QuikForms Setup Assistant to configure and validate Turnstile. It will check that the External Credential, Named Credential, and Site Key are all properly configured.

Per-Form CAPTCHA Control

You can enable or disable Turnstile CAPTCHA on individual forms using the Form Configuration settings.

  1. Open your form in QuikForms Manager
  2. Navigate to Settings tab
  3. Find Disable CAPTCHA checkbox
    • Unchecked (default): Turnstile CAPTCHA is active
    • Checked: Turnstile CAPTCHA is disabled for this form (honeypot remains active)
Security Consideration: Disabling CAPTCHA reduces your protection level. Only disable for internal forms where bot submissions are not a concern. Honeypot protection remains active regardless of this setting.

Honeypot Protection

Honeypot protection is a transparent anti-bot technique that is always active on all QuikForms forms.

How It Works:

  • A hidden field is added to the form that is invisible to human users
  • Automated bots that fill in all form fields will populate this hidden field
  • Submissions with the honeypot field filled are automatically rejected
  • No configuration required — honeypot is enabled by default on all forms
No User Impact: Honeypot protection has zero impact on legitimate users. The hidden field is not visible and does not affect form layout or behavior.

Guest User Permissions

When forms are published on Experience Cloud Sites, they run under the Guest User context. Proper permission configuration is critical for both security and functionality.

Permission Model

QuikForms follows a strict least-privilege permission model:

Object Read Create Edit Delete Purpose
FormDefinition__c Load form metadata
FormConfigVersion__c Load active form configuration
FormField__c Load field definitions
Case (or target object) Create form submissions
Survey__c Load and update survey responses
Security Critical: Guest users should NOT have Read access to submission objects (Case, Lead, etc.). This would allow anyone to query and view submitted data. Only grant Create permission.

Object-Level Security

Configure object permissions through the Guest User Profile or a dedicated Permission Set.

Method 1: Guest User Profile (Recommended)

  1. Setup → Users → Profiles
  2. Find your Site's Guest User Profile (e.g., "MySite Profile")
  3. Click Edit
  4. Scroll to Custom Object Permissions
  5. Configure permissions as shown in the table above
  6. Click Save

[Screenshot: Guest User Profile Object Permissions]

Method 2: Permission Set

  1. Setup → Users → Permission Sets
  2. Create a new Permission Set: "QuikForms Guest Access"
  3. Navigate to Object Settings
  4. Configure permissions for each object
  5. Assign the Permission Set to the Guest User
Organization-Wide Defaults: Set submission objects (Case, Lead, etc.) to Private OWD. This ensures guests can only create records, not read existing ones.

Field-Level Security

Guest users need field-level access to create records. Configure this for each field mapped in your forms.

Required Field Permissions:

  • QuikForms Objects: Read access to all fields on FormDefinition__c, FormConfigVersion__c, and FormField__c
  • Target Objects: Edit access (visible + editable) to mapped fields on Case, Lead, or custom objects
  • Standard Fields: At minimum, enable access to fields like Status, Origin, Subject, Description

Configuration Steps:

  1. In the Guest User Profile or Permission Set, navigate to Field-Level Security
  2. For each object, click View
  3. For QuikForms objects: Set all fields to Visible
  4. For submission objects: Set mapped fields to Visible and Editable
  5. Click Save

[Screenshot: Field-Level Security Configuration]

Example: Case Field Permissions
Case.Status          → Visible ✓  Editable ✓
Case.Origin          → Visible ✓  Editable ✓
Case.Subject         → Visible ✓  Editable ✓
Case.Description     → Visible ✓  Editable ✓
Case.SuppliedEmail   → Visible ✓  Editable ✓
Case.SuppliedName    → Visible ✓  Editable ✓
Case.SuppliedPhone   → Visible ✓  Editable ✓
Case.Type            → Visible ✓  Editable ✓
Case.Priority        → Visible ✓  Editable ✓
Testing: After configuring permissions, always test form submissions as a guest user. Use incognito mode or log out to verify permissions are correct.

External Credential Access

For Turnstile CAPTCHA verification to work, the Guest User must have access to the Turnstile External Credential principal.

Grant Access:

  1. Setup → Security → Named Credentials
  2. Click External Credentials tab
  3. Open Turnstile External Credential
  4. In Permission Set Mappings, click New
  5. Select your Site's Guest User permission set
  6. Choose the Default principal
  7. Click Save
Common Error: If forms fail with "External Credential not found" errors, verify the Guest User has access to the Turnstile credential principal.

Rate Limiting & IP Blocking

QuikForms includes built-in rate limiting to prevent abuse, spam, and denial-of-service attacks. Rate limits are tracked per IP address using Salesforce Platform Cache.

How Rate Limiting Works

When a form submission is received, QuikForms:

  1. Extracts the client's IP address from the HTTP request
  2. Checks the Platform Cache for recent submissions from that IP
  3. Increments the submission counter
  4. If the counter exceeds the configured limit within the time window, the submission is rejected
  5. Returns a 429 (Too Many Requests) error to the client
Rate Limiting Logic (QForm.cls)
// Check rate limit before processing submission
if (isRateLimited(req.remoteAddress)) {
    // Log rate limit exceeded
    QuikExceptionLogger.logException(
        'API-3002',
        'Rate limit exceeded for IP: ' + req.remoteAddress,
        'QForm',
        'handleFormSubmission'
    );

    result.success = false;
    result.message = 'Too many requests. Please try again later.';
    return result;
}

private static Boolean isRateLimited(String ipAddress) {
    // Get rate limit from Custom Metadata
    List<QuikForms_Setting__mdt> settings = [
        SELECT Rate_Limit_Per_Minute__c
        FROM QuikForms_Setting__mdt
        WHERE DeveloperName = 'default'
    ];

    Integer limitVal = 5; // Default
    if (!settings.isEmpty() && settings[0].Rate_Limit_Per_Minute__c != null) {
        limitVal = (Integer)settings[0].Rate_Limit_Per_Minute__c;
    }

    // Check Platform Cache and enforce limit
    // ... implementation details ...
}

Configure Rate Limits

Rate limits are configured globally in QuikForms Custom Metadata.

Configuration Steps:

  1. Setup → Custom Code → Custom Metadata Types
  2. Click Manage Records next to QuikForms Setting
  3. Click Edit on the default record
  4. Set Rate Limit (Per Minute) to your desired value
  5. Click Save

[Screenshot: Rate Limit Configuration]

Recommended Values:

Environment Rate Limit Rationale
Development/Sandbox 5-10 requests/minute Allows testing without being too permissive
Production (Low Traffic) 10-15 requests/minute Protects against basic spam while allowing legitimate use
Production (High Traffic) 20-30 requests/minute Balances protection with user experience for busy forms
Internal/Authenticated 50+ requests/minute Higher limits for trusted, authenticated users
Performance Impact: Very high rate limits (100+) may impact Platform Cache usage. Monitor your cache consumption in Setup → Platform Cache.

Monitoring & Logging

QuikForms logs all rate limit violations to help you identify abuse patterns and adjust settings.

View Rate Limit Exceptions:

  1. Open the QuikForms Error Manager app
  2. Filter by Error Code: API-3002
  3. Review the IP Address and Timestamp columns
  4. Look for patterns indicating bot activity

[Screenshot: Rate Limit Exception Logs]

Key Indicators of Abuse:

  • Single IP address with 10+ violations in an hour
  • Sequential IP addresses (e.g., 192.168.1.1, 192.168.1.2) suggesting bot network
  • Violations outside normal business hours
  • Identical User-Agent strings across multiple IPs
Integration Opportunity: Use Platform Events (QuikForms_Validation_Failed__e) to trigger real-time alerts when rate limits are exceeded.

IP Blocking Strategies

While QuikForms doesn't include built-in IP blocking, you can implement it using Salesforce Site features or external services.

Method 1: Experience Cloud IP Restrictions

  1. Setup → Digital Experiences → All Sites
  2. Click Builder for your site
  3. Settings → Security → IP Address Restrictions
  4. Add IP ranges to block or allow

Method 2: Custom Apex IP Blocking

Implement custom validation in the IQuikFormsSubmissionHandler plugin:

Example IP Block Plugin
public class IPBlockerPlugin implements IQuikFormsSubmissionHandler {

    // Blocked IP addresses or ranges
    private static final Set<String> BLOCKED_IPS = new Set<String>{
        '192.168.1.100',
        '10.0.0.50'
    };

    public void beforeSubmit(QuikFormsSubmissionContext context) {
        String clientIP = context.request.remoteAddress;

        if (BLOCKED_IPS.contains(clientIP)) {
            context.addError('Your IP address has been blocked. Contact support.');
            context.result.success = false;
        }
    }

    public void afterSubmit(QuikFormsSubmissionContext context) {
        // Not needed for IP blocking
    }
}

Method 3: CloudFlare or CDN

If you use a custom domain with CloudFlare or similar CDN:

  • Configure firewall rules to block malicious IPs
  • Enable bot protection and challenge pages
  • Use rate limiting at the CDN level before traffic reaches Salesforce
Best Practice: Combine rate limiting with Cloudflare Turnstile for comprehensive protection. Rate limiting stops simple spam scripts, while Turnstile and honeypot protection prevent sophisticated bots.

Data Validation & XSS Prevention

QuikForms implements multiple layers of input validation and output encoding to prevent Cross-Site Scripting (XSS) and other injection attacks.

Input Validation

All user input is validated before processing:

Validation Layers:

  1. Client-Side Validation: JavaScript validates format, length, and patterns in real-time
  2. Server-Side Validation: Apex re-validates all input (never trust client validation alone)
  3. Regex Validation: Custom patterns defined in FormField__c.Validation_Regex__c
  4. Type Validation: Ensures data types match field definitions (email, phone, number)
  5. Length Validation: Enforces maximum length based on Salesforce field limits
Example Validation Patterns
Email:    ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
Phone:    ^\+?[1-9]\d{1,14}$
ZIP Code: ^\d{5}(-\d{4})?$
URL:      ^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b
Alphanumeric: ^[a-zA-Z0-9\s]+$

Field-Level Validation Configuration:

  1. In QuikField Library, edit a field
  2. Navigate to the Validation tab
  3. Enter a Validation Regex pattern
  4. Set a custom error message
  5. Save the field
Custom Validators: Implement IQuikFormsFieldValidator for complex business logic validation beyond regex patterns.

Output Encoding & Escaping

Salesforce automatically escapes output in Visualforce and Lightning Web Components to prevent XSS attacks.

Automatic Escaping:

Context Mechanism Protection
Visualforce {! expression } (escaped by default) HTML entities encoded
Lightning Web Components {property} bindings Automatic sanitization
Apex Strings String.escapeSingleQuotes() SOQL injection prevention
HTML Output HTMLENCODE() function Converts < > & " ' to entities
Unsafe Contexts: Never use {!HTMLENCODE(false)}, unescapedHtml, or innerHTML with user input. These bypass Salesforce's automatic protection.

customHTML Field Security

QuikForms supports customHTML field types for rich content. These fields require special security considerations.

Security Rules for customHTML:

  1. Admin-Only Creation: Only administrators should create customHTML fields
  2. No User Input: Never populate customHTML from user submissions
  3. Static Content Only: Use for logos, disclaimers, formatting - not dynamic data
  4. Review Before Publishing: Audit all HTML for malicious scripts before activating forms
Critical Security Warning: Never create a customHTML field that includes data from form submissions, URL parameters, or any untrusted source. This creates an XSS vulnerability.

Safe customHTML Examples:

Safe: Static Disclaimer
<div class="disclaimer">
    <p>
        <strong>Privacy Notice:</strong> Your information will be handled in
        accordance with our <a href="/privacy">Privacy Policy</a>.
    </p>
</div>
Unsafe: Dynamic Content (DO NOT USE)
<!-- DANGEROUS - NEVER DO THIS -->
<div>
    Welcome back, {! userInputName }!
</div>

<!-- If userInputName = "<script>alert('XSS')</script>"
     this creates a vulnerability -->

SOQL Injection Prevention

QuikForms uses parameterized queries and binding to prevent SOQL injection attacks.

Safe Query Patterns:

Safe: Bind Variables
// Safe - uses bind variable
String formId = req.formId;
List<FormDefinition__c> forms = [
    SELECT Id, Name
    FROM FormDefinition__c
    WHERE Id = :formId
];

// Safe - uses escapeSingleQuotes
String searchTerm = String.escapeSingleQuotes(userInput);
String query = 'SELECT Id FROM Case WHERE Subject LIKE \'%' + searchTerm + '%\'';
List<Case> results = Database.query(query);
Unsafe: String Concatenation (DO NOT USE)
// DANGEROUS - NEVER DO THIS
String userInput = req.getParameter('search');
String query = 'SELECT Id FROM Case WHERE Subject = \'' + userInput + '\'';
List<Case> results = Database.query(query);

// If userInput = "test' OR '1'='1"
// Query becomes: SELECT Id FROM Case WHERE Subject = 'test' OR '1'='1'
// Returns all records!
QuikForms Protection: The framework uses bind variables for all dynamic queries. Custom plugins should follow the same pattern.

Plugin Security Framework

QuikForms includes a comprehensive security framework for validating and sanitizing plugin code. The QuikFormsPluginSecurity class provides multiple validation methods to prevent malicious plugin execution.

Plugin Class Validation

Before instantiating any plugin, QuikForms validates the class to ensure it's safe.

Validation Checks:

  1. Blocked Patterns: Prevents instantiation of dangerous system classes
  2. Interface Implementation: Ensures class implements IQuikFormsPlugin
  3. Class Existence: Verifies class exists in the org
  4. Instantiation Test: Attempts to create an instance to catch constructor errors

Blocked Class Patterns:

Pattern Reason Examples
System.* Prevent access to system utilities System.debug, System.runAs
Database.* Prevent direct database manipulation Database.query, Database.delete
Schema.* Prevent schema introspection Schema.describe, Schema.getGlobalDescribe
Auth.* Prevent authentication bypass Auth.SessionManagement
Http* Prevent unauthorized callouts Http, HttpRequest, HttpResponse
Messaging.* Prevent email/SMS abuse Messaging.sendEmail
Queueable Prevent async job manipulation System.enqueueJob
Schedulable Prevent scheduled job creation System.schedule
Plugin Validation (QuikFormsPluginSecurity.cls)
public static Boolean isValidPluginClass(String className) {
    if (String.isBlank(className)) {
        return false;
    }

    // Check blocked patterns
    String upperClassName = className.toUpperCase();
    for (String pattern : BLOCKED_PATTERNS) {
        if (upperClassName.startsWith(pattern.toUpperCase())) {
            System.debug(LoggingLevel.WARN, 'Blocked plugin class: ' + className);
            return false;
        }
    }

    // Validate class exists
    Type classType = Type.forName(className);
    if (classType == null) {
        System.debug(LoggingLevel.WARN, 'Plugin class not found: ' + className);
        return false;
    }

    // Validate implements IQuikFormsPlugin
    try {
        Object instance = classType.newInstance();
        if (!(instance instanceof IQuikFormsPlugin)) {
            System.debug(LoggingLevel.WARN, 'Class does not implement IQuikFormsPlugin');
            return false;
        }
    } catch (Exception e) {
        System.debug(LoggingLevel.WARN, 'Failed to instantiate: ' + e.getMessage());
        return false;
    }

    return true;
}
Namespace Support: The validation framework supports both managed and unmanaged packages. Namespaced plugins (e.g., myNamespace.MyPlugin) are validated the same way.

Configuration Sanitization

Plugin configurations are stored as JSON. QuikForms sanitizes this JSON to prevent prototype pollution and other JavaScript-based attacks.

Dangerous Keys Removed:

  • __proto__
  • constructor
  • prototype
  • __defineGetter__
  • __defineSetter__
Configuration Sanitization
public static Map<String, Object> sanitizeConfiguration(String configJson) {
    if (String.isBlank(configJson)) {
        return new Map<String, Object>();
    }

    try {
        Map<String, Object> config =
            (Map<String, Object>) JSON.deserializeUntyped(configJson);

        // Remove dangerous keys
        config.remove('__proto__');
        config.remove('constructor');
        config.remove('prototype');
        config.remove('__defineGetter__');
        config.remove('__defineSetter__');

        return config;
    } catch (Exception e) {
        System.debug(LoggingLevel.WARN, 'Failed to parse configuration: ' + e.getMessage());
        return new Map<String, Object>();
    }
}
Safe Configuration: Always use sanitizeConfiguration() before passing JSON to plugins. Never trust configuration data from Custom Metadata without validation.

HTML Sanitization Methods

QuikFormsPluginSecurity provides utility methods for HTML sanitization and URL validation.

Available Methods:

Method Purpose Use Case
escapeHtml(String) Escape all HTML entities Rendering user input as plain text
sanitizeHtml(String) Remove dangerous HTML tags and attributes Allowing limited HTML markup
isValidUrl(String) Validate URL is safe (http/https only) Validating redirect URLs, external links
isValidStaticResource(String) Validate Static Resource name Preventing path traversal attacks
HTML Escaping Example
// Escape HTML entities
String userInput = '<script>alert("XSS")</script>';
String safe = QuikFormsPluginSecurity.escapeHtml(userInput);
// Result: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;

// Sanitize HTML (remove dangerous tags)
String userHtml = '<p>Hello</p><script>alert("XSS")</script>';
String sanitized = QuikFormsPluginSecurity.sanitizeHtml(userHtml);
// Result: <p>Hello</p> (script removed)
URL Validation Example
// Validate safe URLs
Boolean safe1 = QuikFormsPluginSecurity.isValidUrl('https://example.com');
// Result: true

Boolean safe2 = QuikFormsPluginSecurity.isValidUrl('javascript:alert("XSS")');
// Result: false (blocked)

Boolean safe3 = QuikFormsPluginSecurity.isValidUrl('data:text/html,<script>...');
// Result: false (blocked)

sanitizeHtml() Protections:

  • Removes <script> tags and content
  • Removes <iframe>, <object>, <embed> tags
  • Removes <style> tags (can contain malicious CSS)
  • Strips event handlers (onclick, onload, etc.)
  • Removes javascript: URLs
  • Removes data: URLs
Plugin Development: Always use these security methods in custom plugins. Never implement your own HTML/URL validation - use the framework's tested methods.

Plugin Security Best Practices

Follow these guidelines when developing custom QuikForms plugins:

1. Input Validation

  • Validate all configuration parameters in the plugin constructor
  • Use sanitizeConfiguration() for JSON configs
  • Fail safely - return sensible defaults if configuration is invalid

2. Output Encoding

  • Use escapeHtml() for all user-provided content
  • Never use innerHTML or unescaped bindings with user input
  • Validate URLs with isValidUrl() before rendering links

3. Least Privilege

  • Request only necessary permissions in plugin documentation
  • Use without sharing only when absolutely required
  • Never query sensitive objects (User, Profile, PermissionSet) without business justification

4. Error Handling

  • Catch and handle all exceptions gracefully
  • Log errors using QuikFormsExceptionLogger
  • Never expose stack traces or internal errors to users

5. Testing

  • Test plugins with malicious input (XSS payloads, SQL injection attempts)
  • Verify plugins work correctly with Guest User permissions
  • Test rate limiting and timeout scenarios
Secure Plugin Template
public class SecureExamplePlugin implements IQuikFormsPlugin {

    private Map<String, Object> config;

    public SecureExamplePlugin() {
        // Initialize with safe defaults
        this.config = new Map<String, Object>();
    }

    public void configure(String configJson) {
        // Sanitize configuration
        this.config = QuikFormsPluginSecurity.sanitizeConfiguration(configJson);

        // Validate required parameters
        if (!config.containsKey('requiredParam')) {
            throw new IllegalArgumentException('Missing required parameter: requiredParam');
        }
    }

    public String render(Map<String, Object> data) {
        try {
            // Escape all user input
            String userValue = (String) data.get('userInput');
            String safeValue = QuikFormsPluginSecurity.escapeHtml(userValue);

            // Build safe HTML
            return '<div class="plugin-output">' + safeValue + '</div>';

        } catch (Exception e) {
            // Log error and return safe fallback
            QuikExceptionLogger.logException(
                'PLUGIN-001',
                'Plugin render error: ' + e.getMessage(),
                'SecureExamplePlugin',
                'render'
            );
            return '<div class="plugin-error">Content unavailable</div>';
        }
    }
}
Security Review: Before deploying plugins to production, conduct a security review or peer code review focused on input validation, output encoding, and permission handling.

Security Checklist

Use this checklist to ensure your QuikForms deployment is secure:

Deployment Readiness: Complete all checklist items before publishing forms to production.

Turnstile & Bot Protection Configuration

  • □ Obtained Site Key and Secret Key from Cloudflare Turnstile Dashboard
  • □ Configured Turnstile External Credential with Secret Key
  • □ Added Turnstile Site Key to QuikForms Custom Metadata (Turnstile_Site_Key__c)
  • □ Granted Guest User access to Turnstile External Credential principal
  • □ Tested Turnstile widget loads on forms
  • □ Verified form submissions are validated by Turnstile
  • □ Confirmed honeypot protection is active (enabled by default)

Guest User Permissions

  • □ Granted Read access to FormDefinition__c, FormConfigVersion__c, FormField__c
  • □ Granted Create (only) access to submission objects (Case, Lead, etc.)
  • □ Verified Guest User cannot Read submission objects
  • □ Configured Field-Level Security for all mapped fields
  • □ Set submission object OWD to Private
  • □ Tested form submission as guest user (incognito mode)
  • □ Verified Guest User cannot query submission records

Rate Limiting

  • □ Configured Rate_Limit_Per_Minute__c in Custom Metadata
  • □ Set appropriate limit for environment (5-20 for production)
  • □ Verified Platform Cache is enabled
  • □ Tested rate limit enforcement (submit multiple times rapidly)
  • □ Configured monitoring for rate limit exceptions

XSS & Injection Prevention

  • □ Reviewed all customHTML fields for malicious scripts
  • □ Verified no user input is used in customHTML fields
  • □ Confirmed all plugins use escapeHtml() for user data
  • □ Tested forms with XSS payloads (<script> tags, event handlers)
  • □ Verified all SOQL queries use bind variables or escapeSingleQuotes()
  • □ Confirmed output encoding in Visualforce and LWC

Plugin Security

  • □ Validated all custom plugins implement IQuikFormsPlugin
  • □ Reviewed plugin code for security vulnerabilities
  • □ Confirmed plugins use QuikFormsPluginSecurity utilities
  • □ Tested plugins with malicious input
  • □ Verified plugins handle errors gracefully
  • □ Documented required permissions for each plugin

General Security

  • □ Enabled HTTPS for all Site pages
  • □ Configured Content Security Policy (CSP) headers
  • □ Enabled clickjack protection
  • □ Reviewed Site access logs for suspicious activity
  • □ Configured IP restrictions (if applicable)
  • □ Enabled debug logging for security events
  • □ Documented incident response procedures
Security Monitoring: After deployment, regularly review QuikForms Error Manager logs for security exceptions and suspicious patterns.