create-twig-form-template
Read @.ai/Component/Twig/CONTEXT.md for template conventions (layout, flash messages, routes, form themes). When the template renders a specific form pattern, load only the relevant Forms sub-context:
- CRUD template →
@.ai/Component/Forms/CRUD.md - Settings block →
@.ai/Component/Forms/SETTINGS.md
Two patterns: CRUD forms render one form per page on a dedicated
form.html.twig. Settings forms render one or more option blocks on the listing page (or a dedicated config page) — each block is its ownform_start/form_endwith an explicitactionURL. See section 4 below for the settings differences.
1. Form template
Create src/PrestaShopBundle/Resources/views/Admin/{Section}/{Domain}/form.html.twig:
{% extends '@PrestaShop/Admin/layout.html.twig' %}
{% block content %}
{{ form_start(form) }}
{{ form_widget(form) }}
<button type="submit" class="btn btn-primary">
{{ 'Save'|trans({}, 'Admin.Actions') }}
</button>
{{ form_end(form) }}
{% endblock %}
- Always use a single
form_widget(form)— see Twig/CONTEXT.md for why (module hook compatibility) - The same template typically serves both create and edit — the controller passes different form data
- For file uploads, add
enctype="multipart/form-data"(see CONTEXT.md)
Reference: src/PrestaShopBundle/Resources/views/Admin/Improve/International/Tax/ (simple), src/PrestaShopBundle/Resources/views/Admin/Sell/Catalog/Manufacturer/ (with image)
2. Form theme overrides (only if needed)
When specific fields need custom rendering beyond Symfony defaults:
- Preferred: set the
form_themeoption directly on the form (PrestaShop custom option) — usually in the controller when building the form, or in the form type'sconfigureOptions(). Symfony picks up the theme automatically; no{% form_theme %}directive needed in the Twig file. - Fallback: use the Twig directive when the override is template-specific:
{% form_theme form 'Admin/{Section}/{Domain}/{domain}_theme.html.twig' %} - Override the field block:
{% block _{field_id}_widget %}...{% endblock %} - Common case: image preview next to upload field with a "Remove" checkbox
Form themes are scoped to this form only — no global side effects.
3. JS asset inclusion
If the form needs JavaScript (for initComponents or Vue):
{% block javascripts %}
{{ parent() }}
<script src="{{ asset('themes/new-theme/public/{domain}.bundle.js') }}"></script>
{% endblock %}
The asset path must match the webpack entry name.
4. Settings form block templates
Settings forms are different. The template usually lives alongside the index page (or as an included Twig block) and renders one form per option block. Multiple blocks per page are common (the Advanced > Performance page has six).
{{ form_start(optionsForm, {action: path('admin_{page}_save_options'), attr: {class: 'form', id: '{name}_form'}}) }}
<div class="card">
<h3 class="card-header"><i class="material-icons">settings</i>{{ '{Page} options'|trans }}</h3>
<div class="card-body">
<div class="form-wrapper">
{{ form_widget(optionsForm) }}
</div>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<button class="btn btn-primary">{{ 'Save'|trans({}, 'Admin.Actions') }}</button>
</div>
</div>
</div>
{{ form_end(optionsForm) }}
Two differences from the CRUD pattern:
- The
actionURL is explicit. Settings forms POST to a dedicated save route (e.g.admin_countries_save_options) that is distinct from the page route. Without it, the form would POST to the listing route, which is not a save endpoint. - The submit button sits outside
form_widget, typically in the card footer. The single-form_widget(form)rule still applies (for module hook compatibility) — the button is a sibling, not a child of the form widget call.
For pages that mix a grid and one or more settings blocks (e.g. Improve > International > Locations > Countries), the index template renders the grid and each settings block as separate top-level elements; each block is its own form_start/form_end.
Reference: src/PrestaShopBundle/Resources/views/Admin/Improve/International/Country/Blocks/options.html.twig + index.html.twig (PR #41406).
Rules
Conventions (layout, path(), single form_widget(form), NavigationTabType auto-rendering, file upload enctype, form theme block naming, JS asset inclusion) are in Twig/CONTEXT.md. Skill-specific reminders:
- Form theme overrides should be minimal — Symfony's default rendering handles most cases
- CSRF token is included automatically by
form_end(form) - Settings blocks must set an explicit
action: path('...')— the page route is not the save route