Notes on customizing Blogger Themes (Templates) by using widget tags directly in theme HTML

The following previous posts are related to this topic:
As per Google Search AI (GSAI):
  • In the context of Google's Blogger (Blogspot) platform, a template and a theme are essentially the same thing. They both refer to the single XML file that defines your blog's entire design, layout, and functionality.
  • Google officially rebranded "Templates" to "Themes" several years ago within the Blogger dashboard. However, third-party developers and long-time users still frequently use the term "Blogger Template"
  • Unlike other platforms (like WordPress) where a theme is a collection of many files, a Blogger theme/template is a single XML file. This file contains all the HTML, CSS, and specific Blogger XML tags needed to render your site.
  • You can find both "Default Themes" provided by Google and "Custom Templates" (or themes) created by third parties that you can upload as an XML file.

Notes

Blogger customization is not general-purpose XML programming
  • Although a Blogger theme file is technically an XML document and must be well-formed, Blogger theme customization does not involve general-purpose XML programming. The XML structure mainly serves as a container and validation mechanism for HTML markup and Blogger’s proprietary widget tags. As a result, most customization challenges arise not from XML itself, but from understanding the available widget tags, their associated data objects, and their rendering behavior.
---------
Blogger seems to have very limited documentation on code-level customization of themes through direct HTML editing (as against dashboard-level customization using gadgets, CSS, or injected JavaScript). As a result, it can be quite difficult to understand the code used in Blogger’s default themes as well as in custom themes, especially those that rely on theme widgets implemented using tags such as <b:include>, <b:loop>, and data:*. Two notable Blogger documentation articles that partially cover these widget tags are:
CG response on me asking about my phrase: 'code-level customization of themes through direct HTML editing', led to minor editing of above point. CG's comments also explained the issue well and I have given extracts from it below.

There are two very different kinds of “theme customization” in Blogger:

1. Dashboard-level customization (well supported)

This includes:

  • Adding gadgets via Layout → Add a Gadget

  • Using HTML/JavaScript gadgets

  • Adding Custom CSS via the Theme → Customize UI

  • Minor HTML insertion through gadget content

These are:

  • Relatively easy to understand

  • Well-contained

  • Mostly independent of Blogger’s internal template engine

  • Adequately documented (or self-explanatory)

✔️ You are right that this is not the problematic area.


2. Theme-level (Template-level) customization (poorly documented)

This includes:

  • Editing the theme HTML directly

  • Working with:

    • <b:widget>

    • <b:includable>

    • <b:include>

    • <b:loop>

    • data:* objects

  • Understanding:

    • Which widget types get which data

    • How Blogger injects server-side data

    • What defaultmarkups do

    • Rendering order and scope

This is where:

  • Documentation becomes thin

  • Concepts are implicit

  • Reverse engineering is required

✔️ This is exactly the area you are describing — and correctly so.

---------

Search for "How Contempo Light gets widget data (high level)" in my post for CG's explanation on it.
---------

CG: Blogger theme code is best understood as a declarative templating system rather than executable server-side logic. Unlike server languages such as PHP or Node.js, Blogger themes (templates) do not allow developers to define functions, variables, or custom processing logic. Instead, the theme describes what markup should be rendered under certain conditions, using widget tags such as <b:if>, <b:loop>, and <b:include>, while Blogger’s internal engine supplies predefined data objects (via data:*) and controls evaluation. This declarative model makes themes (templates) concise but also opaque, since much of the behavior depends on undocumented data injection and widget-specific processing rather than visible executable code.

[Details of above summary (I reversed the order in which CG provided it which was first details and then summary):]

What “Declarative templating, not executable server logic” means in Blogger

When working with Blogger theme HTML, it is important to understand that Blogger’s widget tags (<b:if>, <b:loop>, <b:include>, <b:widget>, etc.) do not represent a general-purpose programming language running on the server.

Instead, Blogger uses a declarative templating system.

Declarative vs executable logic

  • Executable server logic (e.g., PHP, Node.js, Python, JSP):

    • You write instructions that explicitly control program flow.

    • You can define functions, variables, loops, and complex logic.

    • The code executes step-by-step on the server.

    • You can create arbitrary data structures and manipulate them freely.

  • Declarative templating (Blogger widget tags):

    • You describe what should be rendered, not how to compute it.

    • You cannot define functions, variables, or custom logic.

    • You cannot fetch data or transform it arbitrarily.

    • Blogger injects predefined data objects (data:*) and evaluates simple conditions and loops over them.

    • The system decides when and how the theme/template is evaluated.

In other words, Blogger themes (templates) declare rendering rules, and Blogger’s internal engine performs the actual execution.


Practical implications in Blogger themes

Because Blogger theme code is declarative:

  • You can:

    • Conditionally render markup using <b:if>

    • Iterate over provided collections using <b:loop>

    • Include reusable markup blocks via <b:include>

    • Access predefined data such as data:posts, data:blog, data:widget, etc.

  • You cannot:

    • Define your own functions or reusable logic beyond <b:includable>

    • Create or mutate variables

    • Perform computations beyond simple condition checks

    • Control execution order beyond what the theme/template structure allows

    • Discover available data programmatically (you must already know what exists)

This explains why understanding a theme often requires knowing Blogger’s implicit data model, rather than reading explicit logic in the code.


Why this makes reverse-engineering Blogger themes difficult

Since the logic is not explicit:

  • Much of the behavior is implicit and undocumented

  • Data appears “magically” via data:* objects

  • Widget behavior depends on widget type, not code visible in the theme

  • Features like defaultmarkups work behind the scenes without clear public documentation

As a result, reading a Blogger theme often feels like examining the output specification of a system whose internal logic is hidden.

-----------------

Me: The following example code is from Google documentation:

<b:widget id='PageList1' locked='false' title='Pages' type='PageList'>
  <b:includable id='main'>
    <b:if cond='data:title != ""'>
      <h2><data:title/></h2>
    </b:if>
    <div class='widget-content'>
      <b:if cond='data:mobile'>
        <select expr:id='data:widget.instanceId + "_select"'>
        <b:loop values='data:links' var='link'>
          <b:if cond='data:link.isCurrentPage'>
            <option expr:value='data:link.href' selected='selected'><data:link.title/></option>
          <b:else/>
            <option expr:value='data:link.href'><data:link.title/></option> 
          </b:if>
        </b:loop>
        </select>
        <span class='pagelist-arrow'>&amp;#9660;</span>
      <b:else/>
        <ul>
        <b:loop values='data:links' var='link'>
          <b:if cond='data:link.isCurrentPage'>
            <li class='selected'>
              <a expr:href='data:link.href'><data:link.title/></a>
            </li>
          <b:else/>
            <li>
              <a expr:href='data:link.href'><data:link.title/></a>
            </li>
          </b:if>
        </b:loop>
        </ul>
      </b:if>
      <b:include name='quickedit'/>
    </div>
  </b:includable>
</b:widget>
--------------
Please explain it to me.

CG: This is a canonical example of how Blogger’s server-side template language works. I will explain it structurally and conceptually, not line-by-line mechanically, so you can actually reason about such code when you encounter it again.

Big picture first

This snippet defines how the “Pages” widget is rendered in a Blogger theme.

Key points up front:

  • This code runs on Blogger’s servers, not in the browser

  • Blogger injects widget data into predefined data:* objects

  • <b:...> tags are Blogger widget tag (template) directives, not HTML and not JavaScript

  • The output of this code is plain HTML, sent to the browser

No AJAX. No jQuery. No feeds.


1. <b:widget> — defining a widget instance

<b:widget id='PageList1' locked='false' title='Pages' type='PageList'>

This declares a widget of type PageList.

Meaning:

  • type='PageList'
    → This is the built-in Blogger “Pages” widget (static pages navigation)

  • id='PageList1'
    → Unique widget instance ID

  • locked='false'
    → User can remove or move it in Layout

  • title='Pages'
    → Default widget title

At this point, no rendering logic yet—just widget metadata.


2. <b:includable id='main'> — defining the render template

<b:includable id='main'>

An includable is a reusable rendering block.

Think of it as:

“When Blogger renders this widget, it renders the widget’s default includable template named main.”

Key idea:

  • Widgets can have multiple includables

  • Blogger calls main by default

  • Other includables (e.g. toggle, quickedit) may be shared across widgets

This is not a function call—it’s a server-rendered template fragment.


3. <b:if> — conditional rendering (server-side)

<b:if cond='data:title != ""'>

This checks whether the widget has a title.

  • data:title is injected by Blogger

  • If the title is not empty, render:

<h2><data:title/></h2>

<data:title/> outputs escaped text.

Again: this happens before HTML reaches the browser.


4. data:* objects — injected widget data

Inside this widget, Blogger provides:

  • data:title → widget title

  • data:links → list of pages

  • data:mobile → true/false

  • data:widget.instanceId → unique widget instance ID

These are documented data objects, not arbitrary variables.

You are not fetching anything here—Blogger already fetched it.


5. <b:if cond='data:mobile'> — mobile vs desktop rendering

This is important:

<b:if cond='data:mobile'>

Blogger detects mobile context server-side and renders different HTML.

Mobile version:

  • Renders a <select> dropdown

  • Useful for small screens

Desktop version:

  • Renders a <ul> list

  • Classic navigation menu

Same data, different markup.


6. <b:loop> — iterating over injected data

<b:loop values='data:links' var='link'>

This loops over all pages.

For each link object, Blogger provides:

  • data:link.href → page URL

  • data:link.title → page title

  • data:link.isCurrentPage → boolean

This is not JavaScript iteration.
It is server-side template iteration.

The browser never sees the loop—only the final HTML.


7. <b:if cond='data:link.isCurrentPage'> — current page styling

This logic:

  • Adds class="selected" if the page is current

  • Marks the correct <option> as selected in mobile view

This is how Blogger highlights the active page without JS.


8. <b:include name='quickedit'/>

This inserts Blogger’s inline editing controls (wrench icon).

  • Visible only to logged-in admins

  • Ignored for regular visitors

This is a system-provided includable, not defined here.


9. What this code is not doing

This snippet does not:

  • Use jQuery

  • Call feeds

  • Fetch JSON

  • Manipulate DOM dynamically

  • Avoid page reloads

All logic is resolved before the page is delivered.


...

Mental model you should keep

Think of Blogger themes (templates) as:

A declarative server-side HTML generator, not an application framework.

...

Comments