This article covers form templates and user management in Localist Events Liquid templates. The content builds on template fundamentals (Article 1), form tags (Article 6), and object references to provide practical guidance for developers implementing custom event submission forms, user dashboards, and messaging interfaces.
Overview
Forms and user management in Localist represent two critical surfaces where developers customize the user experience. Event submission forms allow both public users and administrators to create and edit events. User management templates render account settings, notifications, messages, and profile information.
This article documents:
-
Template variables available on form pages (event submission, user dashboards)
-
Object structures for custom fields, messages, conversations, and notifications
-
Practical patterns for rendering forms with custom field support
-
User-facing dashboard and settings templates
Event Submission Forms
Public Event Submission Form
The public event submission form (Theme Editor: Events → Public Event Submission Form) provides a template for users to create or edit events in the calendar. This template has access to the event being edited (if applicable) and all available form configuration data.
Template Variables
| Variable | Type | Description |
|---|---|---|
event |
Event | The event object being edited (may be empty for new submissions) |
filters |
Hash | Available filter options by filter category |
visible_sections |
Hash | Boolean values indicating which form sections are visible (keyed by section name) |
groups |
Array | Group options formatted as label/value pairs for selection dropdowns |
departments |
Array | Department options formatted as label/value pairs (requires Departments feature) |
selected_groups |
Array of Group/Department | Groups already associated with the event |
selected_departments |
Array of Group/Department | Departments already associated with the event |
selected_tags |
Hash | Tags grouped by context, each value contains an Array of Tag objects |
visibility_options |
Array | Event visibility options (Public, Unlisted, Restricted) |
experience_options |
Array | Available experience types for the event |
status_options |
Array | Event status options; values are live, canceled, postponed, soldout |
has_zoom_integration |
String | Whether the platform’s Zoom integration feature is enabled |
zoom_status |
String | Zoom UI state for the editor: "unauthorized" (no Zoom token), "no_meeting" (token but no meeting on the event), or "meeting" (stream/meeting linked) |
limited_edit |
Boolean | True when the event is not new and its first start date is today or in the past, so certain fields are locked down |
Example: Rendering Form Sections Conditionally
{% if visible_sections.basic_info %}
<fieldset>
<legend>Event Details</legend>
<input type="text" name="event[title]" value="{{ event.name }}" />
<textarea name="event[description]">{{ event.description }}</textarea>
</fieldset>
{% endif %}
{% if visible_sections.filters %}
<fieldset>
<legend>Categories & Tags</legend>
{% for context in site.event_filters %}
{% unless filters[context] == empty %}
{% filter_combobox context on event values:filters[context] selected:selected_tags[context] %}
{% endunless %}
{% endfor %}
</fieldset>
{% endif %}
Example: Rendering Status and Visibility Options
<label>Event Status</label>
<select name="event[status]">
{% for option in status_options %}
<option value="{{ option.value }}" {% if event.status == option.value %}selected{% endif %}>
{{ option.label }}
</option>
{% endfor %}
</select>
<label>Visibility</label>
<select name="event[visibility]">
{% for option in visibility_options %}
<option value="{{ option.value }}" {% if event.visibility == option.value %}selected{% endif %}>
{{ option.label }}
</option>
{% endfor %}
</select>
Example: Conditional Zoom Integration
{% if has_zoom_integration == 'true' %}
<fieldset>
<legend>Zoom Meeting</legend>
<p>Zoom integration status: {{ zoom_status }}</p>
<label>
<input type="checkbox" name="event[zoom_enabled]" {% if event.zoom_enabled %}checked{% endif %} />
Enable Zoom meeting for this event
</label>
</fieldset>
{% endif %}
Rendering Custom Fields on Forms
Custom Field object structure (definitions and the custom_fields value collection) is documented in Article 5 (Shared Object Types). This section focuses on how to render them inside an event submission form.
Iterate over field definitions via site.public_event_custom_fields, then render the appropriate input based on each definition’s field_type. Write values back through the parent object’s custom_fields collection (e.g., event[custom_fields][<key>]).
Example: Iterating Over Custom Field Definitions
{% for field in site.public_event_custom_fields %}
<div class="form-group">
<label for="custom_field_{{ field.key }}">
{{ field.label }}
{% if field.required %}<span class="required">*</span>{% endif %}
</label>
{% if field.field_type == 'text' %}
<input
type="text"
id="custom_field_{{ field.key }}"
name="event[custom_fields][{{ field.key }}]"
{% if field.required %}required{% endif %}
/>
{% elsif field.field_type == 'textarea' %}
<textarea
id="custom_field_{{ field.key }}"
name="event[custom_fields][{{ field.key }}]"
{% if field.required %}required{% endif %}
></textarea>
{% endif %}
</div>
{% endfor %}
Example: Displaying Custom Field Values
{% if event.custom_fields.department %}
<p>Department: {{ event.custom_fields.department }}</p>
{% endif %}
{% if event.custom_fields.room_capacity %}
<p>Room Capacity: {{ event.custom_fields.room_capacity }}</p>
{% endif %}
Example: Rendering Dynamic Form Fields Based on Type
{% for field in site.public_event_custom_fields %}
{% assign field_value = event.custom_fields[field.key] %}
<div class="form-field">
<label>{{ field.label }}</label>
{% case field.field_type %}
{% when 'text' %}
<input type="text" name="event[custom_fields][{{ field.key }}]" value="{{ field_value }}" />
{% when 'textarea' %}
<textarea name="event[custom_fields][{{ field.key }}]">{{ field_value }}</textarea>
{% when 'dropdown' %}
<select name="event[custom_fields][{{ field.key }}]">
<option value="">-- Select --</option>
{% for option in field.options %}
<option value="{{ option }}" {% if field_value == option %}selected{% endif %}>
{{ option }}
</option>
{% endfor %}
</select>
{% when 'checkbox' %}
<label>
<input type="checkbox" name="event[custom_fields][{{ field.key }}]" value="yes" {% if field_value %}checked{% endif %} />
{{ field.label }}
</label>
{% endcase %}
</div>
{% endfor %}
User Dashboard and Profile Templates
Edit Profile Template
The Edit Profile template (Theme Editor: Users → Edit Profile) allows users to modify their account information.
Template Variables
| Variable | Type | Description |
|---|---|---|
user |
User | The currently logged-in user |
the_user |
User | Alias for the user object |
Notification Settings Template
The Notification Settings template (Theme Editor: Users → Notification Settings) enables users to configure how they receive notifications.
Template Variables
| Variable | Type | Description |
|---|---|---|
user |
User | The currently logged-in user |
the_user |
User | Alias for the user object |
sms_notification_options |
Array | SMS notification frequency options formatted as label/value pairs |
sms_notification_setting |
String | The user’s current SMS notification preference |
Example: SMS Notification Settings
<fieldset>
<legend>SMS Notifications</legend>
{% for option in sms_notification_options %}
<label>
<input
type="radio"
name="user[sms_frequency]"
value="{{ option.value }}"
{% if sms_notification_setting == option.value %}checked{% endif %}
/>
{{ option.label }}
</label>
{% endfor %}
</fieldset>
Privacy Settings Template
The Privacy Settings template (Theme Editor: Users → Privacy Settings) controls visibility of the user’s profile and activities.
Template Variables
| Variable | Type | Description |
|---|---|---|
user |
User | The currently logged-in user |
the_user |
User | Alias for the user object |
privacy_options |
Array | Privacy configuration options (e.g., private, friends-only, public) |
Calendar Digests Template
The Calendar Digests template (Theme Editor: Users → Calendar Digests) displays and manages the user’s email digest subscriptions.
Template Variables
| Variable | Type | Description |
|---|---|---|
digest_summaries |
Array of Digest | All calendar digest configurations the user has created |
Example: Rendering Digest List
<div class="digests-list">
{% for digest in digest_summaries %}
<div class="digest-item">
<h3>{{ digest.title }}</h3>
<p>Sent: {{ digest.send_day }}</p>
<p>Status: {% if digest.enabled %}Active{% else %}Inactive{% endif %}</p>
<p>Date Range: {{ digest.date_range }}</p>
<p>Event Limit: {{ digest.limit }}</p>
</div>
{% endfor %}
</div>
Manage Friends & Places Template
The Manage Friends & Places template (Theme Editor: Users → Manage Friends & Places) shows a user’s social connections.
Template Variables
| Variable | Type | Description |
|---|---|---|
the_user |
User | The currently logged-in user |
current_tab |
String | Active tab (e.g., “friends”, “places”) |
friends |
Array | UserFriend or business friendable objects (varies by tab) |
The Conversation Object
The Conversation object represents a message thread between two users.
Used as: conversation (when viewing a thread), recent_conversations (array of conversation items)
Participants
| Variable | Type | Description |
|---|---|---|
user |
User | The current user in the conversation |
with |
User | The other participant (alias: along_with) |
Status
| Variable | Type | Description |
|---|---|---|
last_message_at |
DateTime | Timestamp of the most recent message |
read_at |
DateTime | When the current user last read the conversation |
unread |
Boolean | Whether the conversation has unread messages |
URLs
| Variable | Type | Description |
|---|---|---|
url |
String | Link to the conversation thread |
Example: Displaying a Conversation List
<div class="conversations">
{% for conv in recent_conversations %}
<div class="conversation-item {% if conv.unread %}unread{% endif %}">
<a href="{{ conv.url }}">
<strong>{{ conv.with.name }}</strong>
<p>{{ conv.last_message_at | date: "%b %d, %Y" }}</p>
</a>
</div>
{% endfor %}
</div>
The Message Object
The Message object represents an individual message in a conversation thread.
Used as: messages (array of message objects in conversation context)
Properties
| Variable | Type | Description |
|---|---|---|
message |
String | The message content |
from |
User | The sender of the message |
user |
User | Alias for sender |
sender |
User | Alias for sender |
sent_at |
DateTime | Timestamp when the message was sent |
read_at |
DateTime | Timestamp when the message was read (if applicable) |
is_reply |
Boolean | Whether this message is a reply to another message |
Status
| Variable | Type | Description |
|---|---|---|
unread |
Boolean | Whether the message is unread by the recipient |
deleted |
Boolean | Whether the message has been deleted |
sent |
Boolean | Whether the message was successfully sent |
received |
Boolean | Whether the message was received by the recipient |
Example: Rendering a Message Thread
<div class="conversation-thread">
{% for msg in messages reversed %}
<div class="message {% if msg.unread %}unread{% endif %} {% if msg.from.id == user.id %}sent{% else %}received{% endif %}">
<div class="message-header">
<strong>{{ msg.from.name }}</strong>
<span class="timestamp">{{ msg.sent_at | date: "%b %d at %I:%M %p" }}</span>
</div>
<div class="message-body">
{{ msg.message }}
</div>
{% if msg.unread %}
<span class="badge">New</span>
{% endif %}
</div>
{% endfor %}
</div>
The Notification Object
The Notification object represents notifications and feed items in a user’s dashboard.
Used as: messages (array), feed_item (single in dashboard context)
Properties
| Variable | Type | Description |
|---|---|---|
message |
String | The notification text |
sender |
User | The user who triggered the notification |
sender_name |
String | Display name of the sender |
sent_at |
DateTime | When the notification was generated |
sent_date |
Date | Date the notification was sent |
url |
String | Link to the related object or action |
unread |
Boolean | Whether the notification has been read |
notification_type |
Integer | Type code for the notification (e.g., friend request, message, group invite) |
Example: Rendering a Notification Feed
<div class="notifications">
{% for notification in messages %}
<div class="notification {% if notification.unread %}unread{% endif %}">
<a href="{{ notification.url }}">
<div class="notification-icon">
<img src="{{ notification.sender.photo.square_200 }}" alt="" />
</div>
<div class="notification-content">
<p>{{ notification.message }}</p>
<time>{{ notification.sent_at | date: "%b %d" }}</time>
</div>
</a>
</div>
{% endfor %}
</div>
The Request Object
The Request object represents user requests such as invitations, friend requests, and group membership requests.
Used as: requests (array of request objects)
Common Attributes
| Variable | Type | Description |
|---|---|---|
type |
String | Request type (e.g., “invitation”, “friend_request”, “group_membership_request”) |
message |
String | The request message or reason |
sender |
User | The user who initiated the request |
sent_at |
DateTime | When the request was sent |
accept_url |
String | URL to accept the request |
ignore_url |
String | URL to ignore/decline the request |
delete_url |
String | URL to delete the request |
Example: Rendering a Requests List
<div class="requests">
<h2>Pending Requests</h2>
{% for request in requests %}
<div class="request-item">
<div class="request-info">
<img src="{{ request.sender.photo.square_200 }}" alt="" />
<div>
<strong>{{ request.sender.name }}</strong>
<p>{{ request.type | replace: "_", " " | capitalize }}</p>
{% if request.message %}
<p class="message">{{ request.message }}</p>
{% endif %}
<small>{{ request.sent_at | date: "%b %d" }}</small>
</div>
</div>
<div class="request-actions">
<a href="{{ request.accept_url }}" class="btn btn-primary">Accept</a>
<a href="{{ request.ignore_url }}" class="btn btn-secondary">Decline</a>
</div>
</div>
{% endfor %}
</div>
The Digest Object
The Digest object represents a calendar digest (email subscription) configuration.
Used as: digest (single), digest_summaries (array)
Settings
| Variable | Type | Description |
|---|---|---|
title |
String | Name of the digest |
send_day |
String | Day(s) the digest is sent (e.g., “Monday”, “Daily”) |
enabled |
Boolean | Whether the digest is currently active |
limit |
Integer | Maximum number of events to include in each digest |
date_range |
String | Time span covered (e.g., “1 week”, “2 weeks”) |
match |
String | Filter criteria or matching rules applied to the digest |
URLs
| Variable | Type | Description |
|---|---|---|
token_unsubscribe_link |
String | URL to unsubscribe from this digest |
token_destroy_link |
String | URL to delete this digest configuration |
Example: Rendering a Digest Configuration Form
<form method="post" action="/digest/update">
<fieldset>
<legend>Digest Settings: {{ digest.title }}</legend>
<label>
Digest Title
<input type="text" name="digest[title]" value="{{ digest.title }}" />
</label>
<label>
Send Day
<input type="text" name="digest[send_day]" value="{{ digest.send_day }}" />
</label>
<label>
Date Range
<input type="text" name="digest[date_range]" value="{{ digest.date_range }}" />
</label>
<label>
Event Limit
<input type="number" name="digest[limit]" value="{{ digest.limit }}" min="1" max="50" />
</label>
<label>
<input type="checkbox" name="digest[enabled]" {% if digest.enabled %}checked{% endif %} />
Enable this digest
</label>
<button type="submit">Save Digest</button>
<a href="{{ digest.token_destroy_link }}">Delete Digest</a>
</fieldset>
</form>
The Campaign Object
The Campaign object represents a Bulletin campaign — Localist’s email newsletter product. It does not represent generic email campaigns or other announcements.
Used as: campaign (single context)
Details
| Variable | Type | Description |
|---|---|---|
title |
String | Campaign name or title |
subject |
String | Email subject line |
from_name |
String | Display name for the email sender |
from_address |
String | Email address the campaign is sent from |
reply_to |
String | Email address for reply-to header |
publish_target |
String | Target audience or distribution list |
allow_unsubscribe |
Boolean | Whether recipients can unsubscribe |
URLs
| Variable | Type | Description |
|---|---|---|
subscribe_url |
String | URL for users to subscribe to the campaign |
unsubscribe_url |
String | URL for users to unsubscribe from the campaign |
Common Form Patterns
Building a Custom Event Submission Form
<form method="post" action="/events" class="event-form">
<fieldset>
<legend>Event Information</legend>
<div class="form-group">
<label for="title">Event Title *</label>
<input
type="text"
id="title"
name="event[title]"
value="{{ event.name }}"
required
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea
id="description"
name="event[description]"
>{{ event.description }}</textarea>
</div>
</fieldset>
{% if visible_sections.custom_fields %}
<fieldset>
<legend>Additional Details</legend>
{% for field in site.public_event_custom_fields %}
{% if field.public_form %}
<div class="form-group">
<label for="cf_{{ field.key }}">
{{ field.label }}
{% if field.required %}<span class="required">*</span>{% endif %}
</label>
{% if field.field_type == 'text' %}
<input
type="text"
id="cf_{{ field.key }}"
name="event[custom_fields][{{ field.key }}]"
value="{{ event.custom_fields[field.key] }}"
{% if field.required %}required{% endif %}
/>
{% elsif field.field_type == 'textarea' %}
<textarea
id="cf_{{ field.key }}"
name="event[custom_fields][{{ field.key }}]"
{% if field.required %}required{% endif %}
>{{ event.custom_fields[field.key] }}</textarea>
{% endif %}
</div>
{% endif %}
{% endfor %}
</fieldset>
{% endif %}
<fieldset>
<legend>Settings</legend>
<div class="form-group">
<label for="status">Status</label>
<select id="status" name="event[status]">
{% for option in status_options %}
<option
value="{{ option.value }}"
{% if event.status == option.value %}selected{% endif %}
>
{{ option.label }}
</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="visibility">Visibility</label>
<select id="visibility" name="event[visibility]">
{% for option in visibility_options %}
<option
value="{{ option.value }}"
{% if event.visibility == option.value %}selected{% endif %}
>
{{ option.label }}
</option>
{% endfor %}
</select>
</div>
</fieldset>
<button type="submit">Save Event</button>
</form>
Rendering Custom Fields Dynamically
{% assign all_fields = site.public_event_custom_fields %}
<div class="custom-fields-section">
{% for field in all_fields %}
{% assign value = event.custom_fields[field.key] %}
{% assign is_required = field.required %}
{% assign is_visible = field.public_form %}
{% if is_visible %}
<div class="field-wrapper field-type-{{ field.field_type }}" data-field-id="{{ field.id }}">
<label for="field_{{ field.key }}">
{{ field.label }}
{% if is_required %}<span class="field-required" title="Required">*</span>{% endif %}
</label>
{% case field.field_type %}
{% when 'text' %}
<input
type="text"
id="field_{{ field.key }}"
class="text-input"
name="event[custom_fields][{{ field.key }}]"
value="{{ value }}"
{% if is_required %}required aria-required="true"{% endif %}
/>
{% when 'textarea' %}
<textarea
id="field_{{ field.key }}"
class="textarea-input"
name="event[custom_fields][{{ field.key }}]"
{% if is_required %}required aria-required="true"{% endif %}
>{{ value }}</textarea>
{% when 'checkbox' %}
<label class="checkbox-label">
<input
type="checkbox"
name="event[custom_fields][{{ field.key }}]"
value="true"
{% if value %}checked{% endif %}
/>
{{ field.label }}
</label>
{% endcase %}
{% if field.html %}
<small>HTML allowed in this field</small>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
Handling Form Visibility States
<form class="event-form" data-limited-edit="{{ limited_edit }}">
{% if limited_edit %}
<div class="alert alert-info">
Some fields are read-only because this event has already been published.
</div>
{% endif %}
<fieldset {% if limited_edit %}disabled{% endif %}>
<legend>Basic Event Information</legend>
<input type="text" name="event[title]" value="{{ event.name }}" />
</fieldset>
{% if visible_sections.description %}
<fieldset>
<legend>Description</legend>
<textarea name="event[description]">{{ event.description }}</textarea>
</fieldset>
{% endif %}
{% if visible_sections.zoom %}
{% if has_zoom_integration == 'true' %}
<fieldset>
<legend>Zoom Integration</legend>
<p>Current status: <strong>{{ zoom_status }}</strong></p>
</fieldset>
{% endif %}
{% endif %}
</form>
Related Articles
This article is part of a 9-part series on Localist developer theming:
-
Introduction to Localist Developer Theming
-
Site Configuration and Global Objects
-
Events, Instances, and Related Objects
-
Places, Groups, Departments, and Channels
-
Shared Object Types
-
Custom Liquid Tags Reference
-
Custom Liquid Filters Reference
-
Template Variable Reference
-
Forms and User Management
More Information
For additional guidance on Liquid syntax, filters, and template tags, refer to the Liquid documentation and Article 6 (Custom Liquid Tags Reference).