Custom RFI Form self-updates
WordPress plugin: request-for-information lead form with configurable API endpoints and extensible fields
by Cristian Bladimir Lopez Hurtarte · github.com/bladimirlopez07/wp-custom-rfi-form
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/bladimirlopez07/wp-custom-rfi-form/archive/refs/heads/main.zipShips its own WordPress updater (Plugin Update Checker), so new versions show up under Dashboard → Updates.
A WordPress plugin that adds a request-for-information (RFI) lead form to school marketing sites. The form loads campuses and programs from a partner lead API, guides the visitor through choosing a program, and submits the lead back to the API with tracking and attribution data.
- One-step or two-step layout
- Campus-first or program-first program selection, with dependent dropdowns
- Configurable fields, colors, disclaimer, thank-you page and API environment (production, QA or staging)
- Directory responses cached in transients; one click in the toolbar clears the cache
- Campaign tracking (track ID, session GUID, landing URL, UTM parameters, Google and Microsoft Ads click IDs)
- Extra fields (text, email, phone, number, URL or textarea) in any position, added with a filter
Requirements: WordPress 5.6+, PHP 7.4+
Installation
- Copy this folder to
wp-content/plugins/custom-rfi-form/. - Activate Custom RFI Form under Plugins.
- Open Settings → Form Settings and fill in:
- the Form Track ID
- the Server Switch
- the API URL for that server, either on the settings page or in
wp-config.php(see API endpoints)
- Add
[custom_rfi_form]to a page.
Until the API URL for the selected server is set, administrators see a notice, and the form makes no API requests.
Usage
[custom_rfi_form]
[custom_rfi_form steps="2" form_title="Get Program Info" thank_you_page="https://example.edu/thanks/"]
[custom_rfi_form feature_list_id="1234" prepop_campus="C1" prepop_aos="Business" prepop_program="P1"]
| Attribute | Description |
|---|---|
steps |
1 / one or 2 / two. Defaults to the Form Steps setting. |
feature_list_id |
Feature list ID. Defaults to the Global Feature ID setting. |
prepop_campus |
Campus ID to preselect. |
prepop_aos |
Area of study to preselect, matched by name (case and punctuation ignored). |
prepop_program |
Program ID to preselect. |
form_title |
Heading above the form. |
submit_button_text |
Submit button label. |
thank_you_page |
URL to redirect to after a successful submission. |
The form uses fixed element IDs, so only one form per page is supported.
Settings
| Setting | Purpose |
|---|---|
| Debug Mode | Administrators who submit the form see the JSON payload instead of it being sent. |
| Form Steps | One-step or two-step layout. |
| Form Track ID | Campaign track ID (the API key). Required. Public: it is rendered in the page. |
| Global Feature ID | Default feature list ID. |
| Form Name | Sent with the lead as FormName. |
| Server Switch | Which API URL to use: production, QA or staging. |
| Programs Before Campus | Program-first (programsformicrosites) or campus-first (campusesformicrosites) data. |
| Campus Type / Campus ID | Restrict the directory to a single campus. |
| Form Fields | Which optional fields to show. Email is always shown. |
| Highest Level of Education | Options as Label : value, one per line. |
| Start Term | Options as Label : value, one per line. |
| Program Option Group | Group programs into <optgroup>s by area of study. |
| Option Group Order | Custom order of those groups (alphabetical when empty). |
| Disclaimer | Consent text shown below the form and sent as UserAgreement. |
| Thank You URL Type / URL | Internal page or external URL to redirect to. Falls back to the home page. |
| Return LeadId | Ask the API to return a lead ID even for unsuccessful leads. |
| Honey Pot Field | Adds a hidden field; submissions that fill it are discarded. |
| Color Scheme | Form colors, output as CSS custom properties. |
| API Endpoints | See API endpoints. |
The settings page and the Clear API Cache toolbar button require the manage_options capability. Use a filter to allow other roles:
add_filter( 'custom_rfi_form_settings_capability', fn() => 'edit_pages' );
API endpoints
No API hostnames are hard-coded. Configure them under Settings → Form Settings → API Endpoints, or lock them in wp-config.php. A constant always wins over the setting, and its field on the settings page becomes read-only.
| Setting | Constant | Notes |
|---|---|---|
| Production API URL | CUSTOM_RFI_FORM_API_URL_PROD |
Base URL, e.g. https://partners.example.com/api |
| QA API URL | CUSTOM_RFI_FORM_API_URL_QA |
Base URL |
| Staging API URL | CUSTOM_RFI_FORM_API_URL_STAGE |
Base URL; plain HTTP allowed for internal hosts |
| Zip Code Lookup URL | CUSTOM_RFI_FORM_POSTAL_LOOKUP_URL |
Optional; ?ZipCode= is appended |
| Track ID Check URL | CUSTOM_RFI_FORM_TRACKID_CHECK_URL |
Optional; called from the browser with ?apikey= appended |
| (constant only) | CUSTOM_RFI_FORM_UPDATE_URL |
Optional; plugin-info.json URL that turns on self-hosted updates |
The plugin appends /directory/campusesformicrosites, /directory/programsformicrosites and /institutions/lead-save to the API base URL.
// wp-config.php
define( 'CUSTOM_RFI_FORM_API_URL_PROD', 'https://partners.example.com/api' );
define( 'CUSTOM_RFI_FORM_API_URL_QA', 'https://partners.qa.example.com/api' );
define( 'CUSTOM_RFI_FORM_POSTAL_LOOKUP_URL', 'https://forms.example.com/FormValidation/GetCityStateCountry' );
define( 'CUSTOM_RFI_FORM_UPDATE_URL', 'https://updates.example.com/plugin-info.json' );
Every URL, from the settings or a constant, must:
- use HTTPS (only the staging URL may use plain HTTP)
- include a host
- contain no credentials, query string or fragment
Invalid values are rejected with an error on save, and invalid constants are ignored.
Why wp-config.php? Leads, which include names, emails and phone numbers, are posted to these URLs. A constant keeps the endpoint in server configuration, under version control or deployment tooling. Even an attacker who takes over a wp-admin account cannot redirect lead data to another server. If you use the settings page instead, only users with manage_options can change the URLs.
If the zip lookup or track ID check URL is empty, that step is skipped.
Adding fields
Add your own fields from a small plugin or your theme, without editing this plugin:
add_filter( 'custom_rfi_form_fields', function ( $fields ) {
$fields['job_title'] = array(
'label' => 'Job Title',
'priority' => 25, // Between Last Name (20) and Phone (30)
'required' => true,
);
return $fields;
} );
Fields are validated in the browser and on the server, and sent with the lead automatically. The tutorial Adding custom fields covers every option, the built-in field order and troubleshooting.
How it works
Rendering the form
Custom_RFI_Form_Shortcodereads the settings and shortcode attributes.Custom_RFI_Form_Apiasks the directory API for campuses and programs. Responses are cached for 24 hours. Failures are cached for one minute, so an API outage does not slow every page view.- The one-step or two-step template renders the shared components: program selectors, contact fields and hidden tracking fields.
- The scripts are enqueued and receive their configuration as
window.CustomRfiFormData:rfi-tracking.jsmanages the campaign and session cookies.custom-form-filtering.js(campus-first or program-first) narrows the dependent dropdowns.custom-form-validation.jsvalidates the fields and handles the two-step flow.custom-form-additional.jsfills in the device type and cookie support, and checks the track ID.
Submitting a lead
The form posts to admin-post.php?action=custom_rfi_form_submit. The handler in includes/custom-form-submission.php:
- Verifies the nonce and silently drops honeypot hits.
- Keeps only an allow-list of known fields, unslashed and sanitized.
- Normalizes the name, phone, email, zip and birth date.
- Looks up the city, state and country from the zip code, if a lookup URL is configured.
- Adds the user agent, IP address, analytics click IDs and landing-page query parameters as
AdditionalQuestions. - Posts the JSON to
<API URL>/institutions/lead-save. - Redirects to the thank-you page with the lead ID, processing status, and SHA-256 hashes of the email and E.164 phone. The hashes are for ad platforms' enhanced conversions.
The thank-you URL rendered into the form is signed with an HMAC. The handler only follows it when the signature matches, so the field cannot be used as an open redirect.
Project structure
custom-rfi-form.php Bootstrap: constants, includes, hooks, optional update checker
includes/
class-custom-rfi-form-plugin.php Settings page (Settings API)
class-custom-form-shortcode.php [custom_rfi_form] rendering and asset loading
custom-form-submission.php Lead submission handler
form/
helper-functions.php Settings, API endpoints, cache, display options
fields.php Field registry and the custom_rfi_form_fields filter
class-custom-rfi-form-api.php Cached directory API client
custom-form-one-step.php One-step template
custom-form-two-steps.php Two-step template
components/ Field partials (selectors, inputs, hidden fields)
settings-classes/ Settings field definitions, renderers, sanitizer
js/ css/ Front-end and admin assets
plugin-update-checker/ Third-party library (MIT), loaded only when CUSTOM_RFI_FORM_UPDATE_URL is set
docs/
adding-custom-fields.md Tutorial: adding fields to the form
Security notes
- No secrets or hostnames in the code. The Form Track ID is a public campaign identifier: it is rendered in every page and can be overridden with
?trackid=. Endpoints are configuration (see API endpoints), and can be locked inwp-config.php. - Escaping. All output is escaped, including shortcode attributes, which any Contributor can set.
- Request handling. Submissions require a nonce. Only allow-listed fields are forwarded to the API.
- Debug mode never shows payloads to visitors.
- Settings are saved through the Settings API, with allow-lists. Endpoint URLs must use HTTPS and contain no credentials.
Known limitations
- Page caching and nonces. WordPress nonces expire after 12–24 hours. If pages with the form are cached longer than that, visitors get an "expired session" error. Exclude those pages from full-page caching or keep the cache TTL under 12 hours.
- Names. The server keeps only ASCII letters and hyphens in names, so "José" and "Mary Ann" are changed. Relax this only once the lead API is confirmed to accept other characters.
- Staging may use plain HTTP and is meant only for internal environments.
Author
Cristian Bladimir Lopez Hurtarte