Plume Return to Developer Hub

Syntax

Plume syntax is deliberately small. Templates stay close to HTML, and the extra syntax is reserved for values, control flow, reusable components, resources, and behaviour.

Output

Use {expression} for normal escaped output:

PLUME
<h1>{post.title}</h1>

Expressions can start with values, literals, function calls, or filters:

PLUME
{"Draft" | downcase}
{"/photos/a b.jpg" | urlEncode}
{asset("images/avatar.png")}

Host-provided PlumeSafeHTML renders as HTML. Ordinary strings are escaped. Use | raw only for trusted content.

PLUME
<article>{post.html}</article>
<article>{customHTML | raw}</article>

Expressions

Expressions can read values from the context, local variables, loop variables, component arguments, and host functions:

PLUME
{site.title}
{post.author.name}
{posts.size}
{asset("images/avatar.png")}

Supported literals include strings, numbers, booleans, nil, null, empty, blank, and arrays:

PLUME
@let widths = [480, 960, 1440]
@let fallbackTitle = "Untitled"

Comparisons and boolean operators work in conditionals and bindings:

PLUME
@if post.title && post.urlPath.startsWith("/notes/") {
  <a href="{post.urlPath}">{post.title}</a>
}

<button disabled?="{items.size == 0}">Continue</button>

Note that ! negates the whole expression that follows it, so !a == b evaluates as !(a == b). To compare a negated value, bind it first: @let isHidden = !visible and then isHidden == true.

Use ternaries for small inline choices:

PLUME
<span>{post.title ? post.title : "Untitled"}</span>

For conditionals, empty strings, empty arrays, false, nil, and null are falsey. Non-empty strings, non-empty arrays, numbers, dictionaries, and safe HTML are truthy.

Locals

Use @let for local values:

PLUME
@let currentPath = meta.canonicalUrl.replace(site.url, "")
@let isActive = currentPath == "/photos/"

<a href="/photos/" class:active="{isActive}">Photos</a>

Conditionals

Use @if, else if, and else:

PLUME
@if post.title {
  <h1>{post.title}</h1>
} else if site.title {
  <h1>{site.title}</h1>
} else {
  <h1>Untitled</h1>
}

Loops

Use @for to render arrays:

PLUME
@for post in posts {
  <article>
    <h2>{post.title}</h2>
  </article>
}

Loop metadata is available through forloop:

PLUME
@for item in items {
  <span>{forloop.index}</span>
}

Available loop values are:

  • forloop.index, starting at 1.
  • forloop.index0, starting at 0.
  • forloop.rindex, counting down to 1.
  • forloop.rindex0, counting down to 0.
  • forloop.first.
  • forloop.last.
  • forloop.length.

Comments

Use @comment when you want Plume to ignore a block entirely:

PLUME
@comment {
  <p>This does not render.</p>
  @PostCard(post)
}

Filters

Filters transform values:

PLUME
{post.title | default("Untitled")}
{post.dateIso | date("d MMMM yyyy")}
{tags | join(", ")}
{content | raw}

The most common filters:

  • default(value) — substitute for missing or empty values. The number 0 is kept.
  • date(format) — format a date.
  • join(separator), sort(field), where(field, value), map(field) — work with arrays.
  • upcase, downcase, truncate(length), slugify — transform strings.

See Filters for the complete reference, covering every string, array, number, date, and output filter.

Methods

Some values also support method-style calls:

PLUME
@if post.urlPath.startsWith("/photos/") {
  <span>Photo post</span>
}

{post.title.replace(":", " - ")}

Useful methods include contains, startsWith, endsWith, replace, replaceFirst, split, lowercased, uppercased, and slugify.

Attributes

Plume includes helpers for common conditional attributes:

PLUME
<a
  href="{post.urlPath}"
  class="nav-link"
  class:active="{isActive}"
  class+="{post.kind}"
  aria-current:page="{isActive}"
  target?="{target}"
>
  {post.title}
</a>
  • class:name="{condition}" appends a class when the condition is true.
  • class+="{value}" appends dynamic class names.
  • attribute?="{value}" omits the attribute when the value is empty or false.
  • attribute:value="{condition}" writes attribute="value" when true.
  • style:name="{value}" binds an inline style property.

Style bindings work with ordinary properties and custom properties:

PLUME
<span style:--offset="{offset}px" style:opacity="{visible ? 1 : 0}"></span>