Rendering Forms with Shortcodes and PHP

Render a form with the Elzo Form block, the [elzo_form] shortcode, or PHP, and control its width, alignment and repeated copies.

Free & PRO · Developer Last reviewed

Elzo Forms renders every form through one engine, which you can reach in three ways: the Elzo Form block, the [elzo_form] shortcode, and a PHP call. All three produce the same markup and take the same layout options, so a form can be placed in block content, in classic content and widget areas, or directly in a theme template.

Elzo Form block

The block is registered as elzo-forms/form. It is a dynamic block: the post stores only the block’s attributes, and the form is rendered on each request, so later changes to the form appear on the page without re-saving it.

<!-- wp:elzo-forms/form {"formId":12,"maxWidth":"640px","formAlign":"center"} /-->
Attribute Type Same as shortcode attribute
formId integer, default 0 id
maxWidth string, default empty max_width
formAlign "", left, center or right form_align
textAlign "", left, center or right text_align

The render callback passes the attributes to Form::render_by_id(), so they are validated exactly like the shortcode’s, and wraps the result in a <div> carrying the class elzo-forms-block and the standard block wrapper attributes. With formId at 0, or when the renderer returns nothing for the current visitor, the block outputs nothing at all.

In the editor, the block shows a server-rendered preview of the form. The preview is inert: it cannot be focused, filled in or submitted.

The form list in the editor

The block’s form selector is filled from a small catalogue printed into the editor page. Each entry holds only the form’s id, title, status and statusLabel; the form definition and settings are never sent to the editor. The catalogue is provided to users with the edit_posts capability and lists every published form, plus unpublished forms the user can edit.

elzo_forms_block_form_options filters that list. Use it to hide forms authors should not place:

<?php
add_filter( 'elzo_forms_block_form_options', function ( array $options ): array {
    return array_values( array_filter( $options, function ( array $option ): bool {
        return strpos( $option['title'], '[internal]' ) === false;
    } ) );
} );

Warning: do not add form settings or content to these entries. The list is printed into the page source of the editor, where every user who opens it can read it.

Filtering the list only changes what the selector offers. A block that already points at a hidden form keeps rendering it; the editor reports that form as unavailable.

Shortcode

Place the shortcode in any post, page, or block that runs shortcodes. Only id is required:

[elzo_form id="12"]

The ID is the form’s post ID. The Shortcode column of Forms → All Forms shows the complete shortcode for each form, and the ID also appears in the address bar while the form is being edited.

Attributes

Attribute Default Effect
id 0 The form to render. Required.
max_width empty Maximum width of the form wrapper: a positive number with an optional unit — px, %, rem, em, vw, vh, vmin, vmax, ch or ex. A number without a unit is read as pixels. Any other value, such as calc() or a negative number, is ignored.
form_align empty left or right aligns the wrapper to that side. Any other non-empty value centers it. Only has a visible effect together with max_width.
text_align empty Text alignment inside the wrapper: left, center, right, justify, start or end. Any other value is ignored.
[elzo_form id="12" max_width="640" form_align="center" text_align="left"]

Values that are ignored are dropped silently: the form renders without that style rather than failing.

Warning: do not pass echo in the shortcode. Shortcode attributes arrive as strings, so even echo="false" is truthy — the form would be printed before the shortcode returns, usually at the top of the page. The attribute exists for the PHP API only.

Render from PHP

In a theme template or plugin, call the form renderer directly:

<?php
echo \ElzoForms\Form\Form::render_by_id( [ 'id' => 12 ] );

render_by_id() accepts the same attributes as the shortcode, plus echo:

<?php
\ElzoForms\Form\Form::render_by_id( [
    'id'        => 12,
    'max_width' => 640,
    'echo'      => true,
] );

With 'echo' => true the markup is printed and an empty string is returned. With the default false it is returned for you to echo or store.

If you already have a form instance, call render() on it:

<?php
$form = new \ElzoForms\Form\Form( 12 );
echo $form->render( [ 'max_width' => '40rem' ] );

do_shortcode() also works and produces identical output. Prefer render_by_id() in PHP — it avoids the shortcode parser and takes real types rather than strings.

Guard the call when the plugin may be inactive:

<?php
if ( class_exists( \ElzoForms\Form\Form::class ) ) {
    echo \ElzoForms\Form\Form::render_by_id( [ 'id' => 12 ] );
}

Front-end assets

Elzo Forms enqueues its stylesheet and script on wp_enqueue_scripts for every front-end page, whether or not a form is present. Rendering a form does not trigger the enqueue, and rendering one late — for example in a footer template — does not miss it.

The consequence is that the assets load site-wide. If that matters for your page-weight budget, dequeue them where no form appears:

<?php
add_action( 'wp_enqueue_scripts', function () {
    if ( is_singular( 'post' ) ) {
        wp_dequeue_style( 'elzo-forms-style' );
        wp_dequeue_script( 'elzo-forms-script' );
    }
}, 20 );

Warning: dequeue only where you are certain no form is rendered. Without the script, a form still displays but does not validate, submit, or run conditional logic.

Individual form modules, such as reCAPTCHA, enqueue their own assets when the form boots during rendering.

What the renderer returns

Situation Output
Valid published form The form markup, wrapped in <section class="elzo-forms-wrapper">.
id missing or not a positive number <p>Invalid form ID</p>
No post with that ID, or the post is not an Elzo Form <p>Form not found</p>
Form is not published For users who can edit that form: the form, with a notice that it is unpublished. For everyone else: an empty string.

Users who can edit the form also see an Edit form link above it. Visitors do not.

Rendering more than one form

Several forms can appear on the same page, and so can several copies of the same form.

The first rendering of a form keeps its element ID: elzo-forms-form-{form_id}, or the custom ID set in the form’s settings. Each further copy rendered in the same request gets a suffix — -2, -3 and so on — and the same suffix is added to every field, wrapper and nonce ID inside it, custom IDs included. Labels and ARIA references therefore stay within their own copy, and conditional logic resolves fields inside the copy it belongs to.

Field names and the hidden form ID do not change, so every copy submits the same data structure. To style or script all copies, target the elzo-forms-form class, or an attribute added with elzo_forms_form_data_attributes, rather than the ID.

Customizing the output

Do not modify plugin files to change markup. In order of increasing effort:

  1. CSS against the classes the templates already produce.
  2. elzo_forms_form_data_attributes to add data attributes to the form element.
  3. The template loading filters, or a copied template. See Template Overrides.
  4. A custom field type when a field needs markup of its own. See Build a Custom Field.

Including a template file directly is not supported — templates expect variables the renderer prepares, and a direct include also skips module boot, the per-copy ID suffixes, and data attribute filters.

Troubleshooting

The shortcode prints as plain text

Check: the content is being output without running shortcodes, which happens in some template code and in custom fields rendered with the_meta()-style helpers. Wrap the value in do_shortcode(), or call render_by_id() instead.

Expected behavior: post and page content run shortcodes automatically.

The form appears at the top of the page instead of in place

Check: an echo attribute in the shortcode, or 'echo' => true in a render_by_id() call whose return value is also echoed. Both print the markup immediately rather than returning it.

Expected behavior: the shortcode returns its markup and appears where it is written.

The maximum width or alignment has no effect

Check: the value’s format. max_width accepts a single positive length such as 640, 640px, 40rem or 80%, and anything else is ignored. form_align only shows once a maximum width narrows the form.

Expected behavior: the wrapper <section> carries an inline max-width and, with an alignment, margin declarations.

The form displays but nothing happens on submit

Check: whether elzo-forms-script is present on the page. A dequeue rule, an aggressive asset-optimization plugin, or a JavaScript error from another plugin will leave the markup working but inert.

Expected behavior: submitting posts to admin-ajax.php and shows the form’s success message.

Next steps