Dynamic CTAs

A Dynamic CTA is a block of rich content — a pitch, an offer, a “talk to sales” prompt — that your template asks for and Leed fills in. You do not attach a CTA to a page. You define a pool of them, describe which pages each one is allowed to appear on, and for every page exactly one wins: the closest content match among those still eligible. A reader deep in your API reference gets a different prompt than one finishing a customer story, and neither page had to be edited to make that happen.

Where CTAs are configured

Everything lives on one screen at /settings/recommendations, and that screen answers to four different names — which is worth knowing once so you can find it from any of them:

Where you see itWhat it is called
Settings card gridDynamic CTAs — “Personalized calls-to-action based on visitor context.”
Settings card grid, second cardRecommendations — “AI-served related content for visitors.” Same destination.
Settings navigationDynamic CTAs
The page’s own headerDynamic Calls To Action

Both cards land on the same route because the two features share one similarity engine; only the CTA half has a settings screen. The settings card grid and its full card-to-route map are described in settings home and the settings map.

Settings, Dynamic Calls To Action, with the New Dynamic CTA input at the top right and one CTA expanded to show Name, Active, Description, Page Types, Labels, the content editor, Start Date and End Date

Below Growth this route renders the header and an upgrade panel in place of the list. No CTA is fetched, so you cannot read, edit or even count your CTAs from a downgraded workspace — the records are untouched, they are simply not served.

The same Dynamic Calls To Action screen on a Starter workspace, showing the upgrade panel filling the page body with no CTA list

Creating and configuring a CTA

Type a name into New Dynamic CTA at the top right and press the add control. The CTA appears in the list immediately; click its name to expand it.

Every field saves on its own as you change it. Text fields save about a second after you stop typing, and when you move focus away; switches, selectors and dates save on the change. There is no Save button and no dirty state to manage.

FieldPurposeDefault on a new CTA
NameInternal label. It is what the list shows and what you search for; readers never see it.The text you typed to create it
ActiveMaster switch. Only active CTAs are ever considered.Off
DescriptionInternal notes on what the CTA is for and who it targets. Never rendered.Empty
Page TypesRestrict the CTA to pages of these types. Multi-select.Empty — any page type
LabelsRestrict the CTA to pages carrying at least one of these labels. Multi-select.Empty — any label
ContentThe block readers actually see, written in a rich editor.Empty
Start DateThe CTA is not considered before this date. Date only, no time.Unset — always started
End DateThe CTA is not considered after this date. Date only, no time.Unset — never expires

The fields appear in that order on screen, with Content sitting between Labels and the two dates. The trash control at the bottom of an expanded CTA withdraws it: the record is kept and flagged, its vector is removed on the next index run, and it stops matching anything.

Writing or rewriting Content — and bringing a withdrawn CTA back — requires the dynamiccta:publish privilege, because that content goes onto live pages as HTML. Renaming, targeting, scheduling and flipping Active only require dynamiccta:write. A Content Writer can therefore retarget or retire a CTA without being able to author new markup for it.

How targeting narrows the pool

Every reader request starts from your whole pool and removes CTAs until one is left. The conditions combine, and all of them must pass:

ConditionWhere it is setEffect when unset
The CTA is activeThe Active switchAn inactive CTA is never considered — this is the fastest way to park one
Today falls inside the windowStart Date / End DateNo start date means always started; no end date means never expires
The page’s type is allowedPage TypesEmpty means the CTA is allowed on any page type
The page carries an allowed labelLabelsEmpty means the CTA is allowed on any page, labeled or not

Among everything that survives, exactly one wins, ranked by how close the CTA’s own content is to the content of the page being read. There is no priority field, no weighting and no round-robin: if two CTAs are both eligible, the more topically relevant one is the one your reader sees. Labels are the same labels you already use for topic indexes and series, described in labels and series.

flowchart TD
  A[Reader opens a published page] --> B{Does the template put<br/>a CTA slot on this page?}
  B -- no --> Z[No request is made<br/>and nothing renders]
  B -- yes --> C[Ask Leed for a CTA for this page]
  C --> D{Plan includes<br/>Dynamic CTAs?}
  D -- no --> Y[Empty result<br/>nothing renders]
  D -- yes --> E[Start from every CTA in the pool]
  E --> F[Drop the ones that are not Active]
  F --> G[Drop the ones outside their<br/>Start / End window]
  G --> H[Drop the ones whose Page Types<br/>exclude this page]
  H --> I[Drop the ones whose Labels<br/>do not match this page]
  I --> J{Anything left?}
  J -- no --> Y
  J -- yes --> K[Rank what remains by content similarity]
  K --> L[One winner is returned<br/>and appended to the slot]

When a CTA you expected does not appear, walk that chain in order. Four of the five gates are things you set, and the most common answer is the first one.

Retiring a campaign automatically

An End Date is the difference between a campaign that ends and a campaign somebody has to remember to end. Set one when you create a launch or seasonal CTA and it takes itself out of circulation on schedule, with no follow-up task and no risk of a stale offer sitting on your site in February. Dates are day-granular — the CTA is considered while today falls inside the window, inclusive.

Placing a CTA on a page

A CTA renders where your template says it does, and nowhere else. Put the data-leed-cta attribute on the element that should receive it, style that element however you like, and leave it empty — Leed appends the CTA into it at read time.

A page whose template carries no CTA slot makes no request at all. That is not a failure mode, it is the design: adding CTAs to your site is a template decision you make once, in writing your own partials.

Put the attribute on the receiving element. There is no id to keep in sync, no helper call and no inline script — which is what lets a site ship a strict Content-Security-Policy with no unsafe-inline.

<section data-leed-cta class="mt-16 border-t pt-8"></section>
MechanismStatusExample
data-leed-cta attributeSupported<section data-leed-cta></section>
{{{ CTA "id" }}} shortcodeDeprecated<section id="article-cta"></section> plus {{{ CTA "article-cta" }}}

Both helpers are listed alongside the rest of the template helper surface in form and CTA helpers.

Why the shortcode was deprecated

The shortcode used to emit an inline <script> tag, which forced every site carrying a CTA to allow unsafe-inline in its Content-Security-Policy — and it interpolated your target id straight into a JavaScript string literal, which was an injection sink. Both problems are gone: the helper now emits only a hidden, escaped marker span, and the client discovers it on DOM ready. The attribute is simply the version with nothing left to go wrong, so new templates should use it.

Unlike recommendations, a CTA that matches nothing renders nothing and the slot element is left exactly as it is. There is no empty-container hide, so an empty <section data-leed-cta> with a heading inside it will show that heading with no CTA under it. Put nothing in the slot element that only makes sense when a CTA is present.

Turning CTAs off for one page

Some pages should not sell anything: legal and policy pages, a checkout or sign-up flow, an incident notice. Open the page in the editor, go to the right rail → Page settings → Automations, and check Disable Dynamic CTA — “Suppress the default call-to-action block on this page”.

The Automations section of Page settings in the editor right rail, with the Disable Dynamic CTA checkbox checked above Disable Autolinks

When a CTA change reaches your site

CTA content is served live. Saving a CTA queues a re-index of your content, and once that finishes — seconds later, not instantly — published pages start serving the new version. No deployment is involved and no republish is needed. A CTA you rewrite this morning is on your site this morning, on every page it matches, without touching a single page.

Two consequences worth holding on to. First, there is no staging step for CTA content: the pool you are editing is the pool your readers are matched against, which is why writing content carries the publish privilege rather than the write one. Second, go-live is eventually consistent — if you save a CTA and reload a page immediately, you may still get the previous match for a moment. Wait, then reload.

Measuring a CTA

The rendered CTA is wrapped in an element that carries both the id the click tracker walks up to and the marker the element-visibility tracker’s selector matches, so you get both halves of the measurement with no setup: how many readers saw a CTA, and how many clicked it. That wrapper is created client-side, which is why it works identically on every template.

Every link inside the CTA is also stamped with hidden UTM context — attached to the link as attribution data rather than as query parameters, so your URLs stay clean:

ParameterValue
utm_campaignleed
utm_sourceinternal
utm_mediumcta
utm_termthe CTA’s id
utm_contentthe link’s own text, collapsed and truncated

Those clicks arrive in the destination page’s Attribution block, so “which CTA sent readers to the pricing page” is a question you can answer from the pricing page. How that stamping works and how to read the resulting table is in the hidden UTMs Leed stamps for you. The click side of it — every click on your site, tracked the same way — is described in click tracking and the overlay.

Related-content cards use the same similarity engine with a different set of rules and a different failure mode; they are covered in recommendations.

ESC