Placeholder Text
Detects template leftovers and filler copy that shipped to production
Detects template leftovers and filler copy that shipped to production
| Rule ID | content/placeholder-text |
| Category | Content |
| Scope | Per-page |
| Severity | warning |
| Weight | 6/10 |
Solution
Each family points at a different break in the publishing pipeline. Lorem ipsum and theme boilerplate mean a page was published before its copy was written: replace the text, and add the affected fields to whatever check gates publishing. Unrendered template syntax means the templating engine never ran over that string, usually because the value was interpolated into an already-escaped fragment or the template was served as static HTML: render it server-side, or delete the stale copy. A visible undefined, NaN, null or [object Object] means the value was missing and the code concatenated it into the copy anyway: guard the field at the point of render rather than in CSS. TODO and FIXME markers mean a draft shipped: finish or remove the note, since search engines and readers see it exactly as written.
What it checks
Five families, in visible text only:
| Kind | Example | Meaning |
|---|---|---|
lorem-ipsum | Lorem ipsum dolor sit amet | latin filler was never replaced |
boilerplate-copy | Your Company Name, Insert text here | theme or page-builder filler |
unrendered-template | {{ user.name }}, {% for x in y %}, <%= title %>, ${total}, [[slug]] | the template engine never ran |
stringified-object | [object Object] | an object was concatenated into a string |
js-artifact | Rating: NaN, a table cell reading undefined | a missing value was rendered anyway |
todo-marker | TODO: write this, FIXME(alice) | a draft note shipped |
Severity depends on certainty. Lorem ipsum, unrendered template syntax and [object Object] are machine-generated and nothing legitimate produces them, so they fail. The rest warn, including the bare words undefined, null and NaN: an API reference whose defaults column reads null is ordinary, and telling it apart from a value that failed to render is not possible from the text alone.
What is not visible text
The check reads rendered prose, never raw HTML, so a framework attribute such as v-bind:title="{{ x }}" is not reported. Four kinds of markup are excluded:
<script>,<style>and<noscript>, because a visitor never reads them.<code>,<pre>,<samp>and<kbd>, because a visitor reads them as a quoted literal. This page is the proof: every example above is inside a code span, and this page passes its own rule.<template>, because its content is inert markup for JavaScript to clone. Without this exclusion every Vue, Alpine, Handlebars and htmx page would report its own component templates.- Containers belonging to an in-page code editor or syntax highlighter, matched by class:
hljs,shiki,prism,torchlight,codeblock,code-block, and anything startinglanguage-,cm-ormonaco-. CodeMirror and Monaco build their view from plain divs and spans with no<pre>or<code>anywhere, so a tag-only test reads a Liquid tutorial as a page full of unrendered Liquid.
Text is read with a line break at every block boundary, so a value alone in a table cell or list item is seen as a value alone on its line. A page with no body is skipped rather than passed, and a soft 404 is skipped so one broken error template cannot spray warnings across a crawl.
False positives
Each family is deliberately narrower than its name suggests, because every one of them is also a legitimate thing to write.
Lorem ipsum needs corroboration: three distinct marker words, or the opening phrase dolor sit amet. The Lorem ipsum bigram alone is never enough at any position, so a page that mentions the filler, the way Jinja’s documentation describes its lipsum() helper, stays clean. Words that are real in other languages, including dolor, sit, amet, elit and sed, are not markers at all, so Spanish and Latin copy stays clean.
Boilerplate copy matches whole-word phrases, never single words and never substrings. Request a sample of our placeholder API, we resample textures and the headings Placeholder Text and Example text are all ordinary and stay clean.
JavaScript artifacts match only the JavaScript spellings, case-sensitively, as standalone words. NULL in SQL prose, a sentence beginning Undefined, and identifiers such as nullable or non-null never match. Beyond that, the word has to sit where a value sits, not where a noun sits. It is reported only when it is alone on its line, which is what <h1>{title}</h1> renders as when the title is missing, or when a label such as : or > introduces it. A comma does not count as a label, and neither does a pipe: both read as prose far more often than as a list. All of these stay clean:
This triggers undefined behaviour in C++.
The function returns undefined when the key is missing.
We reject the null hypothesis.
Comparison with NaN
If an operand is a quiet NaN, there is no exception.
Accepts a string, null, or undefined.
The parameter accepts string | null | undefined
TypeError: null/undefined has no properties
Reject the [null] hypothesis.The cost of that strictness is deliberate: Posted by undefined at the end of a line is the same shape as Comparison with NaN, so the ambiguous shape is left alone.
Comment markers match TODO and FIXME in annotation form: followed by : or (, preceded by a comment sigil such as //, or introducing a lowercase sentence on the same line. A bare standalone TODO is left alone, because it is the name of a column on every task board ever shipped. XXX is narrower still and needs the comment sigil, because Super Bowl XXX: the box score and Rated XXX (explicit) fit annotation form exactly. Redacted digits such as 555-XXX-XXXX and $XXX,XXX are not markers either.
Known limit
A page whose SUBJECT is one of these values is reported, as a warning. A JavaScript language reference that lists null, undefined and NaN as navigation entries, an API reference whose defaults column reads null, or an article whose visible heading is the bare word all look exactly like a page whose values failed to render, and no amount of reading the text can tell them apart. That is why this family warns rather than fails. Disable the rule for those pages.
Enable / disable
Disable this rule
[rules]
disable = ["content/placeholder-text"]Disable all Content rules
[rules]
disable = ["content/*"]Enable only this rule
[rules]
enable = ["content/placeholder-text"]
disable = ["*"]