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, 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]
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.
- 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, andaltattributes 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.