Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Link Button Bundle Laravel Package

symfonycontrib/link-button-bundle

A Symfony bundle for rendering link-style buttons in your applications. It offers reusable helpers and template integration so anchors can be presented as buttons with minimal setup, keeping navigation actions consistent and easy to maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a micro-package: it adds one form type that renders a Symfony form button as an <a> element instead of a <button>/<input type="submit">, so you can put a Cancel/Back action in a form's action row without a second route or a hand-written anchor in the template.

Install and enable:

composer require symfonycontrib/link-button-bundle
// Symfony 3.x: app/AppKernel.php
$bundles[] = new SymfonyContrib\Bundle\LinkButtonBundle\LinkButtonBundle();
// Symfony 4/5+: config/bundles.php
SymfonyContrib\Bundle\LinkButtonBundle\LinkButtonBundle::class => ['all' => true],

Before you write any code, check composer.json in the vendor directory for the symfony/form constraint. This package predates Flex, autowiring conventions and Symfony 4, so on a modern skeleton install will usually fail on constraint resolution — in that case stop here and use the ~20-line replacement in Gotchas and Tips rather than pinning your framework back.

Where to look first, in order:

  1. vendor/symfonycontrib/link-button-bundle/Form/Type/ — the type class and the alias it registers (expected to be link_button).
  2. vendor/symfonycontrib/link-button-bundle/Resources/views/Form/ — the Twig block name that actually renders the anchor. This is where class names, target, and href construction live.
  3. DependencyInjection/ — any config keys (default classes, whether the URL comes from a route name or a raw path).
  4. README.md — the option list, which is short and is the real API surface.

First use case — a Cancel link next to Save on an edit form:

use SymfonyContrib\Bundle\LinkButtonBundle\Form\Type\LinkButtonType;

$builder
    ->add('title')
    ->add('body')
    ->add('save', SubmitType::class, ['label' => 'Save'])
    ->add('cancel', LinkButtonType::class, [
        'label' => 'Cancel',
        'route' => 'post_show',
        'route_params' => ['id' => $post->getId()],
    ]);
{{ form_start(form) }}
    {{ form_row(form.title) }}
    {{ form_row(form.body) }}
    {{ form_widget(form.save) }}
    {{ form_widget(form.cancel) }}
{{ form_end(form) }}

Verify with bin/console debug:form that the type is registered, then load the page and confirm the DOM contains <a href="/posts/1">Cancel</a> and not an <input type="submit">.

Implementation Patterns

Treat it as a presentation concern, not a form control. The value it adds is that the link lives in the form definition, so the controller stays clean and the action row is composed in one place. Keep the route name and parameters in the form type, never a hard-coded path.

Action-row builder. For CRUD screens, define a small helper that appends the standard actions so every form is consistent:

private function addActions(FormBuilderInterface $builder, string $cancelRoute, array $params = []): void
{
    $builder
        ->add('save', SubmitType::class, ['label' => 'Save'])
        ->add('cancel', LinkButtonType::class, [
            'label' => 'Cancel',
            'route' => $cancelRoute,
            'route_params' => $params,
        ]);
}

Styling via attr, not via the vendor template. Pass 'attr' => ['class' => 'btn btn-secondary'] and let the bundle's block merge attributes, exactly as you would on a SubmitType. If the block does not merge attributes, override the block locally (see below) rather than editing vendor code.

Override the render block project-wide. The cleanest integration is a one-block Twig override that inherits from the bundle and adds your design-system classes or an icon:

{# templates/form/link_button.html.twig #}
{% block link_button_widget %}
    <a href="{{ href|default('#') }}" class="btn btn-link">{{ label }}</a>
{% endblock %}
# config/packages/twig.yaml
twig:
    form_themes:
        - 'form/link_button.html.twig'

Use it for navigation only. It fits: Cancel on create/edit, "Back to list", "View" beside "Delete", and links to auxiliary screens that logically belong to the same form. It does not fit destructive actions — a GET anchor bypassing CSRF and HTTP method is a data-loss bug waiting for a browser prefetch or a crawler.

Cross-framework note for a Laravel developer. The Laravel equivalent is a Blade component rendering <a href="{{ route(...) }}" class="btn">Cancel</a> inside a <form>; there is no form-type machinery to hook into. In Symfony the reason to reach for a form type instead of plain Twig is that form_widget(form.cancel) keeps field ordering, theming, and form_rest behaviour uniform. If your only use is "one anchor with a route", plain Twig path() is strictly better and dependency-free.

Testing. Symfony's WebTestCase and Panther both see the rendered anchor, so assert on it directly:

$this->assertSelectorExists('a[href="/posts/1"]');
$this->assertSelectorTextContains('a[href="/posts/1"]', 'Cancel');

If you use the form type in a functional test, also assert the form still submits — a stray <button> or a mis-typed anchor that triggers submission is the failure mode to guard against.

Gotchas and Tips

  • Maintenance risk is the headline. Zero stars, no dependents, no dependents' bug reports — this is an abandoned 2016-era bundle. Do not introduce it into a maintained application; vendor the single form type into src/Form/Type/ and own it.

  • Flex will not enable it. There is no recipe, so a missing bundles.php entry is the #1 cause of Could not load type "link_button". Run bin/console debug:form to confirm registration before debugging anything else.

  • Service registration on Symfony 4+. Old-style bundles rely on services.yml in the bundle with explicit form.type tags. If you symlink or copy the type into your app, autoconfiguration handles the tag; if you keep the vendor bundle, verify the tag survived. Without it the alias does not exist even though the class does.

  • Theme resolution failure looks like a different bug. If your custom form theme overrides form_widget without calling parent(), or you replaced form_div_layout.html.twig wholesale, the anchor silently renders as a default button. Symptom: the cancel "link" submits the form. Check which block is actually used with bin/console debug:twig --filter=link_button.

  • form_end still renders hidden fields. _token, _method, and unmapped hidden fields are emitted next to your anchor. That is correct and desirable, but it means the anchor sits inside a real form — be deliberate that its href is a GET navigation target, not a state change.

  • Never point it at a mutating route. No CSRF token on an <a>. Use a real submit button with _method spoofing and csrf_token() for deletes.

  • route_params are URL-encoded, href-style options may not be. If the type accepts a raw url/path option, escape or generate it via the router; never interpolate request input into it.

  • PHP and Symfony version floor. Expect PHP 5.6/7.0 and Symfony 2.3–3.x constraints. If your app is on PHP 8.1+ / Symfony 6+, the install will not resolve and neither will the type, which may reference Symfony\Component\Form\AbstractType::setDefaultOptions() (removed in favour of configureOptions()).

  • The zero-dependency replacement, if you want to drop it:

    final class LinkButtonType extends AbstractType
    {
        public function buildView(FormView $view, FormInterface $form, array $options): void
        {
            $view->vars['href'] = $options['href'];
        }
    
        public function configureOptions(OptionsResolver $resolver): void
        {
            $resolver->setRequired('href');
            $resolver->setAllowedTypes('href', 'string');
        }
    
        public function getBlockPrefix(): string
        {
            return 'link_button';
        }
    }
    

    plus the five-line Twig block above. That is the entire package's value; owning it removes the version-matrix risk and the abandoned-dependency flag from your audit.

  • Accessibility. An anchor used for navigation is fine as-is. If you ever make it behave like an action, it needs role="button", keyboard handling, and a real <button> behind it — at which point you should just use <button>.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums