Skip to content

Forms

Nimbu provides a comprehensive form system with automatic CSRF protection, validation, file uploads, and integration with channels and customer accounts.

Basic Form Structure

{% form %}

Create forms for any content type:

liquid
{% form model %}
  {% input 'field_name' %}
  {% submit_tag 'Save' %}
{% endform %}

Supported Models:

  • channels.channel_name - Channel entry forms
  • customer - Customer account forms
  • order - Order/checkout forms
  • page - Page editable forms (with authentication)

Form Tags

{% input %}

Generate input fields with automatic attributes:

liquid
{% input 'name', label: 'Full Name', required: true %}

{% input 'email', as: 'email', placeholder: '[email protected]' %}

{% input 'phone', as: 'tel', pattern: '[0-9]{10}' %}

Input Types (as parameter):

  • text (default)
  • email
  • tel
  • url
  • number
  • date
  • time
  • datetime-local
  • password
  • hidden

{% text_area %}

Multi-line text input:

liquid
{% text_area 'message', rows: 5, label: 'Your Message' %}

{% text_area 'bio', rows: 10, placeholder: 'Tell us about yourself...' %}

{% select %}

Dropdown selection:

liquid
{% select 'category', options: 'Option 1,Option 2,Option 3', label: 'Category' %}

{% select 'country', options: countries, label: 'Country' %}

{% checkbox %}

Single checkbox:

liquid
{% checkbox 'subscribe', label: 'Subscribe to newsletter' %}

{% checkbox 'terms', label: 'I agree to the terms', required: true %}

{% file_field %}

File upload:

liquid
{% file_field 'photo', label: 'Upload Photo', accept: 'image/*' %}

{% file_field 'document', label: 'Attach File', accept: '.pdf,.doc,.docx' %}

{% submit_tag %}

Submit button:

liquid
{% submit_tag 'Send Message' %}

{% submit_tag 'Save', class: 'btn btn-primary btn-lg' %}

Validation and Errors

{% error_messages_for %}

Display validation errors:

liquid
{% form channels.contact %}
  {% error_messages_for form_model %}

  {% input 'name', required: true %}
  {% input 'email', as: 'email', required: true %}
  {% text_area 'message', required: true %}

  {% submit_tag 'Send' %}
{% endform %}

Custom Error Styling:

liquid
{% error_messages_for form_model, class: 'alert alert-danger' %}

Practical Examples

Contact Form

liquid
<div class="contact-page">
  <h2>Get in Touch</h2>

  {% form channels.contact, class: 'contact-form' %}
    {% error_messages_for form_model %}

    <div class="form-row">
      <div class="col-md-6">
        {% input 'first_name', label: 'First Name', required: true %}
      </div>
      <div class="col-md-6">
        {% input 'last_name', label: 'Last Name', required: true %}
      </div>
    </div>

    <div class="form-group">
      {% input 'email', as: 'email', label: 'Email Address', required: true %}
    </div>

    <div class="form-group">
      {% input 'phone', as: 'tel', label: 'Phone Number' %}
    </div>

    <div class="form-group">
      {% select 'subject', options: 'General Inquiry,Support,Sales,Partnership', label: 'Subject', required: true %}
    </div>

    <div class="form-group">
      {% text_area 'message', rows: 6, label: 'Message', required: true %}
    </div>

    <div class="form-group">
      {% checkbox 'subscribe', label: 'Subscribe to our newsletter' %}
    </div>

    {% submit_tag 'Send Message', class: 'btn btn-primary' %}
  {% endform %}
</div>

Job Application Form

liquid
<div class="job-application">
  <h2>Apply for {{ job.title }}</h2>

  {% form channels.applications, class: 'application-form' %}
    {% error_messages_for form_model %}

    <input type="hidden" name="job_id" value="{{ job.id }}">

    <h3>Personal Information</h3>

    <div class="form-row">
      <div class="col-md-6">
        {% input 'first_name', label: 'First Name', required: true %}
      </div>
      <div class="col-md-6">
        {% input 'last_name', label: 'Last Name', required: true %}
      </div>
    </div>

    <div class="form-group">
      {% input 'email', as: 'email', label: 'Email', required: true %}
    </div>

    <div class="form-group">
      {% input 'phone', as: 'tel', label: 'Phone Number', required: true %}
    </div>

    <div class="form-group">
      {% input 'linkedin', as: 'url', label: 'LinkedIn Profile URL' %}
    </div>

    <h3>Application Details</h3>

    <div class="form-group">
      {% file_field 'resume', label: 'Resume (PDF)', accept: '.pdf', required: true %}
    </div>

    <div class="form-group">
      {% file_field 'cover_letter', label: 'Cover Letter (PDF)', accept: '.pdf' %}
    </div>

    <div class="form-group">
      {% text_area 'why_you', rows: 6, label: 'Why do you want to work with us?', required: true %}
    </div>

    <div class="form-group">
      {% input 'start_date', as: 'date', label: 'Available Start Date' %}
    </div>

    <div class="form-group">
      {% checkbox 'gdpr_consent', label: 'I consent to the processing of my personal data', required: true %}
    </div>

    {% submit_tag 'Submit Application', class: 'btn btn-lg btn-primary' %}
  {% endform %}
</div>

Customer Registration

liquid
<div class="registration-page">
  <h2>Create Account</h2>

  {% form customer, class: 'registration-form' %}
    {% error_messages_for form_model %}

    <div class="form-group">
      {% input 'email', as: 'email', label: 'Email Address', required: true %}
    </div>

    <div class="form-group">
      {% input 'password', as: 'password', label: 'Password', required: true %}
    </div>

    <div class="form-group">
      {% input 'password_confirmation', as: 'password', label: 'Confirm Password', required: true %}
    </div>

    <div class="form-row">
      <div class="col-md-6">
        {% input 'first_name', label: 'First Name', required: true %}
      </div>
      <div class="col-md-6">
        {% input 'last_name', label: 'Last Name', required: true %}
      </div>
    </div>

    <div class="form-group">
      {% checkbox 'newsletter', label: 'Send me news and special offers' %}
    </div>

    <div class="form-group">
      {% checkbox 'terms', label: 'I agree to the Terms and Conditions', required: true %}
    </div>

    {% submit_tag 'Create Account', class: 'btn btn-primary btn-block' %}
  {% endform %}

  <p class="login-link">
    Already have an account? <a href="/login">Sign in</a>
  </p>
</div>

Newsletter Signup

liquid
<section class="newsletter-signup">
  <div class="container">
    <h3>Stay Updated</h3>
    <p>Get the latest news and exclusive offers.</p>

    {% form channels.newsletter, class: 'newsletter-form' %}
      {% error_messages_for form_model %}

      <div class="form-inline">
        {% input 'email', as: 'email', placeholder: '[email protected]', required: true %}
        {% submit_tag 'Subscribe', class: 'btn btn-primary' %}
      </div>
    {% endform %}
  </div>
</section>

Event Registration with Multiple Attendees

liquid
<div class="event-registration">
  <h2>Register for {{ event.title }}</h2>

  {% form channels.registrations, class: 'registration-form' %}
    {% error_messages_for form_model %}

    <input type="hidden" name="event_id" value="{{ event.id }}">

    <h3>Primary Attendee</h3>

    <div class="form-row">
      <div class="col-md-6">
        {% input 'first_name', label: 'First Name', required: true %}
      </div>
      <div class="col-md-6">
        {% input 'last_name', label: 'Last Name', required: true %}
      </div>
    </div>

    <div class="form-group">
      {% input 'email', as: 'email', label: 'Email', required: true %}
    </div>

    <div class="form-group">
      {% input 'company', label: 'Company Name' %}
    </div>

    <h3>Ticket Information</h3>

    <div class="form-group">
      {% select 'ticket_type', options: 'Standard,VIP,Student', label: 'Ticket Type', required: true %}
    </div>

    <div class="form-group">
      {% input 'quantity', as: 'number', label: 'Number of Tickets', value: '1', min: '1', max: '10', required: true %}
    </div>

    <h3>Dietary Requirements</h3>

    <div class="form-group">
      {% checkbox 'vegetarian', label: 'Vegetarian' %}
      {% checkbox 'vegan', label: 'Vegan' %}
      {% checkbox 'gluten_free', label: 'Gluten-free' %}
    </div>

    <div class="form-group">
      {% text_area 'special_requests', rows: 3, label: 'Special Requests or Allergies' %}
    </div>

    {% submit_tag 'Complete Registration', class: 'btn btn-lg btn-success' %}
  {% endform %}
</div>

Product Review Form

liquid
<div class="review-form">
  <h3>Write a Review</h3>

  {% form channels.reviews, class: 'product-review-form' %}
    {% error_messages_for form_model %}

    <input type="hidden" name="product_id" value="{{ product.id }}">

    <div class="form-group">
      <label>Rating *</label>
      <div class="rating-input">
        {% for i in (1..5) %}
          <input type="radio" name="rating" id="rating-{{ i }}" value="{{ i }}" required>
          <label for="rating-{{ i }}">★</label>
        {% endfor %}
      </div>
    </div>

    <div class="form-group">
      {% input 'title', label: 'Review Title', required: true %}
    </div>

    <div class="form-group">
      {% text_area 'comment', rows: 5, label: 'Your Review', required: true %}
    </div>

    <div class="form-group">
      {% input 'reviewer_name', label: 'Your Name', required: true %}
    </div>

    <div class="form-group">
      {% input 'reviewer_email', as: 'email', label: 'Email (not published)', required: true %}
    </div>

    <div class="form-group">
      {% checkbox 'verified_purchase', label: 'I purchased this product' %}
    </div>

    {% submit_tag 'Submit Review', class: 'btn btn-primary' %}
  {% endform %}
</div>

Advanced Form Patterns

Conditional Fields

liquid
{% form channels.bookings %}
  {% select 'booking_type', options: 'Individual,Group', label: 'Booking Type' %}

  <div class="conditional-fields" data-show-if="booking_type:Group">
    {% input 'group_name', label: 'Group Name' %}
    {% input 'group_size', as: 'number', label: 'Number of People' %}
  </div>

  {% submit_tag 'Book Now' %}
{% endform %}

<script>
document.querySelector('[name="booking_type"]').addEventListener('change', function(e) {
  const groupFields = document.querySelector('[data-show-if="booking_type:Group"]');
  groupFields.style.display = e.target.value === 'Group' ? 'block' : 'none';
});
</script>

AJAX Form Submission

liquid
{% form channels.contact, class: 'ajax-form', id: 'contact-form' %}
  {% input 'name', required: true %}
  {% input 'email', as: 'email', required: true %}
  {% text_area 'message', required: true %}

  <div class="form-status"></div>

  {% submit_tag 'Send Message' %}
{% endform %}

<script>
document.getElementById('contact-form').addEventListener('submit', async function(e) {
  e.preventDefault();

  const formData = new FormData(this);
  const statusDiv = this.querySelector('.form-status');

  try {
    const response = await fetch(this.action, {
      method: 'POST',
      body: formData,
      headers: {
        'Accept': 'application/json'
      }
    });

    if (response.ok) {
      statusDiv.innerHTML = '<div class="alert alert-success">Message sent successfully!</div>';
      this.reset();
    } else {
      const errors = await response.json();
      statusDiv.innerHTML = `<div class="alert alert-danger">${errors.message}</div>`;
    }
  } catch (error) {
    statusDiv.innerHTML = '<div class="alert alert-danger">An error occurred. Please try again.</div>';
  }
});
</script>

Best Practices

1. CSRF Protection

All Nimbu forms automatically include CSRF tokens. Never disable this:

liquid
<!-- ✅ Good: Automatic CSRF protection -->
{% form model %}
  <!-- form fields -->
{% endform %}

2. Required Field Validation

liquid
<!-- ✅ Good: Use required attribute -->
{% input 'email', as: 'email', required: true %}

<!-- ✅ Good: Client + server validation -->
{% input 'age', as: 'number', min: '18', required: true %}

3. Accessible Labels

liquid
<!-- ✅ Good: Descriptive labels -->
{% input 'phone', as: 'tel', label: 'Phone Number (optional)' %}

<!-- ❌ Bad: No label -->
<input type="text" name="phone">

4. Error Handling

liquid
<!-- ✅ Good: Show errors clearly -->
{% form model %}
  {% error_messages_for form_model, class: 'alert alert-danger' %}
  <!-- form fields -->
{% endform %}

Next Steps

Build powerful, accessible forms with Nimbu!