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).
widthis an integer percentage clamped to 25–100; default100. Width100omits the inline width style entirely.alignisleft | center | right; defaultcenter(classflow-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 () 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.htmliframe (JS-free,dnt=1). - GitHub Gists (
gist.github.com/{user}/{id}) →.pibbiframe. - Spotify tracks/albums/playlists/episodes/shows → converted to the
matching
/embed/…URL. - Google Maps (
/maps/embedpath 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] — image grid
[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);srcaccepts the same values as[flow-image]. - Maximum 32 slides; slides with unresolvable
srcare skipped. - Gallery-level parameters:
width,alignonly.
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]
countaccepts 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 bycss/flow-columns.css(loaded on demand; collapses to a single column below 768 px). Themes can restyle via.flow-columns*or override the templatetemplates/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) 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]
typeselects 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.
- Rendering goes through
templates/flow-blocks/{name}.html.twig, overridable by the theme — see Creating custom blocks. - If the block is disabled in the plugin config, its shortcode renders empty on the front end (content stays in the page file).
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.