It's your third week learning Ruby on Rails. You've built your first blog app, you've got posts showing up on the screen, and now you want to add a "Delete" button. You've seen link_to everywhere in the tutorial, so you write something like this:
<%= link_to "Delete", post_path(@post), method: :delete %>
You click it. Nothing happens, or worse, it navigates to the wrong page. You scratch your head. You Google. You read five Stack Overflow answers. You feel like the universe is testing you.
Sound familiar?
You're not alone. Almost every beginner Rails developer hits this exact wall. And the confusion? It almost always comes down to not understanding the difference between link_to and button_to.
Today, we're going to fix that, permanently. By the end of this article, you'll know exactly which one to use, why it matters, and how to avoid the classic mistakes that trip up beginners.
Let's go.
First, The Basics
What is link_to?
link_to is a Rails view helper that generates an HTML anchor tag, the classic <a href="..."> you've probably seen a thousand times. It's designed for navigation. You use it when you want the user to travel from one page to another.
<%= link_to "Go to Blog", posts_path %>
Which renders as:
<a href="/posts">Go to Blog</a>
Simple, clean, and semantically correct. When a user clicks it, the browser sends a GET request to /posts. That's it. It fetches a page.
What is button_to?
button_to is also a Rails view helper, but it works very differently under the hood. Instead of generating a plain anchor tag, it wraps a button inside a mini HTML form.
<%= button_to "Delete Post", post_path(@post), method: :delete %>
Which renders something like:
<form action="/posts/1" method="post">
<input type="hidden" name="_method" value="delete" />
<input type="hidden" name="authenticity_token" value="..." />
<button type="submit">Delete Post</button>
</form>
See that? It's a full form with a hidden _method field and an authenticity token (CSRF protection). When clicked, it submits the form with a DELETE request (or POST, PATCH, whatever you specify). It's built for actions, not navigation.
The Key Differences, Side by Side
Let's break it down clearly so it sticks.
1. HTTP Method — The Big One
This is where most beginners get confused.
Helper | Default HTTP Method | Can Use Other Methods?
link_to | GET | Needs JavaScript (data-turbo-method)
button_to | POST | Yes — DELETE, PATCH, PUT natively
A GET request says: "Hey server, give me some data." It's for reading pages.
A DELETE request says: "Hey server, destroy this record." It's for taking action.
When you use link_to with method: :delete, you're fighting against the nature of an anchor tag. In older Rails with jQuery UJS, it sometimes worked, but only because JavaScript was intercepting the click and converting it. In modern Rails with Hotwire/Turbo, you need to use data: { turbo_method: :delete }, or just switch to button_to and stop fighting the current.
<!-- Modern Rails way with link_to for DELETE -->
<%= link_to "Delete", post_path(@post), data: { turbo_method: :delete } %>
<!-- Cleaner, more semantic way -->
<%= button_to "Delete", post_path(@post), method: :delete %>
The second one is almost always the better choice for destructive actions.
2. HTML Output — What Actually Gets Rendered
link_to renders a plain <a> tag:
<a href="/posts/1">Read Post</a>
button_to renders a form with a button inside:
<form action="/posts/1" method="post">
<input type="hidden" name="_method" value="delete" />
<input type="hidden" name="authenticity_token" value="xyz..." />
<button type="submit">Delete Post</button>
</form>
This matters because the form includes CSRF protection by default. Rails is protecting your app from cross-site request forgery attacks without you having to think about it. That's one of the beautiful things about using button_to for actions, security baked right in.
3. UX & Styling — Look and Feel
By default, link_to looks like a link (underlined text). button_to looks like a button (styled by your browser or CSS framework).
Both are fully styleable with CSS, but it's important to be semantically honest with your UI. If something looks like a button and does something (like deleting a record), it should be a button. If it looks like a link and takes you somewhere, it should be a link.
Your users expect certain visual cues. A blue underlined "Delete" link feels wrong, and it probably is.
When to Use Each One — Real World Examples
Now let's get practical. Here are scenarios you'll encounter in real Rails projects.
Use link_to for Navigation
link_to shines when you're taking the user somewhere, to another page, another resource, an external site.
<!-- Navigate to a blog post -->
<%= link_to "Read More", post_path(@post) %>
<!-- Go to the homepage -->
<%= link_to "Home", root_path %>
<!-- Visit an external link -->
<%= link_to "Visit Rails Docs", "https://rubyonrails.org", target: "_blank" %>
<!-- Go to a specific user's profile -->
<%= link_to @user.name, user_path(@user) %>
In a blog app, your post index page might look like this:
<% @posts.each do |post| %>
<div class="post-card">
<h2><%= link_to post.title, post_path(post) %></h2>
<p><%= post.excerpt %></p>
<%= link_to "Read Full Article →", post_path(post), class: "read-more" %>
</div>
<% end %>Clean, readable, and semantically correct. Want to build a full blog like this? Check out this step-by-step guide:
How to Build a Blog with Ruby on Rails 8,it walks you through the entire process from scratch.
Use button_to for Actions
button_to is your go-to when something happens on the server, a record is created, updated, or destroyed.
<!-- Delete a post -->
<%= button_to "Delete", post_path(@post), method: :delete,
data: { turbo_confirm: "Are you sure?" } %>
<!-- Add an item to the cart (e-commerce) -->
<%= button_to "Add to Cart", cart_items_path,
params: { product_id: @product.id }, method: :post %>
<!-- Mark an order as complete (POS system) -->
<%= button_to "Mark as Paid", complete_order_path(@order),
method: :patch, class: "btn btn-success" %>
<!-- Follow a user (social app) -->
<%= button_to "Follow", follows_path,
params: { followee_id: @user.id }, method: :post %>
Notice how every one of these examples changes something on the server. That's the pattern. If data is being written, modified, or removed, use button_to.
Real-World Example: A Blog Post Show Page
Here's what a well-structured Rails show page might look like using both helpers correctly:
<!-- app/views/posts/show.html.erb -->
<article>
<h1><%= @post.title %></h1>
<p class="meta">By <%= @post.author %> · <%= @post.created_at.strftime("%B %d, %Y") %></p>
<div class="content"><%= @post.body %></div>
</article>
<div class="post-actions">
<!-- Navigation → link_to -->
<%= link_to "← Back to All Posts", posts_path %>
<%= link_to "Edit Post", edit_post_path(@post) %>
<!-- Action → button_to -->
<%= button_to "Delete Post", post_path(@post),
method: :delete,
data: { turbo_confirm: "Delete this post forever?" },
class: "btn btn-danger" %>
</div>
Notice how intuitively it reads:
- "Back to All Posts", you're going somewhere → link_to
- "Edit Post", you're navigating to an edit form → link_to
- "Delete Post", you're destroying a record → button_to
Real-World Example: An E-Commerce Product Page
<!-- app/views/products/show.html.erb -->
<div class="product">
<h1><%= @product.name %></h1>
<p class="price">$<%= @product.price %></p>
<p><%= @product.description %></p>
</div>
<div class="product-actions">
<!-- Navigation -->
<%= link_to "View All Products", products_path %>
<%= link_to "View Reviews", product_reviews_path(@product) %>
<!-- Actions -->
<%= button_to "Add to Cart", cart_items_path,
params: { product_id: @product.id },
method: :post,
class: "btn btn-primary btn-lg" %>
<%= button_to "Add to Wishlist", wishlist_items_path,
params: { product_id: @product.id },
method: :post,
class: "btn btn-outline" %>
</div>
Every button that does something uses button_to. Every link that goes somewhere uses link_to. That's the mental model. Lock it in.
Common Beginner Mistakes (And How to Avoid Them)
Mistake #1: Using link_to for Destructive Actions
This is the classic. You write:
<!-- Don't do this in modern Rails -->
<%= link_to "Delete", post_path(@post), method: :delete %>
In Rails 7+ with Turbo, this won't work as expected without extra configuration. The anchor tag will fire a GET request by default, and your destroy action won't be triggered. Always use button_to for delete actions.
<!-- Do this instead -->
<%= button_to "Delete", post_path(@post), method: :delete %>
Mistake #2: Confusing Navigation and Actions
Sometimes beginners think: "A link and a button look different, but they do the same thing, they click."
They don't. The browser, search engines, and screen readers all treat them differently.
- Screen readers announce <a> tags as "link" and <button> as "button"
- Search engine crawlers follow <a href> links to index pages, they don't click buttons
- Browsers may prefetch anchor links; they never pre-submit forms
Semantic correctness isn't just about being tidy, it affects accessibility, SEO, and user experience.
Mistake #3: Forgetting the Confirmation Dialog
When using button_to for a delete action, always add a confirmation prompt. Otherwise, one accidental click destroys data.
<!-- Dangerous — no confirmation -->
<%= button_to "Delete", post_path(@post), method: :delete %>
<!-- Safe — user must confirm -->
<%= button_to "Delete", post_path(@post),
method: :delete,
data: { turbo_confirm: "Are you sure you want to delete this post?" } %>
One line of code. It can save your users from a lot of heartache, and save you from a lot of angry emails.
Mistake #4: Styling button_to Without Accounting for the Form Wrapper
Because button_to wraps its button in a <form> tag, sometimes your CSS layout breaks unexpectedly, especially inside flexbox or grid containers.
<!-- The form wrapper can mess up flex layouts -->
<div class="actions flex gap-2">
<%= link_to "Edit", edit_post_path(@post), class: "btn" %>
<%= button_to "Delete", post_path(@post), method: :delete, class: "btn btn-danger" %>
</div>
The link_to is a direct child of the flex container. The button_to button is a grandchild (the form is the direct child). Fix it by passing form_class or wrapping styles on the form:
<%= button_to "Delete", post_path(@post),
method: :delete,
class: "btn btn-danger",
form_class: "inline" %>
Or in Tailwind:
<%= button_to "Delete", post_path(@post),
method: :delete,
class: "btn btn-danger",
form_class: "inline-flex" %>
Small detail, but it matters when pixel-perfect layouts are involved.
Mini Recap — Your Cheat Sheet
Save this. Screenshot it. Tattoo it on your brain.
Question | Answer
Am I taking the user somewhere? | Use link_to
Am I doing something on the server? | Use button_to
Is it a GET request? | Use link_to
Is it POST, PATCH, PUT, or DELETE? | Use button_to
Is it navigation (show, index, edit page)? | Use link_to
Is it an action (create, update, destroy)? | Use button_to
Does SEO or screen reader semantics matter? | link_to for links, button_to for buttons
Am I deleting a record? | Always button_to with turbo_confirm
Quick Code Reference:
<!-- Navigate to a page -->
<%= link_to "View Post", post_path(@post) %>
<!-- Submit a form-like action -->
<%= button_to "Delete", post_path(@post), method: :delete %>
<!-- Navigate with styling -->
<%= link_to "Edit", edit_post_path(@post), class: "btn btn-secondary" %>
<!-- Action with confirmation -->
<%= button_to "Remove Item", cart_item_path(@item),
method: :delete,
data: { turbo_confirm: "Remove from cart?" },
class: "btn btn-sm btn-outline-danger" %>
Going Deeper: The build vs new Connection
Understanding link_to and button_to is really about understanding HTTP verbs and Rails conventions. Once that clicks, a lot of other Rails concepts start making sense too, like why build and new behave differently in ActiveRecord.
If you're curious about that rabbit hole, this guide dives deep:
Rails Build Vs New: Complete Guide (2025). It's the same kind of "they look similar, but they're different" situation, and mastering it will make you a much sharper Rails developer.
The Bigger Picture: Think in HTTP
Here's the mentor advice I wish someone had given me earlier:
Rails is a thin layer on top of HTTP. The more you understand HTTP, the more Rails makes sense.
Every Rails route maps to an HTTP verb:
Rails Action | HTTP Verb | You should use
index, show | GET | link_to
new, edit | GET | link_to
create | POST | button_to (or a form)
update | PATCH/PUT | button_to (or a form)
destroy | DELETE | button_to
When you start thinking this way, link_to vs button_to stops being a confusing API quirk and starts being obvious. You're not choosing between two similar helpers, you're choosing the right HTTP method for the job, and Rails gives you the right tool for each.
Motivational Conclusion — Keep Building
I want to tell you something important before you close this tab.
The fact that you're here, reading, learning, trying to understand the why behind Rails helpers, already puts you ahead of most people who just copy-paste code without thinking. That curiosity? That's what separates developers who grow fast from those who plateau.
Yes, link_to and button_to confused you. They confused me too. They confuse thousands of developers every year. But now you understand them, not just what they do, but why they exist and how they map to HTTP, semantics, and real-world user actions.
That's real knowledge. And it compounds.
Every small concept you truly understand in Rails builds on the next. The routing system. The asset pipeline. ActiveRecord associations. Turbo Streams. They all connect. And each time you hit a wall like this one, work through it, and come out the other side, you're becoming a better developer.
Want to keep the momentum going? Here are two resources that will take you further:
You've got this. Rails is one of the most productive, elegant, and joyful frameworks ever built, and every day you spend learning it is an investment that pays off in the projects you'll ship, the problems you'll solve, and the career you'll build.
Now go write some Rails. And this time, use button_to for that delete action. 😄
Found this helpful? Share it with a fellow Rails beginner.