Flexible content
6 min read
A Flexible content field is a list of rows where every row uses one of the layouts you define. A Repeater repeats the same fields; Flexible content lets the editor mix different kinds of rows, for example a "Hero" row (a title), a "Quote" row (a quote and an author) and a "Gallery" row, in any order.
Type id |
|
Builder category | Layout |
Column |
|
Returns | a list of rows, each an array with a |
Prints |
|
Needs | Pro, Business or Agency plan |
Build the field
- Edit a field group (see Add and arrange fields) and add a field of type Flexible content.
- Click + Add sub-field and add every sub-field you need, for all layouts. Sub-fields are one flat list, like in a Repeater, and take the same types (no Group, Repeater, Password or Pro types; see Repeater).
- In Layouts, click + Add layout for each kind of row and fill in the layout card (below).
- Tick the sub-fields each layout shows. Every sub-field belongs to exactly one layout.
- Save the field group.
Sub-field keys follow the usual rules (lowercase letters, digits and underscores; no SQL words). Some words are reserved (for example inner and rank); the builder tells you when a key is not allowed.
The layout card
Setting | Meaning |
|---|---|
Label | The name editors see in the add menu and in the row header. If empty, the card is called "Layout 1", "Layout 2" and so on, and the layout name is used as the label when saving. |
Name | The layout's key, stored in every row as |
Minimum rows | How many rows of this layout the editor has to keep. Cannot exceed Maximum rows. |
Maximum rows | How many rows of this layout may exist. |
Sub-fields in this layout | Tick the sub-fields this layout shows. Help text: "Add sub-fields below, then tick the ones this layout shows." |
Each card has buttons to move it up or down ("Move layout … up", "Move layout … down") and to remove it ("Remove layout …"). A field can have at most 50 layouts. Sub-fields that are in no layout are listed: "Not in any layout yet: … Tick them in a layout."
Field settings
Setting | Default | Effect |
|---|---|---|
Minimum rows | 0 | The editor cannot remove rows below this number. It does not add rows for the editor, and the server does not enforce it when saving. Cannot exceed Maximum rows. |
Maximum rows | 0 |
|
Button label | Add row | Text of the button that opens the layout menu. Empty uses "Add row". |
Messages when saving the field group
Message | What to do |
|---|---|
| Add a layout. |
| Rename the layout. |
| Give the layouts different names. |
| Untick it in all but one layout. |
| Tick it in a layout or delete it. |
Edit screen
- The add button (default Add row) opens a menu with one entry per layout. Use the arrow keys, Enter and Escape to work it with the keyboard. An entry is disabled when its layout is at its maximum; the button itself is disabled when the field is at Maximum rows.
- A new row opens with focus in its first input.
- Each row has a header with the layout label (click it to collapse or expand the row) and the buttons Move row up, Move row down and Remove row. Removing asks "Remove this row?".
- Remove row is disabled when the field is at its minimum rows or the row's layout is at its minimum.
- There is no drag and drop. If a required input sits in a collapsed row, the row opens when the browser reports the problem.
- A layout without sub-fields shows "This layout has no fields."
What is saved
- Rows keep the order shown.
- A row whose layout does not exist (any more) is dropped.
- A row of a layout that is already at its Maximum rows is dropped (the first rows are kept).
- Saving stops at the field's Maximum rows.
- Only the sub-fields of the row's layout are kept; a value for another layout's sub-field is ignored.
- Each sub-field is cleaned by its own type.
- Unlike a Repeater, a row whose sub-fields are all empty is kept, because its layout is the content.
- With no rows the field is cleared.
Example stored value (the order of keys inside a row is not guaranteed, because the database stores JSON objects in its own key order; do not rely on it):
[
{ "_layout": "hero", "title": "Welcome" },
{ "_layout": "quote", "quote_text": "To be", "author": "Ann" }
]From code, pass the same shape. Unknown layouts are dropped:
wpmcf_update_field( 'page_blocks', 'blocks', [
[ '_layout' => 'hero', 'title' => 'Welcome' ],
[ '_layout' => 'quote', 'quote_text' => 'To be', 'author' => 'Ann' ],
], $post_id );Changing the layouts later
- Renaming a layout keeps its rows when the new name takes the same position and the layout keeps its label or at least one of its sub-fields. Stored rows are rewritten with the new name.
- Renaming a sub-field keeps its values, as in a Repeater.
- Removing a layout leaves its rows in the database, but they no longer appear on the edit screen and they are removed from the stored value the next time the post is saved. They do not print.
Template usage
$rows = wpmcf_get_field( 'page_blocks', 'blocks' );
foreach ( (array) $rows as $row ) {
switch ( $row['_layout'] ) {
case 'hero':
printf( '<h1>%s</h1>', esc_html( $row['title'] ?? '' ) );
break;
case 'quote':
printf( '<blockquote>%s</blockquote>', esc_html( $row['quote_text'] ?? '' ) );
break;
}
}A path points into a row: wpmcf_get_field( 'page_blocks', 'blocks', null, [ 'path' => '0.title' ] ) returns the title of the first row.
Printed output
wpmcf_the_field() and [wpmcf_field] print every row with its layout's sub-fields, each formatted by its own type. Empty sub-values are left out, and rows of unknown layouts are skipped:
<div class="wpmcf-flexible">
<div class="wpmcf-flexible__row" data-layout="hero">
<div class="wpmcf-flexible__value" data-field="title">Welcome</div>
</div>
<div class="wpmcf-flexible__row" data-layout="quote">
<div class="wpmcf-flexible__value" data-field="quote_text">To be</div>
<div class="wpmcf-flexible__value" data-field="author">Ann</div>
</div>
</div>A path that points at one row (for example 0) prints just that row. The filter wpmcf/field/flexible_content_html receives the markup, the rows and the field and can replace the whole output.
Where it works
- Not inside a Group or Repeater, and not in front-end forms (the add menu needs the edit-screen script).
- Not a Programmatic SEO data source column (it is a JSON column).
- Admin list column: shown as a trimmed text line, not sortable, no filter, no quick edit. See Admin columns.
- Page builders: the Elementor Repeater widget lists rows of Flexible content fields and shows only the sub-fields of each row's layout. See Repeater, gallery and other widgets. Dynamic tags can read a sub-field with a path such as
group.blocks.0.title. - In Elementor display conditions, a Flexible content field with at least one filled row counts as having a value; see Elementor display conditions.
Without a license
Existing rows keep printing. The edit screen shows the field as locked and keeps the value. See License expiry.
Troubleshooting
- My rows disappeared after I removed a layout. Rows of a removed layout are no longer shown and are dropped on the next save. Re-create a layout with the same name before saving to keep them.
- A sub-field's value was not saved. It belongs to a different layout than the row, or the sub-field is in no layout.
- I cannot remove a row. The field or the layout is at its minimum rows.
- The key order of a row differs from my definition. The database returns JSON object keys in its own order; read values by key.
Still stuck? Contact support