Skip to main content

pyhead 🐍🤯

PyPI version License

The Python HTML <head> filler.

pip install pyhead              # core only
pip install "pyhead[flask]"     # core + Flask helpers
pip install "pyhead[django]"    # core + Django helpers

Pyhead is framework-agnostic at its core. Flask and Django are only needed if you want the deferred url_for / reverse / static helpers or the Django template tags.

What is Pyhead?

Pyhead is a Python package that helps you generate the <head> tag for your HTML pages.

Flask example

from flask import Flask, render_template

from pyhead import Head
from pyhead import elements as e


def create_app():
    app = Flask(__name__)

    @app.route("/")
    def index():
        head = Head([
            e.Page(
                title="Hello World",
                description="This is a test",
                keywords="test, hello, world",
                subject="Hello World",
                rating="General",
            ),
            e.Base("http://127.0.0.1:5000"),
            e.Robots("index, follow"),
            e.ContentSecurityPolicy(),
            e.ReferrerPolicy("no-referrer"),
            e.Google(
                googlebot="index, follow",
                no_sitelinks_search_box=True,
                no_translate=True,
            ),
            e.Verification(
                google="1234567890",
                yandex="1234567890",
                bing="1234567890",
            ),
            e.GeoPosition(
                icbm="55.86013028402754, -4.252019430273945",
                geo_position="55.86013028402754;-4.252019430273945",
                geo_region="en_GB",
                geo_placename="Duke of Wellington",
            ),
            e.SocialMediaCard(
                site_account="@example",
                creator_account="@example",
                title="Example",
                description="Example",
                image="https://example.com/image.png",
                image_alt="Example",
                site_name="Example",
                url="https://example.com",
                locale="en_US",
            ),
            e.Favicon(
                ico_icon_href="/static/favicons/favicon.ico",
                png_icon_16_href="/static/favicons/favicon-16x16.png",
                png_icon_32_href="/static/favicons/favicon-32x32.png",
                png_icon_64_href="/static/favicons/favicon-64x64.png",
                png_icon_96_href="/static/favicons/favicon-96x96.png",
                png_icon_180_href="/static/favicons/favicon-180x180.png",
                png_icon_196_href="/static/favicons/favicon-196x196.png",
                png_apple_touch_icon_57_href="/static/favicons/apple-touch-icon-57x57.png",
                png_apple_touch_icon_60_href="/static/favicons/apple-touch-icon-60x60.png",
                png_apple_touch_icon_72_href="/static/favicons/apple-touch-icon-72x72.png",
                png_apple_touch_icon_76_href="/static/favicons/apple-touch-icon-76x76.png",
                png_apple_touch_icon_114_href="/static/favicons/apple-touch-icon-114x114.png",
                png_apple_touch_icon_120_href="/static/favicons/apple-touch-icon-120x120.png",
                png_apple_touch_icon_144_href="/static/favicons/apple-touch-icon-144x144.png",
                png_apple_touch_icon_152_href="/static/favicons/apple-touch-icon-152x152.png",
                png_apple_touch_icon_167_href="/static/favicons/apple-touch-icon-167x167.png",
                png_apple_touch_icon_180_href="/static/favicons/apple-touch-icon-180x180.png",
                png_mstile_70_href="/static/favicons/mstile-70x70.png",
                png_mstile_270_href="/static/favicons/mstile-270x270.png",
                png_mstile_310x150_href="/static/favicons/mstile-310x150.png",
                png_mstile_310_href="/static/favicons/mstile-310x150.png",
            ),
            e.Link(rel="canonical", href="https://example.com"),
            e.Link(rel="canonical", href="https://example.co.uk"),
            e.Script(type_="module", src="/static/example.js"),
            e.Stylesheet("/static/main.css"),
        ])

        return render_template("index.html", head=head)

    return app

index.html — full render (pyhead writes the <head> wrapper itself):

<!DOCTYPE html>
<html lang="en">
{{ head.compile() }}
<body>
{% block content %}{% endblock %}
</body>
</html>

or — split render, where you write the <head> wrapper and let pyhead fill it:

<!DOCTYPE html>
<html lang="en">
<head>
    {{ head.compile(render_head_tag=False) }}
</head>

<body>
{% block content %}{% endblock %}
</body>
</html>

If you also want the <title> separate from the rest of the head, pass both flags and render the title element yourself (it's available at head.title()):

<head>
    <title>{{ head.title() }}</title>
    {{ head.compile(render_head_tag=False, render_title_tag=False) }}
</head>

Results in:

<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="This is a test">
<meta name="keywords" content="test,  hello,  world">
<meta name="subject" content="Hello World">
<meta name="rating" content="General">
<base href="http://127.0.0.1:5000">
<meta name="robots" content="index, follow">
<meta http-equiv="Content-Security-Policy" content="default-src &#39;self&#39;">
<meta name="referrer" content="no-referrer">
<meta name="googlebot" content="index, follow">
<meta name="google" content="nositelinkssearchbox">
<meta name="google" content="notranslate">
<meta name="google-site-verification" content="1234567890">
<meta name="yandex-verification" content="1234567890">
<meta name="msvalidate.01" content="1234567890">
<meta name="ICBM" content="55.86013028402754, -4.252019430273945">
<meta name="geo.position" content="55.86013028402754;-4.252019430273945">
<meta name="geo.region" content="en_GB">
<meta name="geo.placename" content="Duke of Wellington">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@example">
<meta name="twitter:creator" content="@example">
<meta name="twitter:title" content="Example">
<meta name="twitter:description" content="Example">
<meta name="twitter:image" content="https://example.com/image.png">
<meta name="twitter:image:alt" content="Example">
<meta name="twitter:url" content="https://example.com">
<meta property="og:type" content="website">
<meta property="og:locale" content="en_US">
<meta property="og:site_name" content="Example">
<meta property="og:title" content="Example">
<meta property="og:description" content="Example">
<meta property="og:image" content="https://example.com/image.png">
<meta property="og:image:alt" content="Example">
<meta property="og:url" content="https://example.com">
<link rel="icon" href="/static/favicons/favicon.ico" sizes="16x16 32x32" type="image/x-icon">
<link rel="icon" href="/static/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="icon" href="/static/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/static/favicons/favicon-64x64.png" sizes="64x64" type="image/png">
<link rel="icon" href="/static/favicons/favicon-96x96.png" sizes="96x96" type="image/png">
<link rel="icon" href="/static/favicons/favicon-180x180.png" sizes="180x180" type="image/png">
<link rel="icon" href="/static/favicons/favicon-196x196.png" sizes="196x196" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-57x57.png" sizes="57x57" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-60x60.png" sizes="60x60" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-72x72.png" sizes="72x72" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-76x76.png" sizes="76x76" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-114x114.png" sizes="114x114" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-120x120.png" sizes="120x120" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-144x144.png" sizes="144x144" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-152x152.png" sizes="152x152" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-167x167.png" sizes="167x167" type="image/png">
<link rel="apple-touch-icon" href="/static/favicons/apple-touch-icon-180x180.png" sizes="180x180" type="image/png">
<link rel="msapplication-square70x70logo" href="/static/favicons/mstile-70x70.png" type="image/png">
<link rel="msapplication-square270x270logo" href="/static/favicons/mstile-270x270.png" type="image/png">
<link rel="msapplication-wide310x150logo" href="/static/favicons/mstile-310x150.png" type="image/png">
<link rel="msapplication-wide310x150logo" href="/static/favicons/mstile-310x150.png" type="image/png">
<link rel="canonical" href="https://example.com">
<link rel="canonical" href="https://example.co.uk">
<script src="/static/example.js" type="module"></script>
<link rel="stylesheet" href="/static/main.css">
</head>
<body>
<h1>Flask App</h1>
<p>Right-Click view source</p>
</body>
</html>

Django example

Register pyhead.django in INSTALLED_APPS so its template tag library is discoverable:

# settings.py
INSTALLED_APPS = [
    # ...
    "django.contrib.staticfiles",
    "pyhead.django",
]

Build a Head in your view, pass it to the template as context:

# views.py
from django.shortcuts import render

from pyhead import Head
from pyhead import elements as e
from pyhead.django import DjangoStatic, DjangoUrlFor


def index(request):
    head = Head([
        e.Page(
            title="Hello World",
            description="This is a test",
            keywords="test, hello, world",
        ),
        e.Link(rel="canonical", href=DjangoUrlFor("index")),
        e.Stylesheet(DjangoStatic("main.css")),
        e.Script(type_="module", src=DjangoStatic("example.js")),
    ])
    return render(request, "index.html", {"head": head})

index.html — render the full head via the template tag:

{% load pyhead %}
<!DOCTYPE html>
<html lang="en">
{% head head %}
<body>
    <h1>Django App</h1>
</body>
</html>

Or skip the tag library entirely — Head implements __html__, so Django's auto-escape will render it as safe HTML:

<!DOCTYPE html>
<html lang="en">
{{ head }}
<body>...</body>
</html>

Split render — author your own <head> wrapper and place the <title> first. {% head_title %} returns just the (HTML-escaped) title text, so you write the <title> tag yourself:

{% load pyhead %}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{% head_title head %}</title>
    {% head head render_head_tag=False render_title_tag=False %}
</head>
<body>...</body>
</html>

Usage Examples

Route by Route

...
from pyhead import Head
from pyhead import elements as e
...

@app.get("/")
def index():
    head = Head([
        e.Page(
            title="Hello World",
            description="This is a test",
            keywords="test, hello, world",
            subject="Hello World",
            rating="General",
        )
    ])
    return render_template("index.html", head=head)

Copy and Extend

app/page_head.py

...
from pyhead import Head
from pyhead import elements as e
...

head = Head([
    e.Page(
        title="Hello World",
        description="This is a test",
        keywords="test, hello, world",
        subject="Hello World",
        rating="General",
    )
])

app/__init__.py

...
from app.page_head import head
from pyhead import elements as e
...


@app.get("/my-cool-page")
def my_cool_page():
    my_cool_page_head = head.copy().extend([
    e.Page(
      title="This is my cool page",
      description="This is a test",
      keywords="test, hello, world",
      subject="Hello World",
      rating="General",
    ),
    e.Robots(
      "index, follow"
    )
    ])
    return render_template("my_cool_page.html", head=my_cool_page_head)

Class Defined

app/page_head.py

...
from pyhead import HeadClass
from pyhead import elements as e
...

class MyHead(HeadClass):
    elements = [
        e.Page(
            title="Hello World",
            description="This is a test",
            keywords="test, hello, world",
            subject="Hello World",
            rating="General",
        )
    ]

app/__init__.py

...
from app.page_head import MyHead
from pyhead import elements as e
...


@app.get("/my-cool-page")
def my_cool_page():
    return render_template("my_cool_page.html", head=MyHead())

Flask Specific

url_for -> FlaskUrlFor

FlaskUrlFor is a class designed to lazily load the url_for function found in Flask. This is done to ensure that when compiled url_for will be invoked within context.

It has the same arguments as url_for but it will only call the url_for function when the Head is compiled.

Here's an example of how to use it:

...
from pyhead import Head
from pyhead import elements as e
from pyhead.flask import FlaskUrlFor
...

@app.get("/my-cool-page")
def my_cool_page():
    head = Head(
        [
            ...,
            e.Stylesheet(FlaskUrlFor("static", filename="main.css")),
            ...,
        ]
    )
    return render_template("my_cool_page.html", head=head)

Django Specific

Requires pip install "pyhead[django]" and "pyhead.django" in INSTALLED_APPS.

reverse -> DjangoUrlFor

DjangoUrlFor defers Django's reverse() until the Head is compiled, so it runs with the current URL configuration at request time. It accepts the same positional / keyword arguments as reverse.

from pyhead import Head, elements as e
from pyhead.django import DjangoUrlFor

head = Head([
    e.Link(rel="canonical", href=DjangoUrlFor("home")),
    e.Link(rel="alternate", href=DjangoUrlFor("article", pk=42)),
])

static -> DjangoStatic

DjangoStatic defers django.templatetags.static.static() until compile time, so STATIC_URL and any staticfiles storage (e.g. ManifestStaticFilesStorage) are honoured.

from pyhead import Head, elements as e
from pyhead.django import DjangoStatic

head = Head([
    e.Stylesheet(DjangoStatic("main.css")),
    e.Script(type_="module", src=DjangoStatic("example.js")),
    e.Favicon(ico_icon_href=DjangoStatic("favicons/favicon.ico")),
])

Template tags: {% head %} and {% head_title %}

pyhead.django ships a template tag library. Load it with {% load pyhead %}.

Tag What it renders
{% head head %} The full <head>...</head> block, including <title>.
{% head head render_head_tag=False %} The inner elements only — you author the <head> wrapper.
{% head head render_head_tag=False render_title_tag=False %} Inner elements minus <title> — pair with <title>{% head_title head %}</title>.
{% head_title head %} The title text only (HTML-escaped); wrap it in your own <title> tag.

Head also implements __html__, so {{ head }} works without |safe and without loading the tag library — handy for simple pages.

CLI Commands

Generating favicons

You can generate favicons from a source image using the cli command pyhead favicons -s favicon-gen-test.png

This uses the python package favicons to generate the favicons.

You need to install the favicons package to use this command.

pip install favicons

All paths in the cli command are relative to the current working directory.

Only the following source formats are supported:

png, jpg, jpeg, gif, svg, tiff

-s, --source This will look for the image file to use

-o, --output This will be the output directory for the favicons

-hp, --href-prefix This will prefix the href tag in the output html

The following command:

pyhead favicons -s favicon-gen-test.png -o favicons -hp https://example.com

Will create a folder called favicons with the following files:

apple-touch-icon-57x57.png
apple-touch-icon-60x60.png
apple-touch-icon-72x72.png
apple-touch-icon-76x76.png
apple-touch-icon-114x114.png
apple-touch-icon-120x120.png
apple-touch-icon-144x144.png
apple-touch-icon-152x152.png
apple-touch-icon-167x167.png
apple-touch-icon-180x180.png
favicon-16x16.png
favicon-32x32.png
favicon-64x64.png
favicon-96x96.png
favicon-180x180.png
favicon-196x196.png
favicon-delete_me_after_use.html
favicon-delete_me_after_use.py
mstile-70x70.png
mstile-270x270.png
mstile-310x150.png
mstile-310x310.png

The favicon-delete_me_after_use.html file will contain the following:

<link rel="icon" href="https://example.com/favicon.ico" sizes="16x16 32x32">
<link rel="icon" href="https://example.com/favicon-16x16.png" sizes="16x16">
<link rel="icon" href="https://example.com/favicon-32x32.png" sizes="32x32">
<link rel="icon" href="https://example.com/favicon-64x64.png" sizes="64x64">
<link rel="icon" href="https://example.com/favicon-96x96.png" sizes="96x96">
<link rel="icon" href="https://example.com/favicon-180x180.png" sizes="180x180">
<link rel="icon" href="https://example.com/favicon-196x196.png" sizes="196x196">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-57x57.png" sizes="57x57">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-60x60.png" sizes="60x60">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-72x72.png" sizes="72x72">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-76x76.png" sizes="76x76">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-114x114.png" sizes="114x114">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-120x120.png" sizes="120x120">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-144x144.png" sizes="144x144">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-152x152.png" sizes="152x152">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-167x167.png" sizes="167x167">
<link rel="apple-touch-icon" href="https://example.com/apple-touch-icon-180x180.png" sizes="180x180">
<link rel="msapplication-square70x70logo" href="https://example.com/mstile-70x70.png">
<link rel="msapplication-square270x270logo" href="https://example.com/mstile-270x270.png">
<link rel="msapplication-wide310x150logo" href="https://example.com/mstile-310x150.png">
<link rel="msapplication-wide310x150logo" href="https://example.com/mstile-310x150.png">

The favicon-delete_me_after_use.py file will contain the following:

from pyhead.elements import Favicon

Favicon(
    ico_icon_href="https://example.com/favicon.ico",
    png_icon_16_href="https://example.com/favicon-16x16.png",
    png_icon_32_href="https://example.com/favicon-32x32.png",
    png_icon_64_href="https://example.com/favicon-64x64.png",
    png_icon_96_href="https://example.com/favicon-96x96.png",
    png_icon_180_href="https://example.com/favicon-180x180.png",
    png_icon_196_href="https://example.com/favicon-196x196.png",
    png_apple_touch_icon_57_href="https://example.com/apple-touch-icon-57x57.png",
    png_apple_touch_icon_60_href="https://example.com/apple-touch-icon-60x60.png",
    png_apple_touch_icon_72_href="https://example.com/apple-touch-icon-72x72.png",
    png_apple_touch_icon_76_href="https://example.com/apple-touch-icon-76x76.png",
    png_apple_touch_icon_114_href="https://example.com/apple-touch-icon-114x114.png",
    png_apple_touch_icon_120_href="https://example.com/apple-touch-icon-120x120.png",
    png_apple_touch_icon_144_href="https://example.com/apple-touch-icon-144x144.png",
    png_apple_touch_icon_152_href="https://example.com/apple-touch-icon-152x152.png",
    png_apple_touch_icon_167_href="https://example.com/apple-touch-icon-167x167.png",
    png_apple_touch_icon_180_href="https://example.com/apple-touch-icon-180x180.png",
    png_mstile_70_href="https://example.com/mstile-70x70.png",
    png_mstile_270_href="https://example.com/mstile-270x270.png",
    png_mstile_310x150_href="https://example.com/mstile-310x150.png",
    png_mstile_310_href="https://example.com/mstile-310x150.png",
)

Remember to delete the favicon-delete_me_after_use.html and favicon-delete_me_after_use.py files after use.

Metadata

Release files for pyhead 6.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyhead 6.1.1
File Size Uploaded
pyhead-6.1.1.tar.gz 24.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyhead 6.1.1
File Interpreter ABI Platform
pyhead-6.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 56.9 kB

Release files / pyhead-6.1.1.tar.gz

Download URL pyhead-6.1.1.tar.gz
Size 24.8 kB
Tags Source
SHA-256 checksum
How to use checksums
779aec598274524934fa59906625776b5de7a9c26617301bed34c93575dcf007
BLAKE2b-256 checksum
How to use checksums
2a65d0c0ed5e088bc739b1ef025c647b76510a7a612e0130be8a59456fa40ec3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release files / pyhead-6.1.1-py3-none-any.whl

Download URL pyhead-6.1.1-py3-none-any.whl
Size 32.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2829930cf4b5e032583b7d0dc51b52de7c731db8ce71292cbcba6e831a5aabbc
BLAKE2b-256 checksum
How to use checksums
768fbb8b4adbd0f8bd8fd58f53d68736d62f6e33fef6835b2492b44dae3853a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

6.1.1 This release

2 release files

6.1.0

2 release files

6.0.1

2 release files

6.0.0

2 release files

5.3.1

2 release files

5.3.0

2 release files

5.2.1

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.2.0

2 release files

4.1.0

2 release files

4.0.1

2 release files

4.0.0

2 release files

3.2

2 release files

3.1

2 release files

3.0

2 release files

2.1

2 release files

2.0

2 release files

1.9

2 release files

1.8

2 release files

1.7

2 release files

1.6

2 release files

1.5

2 release files

1.4

2 release files

1.3

2 release files

1.2

2 release files

1.1

2 release files

1.0

2 release files

0.8

2 release files

0.7

2 release files

0.6

2 release files

0.5

2 release files

0.4

2 release files

0.3

2 release files

0.2

2 release files

0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page