> ## 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

> Learn how to use links for navigation in your Spree Storefront

Links are used to create navigation throughout the storefront. They can be assigned to [sections](/developer/storefront/rails/sections) and [blocks](/developer/storefront/rails/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](https://github.com/spree/spree/blob/main/core/app/models/spree/page_link.rb) 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):

```ruby app/models/spree/page_sections/promo_banner.rb theme={"theme":"night-owl"}
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):

```ruby app/models/spree/page_sections/footer.rb theme={"theme":"night-owl"}
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:

```ruby app/models/spree/page_blocks/cta_button.rb theme={"theme":"night-owl"}
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

```ruby theme={"theme":"night-owl"}
# 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:

```erb theme={"theme":"night-owl"}
<%# 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:

```erb theme={"theme":"night-owl"}
<% 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

```erb theme={"theme":"night-owl"}
<nav class="footer-links">
  <% section.links.each do |link| %>
    <%= page_builder_link_to link, class: 'footer-link' %>
  <% end %>
</nav>
```

## Admin Form for Links

In your section's admin form, render the link editor:

### Single Link

```erb app/views/spree/admin/page_sections/forms/_promo_banner.html.erb theme={"theme":"night-owl"}
<%= 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:

```ruby theme={"theme":"night-owl"}
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

```ruby theme={"theme":"night-owl"}
# 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:

```ruby theme={"theme":"night-owl"}
link = Spree::PageLink.new(linkable: product)
link.valid?
link.label
# => "Red T-Shirt" (from product.name)
```

The label is derived from (in order):

1. `linkable.title`
2. `linkable.display_name`
3. `linkable.name`

## Related Documentation

* [Sections](/developer/storefront/rails/sections) - Adding links to sections
* [Blocks](/developer/storefront/rails/blocks) - Adding links to blocks
* [Pages](/developer/storefront/rails/pages) - Understanding internal page linking
