📘 HubSpot CMS 組み込み構築 教科書
Chapter 2

HubL(HubSpot Markup Language)基礎

テンプレートとモジュールを書くための言語「HubL」を徹底解説。基本構文から組み込み変数・フィルター・よく使う関数まで、実例コードとともに体系的に整理します。

🎯 対象レベル:基礎〜中級
⏱ 読了目安:60〜80分
🔗 前章:第1章 開発環境のセットアップ

この章の内容

  1. HubL とは何か
  2. 基本構文:3つのデリミタ
  3. 変数・set・データ型
  4. 制御構文:if / for / unless
  5. マクロ(macro)の定義と再利用
  6. 組み込み変数一覧
  7. フィルター完全ガイド
  8. ブログ専用 HubL 関数一覧
  9. HubL と JavaScript の使い分け
  10. 第2章まとめ
Section 2-1

HubL とは何か

HubL(ハブエル)は、HubSpotが独自に設計したテンプレートエンジンです。 PythonのJinja2をベースに、HubSpot専用の変数・フィルター・タグが追加されています。 HTMLファイルの中に HubL を埋め込んで書き、サーバーサイドで処理されて最終的にHTMLとして出力されます。

HubL の位置づけ
HTML + HubL
↓(サーバー処理)
純粋な HTML
ブラウザには最終的な HTMLのみが届く
ベース技術
Jinja2
(Python製テンプレートエンジン)
+ HubSpot独自拡張
Jinja2の知識がある方はすぐ馴染める
使う場所
・テンプレート .html
・モジュール module.html
・パーシャル .html
CSS / JS ファイルには書けない
ℹ️ HubL はサーバーサイドでのみ動作する

HubL はブラウザ(クライアントサイド)では動作しません。 ページがリクエストされた時点でHubSpotのサーバーが処理し、HTMLに変換してから配信します。 そのため、ユーザー操作に反応するインタラクティブな処理(クリック後の表示切り替えなど)はJavaScriptが担当します。 HubLとJavaScriptの使い分けは Section 2-9 で詳しく解説します。


Section 2-2

基本構文:3つのデリミタ

HubLには用途の異なる3種類の記法があります。これがHubLの基本単位です。

{{ }} 出力タグ
{{ 変数名 }}
{{ 変数|フィルター }}
{{ 式 }}
変数の値やフィルター処理の結果をHTMLに出力する
{% %} ステートメントタグ
{% if 条件 %}
{% for x in list %}
{% set 変数 = 値 %}
制御構文・変数定義など。HTMLに出力はされない
{# #} コメント
{# このテキストは
出力されない #}
テンプレートのコメント。HTMLソースにも出力されない
HubL — 3つのデリミタ 実例
{# ====== ① 出力タグ {{ }} ====== #}
{# 変数の値を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: モバイル対応を追加する #}

Section 2-3

変数・set・データ型

変数の定義(set)

{% set %} タグで変数を定義します。一度定義した変数はそのテンプレート内で再利用できます。

HubL — 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 }}     {# ドット記法でもアクセス可 #}

変数の更新(namespace パターン)

HubLではループ内で外側のスコープの変数を直接書き換えることができません。 namespace を使うことで、ループ内から外側の変数を更新できます。 これは実務でよく使うパターンです。

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章でタグ判定パターンとして実際に使います。


Section 2-4

制御構文:if / for / unless

① 条件分岐(if / elif / else / endif)

HubL — if 文の基本パターン
{# 基本的な 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) %}

② ループ(for / endfor)

HubL — for ループの基本パターン
{# リストのループ #}
{% 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 変数 一覧

変数内容
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(ifの否定版)

HubL — unless
{# unless は「〜でない場合」。not を使った if と同義 #}
{% unless content.archived %}
  <div class="post-content">...</div>
{% endunless %}

{# 上記は以下と同じ #}
{% if not content.archived %}
  <div class="post-content">...</div>
{% endif %}

Section 2-5

マクロ(macro)の定義と再利用

マクロは、繰り返し使うHTMLのパターンを関数として定義する仕組みです。 引数を渡せるため、「見た目は同じだが内容が違う」部品を効率よく管理できます。

HubL — マクロの定義と呼び出し
{# ====== マクロの定義 ====== #}

{# カードコンポーネントのマクロ #}
{% 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 する

マクロが増えてきたら、専用の部品ファイルにまとめて import または from … import で読み込めます。

partials/macros.html — マクロ定義ファイル
{% macro render_card(title, description, url, badge="") %}
  ...
{% endmacro %}

{% macro render_badge(label, type="default") %}
  <span class="badge badge--{{ type }}">{{ label }}</span>
{% endmacro %}
templates/blog-listing.html — マクロの import
{# ファイル全体をインポートして使う #}
{% 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 %}

Section 2-6

組み込み変数一覧

HubSpot CMSのテンプレートでは、特定のコンテキストで使える組み込み変数が多数用意されています。スコープ(使える場所)に注意してください。

content — コンテンツ情報(最重要)

現在表示しているページ・記事・メールなどの情報を持つオブジェクトです。テンプレート全体で使える最も頻用する変数です。

変数内容スコープ
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_idHubSpotのポータルID全テンプレート
content.languageコンテンツの言語コード(ja / en 等)全テンプレート
content.archivedアーカイブ済みかどうか(true/false)ブログ

request — リクエスト情報

変数内容
request.path現在のURLパス(例:/blog/post-slug)
request.query_stringクエリストリング(例:?page=2)
request.domainドメイン(例:www.example.com)
request.full_urlフルURL
request.is_hubspot_userHubSpotユーザーのプレビューかどうか

よく使うその他の変数

変数内容スコープ
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("デフォルト値") }} でデフォルト値を設定できます。


Section 2-7

フィルター完全ガイド

フィルターは変数の値を加工・変換するための仕組みです。 {{ 変数|フィルター名 }} の形で使い、|(パイプ)でつないで複数のフィルターをチェーンできます。

HubL — フィルターの基本構文
{# 単一フィルター #}
{{ content.name|upper }}

{# 引数ありフィルター #}
{{ content.meta_description|truncate(100) }}

{# チェーン(複数フィルターを連結) #}
{{ content.name|truncate(30)|upper }}

文字列操作フィルター

truncate(length, end="...")
文字列を指定文字数で切り詰める
{{ "HubSpotで成果を出す完全ガイド"|truncate(10) }}

出力:HubSpotで成...

upper / lower / capitalize
大文字化 / 小文字化 / 先頭大文字
{{ "hubspot"|upper }}
→ HUBSPOT

英語コンテンツのスタイル統一に使用

replace(old, new)
文字列の置換
{{ "type:seminar"|replace("type:", "") }}
→ seminar

タグスラッグからprefixを除去する時に頻用

trim
前後の空白を除去
{{ " HubSpot "|trim }}
→ HubSpot

フォーム入力値の処理などで使用

striptags
HTMLタグを除去してテキストのみ抽出
{{ content.post_body|striptags|truncate(80) }}

記事本文からテキスト抜粋を作る時に使用

escape / e
HTMLエスケープ処理
{{ user_input|escape }}

ユーザー入力値を安全に表示する時に使用

urlencode
URLエンコード
{{ "HubSpot 活用術"|urlencode }}
→ HubSpot%20%E6%B4%BB...

URLパラメータ生成時に使用

wordcount
単語数をカウント
{{ content.post_body|striptags|wordcount }}

記事の読了時間の計算などに使用

日時フィルター

datetimeformat(format)
日時を指定フォーマットで表示
{{ content.publish_date|datetimeformat("%Y年%m月%d日") }}
→ 2024年03月15日

ブログ記事の公開日表示に必須

datetimeformat(時刻あり)
時刻まで含めて表示
{{ content.publish_date|datetimeformat("%Y/%m/%d %H:%M") }}
→ 2024/03/15 14:30

セミナー開催日時など時刻が必要な場合

数値・リストフィルター

abs
絶対値を返す
{{ -5|abs }}
→ 5

差分計算などで使用

round(precision)
数値を四捨五入
{{ 3.14159|round(2) }}
→ 3.14

価格・評価点などの表示に使用

length / count
リストの件数を返す
{{ content.tag_list|length }}
→ 3

タグ数・記事数などのカウントに使用

first / last
リストの最初・最後の要素
{{ content.tag_list|first }}
→ 最初のタグオブジェクト

代表タグを1つだけ取得する時などに使用

join(separator)
リストを区切り文字で結合
{{ ["A","B","C"]|join(", ") }}
→ A, B, C

タグ名一覧の文字列化などに使用

sort / sort(attribute)
リストをソート
{{ posts|sort(attribute="publish_date") }}

日付順・名前順など任意の属性でソート

selectattr(attr, "equalto", val)
属性でリストをフィルタリング
{{ items|selectattr("active","equalto",true)|list }}

有効なアイテムのみ絞り込む時などに使用

map(attribute)
リストから特定の属性値だけ抽出
{{ content.tag_list|map(attribute="name")|join(", ") }}

タグ名の一覧を文字列で取得する時などに使用

💜 実務でよく使うフィルターコンビネーション

記事の抜粋テキスト生成:
{{ content.post_body|striptags|truncate(120, end="…") }}

タグ名をカンマ区切りで表示:
{{ content.tag_list|map(attribute="name")|join(" / ") }}

公開日を日本語形式で表示:
{{ content.publish_date|datetimeformat("%Y年%m月%d日") }}


Section 2-8

ブログ専用 HubL 関数一覧

ブログ機能を実装する上で必須の 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 記事の総件数を返す。ページネーション計算で使用。
HubL — 記事取得の実用パターン
{# 最新記事 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>

URL・リンク生成関数

関数引数説明
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を返す
HubL — 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推定読了時間(分)

Section 2-9

HubL と JavaScript の使い分け

HubLとJavaScriptはどちらもHubSpotテンプレートで使いますが、役割が明確に異なります。両者の違いを正しく理解することで、適切な実装を選択できます。

観点HubL(サーバーサイド)JavaScript(クライアントサイド)
実行タイミング ページリクエスト時(サーバー上) ブラウザでのページ読み込み後
ユーザー操作への反応 ❌ 不可(静的な出力のみ) ✅ 可能(クリック・スクロール等)
HubSpotデータへのアクセス ✅ 直接アクセス可 △ APIを通じてのみ
SEO(コンテンツの可視性) ✅ 検索エンジンが確実に認識 △ クローラーによっては認識されない
動的なUI変化 ❌ 不可 ✅ モーダル・タブ・アコーディオン等
外部APIリアルタイム連携 △ 制限あり(Enterprise除く) ✅ fetch / axios で自由に連携
HubL × JS — 連携パターン
{# 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変数を展開するパターン


Section 2-10

第2章まとめ

📌 この章で押さえるべきポイント

3つのデリミタ

{{ }} 出力 / {% %} 制御・定義 / {# #} コメント。すべてのHubLの基本単位。

namespace パターン

ループ内で外部の変数を更新するには 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 vs JavaScript

サーバーサイドのデータ取得・出力はHubL。ユーザー操作・インタラクションはJS。HubLの変数をJSに渡す連携パターンも重要。

次章:第3章 テンプレート構造設計

テンプレートの種類・ベーステンプレートと継承・グローバルパーシャルの設計。ブログ関連テンプレート4種の具体的な実装パターンを解説します。

第3章へ →