テンプレートとモジュールを書くための言語「HubL」を徹底解説。基本構文から組み込み変数・フィルター・よく使う関数まで、実例コードとともに体系的に整理します。
HubL(ハブエル)は、HubSpotが独自に設計したテンプレートエンジンです。 PythonのJinja2をベースに、HubSpot専用の変数・フィルター・タグが追加されています。 HTMLファイルの中に HubL を埋め込んで書き、サーバーサイドで処理されて最終的にHTMLとして出力されます。
HubL はブラウザ(クライアントサイド)では動作しません。 ページがリクエストされた時点でHubSpotのサーバーが処理し、HTMLに変換してから配信します。 そのため、ユーザー操作に反応するインタラクティブな処理(クリック後の表示切り替えなど)はJavaScriptが担当します。 HubLとJavaScriptの使い分けは Section 2-9 で詳しく解説します。
HubLには用途の異なる3種類の記法があります。これがHubLの基本単位です。
{# ====== ① 出力タグ {{ }} ====== #} {# 変数の値をHTMLに出力する #} <h1>{{ content.name }}</h1> <p>{{ content.meta_description }}</p> {# フィルターを使って加工して出力 #} <p>{{ content.publish_date|datetimeformat("%Y年%m月%d日") }}</p> {# ====== ② ステートメントタグ {% %} ====== #} {# 変数に値をセット(出力なし) #} {% set site_name = "株式会社サンプル" %} {# 条件分岐(出力なし) #} {% if content.featured_image %} <img src="{{ content.featured_image }}" alt="{{ content.featured_image_alt_text }}"> {% endif %} {# ====== ③ コメント {# #} ====== #} {# このコメントはHTMLソースにも表示されない #} {# TODO: モバイル対応を追加する #}
{% set %} タグで変数を定義します。一度定義した変数はそのテンプレート内で再利用できます。
{# 文字列 #} {% set company_name = "株式会社サンプル" %} {# 数値 #} {% set max_posts = 6 %} {# 真偽値 #} {% set is_published = true %} {# リスト(配列) #} {% set nav_items = ["ホーム", "サービス", "会社概要", "お問い合わせ"] %} {# 辞書(dict)— キーと値のペア #} {% set form_ids = { "contact" : "xxxx-xxxx-contact", "seminar" : "xxxx-xxxx-seminar", "download" : "xxxx-xxxx-download" } %} {# 辞書からの値取得 #} {{ form_ids["seminar"] }} {{ form_ids.contact }} {# ドット記法でもアクセス可 #}
HubLではループ内で外側のスコープの変数を直接書き換えることができません。 namespace を使うことで、ループ内から外側の変数を更新できます。 これは実務でよく使うパターンです。
{# 通常の set はループ内で外側の変数を更新できない(NG) #} {% set content_type = "blog" %} {% for tag in content.tag_list %} {% set content_type = tag.slug %} {# ← これはループ内でしか有効にならない! #} {% endfor %} {# ✅ namespace を使った正しいパターン #} {% set ns = namespace(content_type="blog") %} {% for tag in content.tag_list %} {% if tag.slug starts_with "type:" %} {% set ns.content_type = tag.slug|replace("type:", "") %} {% endif %} {% endfor %} {# ループ後に正しい値が取れる #} <div class="post-type-{{ ns.content_type }}">...</div>
HubLのスコープはJavaScriptとは異なります。
{% set %} で定義した変数は、{% for %} ブロック内では独自のスコープを持つため、
ループ内での変更がループ外に反映されません。
ループをまたいで変数を更新したい場合は 必ず namespace を使ってください。
第6章でタグ判定パターンとして実際に使います。
{# 基本的な if #} {% if content.featured_image %} <img src="{{ content.featured_image }}" alt="{{ content.featured_image_alt_text }}"> {% endif %} {# if / elif / else #} {% if content_type == "seminar" %} <span class="badge badge-seminar">セミナー</span> {% elif content_type == "news" %} <span class="badge badge-news">お知らせ</span> {% elif content_type == "case" %} <span class="badge badge-case">事例</span> {% else %} <span class="badge badge-blog">ブログ</span> {% endif %} {# not / and / or を使った複合条件 #} {% if content.featured_image and content.featured_image_alt_text %} {# 画像とalt両方ある時だけ表示 #} {% endif %} {% if not content.archived %} {# アーカイブされていない記事だけ表示 #} {% endif %}
| 演算子 / テスト | 意味 | 例 |
|---|---|---|
| == | 等しい | {% if tag.slug == "news" %} |
| != | 等しくない | {% if count != 0 %} |
| > / < / >= / <= | 数値比較 | {% if loop.index <= 3 %} |
| and / or / not | 論理演算 | {% if a and not b %} |
| starts_with | 前方一致 | {% if tag.slug starts_with "type:" %} |
| ends_with | 後方一致 | {% if url ends_with ".pdf" %} |
| in | リスト・文字列内に含まれるか | {% if "news" in tag.slug %} |
| is defined | 変数が定義されているか | {% if my_var is defined %} |
| is not defined | 変数が未定義か | {% if my_var is not defined %} |
| is divisibleby(n) | n で割り切れるか | {% if loop.index is divisibleby(3) %} |
{# リストのループ #} {% set nav_items = [ {"label": "ホーム", "url": "/"}, {"label": "サービス", "url": "/service"}, {"label": "会社概要", "url": "/about"} ] %} <ul> {% for item in nav_items %} <li><a href="{{ item.url }}">{{ item.label }}</a></li> {% endfor %} </ul> {# loop変数(ループ内で使える特殊変数) #} {% for post in posts %} {% if loop.first %}<div class="first-item">{% endif %} <article class="post-item post-{{ loop.index }}"> <h2>{{ post.name }}</h2> </article> {% if loop.last %}</div>{% endif %} {% endfor %} {# range() で数値ループ #} {% for i in range(1, 6) %} <span>{{ i }}</span> {# 1, 2, 3, 4, 5 #} {% endfor %} {# else 節:リストが空の場合 #} {% for post in posts %} <article>...</article> {% else %} <p>記事がありません</p> {% endfor %}
| 変数 | 内容 | 例 |
|---|---|---|
| loop.index | 現在のインデックス(1始まり) | 1, 2, 3 … |
| loop.index0 | 現在のインデックス(0始まり) | 0, 1, 2 … |
| loop.revindex | 末尾からのインデックス(1始まり) | … 3, 2, 1 |
| loop.first | 最初の要素か(true/false) | 最初のみ特別処理に |
| loop.last | 最後の要素か(true/false) | 最後のみ特別処理に |
| loop.length | リストの総件数 | 10 |
| loop.depth | ネストの深さ(1始まり) | ネストループで使用 |
{# unless は「〜でない場合」。not を使った if と同義 #} {% unless content.archived %} <div class="post-content">...</div> {% endunless %} {# 上記は以下と同じ #} {% if not content.archived %} <div class="post-content">...</div> {% endif %}
マクロは、繰り返し使うHTMLのパターンを関数として定義する仕組みです。 引数を渡せるため、「見た目は同じだが内容が違う」部品を効率よく管理できます。
{# ====== マクロの定義 ====== #} {# カードコンポーネントのマクロ #} {% macro render_card(title, description, url, badge="", image="") %} <div class="card"> {% if image %} <img src="{{ image }}" alt="{{ title }}"> {% endif %} {% if badge %} <span class="badge">{{ badge }}</span> {% endif %} <h3><a href="{{ url }}">{{ title }}</a></h3> <p>{{ description|truncate(80) }}</p> </div> {% endmacro %} {# ====== マクロの呼び出し ====== #} {# 基本的な呼び出し #} {{ render_card( title="HubSpotで成果を出す方法", description="HubSpotを使ったマーケティング施策の具体的な手法を解説します。", url="/blog/hubspot-tips" ) }} {# バッジ・画像ありの呼び出し #} {{ render_card( title="【セミナー】HubSpot活用術", description="2024年最新のHubSpot活用事例を紹介するオンラインセミナーです。", url="/blog/seminar-2024", badge="セミナー", image="https://example.com/seminar.jpg" ) }} {# ブログ一覧でループと組み合わせる #} {% set posts = blog_recent_posts("default", 6) %} {% for post in posts %} {{ render_card( title=post.name, description=post.meta_description, url=post.absolute_url, image=post.featured_image ) }} {% endfor %}
マクロが増えてきたら、専用の部品ファイルにまとめて import または from … import で読み込めます。
{% macro render_card(title, description, url, badge="") %} ... {% endmacro %} {% macro render_badge(label, type="default") %} <span class="badge badge--{{ type }}">{{ label }}</span> {% endmacro %}
{# ファイル全体をインポートして使う #} {% from "../partials/macros.html" import render_card, render_badge %} {% set posts = blog_recent_posts("default", 9) %} {% for post in posts %} {{ render_card(post.name, post.meta_description, post.absolute_url) }} {% endfor %}
HubSpot CMSのテンプレートでは、特定のコンテキストで使える組み込み変数が多数用意されています。スコープ(使える場所)に注意してください。
現在表示しているページ・記事・メールなどの情報を持つオブジェクトです。テンプレート全体で使える最も頻用する変数です。
| 変数 | 内容 | スコープ |
|---|---|---|
| content.name | ページ・記事のタイトル | 全テンプレート |
| content.absolute_url | ページの絶対URL | 全テンプレート |
| content.meta_description | メタディスクリプション | 全テンプレート |
| content.featured_image | アイキャッチ画像のURL | 全テンプレート |
| content.featured_image_alt_text | アイキャッチのalt属性 | 全テンプレート |
| content.publish_date | 公開日時(UNIXタイムスタンプ) | ブログ |
| content.updated | 最終更新日時 | ブログ |
| content.tag_list | タグの配列(name / slug / tag_url) | ブログ |
| content.blog_author | 著者情報オブジェクト | ブログ |
| content.blog_author.display_name | 著者の表示名 | ブログ |
| content.blog_author.avatar | 著者のアバター画像URL | ブログ |
| content.tag | タグアーカイブページの対象タグ | タグアーカイブ |
| content.tag.name | タグ名 | タグアーカイブ |
| content.tag.slug | タグスラッグ(URLの一部) | タグアーカイブ |
| content.portal_id | HubSpotのポータルID | 全テンプレート |
| content.language | コンテンツの言語コード(ja / en 等) | 全テンプレート |
| content.archived | アーカイブ済みかどうか(true/false) | ブログ |
| 変数 | 内容 |
|---|---|
| request.path | 現在のURLパス(例:/blog/post-slug) |
| request.query_string | クエリストリング(例:?page=2) |
| request.domain | ドメイン(例:www.example.com) |
| request.full_url | フルURL |
| request.is_hubspot_user | HubSpotユーザーのプレビューかどうか |
| 変数 | 内容 | スコープ |
|---|---|---|
| hub_id | ポータルID(数値)。フォームのJS埋め込みでよく使う | 全テンプレート |
| site_settings.company_name | サイトの会社名(設定から) | 全テンプレート |
| site_settings.logo_url | サイトのロゴ画像URL | 全テンプレート |
| contents | ブログ一覧の記事配列(listing / tag テンプレートで使用) | ブログ一覧 |
| contents.total_count | 記事の総件数(ページネーションで使用) | ブログ一覧 |
| current_page_num | 現在のページ番号(1始まり) | ブログ一覧 |
| last_page_num | 最終ページ番号 | ブログ一覧 |
| next_page_num | 次のページ番号 | ブログ一覧 |
| previous_page_num | 前のページ番号 | ブログ一覧 |
特定の変数が使えるか・値があるかを確認したい時は、{{ variable }} を一時的にテンプレートに書いて
ブラウザで確認する方法が手軽です。
また {% if variable is defined %} で変数の存在チェック、
{{ variable|default("デフォルト値") }} でデフォルト値を設定できます。
フィルターは変数の値を加工・変換するための仕組みです。
{{ 変数|フィルター名 }} の形で使い、|(パイプ)でつないで複数のフィルターをチェーンできます。
{# 単一フィルター #} {{ content.name|upper }} {# 引数ありフィルター #} {{ content.meta_description|truncate(100) }} {# チェーン(複数フィルターを連結) #} {{ content.name|truncate(30)|upper }}
出力:HubSpotで成...
英語コンテンツのスタイル統一に使用
タグスラッグからprefixを除去する時に頻用
フォーム入力値の処理などで使用
記事本文からテキスト抜粋を作る時に使用
ユーザー入力値を安全に表示する時に使用
URLパラメータ生成時に使用
記事の読了時間の計算などに使用
ブログ記事の公開日表示に必須
セミナー開催日時など時刻が必要な場合
差分計算などで使用
価格・評価点などの表示に使用
タグ数・記事数などのカウントに使用
代表タグを1つだけ取得する時などに使用
タグ名一覧の文字列化などに使用
日付順・名前順など任意の属性でソート
有効なアイテムのみ絞り込む時などに使用
タグ名の一覧を文字列で取得する時などに使用
記事の抜粋テキスト生成:
{{ content.post_body|striptags|truncate(120, end="…") }}
タグ名をカンマ区切りで表示:
{{ content.tag_list|map(attribute="name")|join(" / ") }}
公開日を日本語形式で表示:
{{ content.publish_date|datetimeformat("%Y年%m月%d日") }}
ブログ機能を実装する上で必須の HubL 関数を整理します。これらはブログ関連テンプレートで最も頻繁に使う関数群です。
| 関数 | 引数 | 説明 |
|---|---|---|
| blog_recent_posts(blog, limit) | blog: ブログID or "default" limit: 取得件数 |
最新記事をN件取得する。最もよく使う関数。 |
| blog_posts(blog, limit, offset, tag) | blog / limit / offset(開始位置)/ tag(タグスラッグ) | 条件指定で記事を取得。タグフィルタリングが可能。 |
| blog_popular_posts(blog, limit) | blog / limit | 人気記事(閲覧数順)をN件取得する。 |
| blog_total_post_count(blog) | blog | 記事の総件数を返す。ページネーション計算で使用。 |
{# 最新記事 6件取得 #} {% set recent_posts = blog_recent_posts("default", 6) %} {# タグ指定で記事取得(セミナー記事のみ) #} {% set seminar_posts = blog_posts("default", 3, 0, "seminar") %} {# offset を使ったページネーション(2ページ目:11〜20件目)#} {% set page2_posts = blog_posts("default", 10, 10) %} {# 人気記事 5件 #} {% set popular = blog_popular_posts("default", 5) %} {# 記事の総件数 #} {% set total = blog_total_post_count("default") %} <p>全{{ total }}件の記事</p>
| 関数 | 引数 | 説明 |
|---|---|---|
| blog_tag_url(blog, tag_slug) | blog / tag_slug | 指定タグのアーカイブページURLを返す |
| blog_page_link(page_num) | page_num: ページ番号 | 指定ページ番号のページングURLを返す |
| blog_author_url(blog, author_slug) | blog / author_slug | 著者アーカイブページのURLを返す |
{# タグアーカイブURLを生成 #} <a href="{{ blog_tag_url("default", "seminar") }}">セミナー一覧へ</a> {# 出力例: /blog/tag/seminar #} {# ナビゲーションでタグ一覧リンクを動的生成 #} {% set tag_nav = [ {"label": "ブログ", "slug": "blog"}, {"label": "セミナー", "slug": "seminar"}, {"label": "お知らせ", "slug": "news"}, {"label": "導入事例", "slug": "case"} ] %} <nav class="blog-nav"> <a href="/blog">すべて</a> {% for tag in tag_nav %} <a href="{{ blog_tag_url("default", tag.slug) }}" {% if content.tag.slug == tag.slug %} class="is-active" aria-current="page" {% endif %}> {{ tag.label }} </a> {% endfor %} </nav> {# ページネーションURLを生成 #} <a href="{{ blog_page_link(current_page_num + 1) }}">次へ</a>
blog_recent_posts() などで取得した記事オブジェクトで使えるプロパティ一覧です。
| プロパティ | 内容 |
|---|---|
| post.name | 記事タイトル |
| post.absolute_url | 記事の絶対URL |
| post.featured_image | アイキャッチ画像URL |
| post.featured_image_alt_text | アイキャッチのalt属性 |
| post.meta_description | メタディスクリプション(抜粋として使う) |
| post.publish_date | 公開日時(UNIXタイムスタンプ) |
| post.tag_list | タグの配列 |
| post.blog_author.display_name | 著者表示名 |
| post.blog_author.avatar | 著者アバター画像URL |
| post.post_body | 記事本文HTML(striptags と組み合わせて使用) |
| post.read_time | 推定読了時間(分) |
HubLとJavaScriptはどちらもHubSpotテンプレートで使いますが、役割が明確に異なります。両者の違いを正しく理解することで、適切な実装を選択できます。
| 観点 | HubL(サーバーサイド) | JavaScript(クライアントサイド) |
|---|---|---|
| 実行タイミング | ページリクエスト時(サーバー上) | ブラウザでのページ読み込み後 |
| ユーザー操作への反応 | ❌ 不可(静的な出力のみ) | ✅ 可能(クリック・スクロール等) |
| HubSpotデータへのアクセス | ✅ 直接アクセス可 | △ APIを通じてのみ |
| SEO(コンテンツの可視性) | ✅ 検索エンジンが確実に認識 | △ クローラーによっては認識されない |
| 動的なUI変化 | ❌ 不可 | ✅ モーダル・タブ・アコーディオン等 |
| 外部APIリアルタイム連携 | △ 制限あり(Enterprise除く) | ✅ fetch / axios で自由に連携 |
{# HubL でサーバーサイドのデータをJS変数として渡す #} {# → JSからHubSpotのデータを使いたい場合に使うパターン #} <script> // HubLの変数をJSに渡す const hubspotConfig = { portalId : {{ hub_id }}, pageId : {{ content.id }}, pageTitle : "{{ content.name|escape }}", contentType: "{{ ns.content_type }}", formIds : { seminar : "{{ form_ids.seminar }}", download : "{{ form_ids.download }}" } }; // この後はJSとして処理できる console.log(hubspotConfig.pageTitle); // コンテンツ種別によってフォームを動的に切り替え hbspt.forms.create({ portalId : hubspotConfig.portalId, formId : hubspotConfig.formIds[hubspotConfig.contentType] || hubspotConfig.formIds.download, target : '#hs-form-area' }); </script>
「ページ読み込み時に確定しているデータ」→ HubL(記事タイトル・タグ・公開日・URL等)
「ユーザー操作後に変化するもの」→ JavaScript(モーダル開閉・フォーム切り替え・検索等)
「HubLのデータをJSに引き渡す」→ スクリプトタグ内でHubL変数を展開するパターン
{{ }} 出力 / {% %} 制御・定義 / {# #} コメント。すべてのHubLの基本単位。
ループ内で外部の変数を更新するには namespace が必須。タグ判定など実務で頻出するパターン。
content / hub_id / contents / current_page_num 等。スコープ(使える場所)とセットで覚える。
datetimeformat / truncate / striptags / replace / map が実務最頻出。チェーンで組み合わせる。
blog_recent_posts() / blog_posts() / blog_tag_url() / blog_page_link() がブログ実装の核心。
サーバーサイドのデータ取得・出力はHubL。ユーザー操作・インタラクションはJS。HubLの変数をJSに渡す連携パターンも重要。