flowedit.
FLOWEDIT DOCUMENTATION

Shortcode reference

From the plugin documentation · View source ↗

FlowEdit stores rich media and structured content as shortcodes inside the page’s Markdown. Everything below is rendered server-side by FlowEdit’s own shortcode handlers (via Shortcode Core) — the editor is just one way to produce this markup. You can also write these shortcodes by hand.

General rules

  • Every handler uses a closed allow-list of parameters. An unknown parameter key makes the whole shortcode render nothing (silent fail-safe).
  • width is an integer percentage clamped to 25–100; default 100. Width 100 omits the inline width style entirely.
  • align is left | center | right; default center (class flow-media--align-{align} on the <figure>).
  • All string values are entity-decoded then HTML-escaped on output.

[flow-image] — sized / captioned image

[flow-image src="hero.jpg" alt="Sunset" caption="Lake view" width="75" align="left"]
Parameter Required Notes
src yes https://…, site-root /…, or a page media filename (resolved through the page’s media). .. and protocol-relative //… URLs are rejected.
alt no Plain text.
caption no Rendered as <figcaption>.
width, align no See general rules.

Output: a <figure> with flow-media and alignment classes, an <img> with loading="lazy", and an optional <figcaption>. Non-default widths add an inline percentage width to the figure.

Images with default width, center alignment, and no caption stay plain Markdown (![alt](file.jpg)) instead — FlowEdit only emits [flow-image] when one of those options is used.

[flow-youtube] — YouTube embed

[flow-youtube id="dQw4w9WgXcQ" title="Demo" width="100" align="center" caption="Intro"]
Parameter Required Notes
id yes 11-character video id ([A-Za-z0-9_-]{11}).
title yes Non-empty; used as the iframe title (accessibility).
caption no Rendered as <figcaption>.
width, align no See general rules.

Output: a privacy-enhanced https://www.youtube-nocookie.com/embed/{id} iframe inside a <figure> (class video-embed flow-media …). Only the server-side renderer produces this markup — the in-editor preview is editor-only.

[flow-embed] — third-party embed

[flow-embed url="https://x.com/getgrav/status/1234567890123456789"]
Parameter Required Notes
url yes https-only URL owned by an allow-listed provider.

Allow-listed providers (validated again server-side — unknown URLs render nothing):

  • X / Twitter posts (twitter.com/x.com …/status/{id}) → platform.twitter.com/embed/Tweet.html iframe (JS-free, dnt=1).
  • GitHub Gists (gist.github.com/{user}/{id}) → .pibb iframe.
  • Spotify tracks/albums/playlists/episodes/shows → converted to the matching /embed/… URL.
  • Google Maps (/maps/embed path or ?output=embed) → passthrough iframe.

Output: <figure class="flow-embed flow-embed--{provider}"> with a lazy iframe. Front-end sizing lives in css/flow-embed.css; Gists/Maps use the flow-embed--tall height variant instead of a 16:9 aspect ratio. In the editor, pasting a supported URL onto an empty selection inserts the embed directly, and /embed opens the picker.

[flow-gallery width="80" align="left"]
[flow-slide src="a.jpg" alt="A" caption="First"]
[flow-slide src="b.jpg"]
[/flow-gallery]
  • Slides are [flow-slide src="…" alt="…" caption="…"] tags (self-closing or paired); src accepts the same values as [flow-image].
  • Maximum 32 slides; slides with unresolvable src are skipped.
  • Gallery-level parameters: width, align only.

Output: <figure class="flow-gallery …"><div class="flow-gallery__grid"> with one <figure class="flow-gallery__slide"> per slide.

[flow-video] — uploaded video or Vimeo

Exactly one of src or vimeo must be present:

[flow-video src="clip.mp4" title="Walkthrough" caption="Part 1"]
[flow-video vimeo="76979871" title="Promo"]
Parameter Required Notes
src one of Page media filename, https://…, or site-root path ending in .mp4, .webm, .ogv, or .ogg (query strings allowed). Rendered as <video controls> with the matching MIME type.
vimeo one of Numeric Vimeo video id (5–12 digits). Rendered as a player.vimeo.com iframe.
title yes Non-empty; used as the <video title> / iframe title.
caption no Rendered as <figcaption>.
width, align no See general rules.

[flow-columns] / [flow-column] — column layout

Two to four side-by-side rich-text columns. Each column body is ordinary Markdown (processed by Grav before the shortcode renders):

[flow-columns count="2"]
[flow-column]
Left **column** content.
[/flow-column]
[flow-column]
Right column content.
[/flow-column]
[/flow-columns]
  • count accepts 2–4 and is clamped; the rendered grid follows the actual number of column bodies, so hand-edited markup stays truthful.
  • Column author HTML passes through a rich-text allow-list. Validated FlowEdit media and custom-block renderer output is preserved; raw scripts/iframes and unsafe links remain blocked. Empty columns keep their position.
  • Blocks with fewer than 2 or more than 4 columns render nothing (the raw text is consumed).
  • Output: <div class="flow-columns flow-columns--N"> with one <div class="flow-columns__col"> per column, laid out by css/flow-columns.css (loaded on demand; collapses to a single column below 768 px). Themes can restyle via .flow-columns* or override the template templates/flow-blocks/columns.html.twig.
  • Columns support [flow-image], [flow-gallery], [flow-video], [flow-youtube], [flow-embed], and [flow-block] as direct children.

[flow-block] / [flow-item] — structured custom blocks

Custom blocks store their fields as shortcode attributes and their body between the tags:

[flow-block type="callout" tone="warning" title="Heads up"]
Save your work first.
[/flow-block]

List-type blocks (accordion, tabs, or visual repeating layouts) store items as nested shortcodes:

[flow-block type="accordion" multiple="0" open_first="1"]
[flow-item title="What is FlowEdit?"]
A modern slash editor for Grav.
[/flow-item]
[/flow-block]
  • type selects the block definition; attributes are allow-listed per type (type + the configured field names). Unknown keys render nothing.
  • Item bodies pass through a server-side rich-text allow-list (prose elements, safe links) before Twig renders them. Maximum 32 items.
  • Twig types render through templates/flow-blocks/{name}.html.twig, overridable by the theme. Visual types use that matching template when present, otherwise the shared visual renderer. See Creating custom blocks and the visual builder.
  • If the block is disabled in the plugin config, its shortcode renders empty on the front end (content stays in the page file).
  • Archiving a type hides it from insertion while preserving rendering and editing. Retired fields remain valid attributes and retain their stored values.
  • Image fields store a source string as an ordinary attribute. A visual definition with title, image, and alt attributes plus a content field might produce:
[flow-block type="promo" title="A new chapter" image="reading.jpg" alt="An open book"]
Make time for a good story.
[/flow-block]

This requires a corresponding promo definition; it is not a shipped block ID. The layout remains in configuration. There is no additional visual-layout shortcode, and a visual block’s internal columns do not create [flow-columns] tags. That shortcode is for independent column layouts in page content.

Legacy HTML markers (deprecated)

Older FlowEdit versions used freeform HTML markers:

<!--flow-block:Button-->
<a class="btn btn-primary" href="/contact">Contact us</a>
<!--/flow-block-->

These display a read-only visual preview with a warning and retain their original HTML source. Front-end processing follows Grav’s HTML/security settings. New content uses structured shortcodes. See the stable storage contract and legacy replacement policy.