Documentation Index
Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
Use this file to discover all available pages before exploring further.
Links are used to create navigation throughout the storefront. They can be assigned to sections and blocks, enabling users to navigate to different pages, products, categories, or external URLs.
How Links Work
The Spree::PageLink model provides a flexible way to link to various content types:
- Internal pages - Link to Pages, Products, Taxons, Posts
- External URLs - Link to any external website
- Special links - Email (
mailto:) and phone (tel:) links
Links are managed through the Page Builder interface, where store staff can select what each link points to.
Link Model
The Spree::PageLink model has these key attributes:
| Attribute | Type | Description |
|---|
label | String | Display text for the link |
url | String | Custom URL (for external links) |
linkable | Polymorphic | Reference to internal content (Page, Product, etc.) |
parent | Polymorphic | Section or Block the link belongs to |
open_in_new_tab | Boolean | Whether to open in new browser tab |
position | Integer | Order when multiple links exist |
Linkable Types
Links can point to various Spree models:
| Linkable Type | Description |
|---|
Spree::Page | Internal pages (Home, Shop All, Custom pages) |
Spree::Product | Product detail pages |
Spree::Taxon | Category/collection pages |
Spree::Post | Blog posts |
| Custom URL | Any external or internal URL |
Using Links in Sections
Single Link
For sections that need one link (like a banner):
app/models/spree/page_sections/promo_banner.rb
module Spree
module PageSections
class PromoBanner < Spree::PageSection
has_one :link, ->(ps) { ps.links },
class_name: 'Spree::PageLink',
as: :parent,
dependent: :destroy,
inverse_of: :parent
accepts_nested_attributes_for :link
def default_links
@default_links.presence || [
Spree::PageLink.new(label: Spree.t(:shop_now))
]
end
end
end
end
Multiple Links
For sections that need multiple links (like navigation):
app/models/spree/page_sections/footer.rb
module Spree
module PageSections
class Footer < Spree::PageSection
# Links are provided by Spree::HasPageLinks concern
# which is included in Spree::PageSection by default
def default_links
@default_links.presence || [
Spree::PageLink.new(label: 'About Us'),
Spree::PageLink.new(label: 'Contact'),
Spree::PageLink.new(label: 'Privacy Policy')
]
end
end
end
end
Using Links in Blocks
Blocks can also have links. Use the Spree::HasOneLink concern for single links:
app/models/spree/page_blocks/cta_button.rb
module Spree
module PageBlocks
class CtaButton < Spree::PageBlock
include Spree::HasOneLink
preference :button_style, :string, default: 'primary'
# Called when the link is deleted
def link_destroyed(_link)
destroy if page_links_count.zero?
end
end
end
end
Accessing Links
# Single link on a section
section.link
section.link.label
section.link.linkable_url
# Multiple links on a section
section.links
section.links.each do |link|
link.label
link.linkable_url
end
# Link on a block
block.link
block.link.label if block.link.present?
Rendering Links
Using the Helper
The page_builder_link_to helper renders links with Page Builder support:
<%# Basic usage %>
<%= page_builder_link_to section.link %>
<%# With custom label %>
<%= page_builder_link_to section.link, label: 'Click Here' %>
<%# With CSS class %>
<%= page_builder_link_to section.link, class: 'btn-primary' %>
<%# Open in new tab %>
<%= page_builder_link_to section.link,
target: (section.link.open_in_new_tab ? '_blank' : nil),
rel: (section.link.open_in_new_tab ? 'noopener noreferrer' : nil) %>
<%# With block content %>
<%= page_builder_link_to section.link do %>
<span class="icon">→</span>
<%= section.link.label %>
<% end %>
Manual Link Rendering
For more control, you can render links manually:
<% if section.link.present? %>
<%= link_to section.link.linkable_url,
target: (section.link.open_in_new_tab ? '_blank' : nil),
rel: (section.link.open_in_new_tab ? 'noopener noreferrer' : nil),
class: 'btn-primary' do %>
<%= section.link.label %>
<% end %>
<% end %>
Rendering Multiple Links
<nav class="footer-links">
<% section.links.each do |link| %>
<%= page_builder_link_to link, class: 'footer-link' %>
<% end %>
</nav>
In your section’s admin form, render the link editor:
Single Link
app/views/spree/admin/page_sections/forms/_promo_banner.html.erb
<%= render 'spree/admin/shared/page_section_image', f: f %>
<div class="py-2">
<%= f.fields_for :link do |lf| %>
<div class="form-group">
<%= lf.label :linkable_type, Spree.t(:link) %>
<%= lf.select :linkable_type,
@page_section.allowed_linkable_types,
{ include_blank: false },
{ class: 'custom-select mb-3', data: { action: 'auto-submit#submit' } } %>
<div id="linkable_type_dropdown">
<%= render 'spree/admin/page_links/linkable_type_dropdown',
page_link: lf.object,
form_name: 'page_section[link_attributes]' %>
</div>
</div>
<div class="custom-control custom-checkbox">
<%= lf.check_box :open_in_new_tab,
class: 'custom-control-input',
data: { action: 'auto-submit#submit' } %>
<%= lf.label :open_in_new_tab, class: 'custom-control-label' %>
</div>
<% end %>
</div>
Link URL Resolution
The linkable_url method returns the appropriate URL:
link = Spree::PageLink.new(linkable: product)
link.linkable_url
# => "/products/red-shirt"
link = Spree::PageLink.new(url: "https://example.com")
link.linkable_url
# => "https://example.com"
link = Spree::PageLink.new(url: "example.com")
link.formatted_url
# => "http://example.com" # Adds protocol automatically
link = Spree::PageLink.new(url: "mailto:info@example.com")
link.formatted_url
# => "mailto:info@example.com" # Preserves mailto: protocol
Creating Links Programmatically
# Link to an internal page
link = Spree::PageLink.create!(
parent: section,
label: 'Shop Now',
linkable: store.pages.find_by(type: 'Spree::Pages::ShopAll')
)
# Link to a product
link = Spree::PageLink.create!(
parent: section,
label: product.name,
linkable: product
)
# Link to a taxon
link = Spree::PageLink.create!(
parent: section,
label: 'New Arrivals',
linkable: store.taxons.find_by(name: 'New Arrivals')
)
# External link
link = Spree::PageLink.create!(
parent: section,
label: 'Visit Our Blog',
url: 'https://blog.example.com',
open_in_new_tab: true
)
Automatic Label Setting
When a link’s linkable is set, the label is automatically populated from the linked resource:
link = Spree::PageLink.new(linkable: product)
link.valid?
link.label
# => "Red T-Shirt" (from product.name)
The label is derived from (in order):
linkable.title
linkable.display_name
linkable.name
- Sections - Adding links to sections
- Blocks - Adding links to blocks
- Pages - Understanding internal page linking