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 it | What it is called |
|---|---|
| Settings card grid | Dynamic CTAs — “Personalized calls-to-action based on visitor context.” |
| Settings card grid, second card | Recommendations — “AI-served related content for visitors.” Same destination. |
| Settings navigation | Dynamic CTAs |
| The page’s own header | Dynamic 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.
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.
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.
| Field | Purpose | Default on a new CTA |
|---|---|---|
| Name | Internal label. It is what the list shows and what you search for; readers never see it. | The text you typed to create it |
| Active | Master switch. Only active CTAs are ever considered. | Off |
| Description | Internal notes on what the CTA is for and who it targets. Never rendered. | Empty |
| Page Types | Restrict the CTA to pages of these types. Multi-select. | Empty — any page type |
| Labels | Restrict the CTA to pages carrying at least one of these labels. Multi-select. | Empty — any label |
| Content | The block readers actually see, written in a rich editor. | Empty |
| Start Date | The CTA is not considered before this date. Date only, no time. | Unset — always started |
| End Date | The 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:
| Condition | Where it is set | Effect when unset |
|---|---|---|
| The CTA is active | The Active switch | An inactive CTA is never considered — this is the fastest way to park one |
| Today falls inside the window | Start Date / End Date | No start date means always started; no end date means never expires |
| The page’s type is allowed | Page Types | Empty means the CTA is allowed on any page type |
| The page carries an allowed label | Labels | Empty 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.
- Attribute (supported)
- Shortcode (deprecated)
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>The old form still works. It renders a hidden marker naming the element that receives the CTA, so you need both the target element and the helper call:
<section id="article-cta" class="mt-16 border-t pt-8"></section>
{{{ CTA "article-cta" }}}Use triple braces — the helper outputs an HTML marker element.
| Mechanism | Status | Example |
|---|---|---|
data-leed-cta attribute | Supported | <section data-leed-cta></section> |
{{{ CTA "id" }}} shortcode | Deprecated | <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”.
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:
| Parameter | Value |
|---|---|
utm_campaign | leed |
utm_source | internal |
utm_medium | cta |
utm_term | the CTA’s id |
utm_content | the 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.