Flexible link block and field for Wagtail CMS
Project description
wagtail-link-field
A flexible link block and field for Wagtail CMS that supports internal pages, external URLs, documents, anchors, email, phone, and custom links.
Features
- LinkBlock: A StreamField block for flexible link selection
- LinkField: A model field that stores link data as JSON
- LinkPanel: A panel for editing LinkField in Wagtail admin
- Template tags: For rendering links in templates
- Dynamic field visibility: JavaScript shows/hides relevant fields based on selected link type
- Clean JSON storage: Only relevant fields are stored, irrelevant ones are stripped
Installation
pip install wagtail-link-field
Add to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"wagtail_link_field",
# ...
]
Usage
LinkBlock in StreamField
from wagtail.fields import StreamField
from wagtail import blocks
from wagtail_link_field import LinkBlock
class MyPage(Page):
body = StreamField([
("link", LinkBlock()),
], blank=True, use_json_field=True)
# Restrict to specific link types
body_restricted = StreamField([
("link", LinkBlock(link_types=["internal-link", "external-link"])),
], blank=True, use_json_field=True)
LinkField with LinkPanel
from django.db import models
from wagtail_link_field import LinkField, LinkPanel
class MyPage(Page):
cta = LinkField(blank=True, null=True)
content_panels = Page.content_panels + [
LinkPanel("cta"),
# Or restrict link types:
LinkPanel("cta_restricted", link_types=["internal-link", "external-link"]),
]
Accessing link data in Python
Both LinkBlock values and LinkField values provide the same methods:
# From a LinkField
page = MyPage.objects.first()
link = page.cta # Returns LinkFieldValue instance
# From a LinkBlock in StreamField
for block in page.body:
if block.block_type == 'link':
link = block.value # Returns LinkValue instance
# Both provide the same methods:
url = link.url() # Computed URL (e.g., "/about/", "https://example.com", "mailto:test@example.com")
is_external = link.is_external() # True for external, email, phone, document, custom links
title = link.title() # Page/document title if available, otherwise None
safe_title = link.safe_title() # Formatted title (page title or cleaned URL/email/phone)
action = link.action # Link type: "internal-link", "external-link", etc.
Using in templates
Load the template tags:
{% load wagtail_link_field_tags %}
Option 1: Use render_link tag (recommended)
Automatically renders a complete <a> tag with proper attributes:
{# Basic usage - uses safe_title automatically #}
{% render_link page.cta %}
{# Output: <a href="/about/">About Us</a> #}
{# With CSS class #}
{% render_link page.cta css_class="btn btn-primary" %}
{# With extra attributes #}
{% render_link page.cta extra_attrs='data-action="click"' %}
{# Works with StreamField blocks too #}
{% for block in page.body %}
{% if block.block_type == 'link' %}
{% render_link block.value css_class="content-link" %}
{% endif %}
{% endfor %}
The render_link tag automatically:
- Adds
target="_blank"andrel="noopener noreferrer"for external links - Uses
safe_title()for the link text (page title or formatted URL/email/phone)
Option 2: Use filters for custom markup
{# Get the URL #}
<a href="{{ page.cta|link_url }}">Click here</a>
{# Get page/document title (returns None if not available) #}
<a href="{{ page.cta|link_url }}">
{{ page.cta|link_title|default:"Read more" }}
</a>
{# Get formatted title (always returns something user-friendly) #}
<a href="{{ page.cta|link_url }}">
{{ page.cta|link_safe_title }}
</a>
{# Build custom markup #}
{% if page.cta %}
<a href="{{ page.cta|link_url }}"
class="{% if page.cta.is_external %}external-link{% endif %}">
{{ page.cta|link_safe_title }}
</a>
{% endif %}
Option 3: Access methods directly
{# Access methods on the value object #}
<a href="{{ page.cta.url }}"
{% if page.cta.is_external %}target="_blank" rel="noopener noreferrer"{% endif %}>
{{ page.cta.safe_title }}
</a>
{# Check link type #}
{% if page.cta.action == "internal-link" %}
<a href="{{ page.cta.url }}" class="internal">{{ page.cta.title|default:page.cta.safe_title }}</a>
{% elif page.cta.action == "email-link" %}
<a href="{{ page.cta.url }}" class="email">{{ page.cta.safe_title }}</a>
{% endif %}
When to use each approach:
{% render_link %}: Quick, automatic rendering with sensible defaults- Filters (
|link_url,|link_safe_title): Custom HTML structure but simpler syntax - Direct methods (
.url,.safe_title): Maximum control and conditional logic
Available link types
| Type | Description | Fields |
|---|---|---|
internal-link |
Link to a Wagtail page | internal_link, page_query_string |
document-link |
Link to a Wagtail document | document_link |
external-link |
External URL | external_link |
anchor-link |
Same-page anchor | anchor_link |
email-link |
Email address | url_link (auto-prefixed with mailto:) |
phone-link |
Phone number | url_link (auto-prefixed with tel:) |
custom-link |
Custom URL scheme | url_link |
Restricting link types
Pass link_types to LinkBlock or LinkPanel:
# Only internal and external links
LinkBlock(link_types=["internal-link", "external-link"])
# Only documents
LinkPanel("download", link_types=["document-link"])
Optional links
Set required=False for optional links:
# LinkBlock (default is required=True)
LinkBlock(required=False)
# LinkField (use blank=True, null=True)
cta = LinkField(blank=True, null=True)
Development
Setup
# Clone the repo
git clone https://github.com/infofactory/wagtail-link-field.git
cd wagtail-link-field
# Install with uv
uv sync --extra testing
Running tests
# Run tests
python testmanage.py test
# Run with coverage
python -m coverage run testmanage.py test
python -m coverage report -m
Running linting
ruff check .
ruff format --check .
Running tox (matrix testing)
tox
License
BSD-3-Clause. See LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wagtail_link_field-0.1.4.tar.gz.
File metadata
- Download URL: wagtail_link_field-0.1.4.tar.gz
- Upload date:
- Size: 15.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b48218828bdef9197c5b8f9f7bbc479446f655afcc6cca948d36fdc50e7264c8
|
|
| MD5 |
ba040187c55a6cce3a9444bdb348043c
|
|
| BLAKE2b-256 |
9106d23a34a014856f52b4f4b1894370638f8e90af2c0478e2f4d97f83009182
|
Provenance
The following attestation bundles were made for wagtail_link_field-0.1.4.tar.gz:
Publisher:
python-publish.yml on infofactory/wagtail-link-field
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_link_field-0.1.4.tar.gz -
Subject digest:
b48218828bdef9197c5b8f9f7bbc479446f655afcc6cca948d36fdc50e7264c8 - Sigstore transparency entry: 1517651264
- Sigstore integration time:
-
Permalink:
infofactory/wagtail-link-field@05c85b7f38e413dfa4ebc583e24a254591a320f4 -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/infofactory
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@05c85b7f38e413dfa4ebc583e24a254591a320f4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file wagtail_link_field-0.1.4-py3-none-any.whl.
File metadata
- Download URL: wagtail_link_field-0.1.4-py3-none-any.whl
- Upload date:
- Size: 16.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b26201c0ba8c555a2362eb6929a19dde652b3b81aff0a4b2b2210cd3c6fcf054
|
|
| MD5 |
2d09104e749cade220e864b1f86729fb
|
|
| BLAKE2b-256 |
edbeaf41514ebecef73a574862128cf742dbca72e9aedb5ee26b1aab69ca0f90
|
Provenance
The following attestation bundles were made for wagtail_link_field-0.1.4-py3-none-any.whl:
Publisher:
python-publish.yml on infofactory/wagtail-link-field
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wagtail_link_field-0.1.4-py3-none-any.whl -
Subject digest:
b26201c0ba8c555a2362eb6929a19dde652b3b81aff0a4b2b2210cd3c6fcf054 - Sigstore transparency entry: 1517651329
- Sigstore integration time:
-
Permalink:
infofactory/wagtail-link-field@05c85b7f38e413dfa4ebc583e24a254591a320f4 -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/infofactory
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@05c85b7f38e413dfa4ebc583e24a254591a320f4 -
Trigger Event:
release
-
Statement type: