Software

Hop Survey

Current Version: 1.0.0
  • EE7
  • Overview

    Invite-only surveys for ExpressionEngine: campaigns, unique recipient links, email delivery, open tracking, reminders (including a second reminder), completion status, and CSV exports. Survey answers are stored in Freeform.

    Hop Survey includes a starter Freeform form (hop_survey_default) when Freeform is installed. We recommend Test Send (Single) on each campaign’s Manage page before your first live send.

    The survey page is not linked from site navigation — you distribute invite URLs by email (or any channel you choose).

  • System Requirements

    • ExpressionEngine 7+
    • Solspace Freeform 3.0.0+ (freeform_next add-on; installed and licensed — not Freeform Classic)
    • PHP 8.1+ (match your EE install)
    • Outbound mail: Hop Survey → Email Settings (SMTP host, port, and from address are required before invites, reminders, or test mail will send). Per-environment overrides: Configuration (hop_survey_mail).
  • Installation

    1. Unzip the folder to a temporary location on your local hard drive.
    2. Upload the hop_survey folder to /system/user/addons/ on the server.
    3. Navigate to Add-ons in your EE control panel and install the add-on.

    Install also seeds the hop_survey_emails template group (invite, reminder, reminder_2, results) from bundled HTML in the add-on. Existing templates are never overwritten on upgrade.

    When Freeform Next is installed (or added later before you first open Hop Survey in the CP), Hop Survey creates a starter Freeform form hop_survey_default — one required yes/no question you can customize or replace. It is seeded once per site; if you delete that form in Freeform, Hop Survey will not recreate it.

  • First-time Setup

    1. Freeform form

    Hop Survey includes a starter form hop_survey_default when Freeform is available — one required yes/no question (“Are you satisfied with our work?”). Use it as-is, customize it in Add-ons → Freeform, or build your own form and assign it under campaign Settings.

    If you installed Hop Survey before Freeform, open Add-ons → Hop Survey once after installing Freeform so the default form can be created.

    2. Campaign and recipients

    In Add-ons → Hop Survey, create a campaign (New Campaign), assign a Freeform form under Settings (the default hop_survey_default or your own), and add recipients on Recipients (manually or CSV import — see Control Panel guide). Optional email-me-a-copy toggle: Control Panel guide.

    3. Front-end survey page

    Create a survey page (default /survey) and wrap the form with {exp:hop_survey:gate}. Full setup: Survey page. Custom path: Configuration (hop_survey → survey_path). Tag index: Template tags.

    Local preview of Freeform forms without invites: Configuration (hop_survey_test / {exp:hop_survey:test_gate}).

    4. Email settings

    Configure Hop Survey → Email Settings, or hop_survey_mail in Configuration for environment-specific SMTP. Confirm delivery with Hop Survey → Send Test Email.

    5. Test the full survey flow (recommended)

    Before your first live send, use Campaign → Manage → Test Send (Single). Enter your own email address and send a preview invite. The email is marked [TEST] and does not update recipient sent dates or status in Recipients.

    Open the invite link, complete the survey, and confirm templates, SMTP, {survey_path}, and the assigned Freeform form behave as expected. See Control Panel guide.

    6. Go live

    When you are satisfied with the test flow, send invites from Manage → Send survey.

  • Survey Page

    Create a template at /survey (or your survey_path). Hop Survey builds invite links as {site_url}/{survey_path}?invite=TOKEN — for example https://yoursite.example/survey?invite=…. You do not add the query string to the template.

    Hop Survey does not create a front-end template group for you. Put {exp:hop_survey:gate} on the ExpressionEngine template that serves that path. The template group does not have to be named survey.

    Local form preview without a real invite (developers only): Configuration (hop_survey_test).

    Token resolution

    The gate resolves the invite token in this order (first valid match wins; invalid values are skipped):

    1. POST field hop_survey_invite (hidden input — required for multipage Freeform posts)
    2. GET query ?invite=TOKEN
    3. Request-scoped session cache from earlier in the same request (not durable across page loads)

    Multipage continuity depends on the hidden hop_survey_invite field and Freeform return="{survey_return}" (which re-attaches ?invite=), not on long-lived session storage.

    Default setup
    1. Create a template that answers /survey (commonly template group survey, template index).
    2. Wrap the survey UI in {exp:hop_survey:gate} … {/exp:hop_survey:gate}.
    3. Inside the gate, render your Freeform form when {status} is ok.
    4. Set Freeform’s return to {survey_return} so multipage posts stay on the configured path with the invite token.
    5. Include a hidden field so partial progress and completion stay tied to the invite:
    
    {exp:hop_survey:gate}
        {if status == "ok"}
            {if freeform_form_exists == "yes"}
                {exp:freeform_next:form form="{freeform_handle}" return="{survey_return}" submission_token="{submission_token}"}
                    <input type="hidden" name="hop_survey_invite" value="{token}">
                    {!-- render Freeform fields normally --}
                {/exp:freeform_next:form}
            {/if}
        {if:elseif status == "completed"}
            … thank you …
        {if:elseif status == "expired"}
            … expired message …
        {if:else}
            … invalid / missing token …
        {/if}
    {/exp:hop_survey:gate}
    

    For multipage resume, Freeform uses statuses partially_complete / complete — see Reporting.

    Gate status values

    Use these exact strings in {if status == "…"} conditionals:

    Value Description
    ok Valid, active invite. Show the survey form.
    missing No valid token in the request (no usable POST, ?invite=, or request cache).
    invalid Token present but not found (unknown or deleted invite).
    expired Invite expires_at is in the past.
    completed Recipient already submitted the survey.

    Gate status is not the same as the Recipients table Status column. Funnel stages such as “Email opened” appear only in the CP and CSV exports — see Reporting.

    Gate variables
    Variable Values Description
    {status} ok, missing, invalid, expired, completed Invite validity for template branching.
    {freeform_form_exists} yes, no Whether the campaign’s assigned Freeform form exists on this site.
    {show_send_results_option} yes, no Whether this campaign shows the Freeform survey_send_results checkbox. Always no on {exp:hop_survey:test_gate}.
    {token} string Invite token from POST, URL, or request cache.
    {email} string Recipient email from the invite row.
    {name} string Full Name from the invite row.
    {first_name} string First word of Name (text before the first space).
    {last_name} string Remainder of Name after the first space (empty for a single word).
    {campaign_title} string Campaign title.
    {freeform_handle} string Freeform form handle assigned to the campaign.
    {submission_token} string Freeform submission token for resume / partial progress.
    {survey_path} string Configured survey URL path (no leading slash).
    {survey_return} string Path plus ?invite= for Freeform return=.

    Full tag index (including email placeholders): Template tags.

    Scale endpoint labels

    Use {exp:hop_survey:scale_ends} on numeric/select scale questions so the survey page shows the same low/high labels as invite emails. Labels are parsed from Freeform field instructions (for example 0 = Not at all | 5 = Very satisfied or 0 = Not great 5 = Really great). If instructions omit labels, defaults are Not at all and Very satisfied.

    Self-closing (renders the default markup):

    
    {exp:hop_survey:scale_ends instructions="{field:instructions}"}
    

    Pair form when you need {low} and {high} in your own markup:

    
    {exp:hop_survey:scale_ends instructions="{field:instructions}"}
        <span>{low}</span><span>{high}</span>
    {/exp:hop_survey:scale_ends}
    
    Optional email-me-a-copy checkbox

    When enabled for the campaign, Hop Survey shows the Freeform checkbox with handle survey_send_results on this page automatically — render Freeform fields normally. When disabled, Hop Survey hides that field. How to add the field and use the Settings toggle: Control Panel guide.

    Checkbox “Other” with custom text

    When a checkbox-group question includes an Other option plus free text, model both parts in Freeform — Hop Survey does not merge custom POST fields for you.

    1. Checkbox group — Include an option with value other (label can be “Other (please specify)”).
    2. Detail field — Add an optional textarea (or text field) on the same page, e.g. handle my_question_other.
    3. Survey template — Render the detail field with Freeform tags ({field:render_input} or {field:render}) so partial progress, resume, exports, and submissions all use Freeform’s normal storage. Use JavaScript only to show or hide the detail block when Other is checked.

    Do not use a plain HTML <textarea name="…"> that is absent from the Freeform form layout — Freeform will not persist it.

    Custom checkbox markup for the group is fine; the detail field must still be a real Freeform field on the layout.

    Custom invite URL path

    Config syntax and validation rules: Configuration (hop_survey → survey_path). Place {exp:hop_survey:gate} on the EE template that serves that path:

    survey_path Typical EE template
    survey (default) survey/index
    client-survey client-survey/index
    feedback/take feedback/take

    If Manage shows a survey-path warning before send, the ExpressionEngine template for that path is missing or only on disk — see Control Panel guide and Configuration.

  • Control Panel Guide

    Task Control Panel path
    List campaigns Add-ons → Hop Survey
    Create campaign New Campaign
    Add recipients Campaign → Recipients
    Import recipients CSV Campaign → Recipients (columns email, name — see below)
    Assign Freeform form / expiry / pin / internal notes / email-me-a-copy Campaign → Settings
    Send / test / export CSV Campaign → Manage (Send survey, Send reminder, Send second reminder, Test Send, export)
    Edit email templates Hop Survey → Email Templates (invite, reminder, second reminder, and results — see Template tags)
    SMTP settings Hop Survey → Email Settings (Configuration for hop_survey_mail overrides)
    Send test email Hop Survey → Send Test Email
    Email open webhook Hop Survey → Email Open Webhook (Configuration for signing secret)
    Test Send before going live

    We strongly recommend Manage → Test Send (Single) before Send survey. Enter your own email, send the preview, open the invite link, and complete the survey. Test emails are marked [TEST] in the subject and do not update recipient sent dates or status in Recipients — use them to verify SMTP, email templates, {survey_path}, and the assigned Freeform form end-to-end.

    Default starter form

    Hop Survey seeds hop_survey_default in Freeform when the add-on is installed (or the first time you open Hop Survey in the CP after Freeform is installed). It includes one yes/no question. Customize or replace it in Add-ons → Freeform, or assign a different form under campaign Settings. Deleting hop_survey_default in Freeform will not recreate it.

    Recipients CSV import

    Import accepts two columns: email, name (lowercase headers). A header row is optional. The Recipients for import export type matches this format — see Reporting.

    Email-me-a-copy on survey forms
    1. In Freeform, add a checkbox field with handle survey_send_results (exact handle) to the campaign’s form.
    2. Until that field exists on the selected form, the campaign Settings → “Email me a copy” on survey forms toggle stays disabled.
    3. Set the toggle to Yes to show the checkbox on the survey page (Hop Survey shows or hides it automatically). No hides it.
    4. When a respondent checks the box and submits, Hop Survey can send the results-copy email ({responses} in the results template).
    Reminders

    Manage has Send reminder and Send second reminder:

    • Send reminder — Recipients who have not opened the invite email or the survey.
    • Send second reminder — Recipients who already received the first reminder and still have not opened the email or survey.

    Edit HTML under Hop Survey → Email Templates (REMINDER and SECOND REMINDER). Subjects can also be overridden with reminder_subject and reminder_2_subject — see Configuration.

    Pin, notes, and re-opening a completed invite
    • Pin campaign (New Campaign or Settings) — Pinned campaigns stay at the top of the Campaigns list and in the add-on side navigation.
    • Internal notes (Settings) — Staff-only. Not shown to recipients or included in survey emails.
    • Allow submit again (Recipients → edit a completed recipient) — Clears completion so that invite link can be used again.
    Survey path readiness

    Before a bulk send, review the Manage confirmation checklist. A survey-path warning means no ExpressionEngine template matches survey_path (or it exists only on disk). Fix the front-end template or correct survey_path in Configuration so invite links do not 404. The check does not HTTP-probe the live URL and does not block send.

  • Template Tags

    Reference index for Hop Survey tags. Setup details and status enums live in the linked sections — this page lists every tag in one place.

    • Gate tag / gate variable — Front-end survey template. Setup, token order, and {status} values: Survey page.
    • Test gate — Local preview only. Config, {status} values, and limitations: Configuration (hop_survey_test).
    • Scale ends — Low/high labels for numeric scales on the survey page. Setup: Survey page.
    • Email placeholder — Replaced when invite, reminder, second reminder, or results email is sent. Second reminder uses the same body placeholders as the first reminder. Use the insert-tag buttons under Hop Survey → Email Templates.

    CP Recipients Status labels are separate from gate {status} — see Reporting.

    Tag reference
    Type Tag Description Where used
    Gate tag {exp:hop_survey:gate} Invite gate tag pair. Front-end survey template — Survey page. Path: Configuration (hop_survey → survey_path).
    Gate tag {exp:hop_survey:test_gate} Local preview gate (variant="single" or "multiple"). Front-end template on local only — Configuration (hop_survey_test).
    Gate tag {exp:hop_survey:scale_ends} Low/high scale labels from Freeform field instructions (instructions= parameter). Pair form exposes {low} and {high}. Front-end survey template — Survey page.
    Gate variable {status} Invite or test gate state. Survey gate; Test gate — values on Survey page / Configuration
    Gate variable {token} Invite token from POST, URL, or request cache. Survey gate; Test gate
    Gate variable {survey_path} Configured invite URL path (no leading slash). Survey gate only
    Gate variable {survey_return} Path plus ?invite= for Freeform return=. Survey gate only
    Gate variable {freeform_handle} Campaign or test Freeform form handle. Survey gate; Test gate
    Gate variable {freeform_form_exists} Whether the configured form exists (yes / no). Survey gate only
    Gate variable {show_send_results_option} Whether the campaign shows survey_send_results (yes / no). Always no on the test gate. Survey gate; Test gate
    Gate variable {submission_token} Freeform submission token for resume / partial progress. Survey gate only
    Gate variable {campaign_title} Campaign name (or test preview title). Survey gate; Test gate; Email subject, invite body, reminder body, second reminder body
    Gate variable {name} Full Name from the invite row. Survey gate; Test gate; Email subject, invite body, reminder body, second reminder body
    Gate variable {first_name} First word of Name (before the first space). Same split as {greeting}. Survey gate; Test gate; Email subject, invite body, reminder body, second reminder body
    Gate variable {last_name} Remainder of Name after the first space (empty for a single word). Survey gate; Test gate; Email subject, invite body, reminder body, second reminder body
    Gate variable {email} Recipient email from the invite row. Survey gate; Test gate; Email invite body, reminder body, second reminder body
    Test gate variable {variant} Test config key from hop_survey_test.forms (e.g. single, multiple). Test gate only
    Test gate variable {status_heading} Error heading when test gate status is not ok. Test gate only
    Test gate variable {status_message} Error message when test gate status is not ok. Test gate only
    Test gate variable {is_test} Always yes on test gate output. Test gate only
    Email placeholder {greeting} Hi + first name + comma (e.g. Hi Jane,), or Hi, when no name is saved. Invite body; Reminder body; Second reminder body; Results body
    Email placeholder {invite_url} Survey URL for this invite. Invite and reminder emails render this through the survey_click ACT endpoint so a click records an open even when the tracking pixel is blocked, then redirects to {survey_path}?invite=TOKEN. Used by {survey_button}; answer buttons in {first_question_embed} add &a= for one-click prefill. Invite body; Reminder body; Second reminder body
    Email placeholder {first_question} Plain-text label of the first Freeform field on page 1 (skips submit, HTML, and hidden layout fields). Works for any field type including text and textarea. Empty when page 1 has no labeled field. Invite body; Reminder body; Second reminder body
    Email placeholder {first_question_embed} HTML block for the first field on page 1. Discrete answers (radio, select, scales, etc.): one clickable button per value via survey_click with answer prefill (a=); on multipage forms a click can save the first answer and land on page 2. Open-ended fields (text, textarea, etc.): question label plus a survey CTA (no prefill). Scale endpoint labels come from field instructions or option labels. Empty when page 1 has no labeled field. Invite body; Reminder body; Second reminder body
    Email placeholder {survey_button} Orange “Take the survey” button linking through survey_click (same open-tracking redirect as {invite_url}). Invite body; Reminder body; Second reminder body
    Email placeholder {email_open_pixel} 1×1 open-tracking image. Include once per email (usually before </body>). Auto-appended when the rendered body does not already contain this invite’s open-pixel URL. Invite body; Reminder body; Second reminder body
    Email placeholder {open_pixel_url} Raw open-tracking URL used by {email_open_pixel}. Invite body; Reminder body; Second reminder body
    Email placeholder {test_banner} Yellow test banner on test sends only. Empty on live campaign sends. Invite body; Reminder body; Second reminder body
    Email placeholder {responses} HTML table of the respondent’s submitted answers from the campaign Freeform form. Results body
  • Configuration

    All optional config.php keys are documented below. Use the Control Panel for day-to-day settings where noted; these blocks are for per-environment overrides. Other documentation sections link here instead of repeating examples.

    hop_survey → survey_path

    Custom invite URL path (default survey). Hop Survey appends ?invite= to invite links, for example /survey?invite=….

    
    $config['hop_survey']['survey_path'] = 'client-survey'; // invite links: /client-survey?invite=…
    // or a nested template:
    $config['hop_survey']['survey_path'] = 'feedback/take'; // invite links: /feedback/take?invite=…
    

    Rules:

    • No leading or trailing slash
    • Letters, numbers, _, -, and / only
    • Invalid or empty values fall back silently to survey

    Put {exp:hop_survey:gate} on the EE template that serves the configured path — template examples: Survey page. Manage readiness warnings: Control Panel guide.

    hop_survey_mail

    Configure Hop Survey → Email Settings in the Control Panel first. Override SMTP credentials and mail subjects per environment without storing secrets in the database. Keys match the CP form: smtp_host, smtp_port, smtp_user, smtp_pass, smtp_crypto, from_email, from_name, subject, reminder_subject, reminder_2_subject, results_subject. Unset or blank values keep the CP-saved setting.

    smtp_crypto values: tls (STARTTLS, typical port 587), ssl (typical port 465), or empty for unencrypted.

    
    $config['hop_survey_mail'] = [
        'smtp_host'   => 'smtp.example.com',
        'smtp_port'   => '587',
        'smtp_user'   => '[email protected]',
        'smtp_pass'   => '…',
        'smtp_crypto' => 'tls',
        'from_email'  => '[email protected]',
        'from_name'   => 'Example Surveys',
    ];
    
    hop_survey_test

    Enable survey preview URLs on local environments only (ENV must be local). Map test variants to your Freeform form handles, then serve {exp:hop_survey:test_gate} on templates such as /survey_test/single.

    
    $config['hop_survey_test'] = [
        'enabled' => true,
        'forms'   => [
            'single'   => 'your_single_page_handle',
            'multiple' => 'your_multipage_handle',
        ],
    ];
    
    
    {exp:hop_survey:test_gate variant="single"}
        {if status == "ok"}
            … Freeform form …
        {if:else}
            <h2>{status_heading}</h2>
            <p>{status_message}</p>
        {/if}
    {/exp:hop_survey:test_gate}
    

    Tag parameter: variant="single" or variant="multiple" (aliases: form=, or URL segment). Keys must match hop_survey_test.forms.

    Test tokens use the hst_ prefix plus a configured Freeform handle from hop_survey_test.forms and do not mark production invites complete.

    The test gate does not output {survey_path}, {survey_return}, {freeform_form_exists}, or {submission_token}. Hardcode Freeform return= (and path) in test templates. The optional survey_send_results checkbox is not shown on test previews.

    Test gate status values
    Value Description
    ok Test surveys enabled and form loaded.
    disabled hop_survey_test.enabled is false or ENV is not local.
    invalid Missing or empty variant parameter.
    missing_form Variant not configured, or configured Freeform handle not found on this site.

    Test-only variables: {variant}, {is_test} (always yes), {status_heading}, {status_message}. Tag index: Template tags.

    hop_survey_email_open_webhook → webhook_secret

    Optional Svix signing secret for SMTP provider email.opened webhooks. Register the endpoint URL under Hop Survey → Email Open Webhook in the Control Panel.

    
    $config['hop_survey_email_open_webhook'] = [
        'webhook_secret' => 'whsec_…',
    ];
    

    Without a secret, the endpoint returns 503. Complements the {email_open_pixel} GIF in invite and reminder emails — see Template tags.

    Email open tracking: {email_open_pixel} loads the open_pixel ACT GIF. {invite_url}, {survey_button}, and answer links in {first_question_embed} use the survey_click ACT endpoint so a recipient click still records an open when clients block remote images.

  • Reporting

    Survey answers live in Freeform. Hop Survey tracks invite delivery and engagement in the CP Recipients table and CSV exports. Use Manage → Full campaign data to export answers joined with invite tracking.

    Recipients Status column

    Highest-priority label wins. Terminal states override funnel stages.

    Status Description
    Completed Survey submitted (completed_at set).
    Expired Invite past expires_at.
    Invalid Token not found in the database.
    Missing token Invite row has no token.
    Partially complete Multipage survey started but not finished (Freeform submission partially_complete).
    Survey opened Recipient loaded the survey page (survey_visited_at).
    Email opened Invite email opened (opened_at via pixel or webhook).
    Second reminder sent Second reminder email sent.
    Reminder sent First reminder email sent.
    Email sent Invite email sent (sent_at).
    Not emailed On the list but invite not yet sent.

    These labels are not the same strings as gate {status} (ok, missing, invalid, expired, completed).

    Export types (Manage page)
    Type Description
    Full campaign data Invite metadata plus Freeform submission answers (answers live in Freeform; this export joins them to each invite).
    Recipients & tracking Invite rows with sent, opened, and completed timestamps (no survey answers).
    Recipients for import Two columns only: email, name (lowercase headers), matching the Recipients CSV import format. Import steps: Control Panel guide.
    Invite CSV columns

    Full / recipients exports include: Email, Name, Campaign, Token, Invite URL, Status, Sent At, Opened At, Reminded At, Survey Visited At, Partial Started At, Completed At, Expires At, Submission ID.

    Freeform submission statuses

    Hop Survey creates these Freeform status handles on install when missing:

    Handle Description
    partially_complete Applied while a multipage survey is in progress.
    complete Applied when the respondent submits the final page.

    Partial answers are saved as Freeform submissions. The invite row stores submission_id and page index so returning visitors can resume (use {submission_token} on the Survey page).

  • Troubleshooting

    Common checks
    • Invites or reminders not sending — Confirm Hop Survey → Email Settings (SMTP host, port, and from address), then use Send Test Email.
    • First campaign send — Use Manage → Test Send (Single) before Send survey to preview the full invite and survey flow without affecting recipient status.
    • Second reminder has no recipients — Recipients must have received the first reminder and still not opened the email or survey.
    • Survey path warning on send — Create an ExpressionEngine template that matches survey_path, or correct the path in Configuration.
    • Gate shows missing, invalid, or expired — Confirm the invite URL still includes ?invite=, and that the invite has not expired or already completed. See Survey page.
    • Default form missing — If Hop Survey was installed before Freeform, open Add-ons → Hop Survey once after installing Freeform. Hop Survey creates hop_survey_default only once; if you deleted it, create or assign another form under campaign Settings.
    • Answers missing from Hop Survey exports — Answers live in Freeform; use Manage → Full campaign data to join them with invite tracking. See Reporting.

    Still stuck? Contact [email protected].

  • Updating

    1. Download a new version from this website, and extract on your hard drive.
    2. Upload the new files to your system’s addon directory.
  • Uninstalling

    1. Navigate to Add-ons → Hop Survey and uninstall the module.
    2. Remove system/user/addons/hop_survey/ from the server.

    Uninstall does not delete the hop_survey_emails template group or the seeded Freeform form hop_survey_default. Remove those in Design → Templates and Add-ons → Freeform if you no longer need them.

Have a Project for Us?

Request a Proposal