Utility Helpers

The helpers that do not belong to a domain. They are grouped here by what they are for rather than by what they touch, because that is the only thing they have in common: two answer questions about files, four work on strings and values, one prints to your terminal, two read the search index and one formats OpenAPI security names. All of them assume the call mechanics established at How Helpers Work.

Template slots: useCustomTemplate and templateExists

Two helpers that look alike and answer different questions.

useCustomTemplate takes one of exactly four slot names and answers did the build detect an override file for this slot? The four names are unsubscribe, unsubscribed, docsFooter and docsHeader, and nothing else is accepted — an unrecognized name returns false.

{{#if (useCustomTemplate "unsubscribe")}}
  {{> unsubscribe }}
{{else}}
  ...Leed's default unsubscribe page...
{{/if}}

Detection is pure file existence in your own repository. There is no CMS toggle and no setting to enable; creating the file is what turns the slot on.

templateExists takes a path relative to the resolved _includes directory, extension included, and answers does this file exist right now? — a synchronous existence check evaluated at render time rather than cached. Its one production call site is the first line of Leed’s head partial, and it is what makes the shared head hook work:

{{#if (templateExists "header-includes.hbs")}}
  {{> header-includes }}
{{/if}}

An empty name, or a build with no resolvable includes directory, returns false rather than throwing. What belongs in that file — and why it is the only supported way to add a tag to every page’s head, documentation included — is at Customizing the Head.

The four slots and their files

Slot nameFile that fills itTier requiredWhat it replaces
docsHeader_includes/docs-header.hbsStarterthe documentation header, inside Leed’s positioned wrapper
docsFooter_includes/docs-footer.hbsStarterthe documentation footer, inside #docs-footer-wrapper
unsubscribe_includes/unsubscribe.hbsnonethe /stubs/unsubscribe.html page your marketing email links to
unsubscribed_includes/unsubscribed.hbsnonethe post-unsubscribe confirmation page

These four are not the whole override surface. Two more Leed templates can be replaced and neither is reachable through useCustomTemplate: header-includes.hbs is found by templateExists, and the recommendation card template is resolved by a direct file lookup of its own. Overriding Leed Templates is the authoritative list of all six, with what each is allowed to replace and what leed site eject writes for it. The two unsubscribe slots also have obligations of their own — the replacement page still has to do the unsubscribing — described at Unsubscribes and Opt-Outs.

The helper returns a plain boolean, so {{#if}} is the whole of the API. There is nothing on the truthy side to inspect.

Strings and values

Four helpers for the small manipulations a template needs and Handlebars does not provide. Note that Leed does not install the string category of the handlebars-helpers library, so uppercase, truncate, replace and 33 others are not available — these four are the stand-ins, and the full list of what is missing is on How Helpers Work.

concat joins its arguments. Its everyday use is building a dynamic collection or partial name:

{{#forEach (collection (concat "pageTypeId-" @key ":unpaged"))}}

firstTruthy is a block helper. It tests its arguments in order and renders the block with the first truthy one as this; when none is truthy it renders nothing.

{{#firstTruthy title ogCard.title}}
  <meta property="og:title" content="{{ this }}" />
{{/firstTruthy}}

It exists because the library’s (or a b) returns a boolean rather than the value that made it true, which is useless when you want to print the winner. firstTruthy is the value-returning version. Because it always calls the block, it is block-only — an inline {{ firstTruthy a b }} has no block function to call.

stripTags removes anything between angle brackets and then replaces every & and = with a hyphen. Leed’s menu partial uses it to fold a menu item’s name into a query-string value:

data-reason="...&utm_content={{ stripTags item.name }}"

wrapSubstring wraps every occurrence of a substring in a classed span, and returns a SafeString so double braces are correct:

{{ wrapSubstring title "vs." "no-bold" }}
Leed <span class="no-bold">vs.</span> The Rest

All three parameters are required strings and all three are checked, so a non-string in any position throws a TypeError carrying the parameter name. The substring is regex-escaped before matching, so "vs." matches a literal vs. rather than any three characters.

No shipped Leed template calls wrapSubstring — the same is true of staticImage and svgDiagram on Image and Media Helpers. That is a statement about Leed’s templates, not about support: all three are registered on every build and are yours to use.

JSONsafe — a JSON string literal

JSONsafe turns a string into a JSON string literal — including the surrounding quotes — and returns it as a SafeString. A falsy value produces a quoted empty string, "", rather than nothing.

That detail is the whole point of the helper, and it is why the JSON Feed template has no quotes of its own around the value:

"content_text": {{ JSONsafe data.summary }},
"content_text": "A summary with a \"quoted\" phrase and a\nnewline.",

Adding your own quotes produces ""A summary"" and an invalid feed. Omitting JSONsafe and quoting manually produces invalid JSON the first time a summary contains a quotation mark or a newline — which is exactly the case that will not show up in your test data.

logger — printing context during a build

logger is variadic, emits nothing into the page, and prints each argument to the build console as JSON. It is the answer to “what data does this template actually have?”.

{{ logger this }}
{{ logger page.url this.entitlements }}

The output lands in the console of leed site build alongside every other message this reference mentions, so keep that terminal in view while you work. On a value that cannot be serialized — a circular structure, which the page context often is — it falls back to printing the object’s top-level keys instead of failing, which is usually the more useful output anyway.

Search hashes: searchIndexHash and searchDocumentHash

Two no-parameter helpers that return the current build’s index hash and document hash for the page’s configured search index. Leed’s head partial emits both into a small script block so the reader’s browser can tell a cached index from a stale one.

{{#unless (hasFeature "docsLiveSearch")}}
  {{#if documentationConfiguration.searchIndexId}}
    const searchIndexHash = "{{ searchIndexHash }}";
    const searchDocumentsHash = "{{ searchDocumentHash }}";
  {{/if}}
{{/unless}}

Both guards in that snippet are load-bearing, for different reasons.

The outer {{#unless}} is a plan check rather than a safety check. When a site has live documentation search, no prebuilt index is generated at all — the index plugin is skipped for the whole build — so there is nothing for either helper to hash and both would return empty strings. Both sides of that split are at Live Documentation Search, and what a reader gets from the static index is at Site Search for Readers.

formatSecurityName — API documentation

formatSecurityName takes an OpenAPI security-requirement object and turns its keys into a human-readable label. It only appears on pages generated from an OpenAPI specification, where it labels the authentication selector:

<span>{{ formatSecurityName this }}</span>
Input keysOutput
api_keyAPI Key
bearerAuthBearer Auth
oauth2OAuth2
openIdConnectOpenID Connect
bearerAuth and api_key togetherBearer Auth + API Key

Anything that is not an object returns an empty string, and an object with no keys returns an empty string too.

The exact transform rules

Applied per key, in this order:

  1. Special cases first. Lowercased, oauth2 becomes OAuth2 and openidconnect becomes OpenID Connect. Neither goes through the steps below.
  2. Split on underscores.
  3. Split each part on camelCase boundaries — a space is inserted before every capital letter.
  4. Title-case each resulting word, except that any word starting with api (case-insensitively) is fully upper-cased. That is what turns api into API rather than Api.
  5. Join the words with spaces, then join multiple keys with " + ".

Only underscores and camelCase are understood. A hyphenated scheme name is not split on its hyphens and comes out awkwardly — name your schemes in the spec accordingly.

Where these labels appear, and how a specification becomes a documentation page in the first place, is at API Reference Pages.

Reference

HelperFormParametersRequires on contextReturnsBracesOn bad input
useCustomTemplatesubexpression → boolean1. name, required — one of unsubscribe, unsubscribed, docsFooter, docsHeadernonetrue when the override file was found at build start{{ }}an unknown name returns false
templateExistssubexpression → boolean1. name string, required — a path under _includes, extension includednonetrue when the file exists at render time{{ }}an empty name, or no includes directory, returns false
concatinline, variadicany number of valuesnonethe concatenation of the string arguments{{ }}non-strings are skipped silently, numbers included
firstTruthyblock, variadicany number of candidate valuesnonethe block, rendered with the first truthy argument as this; "" when none is{{ }}called inline it has no block to render
stripTagsinline1. input string, requirednoneinput with <…> removed and every & and = replaced by -{{ }}null or a non-string throws TypeError
wrapSubstringinline1. field · 2. stringToWrap · 3. className — all required stringsnoneSafeString of field with each occurrence wrapped in a classed <span>{{ }}a non-string in any position throws TypeError
JSONsafeinline1. content stringnoneSafeString of the JSON literal, quotes included; "" when falsy{{ }}none — a falsy value is the empty literal
loggerinline, variadicany number of valuesnonenothing; prints to the build console{{ }}an unserializable value prints its top-level keys instead
searchIndexHashinlinenonedocumentationConfiguration.searchIndexIdthe build’s index hash, or "" for an unknown id{{ }}no documentationConfiguration throws
searchDocumentHashinlinenonedocumentationConfiguration.searchIndexIdthe build’s document hash, or "" for an unknown id{{ }}no documentationConfiguration throws
formatSecurityNameinline1. securityMethods objectthis is an OpenAPI security requirementthe formatted label, keys joined with " + "{{ }}a non-object returns ""

One more helper lives in the same corner of the source and is documented elsewhere: leedConfiguredCSS, which assembles the font, layout and theme class names for a documentation page’s <html> element, belongs with the rest of the documentation chrome at Documentation Navigation Helpers.

Four helpers on this page can stop a build — stripTags, wrapSubstring and both search hashes — and they are collected with every other way a helper fails at Helper Gotchas and Failures. When you have a name and need the page, start at the Helper Index (A–Z).

ESC