Documentation

Third-Party Widgets

Reviews, forms, booking and chat embeds take one of three shapes. Each has one correct home in a section.

Every embed is one of three shapes

Reviews, forms, booking widgets, chat: whatever the vendor, the snippet takes one of three forms, and each has one correct home in a section.

Shape Examples Where it goes
Shortcode Trustindex, booking plugins, any plugin shortcode. For forms, see Forms below. A shortcode_ content field
Script + container Trustindex loader, Trustpilot TrustBox, Elfsight, SociableKit Container in template_html, loader in section_js
Iframe Calendly, TypeForm, Google Maps embed Straight into template_html

Shortcode widgets

Put the shortcode in a shortcode_-prefixed content field and reference it as a placeholder. The renderer runs do_shortcode() on those fields automatically.

{ "shortcode_contact": "[contact-form-7 id=\"63\" title=\"Contact form 1\"]" }
<div class="contact__form">{{shortcode_contact}}</div>

Keeping it in a field rather than the markup means the site owner can swap forms later without touching code.

Forms

Embed your form plugin by shortcode in a shortcode_ field. list_forms finds forms from Gravity Forms, JetFormBuilder, Contact Form 7, WPForms, Fluent Forms, Ninja Forms and Formidable, and says per plugin what it counted, so an empty result tells you whether there are no forms or nothing was looked at.

With a form integration switched on (Settings → Integrations) the AI can also create and edit Gravity Forms, JetFormBuilder and Contact Form 7 forms.

Never hand-write a <form>. The plugin has no form backend, so it would submit nowhere. The AI is warned when one is written, the Send button is made inert on the live page, and editors (never visitors) see it outlined as a placeholder. A deliberately decorative form takes data-ppd-decorative="1". With no form plugin, ask for the no-form contact section: phone, email and address from Business Info, where every link works.

Editing a JetFormBuilder form

Reading a form shows each email's real recipient, subject and body; passwords and API keys show as [masked] and are kept when the form is saved back. To change one email, send only that action with actions_mode: "merge"; every other action, field, step, validation rule, captcha setting and message stays as it was. save_records: true switches on JetFormBuilder's Save Form Record, placed first so a failed email cannot lose a submission.

Every change stores a backup first (export_jfb_form lists them, restore_jfb_form puts one back). Before switching on an email, the health check's mail.verified_sender says which From address the site can send from; if it is empty, leave email off. Put %NI::AllFields% in an HTML email to include every submitted field with its label, including fields added later. get_jfb_form with preview_emails: true shows each email filled with sample answers; nothing is sent.

When a submission fails

Reading a JetFormBuilder form lists its recent failed submissions under last_failures: which step failed (for example the email or the record save), JetFormBuilder's error code, the mail error text, and whether the submission was still saved. What the visitor typed is never shown, and email addresses in error messages are partly hidden. The health check's mail.recent_error shows the site's last failed email from any plugin. Recording starts when version 0.49.4 is installed.

A form can be switched between AJAX and page-reload submits with submit_type. The page check warns when two or more forms on one page submit by page reload, because a message can then show under the wrong form.

Emails that cannot send

JetFormBuilder fails the whole submission when an email cannot be sent, so an email with an empty body cannot be saved. A new email added with no body gets a table of the form's fields, a subject naming the form, and a Reply-To taken from the form's email field. Forms saved earlier with an empty body show a "CANNOT SEND" notice whenever they are read. With preview_emails: true, each email is marked would_send: true or false, with the reason.

SEO plugins: Rank Math and Yoast

Turn on Rank Math or Yoast SEO under Settings → Integrations → SEO. The AI can then read and set a page's SEO title, meta description, focus keyword, robots, canonical URL and social previews (get_seo, update_seo), and the same for category and tag archives (get_term_seo, update_term_seo).

With Rank Math it can also edit the site-wide settings, such as the organization, Website Name and homepage meta (get_rankmath_global, update_rankmath_global, get_rankmath_options, update_rankmath_options), and add, list or remove redirects (manage_redirects; removing one needs confirmation). With Yoast it can read the site-wide defaults (get_yoast_global).

Every reply names the SEO plugin it used. With no SEO plugin active, changes are refused. If both plugins are active, choose which one is written to on the same screen; Vision Builder Control follows the same choice.

Code snippets: WPCode

Turn on WPCode under Settings → Integrations. The AI can then list and read your snippets (list_wpcode_snippets, get_wpcode_snippet), create and edit them (create_wpcode_snippet, update_wpcode_snippet), switch one on or off (set_wpcode_snippet_active), copy one (duplicate_wpcode_snippet) and delete one (delete_wpcode_snippet; this asks for confirmation first). New snippets start switched off unless you ask for them on.

CSS, JavaScript, HTML and text snippets work as soon as WPCode is on. PHP and Universal snippets run as code on your server, so writing or switching one on also needs the "Allow PHP / Universal snippets" box on the WPCode card. Only a person in wp-admin can tick it; a chat can't. Switching a snippet off or deleting it always works, so a snippet that breaks the site can be stopped either way.

Every change is recorded: list_wpcode_changes shows them and restore_wpcode_change puts one back. A permanently deleted snippet comes back with a new ID, switched off. Vision Builder Control has the same seven snippet tools and gives the same answers.

Script + container widgets

Put the vendor's container markup in template_html, with the exact class and data attributes the loader looks for. Inject the loader <script> via section_js — not via template_html — so it runs once per render and stays scoped.

<!-- template_html -->
<section class="reviews">
  <h2>What clients say</h2>
  <div class="ti-widget" data-widget-id="YOUR_WIDGET_ID"></div>
</section>
// section_js — guard against double-inserting on re-render
if (!document.querySelector('script[src*="trustindex.io"]')) {
  var s = document.createElement('script');
  s.src = 'https://cdn.trustindex.io/loader.js';
  s.defer = true;
  document.head.appendChild(s);
}

The most common failure is a container/loader mismatch. Trustindex loaders expect the widget-ID-specific container generated in their dashboard, not a generic div. Copy the embed snippet verbatim from the vendor rather than reconstructing it.

Iframe widgets

Paste the <iframe> directly into template_html. No loader needed.

Embeds in post content (YouTube, Vimeo, Visme, Google Maps and similar) are kept when the AI writes a post; administrators can embed any address. Anything removed is listed in a content_stripped warning.

Video

Ask for the video component rather than a hand-pasted iframe. A YouTube embed can render and never play when the site sends a strict referrer header. The component sets what YouTube needs, and validate_page flags embeds that are missing it, a title for screen readers, or fullscreen.

A note on oEmbed

Plain URLs in template_html do not auto-oEmbed — the plugin renders pre-filter HTML. For YouTube, Vimeo or Twitter, either hardcode the <iframe> (preferred, no external dependency), or call render_page with apply_filters=true to preview the post-filter output.