<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator>
  <link href="https://en.zhgchg.li/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://en.zhgchg.li/" rel="alternate" type="text/html" hreflang="en" />
  <updated>2026-06-08T14:53:27+08:00</updated>
  <id>https://en.zhgchg.li/feed.xml</id>
  <title type="html">ZhgChgLi Tech &amp;amp; Travel</title>
  <subtitle>Notes on iOS engineering, automation, open source, and travel by an iOS / web developer from Taiwan.</subtitle><author>
    <name>ZhgChgLi</name><email>zhgchgli@gmail.com</email>
  </author><entry>
    <title type="html">AI Collaboration in Data Structures: Crafting RFCs and Multilingual Implementations｜Deep Technical Research with AI</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ai-collaboration-in-data-structures-crafting-rfcs-and-multilingual-implementations-deep-technical-research-with-ai-f7bec8f79c08/" rel="alternate" type="text/html" title="AI Collaboration in Data Structures: Crafting RFCs and Multilingual Implementations｜Deep Technical Research with AI" />
    <published>2026-05-10T15:01:01+08:00</published>
    <updated>2026-05-10T15:28:40+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/f7bec8f79c08</id><summary type="html">Explore how AI enhances technical research by collaborating on data structure RFCs and multilingual implementations, addressing abstraction, algorithm selection, and semantic definitions to deliver practical Ruby and Swift solutions.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="leetcode" /><category term="claude-code" /><category term="ai-agent" /><category term="algorithms" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/f7bec8f79c08/1*H5MBf1d1kEqBQnqtG9-zgg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ai-collaboration-in-data-structures-crafting-rfcs-and-multilingual-implementations-deep-technical-research-with-ai-f7bec8f79c08/"><![CDATA[<h3 id="ai-does-more-than-just-applications-collaborating-with-ai-to-complete-a-data-structure-rfc-and-multi-language-implementation"><strong>AI Does More Than Just Applications: Collaborating with AI to Complete a Data Structure RFC and Multi-language Implementation</strong></h3>

<p>Using Rangeable RFC as an example, this records how AI participates in deeper technical research and design, from problem abstraction, algorithm selection, semantic definition to Ruby / Swift reference implementation.</p>

<p><img src="/assets/f7bec8f79c08/1*H5MBf1d1kEqBQnqtG9-zgg.webp" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/f7bec8f79c08/1*H5MBf1d1kEqBQnqtG9-zgg.jpeg" /></p>

<h3 id="background">Background</h3>

<p>A few weeks ago, to redesign my <a href="https://zhgchg.li/" target="_blank">personal website</a>, I upgraded to Claude Code Max and later used AI with Google Apps Script to create a personal desktop Dashboard Deck, turning an old iPhone from junk into treasure. This week, I shifted my focus to a previous open-source project, using AI to refactor the original handcrafted code, optimize efficiency, add documentation, and complete tests.</p>

<blockquote>
  <p><strong><em>TL;DR</em></strong></p>
</blockquote>

<blockquote>
  <p><em>First, I used AI to refactor the application layers of several open-source projects, which inspired me to dive deeper into foundational data structure design.</em></p>
</blockquote>

<h4 id="linkyee-your-own-link-page"><a href="https://github.com/ZhgChgLi/linkyee" target="_blank">Linkyee— Your Own Link Page</a></h4>

<p>A fully customized, 100% free, open-source LinkTree alternative — deployed straight to GitHub Pages.</p>

<p><a href="https://github.com/ZhgChgLi/linkyee" target="_blank"><img src="https://repository-images.githubusercontent.com/877945203/333b5db1-e5e7-4d71-8b53-986a60e75033" alt="" /></a></p>

<p>The first optimization was for the previously made Linktree-like free open-source self-hosted version. I used AI to restructure the architecture, design several new themes, add more built-in plugins, introduce a Design AI Skill for easy user customization, and enhance the local testing environment.</p>

<h4 id="zmediumtomarkdown"><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a></h4>

<p>Download Medium posts as clean Markdown, preserving structure, images, links, code blocks, and common embeds for plain Markdown or Jekyll workflows.</p>

<p>I will download all the article content, including images, embedded code, and YouTube links, and convert them into Markdown format. The images will be saved together in the <code class="language-plaintext highlighter-rouge">./assets</code> folder.</p>

<p><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank"><img src="https://repository-images.githubusercontent.com/493527574/9b5b7025-cc95-4e81-84a9-b38706093c27" alt="" /></a></p>

<p>The second is a project I have been using and maintaining for over four years — a tool to download and convert Medium articles into Markdown format. Since later on, I mostly treat Medium as a backend editor, the main use is to download and convert the original text with this tool and then upload it to my <a href="https://zhgchg.li/" target="_blank">self-hosted website</a>.</p>

<p>This project is a crucial bridge between my website and Medium that cannot be broken; the first version took me a lot of time and effort to develop. Over the years, I’ve been fixing minor issues, but it has been a long time since I thoroughly reviewed and optimized the design.</p>

<p>Starting with AI optimization for the application, besides asking it to refactor and add tests for the original manually written rendering logic, it also improved the Medium Cloudflare Anti-bot process. This allows users to gracefully log in through Chrome and automatically obtain cookies when Medium blocks the crawler, then execute automatically.</p>

<h4 id="mcp-medium-reader"><a href="https://github.com/ZhgChgLi/mcp-medium-reader" target="_blank">mcp-medium-reader</a></h4>

<p>Finally, AI also built the Medium.com article Reader MCP service.</p>

<p><a href="https://github.com/ZhgChgLi/mcp-medium-reader" target="_blank"><img src="https://opengraph.githubassets.com/be5c8e0489d9add1b623ab52d36b56df26fe36b6d911ba3df2be6bf6c03fe966/ZhgChgLi/mcp-medium-reader" alt="" /></a></p>

<p>The basic conversion service <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a> is already completed. MCP is just a wrapper that calls it to get the job done.</p>

<p>The problem solved is that AI like ChatGPT, Codex, Claude, and Claude Code are often blocked by Medium’s Cloudflare Anti-bot when trying to scrape content from Medium articles. As a result, AI cannot directly read the articles, and even if it can, it only reads HTML, which is not AI-friendly Markdown.</p>

<blockquote>
  <p><a href="https://github.com/ZhgChgLi/mcp-medium-reader" target="*blank"><em>mcp-medium-reader</em></a> <em>Allows your AI to bypass blocks and read the Markdown version of Medium articles, saving tokens while improving AI comprehension.</em></p>
</blockquote>

<h3 id="labeled-interval-set-problem"><strong>Labeled Interval Set Problem</strong></h3>

<blockquote>
  <p><em>Back when I was working on <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a>, I encountered <strong>this Foundation data structure design (algorithm) problem</strong>. At that time, I didn’t have the extra energy or ability to solve it, but now with AI, I thought I could try to use AI to solve this problem.</em></p>
</blockquote>

<h4 id="case-1--medium-rendering-issue">Case 1 — Medium Rendering Issue</h4>

<p><strong>The article content data returned by Medium.com’s GraphQL API is in the following format:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"markups"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="mi">169</span><span class="p">,</span><span class="w">
      </span><span class="nl">"end"</span><span class="p">:</span><span class="w"> </span><span class="mi">207</span><span class="p">,</span><span class="w">
      </span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://zhgchg.li/posts/f6713ba3fee3/"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"anchorType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"LINK"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"linkMetadata"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Markup"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"STRONG"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"end"</span><span class="p">:</span><span class="w"> </span><span class="mi">29</span><span class="p">,</span><span class="w">
      </span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"anchorType"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"linkMetadata"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Markup"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"EM"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w">
      </span><span class="nl">"end"</span><span class="p">:</span><span class="w"> </span><span class="mi">55</span><span class="p">,</span><span class="w">
      </span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"anchorType"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"linkMetadata"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Markup"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"STRONG"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="mi">69</span><span class="p">,</span><span class="w">
      </span><span class="nl">"end"</span><span class="p">:</span><span class="w"> </span><span class="mi">88</span><span class="p">,</span><span class="w">
      </span><span class="nl">"href"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"anchorType"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"userId"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"linkMetadata"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Markup"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>Plain Language Translation:</strong></p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="m">0</span><span class="p">,</span><span class="m">14</span><span class="p">]</span>    <span class="n">STRONG</span>       <span class="p">=</span> <span class="n">Lorem</span> <span class="n">ipsum</span> <span class="n">dol</span>
<span class="p">[</span><span class="m">15</span><span class="p">,</span><span class="m">29</span><span class="p">]</span>   <span class="n">STRONG</span> <span class="p">+</span> <span class="n">EM</span>  <span class="p">=</span> <span class="n">or</span> <span class="n">sit</span> <span class="n">amet</span><span class="p">,</span> <span class="n">co</span>
<span class="p">[</span><span class="m">30</span><span class="p">,</span><span class="m">55</span><span class="p">]</span>   <span class="n">EM</span>           <span class="p">=</span> <span class="n">nsectetur</span> <span class="n">adipiscing</span> <span class="n">elit</span><span class="p">,</span>
<span class="p">[</span><span class="m">56</span><span class="p">,</span><span class="m">68</span><span class="p">]</span>   <span class="n">normal</span>       <span class="p">=</span>  <span class="n">sed</span> <span class="k">do</span> <span class="n">eiusm</span>
<span class="p">[</span><span class="m">69</span><span class="p">,</span><span class="m">88</span><span class="p">]</span>   <span class="n">STRONG</span>       <span class="p">=</span> <span class="n">od</span> <span class="n">tempor</span> <span class="n">incididunt</span>
<span class="p">[</span><span class="m">89</span><span class="p">,</span><span class="m">168</span><span class="p">]</span>  <span class="n">normal</span>       <span class="p">=</span>  <span class="n">ut</span> <span class="n">labore</span> <span class="n">et</span> <span class="n">dolore</span> <span class="n">magna</span> <span class="n">aliqua</span><span class="p">.</span> <span class="n">Ut</span> <span class="n">enim</span> <span class="n">ad</span> <span class="n">minim</span> <span class="n">veniam</span><span class="p">,</span> <span class="n">quis</span> <span class="n">nostrud</span> <span class="n">exercit</span>
<span class="p">[</span><span class="m">169</span><span class="p">,</span><span class="m">207</span><span class="p">]</span> <span class="n">A</span>            <span class="p">=</span> <span class="n">ation</span> <span class="n">ullamco</span> <span class="n">laboris</span> <span class="n">nisi</span> <span class="n">ut</span> <span class="n">aliquip</span> <span class="n">e</span>
</code></pre></div></div>

<p><strong>Expected Rendering Result:</strong></p>

<p><img src="/assets/f7bec8f79c08/1*0cOIQ8YgvcrBZKUdCEaxGw.webp" alt="" loading="lazy" decoding="async" width="652" height="99" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTIiIGhlaWdodD0iOTkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/f7bec8f79c08/1*0cOIQ8YgvcrBZKUdCEaxGw.png" /></p>

<blockquote>
  <p><em>Note: The meaning of “end” in the original Medium data should be converted according to the actual API definition; this article will use the closed interval meaning defined by the Rangeable RFC for explanation.</em></p>
</blockquote>

<h4 id="problem">Problem</h4>

<ul>
  <li>
    <p><strong>Range Overlapping</strong><br />
The same range can have multiple overlapping states, for example [15,29] is STRONG + EM</p>
  </li>
  <li>
    <p><strong>Range Overlapping</strong><br />
In extreme cases, users may set overlapping styles, such as [0,29] being STRONG and [15,55] being EM.</p>
  </li>
</ul>

<p><img src="/assets/f7bec8f79c08/1*nagAmJcFkHpMSdbELnT7Mg.webp" alt="" loading="lazy" decoding="async" width="386" height="77" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzODYiIGhlaWdodD0iNzciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/f7bec8f79c08/1*nagAmJcFkHpMSdbELnT7Mg.png" /></p>

<ul>
  <li><strong>Continuous Interval Merging</strong><br />
Data may provide [0,12] as STRONG / [12,29] as STRONG and need to be merged into [0,29] as STRONG</li>
</ul>

<p>Overlapping intervals and continuous merging are relatively easy to handle. The most troublesome part is managing intersecting and closing intervals. You can’t render them directly by index, as it would become <code class="language-plaintext highlighter-rouge">**AA_AA**BBB_</code>. You need to handle the open and close markers yourself to get <code class="language-plaintext highlighter-rouge">**AA_AA_**_BBB_</code>, which is the correct Markdown.</p>

<p>iOS developers who have used <code class="language-plaintext highlighter-rouge">NSAttributedString attributes</code> face the same issue, except Apple Foundation handles the interval merging and overlay for us.</p>

<blockquote>
  <p><em>At that time, I spent a long time thinking about it. I regretted not having studied more since I rarely practiced problems and had no tools when I actually needed them. I ended up using the most brute-force walkthrough solution, which was correct but had terrible performance and code quality.</em></p>
</blockquote>

<h4 id="related-leetcode-problems">Related LeetCode Problems</h4>

<ul>
  <li><strong>Merge Intervals</strong></li>
</ul>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">56</span><span class="p">.</span> <span class="n">Merge</span> <span class="n">Intervals</span>
<span class="mi">57</span><span class="p">.</span> <span class="k">Insert</span> <span class="n">Interval</span>
</code></pre></div></div>

<p><strong>Usage:</strong> Merge STRONG [0,12], STRONG [13,29] -&gt; STRONG [0,29].</p>

<ul>
  <li><strong>Sweep Line</strong></li>
</ul>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">253.</span> Meeting Rooms II
<span class="p">731.</span> My Calendar II
<span class="p">732.</span> My Calendar III
<span class="p">1094.</span> Car Pooling
<span class="p">1851.</span> Minimum Interval to Include Each Query
</code></pre></div></div>

<p><strong>Purpose:</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">STRONG</span> <span class="o">[</span><span class="err">0</span><span class="o">,</span><span class="err">29</span><span class="o">]</span>
<span class="nt">EM</span>     <span class="o">[</span><span class="err">15</span><span class="o">,</span><span class="err">55</span><span class="o">]</span>
<span class="nt">A</span>      <span class="o">[</span><span class="err">169</span><span class="o">,</span><span class="err">207</span><span class="o">]</span>
</code></pre></div></div>

<p>to</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">0</span>    <span class="nt">open</span> <span class="nt">STRONG</span>
<span class="err">15</span>   <span class="nt">open</span> <span class="nt">EM</span>
<span class="err">30</span>   <span class="nt">close</span> <span class="nt">STRONG</span>
<span class="err">56</span>   <span class="nt">close</span> <span class="nt">EM</span>
<span class="err">169</span>  <span class="nt">open</span> <span class="nt">A</span>
<span class="err">208</span>  <span class="nt">close</span> <span class="nt">A</span>
</code></pre></div></div>

<ul>
  <li><strong>Difference Array, but not just numeric differences</strong></li>
</ul>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">370.</span> Range Addition
<span class="p">1109.</span> Corporate Flight Bookings
<span class="p">1094.</span> Car Pooling
</code></pre></div></div>

<p><strong>Purpose:</strong> Maintain active markup set</p>

<ul>
  <li><strong>Interval Split / Segment Construction</strong></li>
</ul>

<p><strong>Purpose:</strong></p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">STRONG</span> <span class="o">[</span><span class="err">0</span><span class="o">,</span><span class="err">29</span><span class="o">]</span>
<span class="nt">EM</span>     <span class="o">[</span><span class="err">15</span><span class="o">,</span><span class="err">55</span><span class="o">]</span>
</code></pre></div></div>

<p>to</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span><span class="err">0</span><span class="o">,</span><span class="err">14</span><span class="o">]</span>   <span class="nt">STRONG</span>
<span class="o">[</span><span class="err">15</span><span class="o">,</span><span class="err">29</span><span class="o">]</span>  <span class="nt">STRONG</span> <span class="o">+</span> <span class="nt">EM</span>
<span class="o">[</span><span class="err">30</span><span class="o">,</span><span class="err">55</span><span class="o">]</span>  <span class="nt">EM</span>
</code></pre></div></div>

<p><strong>Other Used Problem Types and Data Structures:</strong></p>

<ul>
  <li>
    <p>Event Sorting</p>
  </li>
  <li>
    <p>Interval Partition / Segment Split</p>
  </li>
  <li>
    <p>Stack / Parentheses Matching</p>
  </li>
  <li>
    <p>Binary Search</p>
  </li>
  <li>
    <p>Ordered Set / LinkedHashSet</p>
  </li>
  <li>
    <p>Canonicalization</p>
  </li>
</ul>

<h4 id="case-2--avplayer-cache-segment-issue">Case 2 — AVPlayer Cache Segment Issue</h4>

<p>The interval problem encountered above on Medium is actually similar to one I faced before, during the development of “<a href="/posts/zrealm-dev/avplayer-local-cache-implementation-master-avassetresourceloaderdelegate-for-smooth-playback-6ce488898003/">AVPlayer local playback with caching</a>”.</p>

<p>Because streaming data is discontinuous, AVPlayer may request discontinuous or overlapping data ranges such as <code class="language-plaintext highlighter-rouge">[0,100] [300–500] [150, 200]…</code> from a Data of Size: 1000.</p>

<p><img src="/assets/f7bec8f79c08/1*9cNp02AnTbilWCpadldfBw.webp" alt="" loading="lazy" decoding="async" width="1189" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTg5IiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/f7bec8f79c08/1*9cNp02AnTbilWCpadldfBw.png" /></p>

<p>As shown in the above diagram, assuming <strong>the current data segments are:</strong> <code class="language-plaintext highlighter-rouge">[250,566] [850,959]</code>, ideally:</p>

<ul>
  <li>
    <p><strong>When AVPlayer requests [0,300]:</strong> [0,249] fetched from remote, [250,300] fetched locally</p>
  </li>
  <li>
    <p><strong>When AVPlayer queries [350, 920]:</strong> [350,566] fetched locally, [567,849] fetched remotely, [850,920] fetched locally</p>
  </li>
</ul>

<p>At that time, I encountered the interval calculation problem — <strong>Covered / Uncovered Interval Query</strong> — but it was simpler than the Medium problem because it only involved distinguishing between binary data 0 and 1, requiring just the calculation of results.</p>

<blockquote>
  <p><em>At that time, the issue was temporarily unresolved because developing the AVPlayer’s simultaneous playback and caching feature took most of the time; plus, the scenario involved audio files, which were not large; <strong>so we directly used the approach of fetching and overwriting the entire range if data was missing — for details, please refer to the original article “<a href="/posts/zrealm-dev/avplayer-local-cache-implementation-master-avassetresourceloaderdelegate-for-smooth-playback-6ce488898003/">AVPlayer Local Cache Implementation Guide｜Using AVAssetResourceLoaderDelegate to Save iOS Music Streaming Data</a>“</strong>.</em></p>
</blockquote>

<h3 id="overall-collaboration-process">Overall Collaboration Process</h3>

<p><img src="/assets/f7bec8f79c08/1*t1oXXy1czhI_yV0LKBVf0Q.webp" alt="" loading="lazy" decoding="async" width="1672" height="941" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjcyIiBoZWlnaHQ9Ijk0MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/f7bec8f79c08/1*t1oXXy1czhI_yV0LKBVf0Q.jpeg" /></p>

<h3 id="implementing-foundation-data-structure-design-with-ai">Implementing Foundation Data Structure Design with AI</h3>

<h4 id="tools">Tools</h4>

<ul>
  <li>
    <p>Claude Code Max</p>
  </li>
  <li>
    <p>Effort: Max</p>
  </li>
  <li>
    <p>Model: Opus 4.7</p>
  </li>
</ul>

<h4 id="process">Process</h4>

<ul>
  <li>
    <p>First, Have AI Plan Mode Draft the Research RFC Writing Proposal</p>
  </li>
  <li>
    <p>Please have the AI start the research with two agent roles: one is a graduate student responsible for researching and writing the RFC, and the other is a professor who focuses solely on reviewing the algorithm’s efficiency and validity. (Review until approval)</p>
  </li>
  <li>
    <p>The graduate student can start by experimenting with the Medium case and try developing it using Ruby.</p>
  </li>
  <li>
    <p>Final Output RFC.md File</p>
  </li>
  <li>
    <p>Delegating implementation to other Agents in various languages (I handle ZMediumToMarkdown in Ruby / AVPlayer in iOS Swift)</p>
  </li>
</ul>

<h4 id="1-ai-research-and-design-of-data-structure-rfc--rangeable-rfc">1. AI Research and Design of Data Structure RFC — Rangeable RFC</h4>

<p><strong>Prompt (Plan Mode):</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/plan

Please use English and refer to academic papers to draft an RFC implementation document. This document will later be delivered <span class="k">for </span>other languages to follow when implementing. During drafting, you can use Ruby as the experimental language.
You need to study all data structures and algorithms to find a balanced solution <span class="k">in </span>terms of <span class="nb">time </span>and space efficiency to meet the requirements.
Tool name: Rangeable
Function: Calculate continuous and discontinuous sets of generic object combinations.
Example:
var strings:Rangeable&lt;String&gt; <span class="o">=</span> <span class="o">[]</span>
strings.insert<span class="o">(</span>Strong<span class="o">()</span>, start: 2, end: 5<span class="o">)</span> // 2-5 Strong
strings.insert<span class="o">(</span>Strong<span class="o">()</span>, start: 3, end: 7<span class="o">)</span> // 3-7 Strong
strings.insert<span class="o">(</span>Strong<span class="o">()</span>, start: 9, end: 11<span class="o">)</span> // 9-11 Strong
strings.insert<span class="o">(</span>Italic<span class="o">()</span>, start: 3, end: 8 <span class="o">)</span> // 3-8 Italic

// Usage:
Strong<span class="o">()</span>.getRange<span class="o">(</span>from: strings<span class="o">)</span> -&gt; <span class="o">[{</span>2,7<span class="o">}</span>,<span class="o">{</span>9-11<span class="o">}]</span>
Italic<span class="o">()</span>.getRange<span class="o">(</span>from strings<span class="o">)</span> -&gt; <span class="o">[{</span>3,8<span class="o">}]</span>
// 
strings[4].objs -&gt; <span class="o">[</span>Strong<span class="o">()</span>, Italic<span class="o">()]</span>
strings[8].objs -&gt; <span class="o">[</span>Italic<span class="o">()]</span>
strings[10].objs -&gt; <span class="o">[</span>Strong<span class="o">()]</span>
// 
Please note Strong and Italic themselves are generic<span class="p">;</span> this is just an example
//

During the actual RFC drafting, use sub-agents: one as a graduate student responsible <span class="k">for </span>researching implementation methods and writing the RFC, and another as a professor focusing on academic or deeper algorithms to review the graduate student<span class="s1">'s output. If rejected by the professor agent, research and rewrite the RFC.
You can first create ./RubyRangeable for the Ruby implementation and ./SwiftRangeable for the Swift implementation.
Feel free to consume tokens and resources for research.
//
Provide a real-world use case: ../ZMediumToMarkdown in MarkupStyleRender.rb
Currently parses Medium GraphQL returned Paragraphs
{"id": "f30dc1c4fe6c_19", "name": "b2ba", "type": "BQ", "href": null, "layout": null, "metadata": null, "text": "A notification webhook is an endpoint you create on your server.\n通知型 Webhook 是你在自己伺服器上建立的一個端點(endpoint)。", "hasDropCap": null, "dropCapImage": null, "markups": [{"type": "EM", "start": 0, "end": 104, "href": null, "anchorType": null, "userId": null, "linkMetadata": null, "__typename": "Markup"}], "__typename": "Paragraph", "codeBlockMetadata": null, "iframe": null, "mixtapeMetadata": null},
{"id": "f30dc1c4fe6c_20", "name": "f2e8", "type": "BQ", "href": null, "layout": null, "metadata": null, "text": "This webhook endpoint receives HTTP POST requests from App Store Connect.\n這個 Webhook 端點會接收來自 App Store Connect 的 HTTP POST 請求。", "hasDropCap": null, "dropCapImage": null, "markups": [{"type": "EM", "start": 0, "end": 126, "href": null, "anchorType": null, "userId": null, "linkMetadata": null, "__typename": "Markup"}], "__typename": "Paragraph", "codeBlockMetadata": null, "iframe": null, "mixtapeMetadata": null},
{"id": "f30dc1c4fe6c_21", "name": "1725", "type": "BQ", "href": null, "layout": null, "metadata": null, "text": "The POST requests describe important events about your app.\n這些 POST 請求會描述與你的 App 相關的重要事件。", "hasDropCap": null, "dropCapImage": null, "markups": [{"type": "EM", "start": 0, "end": 89, "href": null, "anchorType": null, "userId": null, "linkMetadata": null, "__typename": "Markup"}], "__typename": "Paragraph", "codeBlockMetadata": null, "iframe": null, "mixtapeMetadata": null},
{"id": "f30dc1c4fe6c_22", "name": "3e9d", "type": "BQ", "href": null, "layout": null, "metadata": null, "text": "Use the webhooks notifications endpoint to configure the notifications for events happening to your apps.\n你可以使用 Webhook 通知端點，來設定當你的 App 發生各種事件時所要接收的通知。", "hasDropCap": null, "dropCapImage": null, "markups": [{"type": "EM", "start": 0, "end": 151, "href": null, "anchorType": null, "userId": null, "linkMetadata": null, "__typename": "Markup"}], "__typename": "Paragraph", "codeBlockMetadata": null, "iframe": null, "mixtapeMetadata": null},
Then render Markdown according to start and end, but currently no algorithm exists so a loop filling method is used.
</span></code></pre></div></div>

<h4 id="2-after-the-plan-is-confirmed-the-graduate-student-agent-starts-research---produces-the-first-version-of-the-rfc">2. After the plan is confirmed, the graduate student Agent starts research -&gt; produces the first version of the RFC</h4>

<h4 id="3-professor-agent-starts-review---rejected">3. Professor Agent Starts Review -&gt; REJECTED</h4>

<p>The main reason is that the professor found <strong>6 MUST-FIX</strong> issues in the RFC, meaning specification/algorithm correctness problems that must be fixed to pass.</p>

<p><img src="/assets/f7bec8f79c08/1*wJuWgRv0aQqpDWkjORt1nw.webp" alt="" loading="lazy" decoding="async" width="967" height="469" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjciIGhlaWdodD0iNDY5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/f7bec8f79c08/1*wJuWgRv0aQqpDWkjORt1nw.png" /></p>

<h4 id="4-graduate-student-agent-revisits-research-and-revisions---produces-second-version-of-rfc">4. Graduate Student Agent Revisits Research and Revisions -&gt; Produces Second Version of RFC</h4>

<h4 id="5-professor-agent-re-review---approved">5. Professor Agent Re-Review -&gt; APPROVED</h4>

<h4 id="6-done">6. Done</h4>

<ul>
  <li>
    <p>Time spent: About 1 hr 30 mins</p>
  </li>
  <li>
    <p>Token usage: 178K (about 30% of Claude Code Max 5 hr quota)</p>
  </li>
</ul>

<h3 id="rangeable-rfc">Rangeable RFC</h3>

<p><a href="https://github.com/ZhgChgLi/RangeableRFC/blob/main/RFC.md" target="_blank"><img src="https://opengraph.githubassets.com/c1bbe414aeed52b95e22e3420b934b9cc8d1b4fc8feb04da559f7d9b381f7f0f/ZhgChgLi/RangeableRFC" alt="" /></a></p>

<h4 id="tldr">TL;DR</h4>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">Rangeable&lt;Element&gt;</code> <em>is a generic data structure for managing “which elements are active within which integer closed intervals.” It automatically merges overlapping or adjacent intervals of the same element and supports querying which elements are active at a given index, as well as outputting open/close transition events within a range. It was originally designed to solve Medium Markdown markup rendering issues, such as determining which styles like <code class="language-plaintext highlighter-rouge">STRONG</code>, <code class="language-plaintext highlighter-rouge">EM</code>, or <code class="language-plaintext highlighter-rouge">LINK</code> are applied to a character. However, it can also be applied to calendars, game states, genome annotation, AVPlayer byte-range cache, and more. Its core value is abstracting “interval merging, active set querying, and boundary event generation” into a deterministic, cross-language consistent specification suitable for Ruby and Swift implementations.</em></p>
</blockquote>

<blockquote>
  <p>The following RFC details can be skipped unless you are very interested. This section is also directly translated and summarized by AI.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">Rangeable&lt;Element&gt;</code> is a generic data structure that <strong>maps elements to multiple integer closed intervals and allows fast queries of which elements are active at a given position</strong>.</p>

<p>It doesn’t just solve a simple <code class="language-plaintext highlighter-rouge">Range</code> problem, but gives me many <code class="language-plaintext highlighter-rouge">(element, start, end)</code> tuples, and I need to be able to:</p>

<ol>
  <li>
    <p>Find which merged ranges contain a specific element</p>
  </li>
  <li>
    <p>Check which elements are active at a specific index</p>
  </li>
  <li>
    <p>Find which open/close boundary events occur within a certain range</p>
  </li>
</ol>

<p>The RFC clearly defines <code class="language-plaintext highlighter-rouge">Rangeable</code> as a language-neutral, generic, integer-coordinate, closed-interval set container. Elements must be hashable and compared by value equality. It supports three types of queries: <code class="language-plaintext highlighter-rouge">getRange</code>, <code class="language-plaintext highlighter-rouge">r[i].objs</code>, and <code class="language-plaintext highlighter-rouge">transitions</code>.</p>

<h4 id="main-motivation">Main Motivation</h4>

<p>This RFC originated from the Medium markup rendering issue in <code class="language-plaintext highlighter-rouge">ZMediumToMarkdown</code>.</p>

<p>Inside Medium paragraphs, there will be markups like this:</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">STRONG</span><span class="o">:</span> <span class="nf">[2</span><span class="p">,</span><span class="nf"> 5]</span>
<span class="nl">STRONG</span><span class="o">:</span> <span class="nf">[3</span><span class="p">,</span><span class="nf"> 7]</span>
<span class="nl">EM</span><span class="o">:</span>     <span class="nf">[4</span><span class="p">,</span><span class="nf"> 10]</span>
</code></pre></div></div>

<p>Currently, rendering requires scanning each character to determine which tags are active, such as bold, italic, link, code, etc. The RFC mentions that the original approach converts markups into <code class="language-plaintext highlighter-rouge">TagChar</code>, sorts them by <code class="language-plaintext highlighter-rouge">startIndex</code>, and then scans all tags linearly for each character, with a worst-case complexity of <code class="language-plaintext highlighter-rouge">O(L · m)</code>. <code class="language-plaintext highlighter-rouge">Rangeable</code> aims to abstract this into a general container, allowing the renderer to simply insert markups and then query using <code class="language-plaintext highlighter-rouge">r[i].objs</code> or <code class="language-plaintext highlighter-rouge">transitions</code>.</p>

<p>The RFC also extends this problem to other scenarios, such as:</p>

<p><img src="/assets/f7bec8f79c08/1*AWs90nwJGkKee9iEzLwexQ.webp" alt="" loading="lazy" decoding="async" width="611" height="275" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MTEiIGhlaWdodD0iMjc1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/f7bec8f79c08/1*AWs90nwJGkKee9iEzLwexQ.png" /></p>

<p>The RFC clearly states the common problem: given many <code class="language-plaintext highlighter-rouge">(eᵢ, lᵢ, hᵢ)</code>, when querying a position <code class="language-plaintext highlighter-rouge">i</code>, return all elements satisfying <code class="language-plaintext highlighter-rouge">l ≤ i ≤ h</code>.</p>

<h4 id="core-concept-translation">Core Concept Translation</h4>

<h4 id="1-rangeableelement">1. <code class="language-plaintext highlighter-rouge">Rangeable&lt;Element&gt;</code></h4>

<p>It can be thought of as:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">Element</span> <span class="nt">-</span><span class="o">&gt;</span> <span class="o">[</span><span class="nt">ClosedRange</span><span class="o">&lt;</span><span class="nt">Int</span><span class="o">&gt;]</span>
</code></pre></div></div>

<p>But it is not an ordinary Dictionary, because it can:</p>

<ol>
  <li>
    <p>Automatically merge overlapping intervals of the same element</p>
  </li>
  <li>
    <p>Automatically merge adjacent intervals</p>
  </li>
  <li>
    <p>Preserve the order of elements based on their first insertion</p>
  </li>
  <li>
    <p>Support fast queries of active elements at a specific index</p>
  </li>
  <li>
    <p>Support output of open/close transition events</p>
  </li>
</ol>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">STRONG</span><span class="o">,</span> <span class="nt">2</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">STRONG</span><span class="o">,</span> <span class="nt">3</span><span class="o">,</span> <span class="nt">7</span><span class="o">)</span>
</code></pre></div></div>

<p>Finally, it will become:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">STRONG</span> <span class="nt">-</span><span class="o">&gt;</span> <span class="o">[(</span><span class="err">2</span><span class="o">,</span> <span class="err">7</span><span class="o">)]</span>
</code></pre></div></div>

<p>Because <code class="language-plaintext highlighter-rouge">[2,5]</code> and <code class="language-plaintext highlighter-rouge">[3,7]</code> overlap.</p>

<h4 id="api-highlights">API Highlights</h4>

<p>The main APIs defined by the RFC are as follows.</p>

<h4 id="inserte-start-end"><code class="language-plaintext highlighter-rouge">insert(e, start, end)</code></h4>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span><span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="n">Element</span><span class="p">,</span> <span class="k">start</span><span class="p">:</span> <span class="nb">Int</span><span class="p">,</span> <span class="k">end</span><span class="p">:</span> <span class="nb">Int</span><span class="p">)</span>
</code></pre></div></div>

<p>The effect is:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">R</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="o">=</span> <span class="n">canonicalize</span><span class="p">(</span><span class="n">R</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="err">∪</span> <span class="p">[</span><span class="k">start</span><span class="p">,</span> <span class="k">end</span><span class="p">])</span>
</code></pre></div></div>

<p>That is, add a new interval with element <code class="language-plaintext highlighter-rouge">e</code> to the existing set of intervals, then canonicalize: merge all overlapping or adjacent intervals and sort them. <code class="language-plaintext highlighter-rouge">start &gt; end</code> must throw an <code class="language-plaintext highlighter-rouge">InvalidIntervalError</code>; inserting the same content repeatedly is idempotent, should not change the result, nor increase the version.</p>

<h4 id="riobjs--activeatindex"><code class="language-plaintext highlighter-rouge">r[i].objs</code> / <code class="language-plaintext highlighter-rouge">activeAt(index:)</code></h4>

<pre><code class="language-less">r[i] -&gt; Slot
r.activeAt(index: i) -&gt; Slot
</code></pre>

<p>Return the active elements at a given index.</p>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">2</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Italic</span><span class="o">,</span> <span class="nt">3</span><span class="o">,</span> <span class="nt">7</span><span class="o">)</span>
</code></pre></div></div>

<p>Query:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">r</span><span class="o">[</span><span class="err">3</span><span class="o">]</span><span class="nc">.objs</span>
</code></pre></div></div>

<p>The result is:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Strong</span><span class="p">,</span> <span class="n">Italic</span><span class="p">]</span>
</code></pre></div></div>

<p>The query complexity goal is:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">O</span><span class="o">(</span><span class="nt">log</span> <span class="nt">M</span> <span class="o">+</span> <span class="nt">r</span><span class="o">)</span>
</code></pre></div></div>

<p>Here, <code class="language-plaintext highlighter-rouge">M</code> is the total number of intervals after merging all elements, and <code class="language-plaintext highlighter-rouge">r</code> is the actual number of elements returned at that position.</p>

<h4 id="getrangeof"><code class="language-plaintext highlighter-rouge">getRange(of:)</code></h4>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">getRange</span><span class="o">(</span><span class="nt">of</span> <span class="nt">e</span><span class="o">)</span> <span class="nt">-</span><span class="o">&gt;</span> <span class="o">[(</span><span class="nt">Int</span><span class="o">,</span> <span class="nt">Int</span><span class="o">)]</span>
</code></pre></div></div>

<p>Return the canonical ranges currently merged for a given element.</p>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">2</span><span class="o">,</span> <span class="nt">4</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">5</span><span class="o">,</span> <span class="nt">7</span><span class="o">)</span>
</code></pre></div></div>

<p>Because <code class="language-plaintext highlighter-rouge">[2,4]</code> and <code class="language-plaintext highlighter-rouge">[5,7]</code> are adjacent on the integer coordinates, the result is:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[(</span><span class="err">2</span><span class="o">,</span> <span class="err">7</span><span class="o">)]</span>
</code></pre></div></div>

<p>The RFC explicitly requires that the returned intervals must be sorted, non-overlapping, and non-adjacent.</p>

<h4 id="transitionsover"><code class="language-plaintext highlighter-rouge">transitions(over:)</code></h4>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">transitions</span><span class="err">(</span><span class="na">over</span><span class="p">:</span> <span class="n">ClosedRange</span><span class="o">&lt;</span><span class="n">Int</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">[</span><span class="n">TransitionEvent</span><span class="p">]</span>
</code></pre></div></div>

<p>Return the open/close boundary events within a range.</p>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">2</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Italic</span><span class="o">,</span> <span class="nt">3</span><span class="o">,</span> <span class="nt">7</span><span class="o">)</span>
</code></pre></div></div>

<p>Then the transitions will be:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span>
  <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">open</span><span class="p">,</span>  <span class="n">Strong</span><span class="p">),</span>
  <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">open</span><span class="p">,</span>  <span class="n">Italic</span><span class="p">),</span>
  <span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="n">close</span><span class="p">,</span> <span class="n">Strong</span><span class="p">),</span>
  <span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="n">close</span><span class="p">,</span> <span class="n">Italic</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Note that the close event position is <code class="language-plaintext highlighter-rouge">hi + 1</code> because the external semantics use a closed interval <code class="language-plaintext highlighter-rouge">[lo, hi]</code>, but the sweep-line internally uses the “first position no longer active” as the close coordinate. The RFC clearly states that <code class="language-plaintext highlighter-rouge">transitions(over: lo..hi)</code> will include the close event at <code class="language-plaintext highlighter-rouge">hi + 1</code>, making it easier to handle the right boundary.</p>

<h4 id="interval-semantics">Interval Semantics</h4>

<h4 id="1-end-is-inclusive">1. <code class="language-plaintext highlighter-rouge">end</code> is inclusive</h4>

<p>This RFC is very clear: <code class="language-plaintext highlighter-rouge">insert(e, start: a, end: b)</code> represents a closed interval <code class="language-plaintext highlighter-rouge">[a, b]</code>, where <code class="language-plaintext highlighter-rouge">b</code> itself is also included in the active range.</p>

<p>That is to say:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">2</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
</code></pre></div></div>

<p>Representative:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span> <span class="nx">are</span> <span class="nx">all</span> <span class="nx">active</span>
</code></pre></div></div>

<p>Not <code class="language-plaintext highlighter-rouge">[2, 5)</code>.</p>

<p>The reasons for choosing inclusive in the RFC include:</p>

<ol>
  <li>
    <p>Historical Data Model Compatible with Medium Markup / ZMediumToMarkdown</p>
  </li>
  <li>
    <p>More aligned with the human intuition of “whether this character is active”</p>
  </li>
  <li>
    <p>Integer adjacency merge can be written as <code class="language-plaintext highlighter-rouge">hi + 1 == lo</code></p>
  </li>
  <li>
    <p>A single-point range <code class="language-plaintext highlighter-rouge">[k, k]</code> can naturally represent a valid position.</p>
  </li>
</ol>

<h4 id="2-adjacent-intervals-should-be-merged">2. Adjacent intervals should be merged</h4>

<p>On integer coordinates:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="m">2</span><span class="p">,</span> <span class="m">4</span><span class="p">]</span> <span class="p">+</span> <span class="p">[</span><span class="m">5</span><span class="p">,</span> <span class="m">7</span><span class="p">]</span> <span class="p">=&gt;</span> <span class="p">[</span><span class="m">2</span><span class="p">,</span> <span class="m">7</span><span class="p">]</span>
</code></pre></div></div>

<p>Because there is no integer position between 4 and 5 to represent “not active,” the RFC requires that adjacent integer intervals of the same element must be merged.</p>

<p>But:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="m">2</span><span class="p">,</span> <span class="m">4</span><span class="p">]</span> <span class="p">+</span> <span class="p">[</span><span class="m">6</span><span class="p">,</span> <span class="m">7</span><span class="p">]</span> <span class="p">=&gt;</span> <span class="p">[(</span><span class="m">2</span><span class="p">,</span> <span class="m">4</span><span class="p">),</span> <span class="p">(</span><span class="m">6</span><span class="p">,</span> <span class="m">7</span><span class="p">)]</span>
</code></pre></div></div>

<p>Because there is a gap of <code class="language-plaintext highlighter-rouge">5</code> in the middle.</p>

<h4 id="3-start--end-is-a-valid-singleton-interval">3. <code class="language-plaintext highlighter-rouge">start == end</code> is a valid singleton interval</h4>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">e</span><span class="o">,</span> <span class="nt">5</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
</code></pre></div></div>

<p>It means only index <code class="language-plaintext highlighter-rouge">5</code> is active. The RFC requires this to be a valid and non-empty range.</p>

<h4 id="4-start--end-must-throw-an-error">4. <code class="language-plaintext highlighter-rouge">start &gt; end</code> must throw an error</h4>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">e</span><span class="o">,</span> <span class="nt">10</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>
</code></pre></div></div>

<p>Cannot automatically reverse to <code class="language-plaintext highlighter-rouge">[5,10]</code> nor silently normalize. The RFC requires throwing <code class="language-plaintext highlighter-rouge">InvalidIntervalError</code>, and the container state must not change.</p>

<h4 id="element-equality-semantics">Element equality semantics</h4>

<p><code class="language-plaintext highlighter-rouge">Rangeable</code> determines whether two elements are the same by using the language’s native value equality.</p>

<p>Ruby:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eql? + <span class="nb">hash</span>
</code></pre></div></div>

<p>Swift:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Hashable</span> <span class="o">/</span> <span class="nx">Equatable</span>
</code></pre></div></div>

<p>So:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">Link</span><span class="o">(</span><span class="s2">"a"</span><span class="o">)</span> <span class="nt">and</span> <span class="nt">Link</span><span class="o">(</span><span class="s2">"a"</span><span class="o">)</span> <span class="nt">are</span> <span class="nt">considered</span> <span class="nt">the</span> <span class="nt">same</span> <span class="nt">element</span><span class="o">,</span> <span class="nt">intervals</span> <span class="nt">will</span> <span class="nt">merge</span>
<span class="nt">Link</span><span class="o">(</span><span class="s2">"a"</span><span class="o">)</span> <span class="nt">and</span> <span class="nt">Link</span><span class="o">(</span><span class="s2">"b"</span><span class="o">)</span> <span class="nt">are</span> <span class="nt">different</span> <span class="nt">elements</span><span class="o">,</span> <span class="nt">will</span> <span class="nt">not</span> <span class="nt">merge</span>
<span class="nt">Strong</span><span class="o">()</span> <span class="nt">and</span> <span class="nt">Strong</span><span class="o">()</span> <span class="nt">will</span> <span class="nt">merge</span> <span class="nt">if</span> <span class="nt">equality</span> <span class="nt">matches</span>
</code></pre></div></div>

<p>The RFC requires equality to satisfy reflexive, symmetric, transitive, and hash consistency properties, and elements should not be externally mutated to break hash/equality after insertion.</p>

<h4 id="sorting-rules">Sorting Rules</h4>

<p>This is a very important part of the RFC.</p>

<h4 id="active-set-sorting">Active set sorting</h4>

<p>The order of <code class="language-plaintext highlighter-rouge">r[i].objs</code> is not based on hash, alphabetical order, or range length, but rather:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">The</span> <span class="n">order</span> <span class="k">in</span> <span class="n">which</span> <span class="n">elements</span> <span class="n">are</span> <span class="n">first</span> <span class="n">inserted</span>
</code></pre></div></div>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">1</span><span class="o">,</span> <span class="nt">10</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Italic</span><span class="o">,</span> <span class="nt">1</span><span class="o">,</span> <span class="nt">10</span><span class="o">)</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Code</span><span class="o">,</span> <span class="nt">1</span><span class="o">,</span> <span class="nt">10</span><span class="o">)</span>
</code></pre></div></div>

<p>Then:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">r</span><span class="p">[</span><span class="mi">5</span><span class="p">].</span><span class="nx">objs</span> <span class="o">==</span> <span class="p">[</span><span class="nx">Strong</span><span class="p">,</span> <span class="nx">Italic</span><span class="p">,</span> <span class="nx">Code</span><span class="p">]</span>
</code></pre></div></div>

<p>The RFC states that this approach ensures determinism, cross-language consistency, and aligns with Markdown nesting: styles that appear earlier are usually outer layers.</p>

<h4 id="merge-does-not-change-insertion-order">Merge does not change insertion order</h4>

<p>For example:</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">1</span><span class="o">,</span> <span class="nt">5</span><span class="o">)</span>   <span class="o">//</span> <span class="nt">Strong</span> <span class="nt">ord</span> <span class="o">=</span> <span class="nt">1</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Italic</span><span class="o">,</span> <span class="nt">3</span><span class="o">,</span> <span class="nt">7</span><span class="o">)</span>   <span class="o">//</span> <span class="nt">Italic</span> <span class="nt">ord</span> <span class="o">=</span> <span class="nt">2</span>
<span class="nt">insert</span><span class="o">(</span><span class="nt">Strong</span><span class="o">,</span> <span class="nt">4</span><span class="o">,</span> <span class="nt">8</span><span class="o">)</span>   <span class="o">//</span> <span class="nt">Strong</span> <span class="nt">ranges</span> <span class="nt">merge</span> <span class="nt">into</span> <span class="o">[</span><span class="nt">1</span><span class="o">,</span><span class="nt">8</span><span class="o">],</span> <span class="nt">but</span> <span class="nt">ord</span> <span class="nt">remains</span> <span class="nt">1</span>
</code></pre></div></div>

<p>Query:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">r</span><span class="o">[</span><span class="err">6</span><span class="o">]</span><span class="nc">.objs</span>
</code></pre></div></div>

<p>The result is:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">Strong</span><span class="p">,</span> <span class="n">Italic</span><span class="p">]</span>
</code></pre></div></div>

<p>Although Strong extends to index 6 only in the third step, its first-insert order is still earlier than Italic. The RFC uses this case to fix “the order of an element’s first appearance” as the sorting criterion.</p>

<h4 id="transitions-sorting">Transitions Sorting</h4>

<p>At the same coordinate:</p>

<ol>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.open</code> comes before <code class="language-plaintext highlighter-rouge">.close</code></p>
  </li>
  <li>
    <p>Multiple <code class="language-plaintext highlighter-rouge">.open</code>: ascending by insertion order</p>
  </li>
  <li>
    <p>Multiple <code class="language-plaintext highlighter-rouge">.close</code>: in descending insertion order, which is LIFO</p>
  </li>
</ol>

<p>This complies with Markdown stack discipline:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">**</span> <span class="n">open</span>
<span class="n">_</span> <span class="n">open</span>
<span class="n">_</span> <span class="n">close</span>
<span class="o">**</span> <span class="n">close</span>
</code></pre></div></div>

<p>The RFC clearly defines this tie-breaking to avoid inconsistent Ruby / Swift hash order causing different cross-language outputs.</p>

<h4 id="internal-data-structures">Internal Data Structures</h4>

<p>The core design chosen for the RFC is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Map&lt;Element, SortedList&lt;Interval&gt;&gt;
+ insertion_order
+ lazy boundary-event index
+ version counter
</code></pre></div></div>

<h4 id="1-per-element-sorted-disjoint-list">1. Per-element sorted disjoint list</h4>

<p>Each element has its own interval list:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">intervals</span><span class="p">:</span> <span class="kt">Map</span><span class="o">&lt;</span><span class="kt">Element</span><span class="p">,</span> <span class="kt">SortedList</span><span class="o">&lt;</span><span class="kt">Interval</span><span class="o">&gt;&gt;</span>
</code></pre></div></div>

<p>And it must maintain the canonical form:</p>

<ol>
  <li>
    <p>Sort in ascending order by <code class="language-plaintext highlighter-rouge">lo</code></p>
  </li>
  <li>
    <p>Non-overlapping with each other</p>
  </li>
  <li>
    <p>Non-adjacent to each other</p>
  </li>
  <li>
    <p>Each interval satisfies <code class="language-plaintext highlighter-rouge">lo &lt;= hi</code></p>
  </li>
</ol>

<p>The RFC calls this the I1 invariant.</p>

<h4 id="2-lazy-boundary-event-index">2. Lazy boundary-event index</h4>

<p><code class="language-plaintext highlighter-rouge">Rangeable</code> does not rebuild the query index on every insert; it only builds it at the first query.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">version</span><span class="o">:</span> <span class="nf">Int</span>
<span class="nl">event_index</span><span class="o">:</span> <span class="nf">EventIndex?</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">event_index</code> contains:</p>

<pre><code class="language-vbnet">events: SortedArray&lt;Event&gt;
segments: SortedArray&lt;Segment&gt;
version: Int
</code></pre>

<p>This design is meant to fit the “build-once-then-query-densely” workload: a large number of inserts followed by dense queries at each position. The RFC clearly states that a lazy index suits this pattern because the build phase does not require constant index rebuilding, and the query phase pays the <code class="language-plaintext highlighter-rouge">O(M log M)</code> cost only once.</p>

<h4 id="algorithm-highlights">Algorithm Highlights</h4>

<h4 id="core-process-of-insert">Core Process of <code class="language-plaintext highlighter-rouge">insert</code></h4>

<p>The pseudocode of the RFC is roughly:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>insert<span class="o">(</span>r, e, start, end<span class="o">)</span>:
  <span class="k">if </span>start <span class="o">&gt;</span> end:
    raise InvalidIntervalError

  e_frozen <span class="o">=</span> freeze_for_insert<span class="o">(</span>e<span class="o">)</span>

  <span class="k">if </span>e not <span class="k">in </span>intervals:
    intervals[e] <span class="o">=</span> <span class="o">[]</span>
    insertion_order.append<span class="o">(</span>e<span class="o">)</span>
    ord[e] <span class="o">=</span> insertion_order.length

  list <span class="o">=</span> intervals[e]
  lo <span class="o">=</span> start
  hi <span class="o">=</span> end

  Find the first interval that may overlap or be adjacent to <span class="o">[</span>lo, hi]

  <span class="k">while </span>interval overlaps or is adjacent to <span class="o">[</span>lo, hi]:
    lo <span class="o">=</span> min<span class="o">(</span>lo, interval.lo<span class="o">)</span>
    hi <span class="o">=</span> max<span class="o">(</span>hi, interval.hi<span class="o">)</span>
    Remove the old interval

  Insert the merged new <span class="o">[</span>lo, hi]

  If there is a real change:
    version +<span class="o">=</span> 1
    event_index <span class="o">=</span> nil
</code></pre></div></div>

<p>Note that the RFC warns against using <code class="language-plaintext highlighter-rouge">lo - 1</code> for checks because it can underflow when <code class="language-plaintext highlighter-rouge">lo == Int.min</code>; instead, use <code class="language-plaintext highlighter-rouge">hi + 1 &gt;= lo</code> or an equivalent successor model.</p>

<h4 id="complexity">Complexity</h4>

<p>The reference structure chosen for the RFC is:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">per</span><span class="p">-</span><span class="n">element</span> <span class="n">sorted</span> <span class="n">disjoint</span> <span class="n">list</span> <span class="p">+</span> <span class="n">lazy</span> <span class="k">event</span> <span class="n">index</span>
</code></pre></div></div>

<p>The complexity is roughly as follows:</p>

<p><img src="/assets/f7bec8f79c08/1*5HKeVhHLMRIWnuJJmmGTbQ.webp" alt="" loading="lazy" decoding="async" width="396" height="197" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTYiIGhlaWdodD0iMTk3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/f7bec8f79c08/1*5HKeVhHLMRIWnuJJmmGTbQ.png" /></p>

<p>Among them:</p>

<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="py">m_e</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">The number of merged intervals of a certain element itself</span>
<span class="py">k</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">The number of old intervals absorbed/merged in this insert</span>
<span class="py">M</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">The total number of merged intervals of all elements</span>
<span class="py">r</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">The actual number of active elements at the query position</span>
</code></pre></div></div>

<p>The RFC also points out that if there are truly <code class="language-plaintext highlighter-rouge">m</code> active elements at the same position, the output itself requires <code class="language-plaintext highlighter-rouge">Ω(m)</code>, so <code class="language-plaintext highlighter-rouge">O(log M + m)</code> is already a reasonable output-sensitive bound.</p>

<h4 id="why-not-use-interval-tree--segment-tree--roaring-bitmap">Why not use Interval Tree / Segment Tree / Roaring Bitmap?</h4>

<p>The RFC has a whole section comparing alternative solutions and ultimately chooses <code class="language-plaintext highlighter-rouge">(a) Per-element sorted disjoint list + lazy event index</code>.</p>

<p>Key points:</p>

<p><img src="/assets/f7bec8f79c08/1*psrNFxHTKrwba92dA0rOLw.webp" alt="" loading="lazy" decoding="async" width="891" height="275" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTEiIGhlaWdodD0iMjc1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/f7bec8f79c08/1*psrNFxHTKrwba92dA0rOLw.png" /></p>

<p>Three reasons for choosing the main structure in the RFC summary:</p>

<ol>
  <li>
    <p>The term “per-element merge” is natural, and <code class="language-plaintext highlighter-rouge">getRange</code> can directly return <code class="language-plaintext highlighter-rouge">R(e)</code>.</p>
  </li>
  <li>
    <p>deterministic, cross-language reproducible</p>
  </li>
  <li>
    <p>lazy index is well suited for build-once-then-query workloads</p>
  </li>
</ol>

<h4 id="features-not-included-in-v1">Features not included in v1</h4>

<p>RFC clearly lists what v1 will not do:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">remove</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="k">start</span><span class="p">,</span> <span class="k">end</span><span class="p">)</span>
<span class="n">remove</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
<span class="n">clear</span>
<span class="k">union</span> <span class="o">/</span> <span class="k">intersect</span> <span class="o">/</span> <span class="n">difference</span>
<span class="n">persistent</span> <span class="k">immutable</span> <span class="n">snapshot</span>
<span class="n">floating</span><span class="o">-</span><span class="n">point</span> <span class="n">coordinates</span>
<span class="n">multi</span><span class="o">-</span><span class="n">dimensional</span> <span class="n">rectangle</span> <span class="n">stabbing</span>
<span class="n">r</span><span class="p">[</span><span class="n">lo</span><span class="p">...</span><span class="n">hi</span><span class="p">].</span><span class="n">objs</span> <span class="k">range</span> <span class="n">slot</span> <span class="n">query</span> <span class="k">like</span> <span class="n">this</span>
</code></pre></div></div>

<p>Please also have AI handle deletion and intersection in the RFC V2 implementation.</p>

<h3 id="ask-ai-to-implement-language-versions-based-on-rangeable-rfc">Ask AI to implement language versions based on <a href="https://github.com/ZhgChgLi/RangeableRFC" target="_blank">Rangeable RFC</a></h3>

<h4 id="process-1">Process</h4>

<ol>
  <li>
    <p>Start with <code class="language-plaintext highlighter-rouge">/plan</code> Plan Mode, ask AI to carefully read the RFC and plan the implementation.</p>
  </li>
  <li>
    <p>Split the roles as developer/reviewer, one writes and the other reviews, until there are no issues.</p>
  </li>
  <li>
    <p>Write Readme Documentation and Push to GitHub</p>
  </li>
</ol>

<blockquote>
  <p><em>The most difficult RFC specification research has been completed. As long as AI implements according to this specification, there will hardly be any issues in the later implementation.</em></p>
</blockquote>

<h4 id="currently-implemented-languages-are-as-follows">Currently implemented languages are as follows</h4>

<ul>
  <li>
    <p>Ruby 3.2+ <a href="https://github.com/ZhgChgLi/RubyRangeable" target="_blank">github.com/ZhgChgLi/RubyRangeable</a></p>
  </li>
  <li>
    <p>Swift 5.7+ <a href="https://github.com/ZhgChgLi/SwiftRangeable" target="_blank">github.com/ZhgChgLi/SwiftRangeable</a></p>
  </li>
  <li>
    <p>Python 3.10+ <a href="https://github.com/ZhgChgLi/PythonRangeable" target="_blank">github.com/ZhgChgLi/PythonRangeable</a></p>
  </li>
  <li>
    <p>TypeScript / JS (Node 18+) <a href="https://github.com/ZhgChgLi/JSRangeable" target="_blank">github.com/ZhgChgLi/JSRangeable</a></p>
  </li>
  <li>
    <p>Kotlin / JVM 11+ <a href="https://github.com/ZhgChgLi/KotlinRangeable" target="_blank">github.com/ZhgChgLi/KotlinRangeable</a></p>
  </li>
  <li>
    <p>Go 1.22+ <a href="https://github.com/ZhgChgLi/GoRangeable" target="_blank">github.com/ZhgChgLi/GoRangeable</a></p>
  </li>
</ul>

<h3 id="practical-ai-designed-development--rangeable-foundation">Practical AI-Designed Development — Rangeable Foundation</h3>

<p>After everything is ready, return to the original algorithm issue in the open-source project ZMediumToMarkdown. Have the AI apply this Lib to replace and remove the original complex walkthrough approach, optimizing the architecture + <strong>adding validation tests before and after application</strong>.</p>

<h4 id="zmediumtomarkdown-360"><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown/releases/tag/v3.6.0" target="_blank">ZMediumToMarkdown 3.6.0</a></h4>

<p><strong>Performance</strong></p>

<ul>
  <li>
    <p>Micro-benchmark: 5.5× faster on the markup render hot path.</p>
  </li>
  <li>
    <p>End-to-end on a real Medium article: 2.23× faster<br />
(2073 µs → 930 µs per paragraph on average).</p>
  </li>
</ul>

<h4 id="avplayer-local-cache-implementation-guide"><a href="https://zhgchg.li/posts/zrealm-dev/avplayer-%E6%9C%AC%E5%9C%B0-cache-%E5%AF%A6%E4%BD%9C%E6%94%BB%E7%95%A5-%E4%BD%BF%E7%94%A8-avassetresourceloaderdelegate-%E7%AF%80%E7%9C%81-ios-%E9%9F%B3%E6%A8%82%E4%B8%B2%E6%B5%81%E6%B5%81%E9%87%8F-6ce488898003/#20260510-updated" target="_blank">AVPlayer Local Cache Implementation Guide</a></h4>

<p>The article also adds more examples of applying SwiftRangeable.</p>

<h3 id="admiring-ai">Admiring AI</h3>

<p>Previously, AI was only used for application tasks, such as <a href="/posts/zrealm-dev/create-your-unique-blog-style-claude-design-claude-code-for-custom-jekyll-themes-6bf79c5b4dab/">redesigning websites</a>, <a href="/posts/zrealm-dev/ai-agent-for-google-apps-script-streamline-your-coding-and-integration-effortlessly-35cc65327d28/">personal dashboards</a>, or fixing product issues at work; this was the first time it was asked to deeply research algorithm solutions and underlying data design.</p>

<p>The results exceeded my expectations. After roughly reading the RFC he wrote, both the format and content are as good as those produced by real researchers (at least definitely better than what I write). I observed what he was doing: he used Web Search to find public related papers or technical documents, then integrated and assessed the feasibility of the implementation. Splitting the roles into doer and reviewer was also very effective—the doer tends to focus too much on implementation, while the reviewer can take a broader and higher-level view to check if the doer’s work has any side effects. Finally, after the RFC was finalized, the AI implemented based on the document with almost 100% accuracy.</p>

<h4 id="reflections-on-ai-usage">Reflections on AI Usage</h4>

<p>However, there is no need to fear being replaced by AI. The key is problem-solving thinking, which AI is still weak at; for example, if you directly ask it to optimize ZMediumToMarkdown, it may only focus on the Medium scenario and the original code style. <strong>But humans know to abstract it into a Foundation, study the RFC first, and then implement it, which works much better. Of course, we can do it ourselves, but it takes time. AI helps us speed up this process, not replace us.</strong></p>]]></content>
  </entry><entry>
    <title type="html">IQUNIX MG Pro Mechanical Keyboards｜65/75/96 Layouts with Low-Profile Switches and Aluminum Case</title>
    <link href="https://en.zhgchg.li/posts/zrealm-life/iqunix-mg-pro-mechanical-keyboards-65-75-96-layouts-with-low-profile-switches-and-aluminum-case-0d4a5fd2929b/" rel="alternate" type="text/html" title="IQUNIX MG Pro Mechanical Keyboards｜65/75/96 Layouts with Low-Profile Switches and Aluminum Case" />
    <published>2026-05-09T00:33:11+08:00</published>
    <updated>2026-05-09T00:33:11+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-life/0d4a5fd2929b</id><summary type="html">Discover the IQUNIX MG75 Pro mechanical keyboard that combines MacOS compatibility, a lightweight aluminum chassis, and smooth low-profile switches, delivering a premium typing experience for efficient and comfortable use.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Life." /><category term="iqunix" /><category term="taobao" /><category term="lifestyle" /><category term="unboxing" /><category term="mechanical-keyboard" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/0d4a5fd2929b/1*UYsoPN7LJA8eLwCW3neySg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-life/iqunix-mg-pro-mechanical-keyboards-65-75-96-layouts-with-low-profile-switches-and-aluminum-case-0d4a5fd2929b/"><![CDATA[<h3 id="iqunix-mg-657596-pro-low-profile-light-feather-switch-tri-mode-aluminum-alloy-mechanical-keyboard-night-black-unboxing">IQUNIX MG 65/75/96 Pro Low Profile Light Feather Switch Tri-mode Aluminum Alloy Mechanical Keyboard Night Black Unboxing</h3>

<p>Unboxing the IQUNIX MG75 Pro: Aluminum Build and Light Typing Feel for MacOS</p>

<p><img src="/assets/0d4a5fd2929b/1*UYsoPN7LJA8eLwCW3neySg.webp" alt="MG75 Pro Unboxing Experience" loading="lazy" decoding="async" width="1200" height="902" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*UYsoPN7LJA8eLwCW3neySg.png" /></p>

<p><a href="https://iqunix.com/products/iqunix-magi75-96-aluminum-low-profile-mechanical-keyboard?variant=50355797393714" target="_blank">MG75 Pro Unboxing Experience</a></p>

<h4 id="introduction">Introduction</h4>

<p>It’s been a long time since I wrote an unboxing post, and I didn’t have anything special to unbox. Recently, my second-hand mechanical keyboard that I’ve used for two years finally died (actually, I spilled water on it). It was a high-profile keyboard assembled by a colleague (based on the DUKHARO — VN66), probably with yellow/white switches.</p>

<p><img src="/assets/0d4a5fd2929b/1*tBmCni-mJ0ZPat8Uf7dqfQ.webp" alt="Old Keyboard" loading="lazy" decoding="async" width="1200" height="491" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*tBmCni-mJ0ZPat8Uf7dqfQ.png" /></p>

<p>Old Keyboard</p>

<p>Before getting into mechanical keyboards, my favorite was the Apple Magic Keyboard. I prefer low-profile switches and, as an Apple user, this one is the perfect choice.</p>

<p><img src="/assets/0d4a5fd2929b/1*vMEz6OFmApz2ZXj2aeQq5A.webp" alt="Apple Magic Keyboard" loading="lazy" decoding="async" width="1200" height="331" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjMzMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*vMEz6OFmApz2ZXj2aeQq5A.jpeg" /></p>

<p>Apple Magic Keyboard</p>

<p>But the feel is just like a pure chocolate keyboard—flat and without any tactile feedback.</p>

<h3 id="iqunix-mg657596-pro-low-profile-light-feather-switch-tri-mode-aluminum-mechanical-keyboard">IQUNIX MG65/75/96 Pro Low-Profile Light Feather Switch Tri-Mode Aluminum Mechanical Keyboard</h3>

<p><img src="/assets/0d4a5fd2929b/1*5tbfNVtQ2DxnxZNDApzf_A.webp" alt="&lt;https://iqunix.com/products/iqunix-magi75-96-aluminum-low-profile-mechanical-keyboard?variant=50355797590322&gt;" loading="lazy" decoding="async" width="2908" height="1042" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTA4IiBoZWlnaHQ9IjEwNDIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*5tbfNVtQ2DxnxZNDApzf_A.png" /></p>

<p><a href="https://iqunix.com/products/iqunix-magi75-96-aluminum-low-profile-mechanical-keyboard?variant=50355797590322" target="_blank">https://iqunix.com/products/iqunix-magi75-96-aluminum-low-profile-mechanical-keyboard?variant=50355797590322</a></p>

<p>This keyboard caught my attention because a colleague bought it shortly after I got my previous one. I tried pressing a few keys myself and confirmed it was my dream keyboard. The low-profile switches match my preference for chocolate-style keys combined with the “Light Feather Switch,” offering good tactile feedback without being too noisy. It also has a sleek appearance and a multifunction control panel. Most importantly, it comes with official MacOS keycaps. Now, I finally have the chance to get it.</p>

<p><img src="/assets/0d4a5fd2929b/1*qKmRbmaj27xzB1Fdb4elhQ.webp" alt="Image from Official Product Image" loading="lazy" decoding="async" width="1200" height="509" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*qKmRbmaj27xzB1Fdb4elhQ.png" /></p>

<p>Image taken from <a href="https://e.tb.cn/h.iBDV6UGnYYloGER?tk=m11t5opK0Zg" target="_blank">Official Product Image</a></p>

<h4 id="switch--custom-made-light-feather-switch-official-marketing-term-gold-red-switch-spec-sheet"><strong>Switch — Custom Made</strong> Light Feather Switch (official marketing term), Gold Red Switch (spec sheet)</h4>

<ul>
  <li>
    <p>Actuation Force: 40 ± 10 Ggf</p>
  </li>
  <li>
    <p>Total travel (full distance): 2.8 mm ± 0.25</p>
  </li>
  <li>
    <p>Actuation travel (the distance the key is pressed down from the top until the “signal is triggered”): 1.2 mm ± 0.30</p>
  </li>
  <li>
    <p>Lifespan: 50 million keystrokes</p>
  </li>
</ul>

<h4 id="keycaps">Keycaps</h4>

<ul>
  <li>
    <p>High-content PBT + Anti-stain Coating</p>
  </li>
  <li>
    <p>Skin-like texture</p>
  </li>
</ul>

<h4 id="case">Case</h4>

<ul>
  <li>
    <p>CNC Machined Full Aluminum Alloy</p>
  </li>
  <li>
    <p>Height: 11 mm (lowest point) — 25.5 mm (highest point)</p>
  </li>
  <li>
    <p>LE-Tray structure, Poron sandwich foam, PET bottom pad, IXPE pad under switches, Poron bottom foam, IXPE bottom pad</p>
  </li>
</ul>

<h4 id="usage-parameters">Usage Parameters</h4>

<ul>
  <li>
    <p>Type-C</p>
  </li>
  <li>
    <p>Supports hot-swappable switches and VIA software</p>
  </li>
  <li>
    <p>Supported connections: Wired, Bluetooth 5.1, 2.4 GHz wireless</p>
  </li>
  <li>
    <p>Supports colorful RGB backlight control</p>
  </li>
  <li>
    <p>Support: Windows / macOS / iOS / Android<br />
Includes replaceable Windows / macOS keycaps</p>
  </li>
</ul>

<h4 id="models-mg65--mg75--mg96--mg65-pro--mg75-pro--mg96-pro">Models MG65 / MG75 / MG96 / MG65 Pro / MG75 Pro / MG96 Pro</h4>

<p><img src="https://curly-lab-d1bd.zhgchgli-b1d.workers.dev/1*XHoih2u5-Azazt5Gkd1IKA.png" alt="" /></p>

<p>The main difference is the keyboard layout (65/75/96) and the Pro version has a multimedia control panel on the right (+5 keys).</p>

<ul>
  <li>
    <p>MG65: 68 keys, 3,000mAh battery<br />
900g / MG65 Pro 1030g</p>
  </li>
  <li>
    <p>MG75: 84 keys, 4,000mAh battery<br />
947g / MG75 Pro 1110g</p>
  </li>
  <li>
    <p>MG96: 100 keys, 4,000mAh battery<br />
1144g / MG96 Pro 1296g</p>
  </li>
</ul>

<p><strong>Multimedia Control Panel Functions:</strong></p>

<p><img src="/assets/0d4a5fd2929b/1*zIGfXexH_24_Pt1sTnVjBA.webp" alt="" loading="lazy" decoding="async" width="1172" height="1042" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTcyIiBoZWlnaHQ9IjEwNDIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*zIGfXexH_24_Pt1sTnVjBA.png" /></p>

<p>At the bottom is the storage compartment for the 2.4G USB receiver, which has no other function.</p>

<p><strong>Appearance:</strong></p>

<p><img src="/assets/0d4a5fd2929b/1*D69-323N4fUoRribRtXGnQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="870" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*D69-323N4fUoRribRtXGnQ.png" /></p>

<ul>
  <li>
    <p>Mist White</p>
  </li>
  <li>
    <p>Night Tour Black</p>
  </li>
</ul>

<h4 id="price">Price</h4>

<p>According to Taobao <a href="https://e.tb.cn/h.iBDV6UGnYYloGER?tk=m11t5opK0Zg" target="_blank"><strong>(Tmall — IQUNIX Official Store)</strong></a>, the price as of 2026/05 is:</p>

<ul>
  <li>
    <p>MG65: RMB $699</p>
  </li>
  <li>
    <p>MG75: RMB $899</p>
  </li>
  <li>
    <p>MG96: RMB $899</p>
  </li>
  <li>
    <p>MG65 Pro: RMB $899</p>
  </li>
  <li>
    <p>MG75 Pro: RMB $999</p>
  </li>
  <li>
    <p>MG96 Pro: RMB ¥1,099</p>
  </li>
</ul>

<h4 id="choice">Choice</h4>

<p>MG65 does not have F-row keys, so it was not considered. The base model starts from MG75, while MG96 feels too large. The Pro features are optional, but since the price difference is small, I upgraded. <strong>The final choice — MG75 Pro.</strong></p>

<h4 id="purchase-channels">Purchase Channels</h4>

<ul>
  <li>
    <p>No official distributor in Taiwan or the distributor has stopped restocking.</p>
  </li>
  <li>
    <p>Official international website <a href="https://iqunix.com/products/iqunix-magi75-96-aluminum-low-profile-mechanical-keyboard?variant=50355797590322" target="_blank">https://iqunix.com/</a>, but the price (MG75Pro) plus shipping is close to <code class="language-plaintext highlighter-rouge">NT$6,200</code>.</p>
  </li>
  <li>
    <p><strong>Taobao Official <a href="https://e.tb.cn/h.iBDV6UGnYYloGER?tk=m11t5opK0Zg" target="_blank">IQUNIX Flagship Store</a></strong> MG75PRO total cost including shipping and tax: <code class="language-plaintext highlighter-rouge">NT$4,798</code></p>
  </li>
</ul>

<blockquote>
  <p><em>White is even more popular than black. I noticed it sells out every week from Monday to the weekend; I bought the <strong>black MG75 PRO.</strong></em></p>
</blockquote>

<h4 id="taobao-official-iqunix-flagship-store"><strong>Taobao Official <a href="https://e.tb.cn/h.iBDV6UGnYYloGER?tk=m11t5opK0Zg" target="_blank">IQUNIX Flagship Store</a></strong></h4>

<p>I have to say, buying from Taobao is super convenient now. You don’t need to figure out consolidated shipping or have a Chinese account or credit card; you can pay directly online using Jkopay, Apple Pay, or credit cards. Then, the official consolidated or direct shipping delivers to your home, or you can even pick up at convenience stores.</p>

<ul>
  <li>
    <p>Order placed after midnight on 05/03, shipped in the morning</p>
  </li>
  <li>
    <p>05/05 Shipment departed</p>
  </li>
  <li>
    <p>05/06 Arrived in Taiwan</p>
  </li>
  <li>
    <p>Received the package at noon on 05/08</p>
  </li>
</ul>

<p>If you have no experience buying items from overseas, <a href="https://www.rakuten.com.tw/magazine/life/2022/012802/#D" target="_blank">be sure to complete EZWay real-name verification by downloading the EZWay App, registering, and completing the verification</a>; <strong>a few days after shipping, you will be notified of a customs declaration record—just open the app and confirm it.</strong></p>

<h3 id="iqunix-mg75-pro-low-profile-light-feather-switch-tri-mode-aluminum-mechanical-keyboard-night-black-unboxing">IQUNIX MG75 Pro Low-Profile Light Feather Switch Tri-Mode Aluminum Mechanical Keyboard Night Black Unboxing</h3>

<p><img src="/assets/0d4a5fd2929b/1*8yLYKhBLcd0WbWRXtVteCw.webp" alt="" loading="lazy" decoding="async" width="1200" height="844" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*8yLYKhBLcd0WbWRXtVteCw.png" /></p>

<p>The packaging is very simple, just a cardboard box the same size as the keyboard… I was a bit worried it might get damaged, but luckily the keyboard case is hard.</p>

<p><img src="/assets/0d4a5fd2929b/1*PL9Fbr5_nRpgLEgKtrcVJA.webp" alt="" loading="lazy" decoding="async" width="1200" height="849" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*PL9Fbr5_nRpgLEgKtrcVJA.png" /></p>

<p>Opening it reveals the keyboard storage pouch; the packaging is simply placing it directly into a cardboard box.</p>

<p><img src="/assets/0d4a5fd2929b/1*-hTJy3Cz8YSTFHX60HRzlg.webp" alt="" loading="lazy" decoding="async" width="569" height="178" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjkiIGhlaWdodD0iMTc4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/0d4a5fd2929b/1*-hTJy3Cz8YSTFHX60HRzlg.png" /></p>

<p>The official product page says it’s a new packaging that directly replaces the keyboard box with a carrying pouch (which means you get an extra case for free).</p>

<p><img src="/assets/0d4a5fd2929b/1*o4caqnDEgtJQBynfPEEKAg.webp" alt="" loading="lazy" decoding="async" width="1690" height="1240" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjkwIiBoZWlnaHQ9IjEyNDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*o4caqnDEgtJQBynfPEEKAg.png" /></p>

<p>Inside the keyboard bag, you will find the keyboard itself, a manual, and accessories.</p>

<p><img src="/assets/0d4a5fd2929b/1*aRdH-fuap85bju8r5WHMdg.webp" alt="" loading="lazy" decoding="async" width="1602" height="1205" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjAyIiBoZWlnaHQ9IjEyMDUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*aRdH-fuap85bju8r5WHMdg.png" /></p>

<p>Accessories include: 2.4GHz receiver, USB-A to USB-C cable, extra keycaps, and a keycap puller.</p>

<p><img src="/assets/0d4a5fd2929b/1*lqklyMPAqRFuNskhqcJwrg.webp" alt="" loading="lazy" decoding="async" width="1200" height="887" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg4NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*lqklyMPAqRFuNskhqcJwrg.png" /></p>

<p>The default keycaps are macOS style, and you can choose to replace them with the extra X aluminum keycaps for a more premium feel. The included data cable is a coiled cable that can stretch longer and is easy to store, which is a nice touch.</p>

<p><img src="/assets/0d4a5fd2929b/1*DZJLlUoscPXZsDWKq7sVpw.webp" alt="" loading="lazy" decoding="async" width="1594" height="621" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTk0IiBoZWlnaHQ9IjYyMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*DZJLlUoscPXZsDWKq7sVpw.png" /></p>

<p>The back of the keyboard also has a metallic texture; the only drawback is the lack of built-in stands to adjust the tilt angle.</p>

<p><img src="/assets/0d4a5fd2929b/1*IbjacAUuMmG36KG1yAA11w.webp" alt="" loading="lazy" decoding="async" width="1724" height="1038" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNzI0IiBoZWlnaHQ9IjEwMzgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*IbjacAUuMmG36KG1yAA11w.png" /></p>

<p>Key height compared to the original keyboard.</p>

<p><img src="/assets/0d4a5fd2929b/1*8Q3XPHzX7coQBMmshcxLlQ.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*8Q3XPHzX7coQBMmshcxLlQ.jpeg" /></p>

<p>I directly replaced it with X.</p>

<h4 id="body-details">Body Details</h4>

<p><img src="/assets/0d4a5fd2929b/1*CsWUoUK0HSqcgPJ7rWFMBw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*CsWUoUK0HSqcgPJ7rWFMBw.jpeg" /></p>

<p>I didn’t notice when buying it, but there is actually a light strip on the panel that lights up.</p>

<p><img src="/assets/0d4a5fd2929b/1*_-epZwLtuUlUBwUolthLgw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*_-epZwLtuUlUBwUolthLgw.jpeg" /></p>

<p><img src="/assets/0d4a5fd2929b/1*eCMkEokNFYEDkdmPkqGpeg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*eCMkEokNFYEDkdmPkqGpeg.jpeg" /></p>

<p><img src="/assets/0d4a5fd2929b/1*aJ3Q02uvkViRzL2eoQYXYQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*aJ3Q02uvkViRzL2eoQYXYQ.jpeg" /></p>

<p><img src="/assets/0d4a5fd2929b/1*bYC27o6XOVINVrab6Nq7rQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="901" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*bYC27o6XOVINVrab6Nq7rQ.png" /></p>

<p><img src="/assets/0d4a5fd2929b/1*nAz4ZUL677wsJsYBf7Zz-g.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*nAz4ZUL677wsJsYBf7Zz-g.jpeg" /></p>

<p>The keycaps have a calm black matte texture, and the keyboard body is made of matte aluminum alloy, giving it a premium feel. It feels solid and practical when held.</p>

<h4 id="manual">Manual</h4>

<p><img src="/assets/0d4a5fd2929b/1*kuAtQ-dLWNNAjm37DuTWwA.webp" alt="" loading="lazy" decoding="async" width="1200" height="455" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ1NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/0d4a5fd2929b/1*kuAtQ-dLWNNAjm37DuTWwA.png" /></p>

<p>Basically, it’s similar to all mechanical keyboards, mainly just adjusting the lighting and connection settings.</p>

<h4 id="startup-process">Startup Process</h4>

<ul>
  <li>
    <p>Plug the cable into the computer</p>
  </li>
  <li>
    <p>Press and hold <code class="language-plaintext highlighter-rouge">FN + ESC</code> for more than 3 seconds to power on the keyboard (Tab backlight will flash)</p>
  </li>
  <li>
    <p>Switch to macOS key layout: <code class="language-plaintext highlighter-rouge">FN + Tab</code> <strong>(Must set for macOS, otherwise CMD will require pressing CTRL)</strong></p>
  </li>
  <li>
    <p>Set backlight: <code class="language-plaintext highlighter-rouge">FN + [</code> / <code class="language-plaintext highlighter-rouge">FN + ]</code> / <code class="language-plaintext highlighter-rouge">FN + Shift</code> / <code class="language-plaintext highlighter-rouge">FN + Enter</code></p>
  </li>
</ul>

<p><img src="/assets/0d4a5fd2929b/1*2XHm8O4TUpCVmHiF0GrIrQ.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/0d4a5fd2929b/1*2XHm8O4TUpCVmHiF0GrIrQ.jpeg" /></p>

<h4 id="actual-key-sound">Actual Key Sound</h4>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/Md2yzNBa1K4" title="IQUNIX MG 65/75/96 Pro 矮軸輕羽軸三模鋁合金外殼機械鍵盤夜遊黑開箱、按鍵聲音" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<blockquote>
  <p><em>Please focus on relative volume levels, as the iPhone’s microphone tends to amplify the actual volume.</em></p>
</blockquote>

<h4 id="typing-feel">Typing Feel</h4>

<p>The sound is much deeper and quieter than before, yet it still retains the tactile feedback of a mechanical keyboard. It feels quite comfortable to use so far. The downside might be that the keys on the right side are too densely packed, which takes some getting used to and can easily cause mistakes, such as pressing the Home key when intending to press Delete.</p>]]></content>
  </entry><entry>
    <title type="html">AI Agent for Google Apps Script｜Streamline Your Coding and Integration Effortlessly</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ai-agent-for-google-apps-script-streamline-your-coding-and-integration-effortlessly-35cc65327d28/" rel="alternate" type="text/html" title="AI Agent for Google Apps Script｜Streamline Your Coding and Integration Effortlessly" />
    <published>2026-05-03T21:06:17+08:00</published>
    <updated>2026-05-03T21:28:55+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/35cc65327d28</id><summary type="html">Discover how AI Agents transform your coding experience by handling Google Apps Script development and integration, saving time and boosting productivity for developers and tech enthusiasts alike.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="claude-code" /><category term="google-apps-script" /><category term="dashboard" /><category term="ai" /><category term="claude" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/35cc65327d28/1*QFRr50u8zbS5WtVtvprG9w.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ai-agent-for-google-apps-script-streamline-your-coding-and-integration-effortlessly-35cc65327d28/"><![CDATA[<h3 id="stop-coding-from-scratch-let-ai-agents-handle-google-apps-script-integration-and-development-for-you">Stop Coding from Scratch: Let AI Agents Handle Google Apps Script Integration and Development for You</h3>

<p>Turning Waste into Treasure with AI — Using Claude Design &amp; Claude Code to Build Your Personal Desktop Dashboard as an Example.</p>

<p><img src="/assets/35cc65327d28/1*QFRr50u8zbS5WtVtvprG9w.webp" alt="" loading="lazy" decoding="async" width="1448" height="1086" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDQ4IiBoZWlnaHQ9IjEwODYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/35cc65327d28/1*QFRr50u8zbS5WtVtvprG9w.png" /></p>

<h4 id="finished-product-demo">Finished Product Demo</h4>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/Zu0rbnu_iy8" title="AI 變廢為寶 - 用 Claude Design &amp; Claude Code 打造屬於你的個人桌面 Dashboard" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<blockquote>
  <p><em>Turn an iPhone 8 Plus that has been sitting unused in a drawer for years into a personal desktop Dashboard Deck, fully customized and completely free.</em></p>
</blockquote>

<h3 id="introduction">Introduction</h3>

<blockquote>
  <p><strong><em>TL;DR If you only care about how to use AI to build your personal desktop Dashboard, please skip this section.</em></strong></p>
</blockquote>

<p>I started using Google Apps Script around 2021 to develop small tools for automating and optimizing workflows. Back then, AI and RPA automation were rarely discussed. The goal was simply to improve team collaboration efficiency or enhance personal comfort. All scripts were hand-coded line by line (which now seems like an old-fashioned way of programming). <strong>But the hardest part wasn’t the coding itself; it was whether and how to integrate with other services, and how to think outside the box for integration.</strong></p>

<h4 id="previous-projects">Previous Projects</h4>

<ol>
  <li>
    <p><strong>Goal — When the public-facing email inbox receives a message, forward it to the Slack work channel.</strong><br />
<strong>Technically, Google Apps Script does not have an event triggered when Gmail receives an email</strong>, so we use a different approach with a scheduled trigger running every minute (or longer): check unread emails ➡️ read content ➡️ strip HTML and forward to Slack ➡️ mark as read.<br />
If you only want to target specific emails (by sender or subject), set up a Label Filter in Gmail, then change the check from unread emails to unread emails with that Label to achieve this.<br />
ref: 「 <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">Using Google Apps Script to Forward Gmail Messages to Slack</a> 」</p>
  </li>
  <li>
    <p><strong>Goal — Automatically query GA traffic/Crash-free rate and send it to the Slack work channel.</strong><br />
Google Apps Script already has built-in AnalyticsData / AdSense Libraries. You just need to import and set the parameters to use them directly, making integration almost painless.<br />
If the app wants to query crash issue details or the Top 10, you just need an extra step: <strong>Firebase to BigQuery</strong>, then import the BigQuery Library in Google Apps Script and run SQL queries to achieve this.<br />
ref: 「 <a href="/posts/zrealm-robotic-process-automation/crashlytics-google-analytics-automate-crash-free-user-rate-queries-with-google-apps-script-793cb8f89b72/">Crashlytics + Google Analytics Auto Query App Crash-Free Users Rate</a> 」、「 <a href="/posts/zrealm-robotic-process-automation/crashlytics-bigquery-integration-for-real-time-crash-tracking-and-alerts-e77b80cc6f89/">Crashlytics + Big Query Build More Real-Time Convenient Crash Tracking Tool</a> 」</p>
  </li>
  <li>
    <p><strong>Goal — Automatically consolidate operational data into Google Sheet.</strong><br />
Following the previous approach, a Web App was created so the entire operational data could be clearly displayed on the team’s TV wall; <strong>the technical issue at that time was that some data was internal</strong>, inaccessible from the external network. Fortunately, the solution was to switch to an internal scheduled task that sends data daily to the Google Apps Script Web App, which then processes and writes it back to Google Sheet.<br />
ref:「 <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Using Google Apps Script to Automate Daily Data Reports with RPA</a> 」</p>
  </li>
  <li>
    <p><strong>Goal — Build a Simple App Packaging Platform Using Existing Resources</strong><br />
The background was that the app packaging service moved to GitHub Actions, but non-engineering teams also needed app packaging, which was previously handled manually. A service was needed within the company team to connect to GitHub Actions for packaging.<br />
Using Google Apps Script Web App as the platform, access was restricted to organization accounts only. Users filled in packaging info on a form, which triggered GitHub Actions (via GitHub API) and sent packaging notifications to Slack.<br />
The packaged app was uploaded to Firebase App Distribution. <strong>However, Google Apps Script has no built-in library, but <a href="/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/">after some research, it can be done directly with googleapis</a>, and it was smoothly integrated in the end.</strong><br />
ref: “<a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy Packaging Tool Platform</a>”</p>
  </li>
</ol>

<p>There are also some smaller cases not listed here. <strong>In short, the point is “there are always more solutions than problems.” Actually writing the code is the easiest part; the real challenge is overcoming various obstacles to integrate everything together.</strong></p>

<h4 id="technical-limitations">Technical Limitations</h4>

<p>The premise of “there are more solutions than difficulties” is that it must be <strong>technically feasible and reasonable</strong>; in other words, “if the direction is wrong, effort is in vain.” We may not need to actually write code, but we must know when to “choose” to use Google Apps Script to develop small tools.</p>

<ul>
  <li>
    <p><strong>User-Agent Cannot Be Customized:</strong> For security and abuse prevention reasons, Google Apps Script User-Agent cannot be specified by the user. (However, other header fields can be set.)<br />
<strong>Impact:</strong> Some rare APIs require data in the User-Agent, or target websites/APIs block the Google Apps Script User-Agent. Both cases are hard limits and cannot be bypassed.<br />
<strong>Workaround:</strong> <a href="https://zhgchg.li/posts/88f0fb935120/" target="_blank">If you really need to use it, you can proxy through a Cloudflare Worker.</a></p>
  </li>
  <li>
    <p><strong>Maximum Execution Time 6 Minutes:</strong> Each run can last up to 6 minutes. For large or numerous tasks, it is recommended to split them into segments.<br />
Additionally, there is a total quota limit, such as Triggers running up to 90 minutes per day. <strong>(However, this seems to be a soft limit; many of my scripts exceed this limit without being blocked.)</strong></p>
  </li>
  <li>
    <p><strong>Single execution mainly uses synchronous blocking:</strong> You cannot dispatch multiple threads to run tasks concurrently within a single execution; if tasks are time-consuming or require extensive parallel processing, it is recommended to split them into multiple executions and use scheduled triggers.</p>
  </li>
  <li>
    <p><strong>Cold Start:</strong> A common issue with Faas services is that when a task hasn’t been triggered for a long time, it goes to sleep, causing a longer wait time when triggered again.<br />
This problem, combined with the requirement for synchronous (blocking) execution, means that if deployed as a Webhook for other services to call, it can easily be judged as a failed request, leading to repeated requests, repeated receptions, and repeated executions.<br />
For example, Slack strictly requires the Webhook server to respond within 3 seconds, and this issue has often been encountered in past experience.</p>
  </li>
  <li>
    <p><strong>Web App API will redirect:</strong> For security reasons, the Content service response will redirect to a one-time <code class="language-plaintext highlighter-rouge">script.googleusercontent.com</code> URL; HTTP clients need to support following redirects.<br />
Some webhook services will fail if they do not automatically follow 302 redirects (e.g., Jira Webhook).</p>
  </li>
  <li>
    <p><strong>Web App HTML:</strong> The webpage header includes an anti-abuse notice. True RWD is not achievable (the page is actually an iframe inside Google’s framework). The URL is ugly and cannot be customized. Full-screen PWA pages are not possible.</p>
  </li>
</ul>

<p><img src="/assets/35cc65327d28/1*NI80VyLt-hHAKJskJLdTmg.webp" alt="" loading="lazy" decoding="async" width="693" height="196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTMiIGhlaWdodD0iMTk2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/35cc65327d28/1*NI80VyLt-hHAKJskJLdTmg.png" /></p>

<ul>
  <li><strong>Service Limits:</strong> There are some usage limits for services, but under normal use, you usually won’t hit them. However, if you need high-frequency checks or real-time responses, it’s easy to reach those limits.</li>
</ul>

<p><img src="/assets/35cc65327d28/1*bg8nLfiGaVJ7xqhIQewp1g.webp" alt="&lt;https://developers.google.com/apps-script/guides/services/quotas?hl=zh-tw&gt;" loading="lazy" decoding="async" width="1200" height="954" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*bg8nLfiGaVJ7xqhIQewp1g.png" /></p>

<p><a href="https://developers.google.com/apps-script/guides/services/quotas?hl=zh-tw" target="_blank">https://developers.google.com/apps-script/guides/services/quotas?hl=zh-tw</a></p>

<h4 id="advantages-and-features">Advantages and Features</h4>

<p>After understanding the limitations, let’s look at the advantages and features.</p>

<p>The biggest advantage is the seamless integration with Google’s native services. The Web App access can be set to the script owner, organization members, signed-in Google accounts, or anyone, <strong>without the need to handle complex OAuth flows yourself—just one click to set up, one click to complete, and one click to start running</strong>:</p>

<p><strong>Built-in Services:</strong><br />
<strong>Can be used without referencing in GAS services.</strong></p>

<ul>
  <li>
    <p>DocumentApp → Google Docs</p>
  </li>
  <li>
    <p>SpreadsheetApp → Google Sheets</p>
  </li>
  <li>
    <p>SlidesApp → Google Slides</p>
  </li>
  <li>
    <p>FormApp → Google Forms</p>
  </li>
  <li>
    <p>GmailApp → Gmail</p>
  </li>
  <li>
    <p>CalendarApp → Google Calendar</p>
  </li>
  <li>
    <p>DriveApp → Google Drive</p>
  </li>
  <li>
    <p>SitesApp → Google Sites</p>
  </li>
  <li>
    <p>Maps → Maps / Distance / Routes</p>
  </li>
  <li>
    <p>Translate → Translation</p>
  </li>
</ul>

<p><strong>Advanced Services:</strong>
<strong>Must be enabled in the GAS services to use.</strong></p>

<ul>
  <li>
    <p>Sheets API</p>
  </li>
  <li>
    <p>Drive API</p>
  </li>
  <li>
    <p>Calendar API</p>
  </li>
  <li>
    <p>Gmail API</p>
  </li>
  <li>
    <p>Analytics API / Analytics Data API</p>
  </li>
  <li>
    <p>BigQuery API</p>
  </li>
  <li>
    <p>Adsense API</p>
  </li>
  <li>
    <p>YouTube Data API</p>
  </li>
  <li>
    <p>Tasks API</p>
  </li>
  <li>
    <p>Googleapis (Other/GCP)</p>
  </li>
</ul>

<p><strong>Tools / System Services:</strong></p>

<ul>
  <li>
    <p>UrlFetchApp → Calling External APIs</p>
  </li>
  <li>
    <p>PropertiesService → Key-Value Storage</p>
  </li>
  <li>
    <p>CacheService → Cache</p>
  </li>
  <li>
    <p>LockService → Concurrency Control Prevention</p>
  </li>
  <li>
    <p>Utilities → Date / Hash / Base64</p>
  </li>
  <li>
    <p>Logger → Log Output</p>
  </li>
  <li>
    <p>HtmlService → Build Web UI, support Ajax asynchronous content updates</p>
  </li>
  <li>
    <p>ContentService → Creating an API endpoint</p>
  </li>
  <li>
    <p>Trigger → Scheduled Automatic Execution</p>
  </li>
</ul>

<p>Basically, everything needed for development is already available. In practice, you just need to connect and combine these services to achieve process automation, for example:</p>

<ul>
  <li>
    <p>Automatically Query GA Traffic/Crash-free Rate and Send to Slack Work Channel:<br />
Use Analytics Data API to fetch data, PropertiesService to store Slack Bot Token, and UrlFetchApp to call Slack Send Message API.</p>
  </li>
  <li>
    <p>Automatically aggregate operational data into Google Sheet and data Dashboard Web:<br />
Trigger scheduled execution of functions, use LockService to ensure only I run it, fetch data with Analytics Data API, get external service data via UrlFetchApp, write to Google Sheet with SpreadsheetApp, output Web App interface using HtmlService, output JSON data with ContentService plus data caching via CacheService.</p>
  </li>
</ul>

<h4 id="differences-from-faas-like-cloud-function--lambda">Differences from FAAS like Cloud Function / Lambda</h4>

<p>Google Apps Script can be seen as a FAAS scripting tool centered around Google services. It has more limitations but is <strong>currently completely free and seamlessly integrates with Google services;</strong> other FAAS services usually require payment, though some offer free tiers, and integrating with Google services involves formal IAM or OAuth processes that are more complex and cumbersome.</p>

<h4 id="unsuitable-scenarios">Unsuitable Scenarios</h4>

<ul>
  <li>
    <p>Not part of the Google ecosystem, such as wanting to connect with Microsoft Office or OneNote…</p>
  </li>
  <li>
    <p>Hybrid local and cloud setup is more suitable for n8n or AI Agent.</p>
  </li>
  <li>
    <p>Complex calculations or large data processing may not complete within the 6-minute maximum execution time per run.</p>
  </li>
  <li>
    <p>Large-scale web scraping or ticket-snatching programs are out of the question. Google Apps Script is easily blocked or stopped by Cloudflare’s anti-scraping measures.</p>
  </li>
</ul>

<h4 id="suitable-scenarios">Suitable Scenarios</h4>

<p>For personal or team workflow integration, I have many scripts managing my daily routines. For example, I get daily notifications of <a href="https://zhgchg.li/" target="_blank">zhgchg.li</a> website traffic overview, GitHub Repo Issues, and even update the current stock prices in a Google Sheet. For teams, the app release process can integrate with Google Calendar to check events and trigger the corresponding CI/CD process (calling GitHub Actions), then send messages to the team Slack channel and forward app review failure emails to Slack.</p>

<blockquote>
  <p><em>The above is a human perspective review of my experiences developing Google Apps Script over the years. <strong>Next, I will share my recent experience using AI Agents to develop GAS from scratch — a personal desktop Dashboard Deck — along with practical examples.</strong></em></p>
</blockquote>

<h4 id="let-ai-agent-handle-google-apps-script-integration-and-development-for-you-directly">Let AI Agent Handle Google Apps Script Integration and Development for You Directly</h4>

<p>Recently, I started trying to develop Google Apps Script directly from scratch using AI, combined with the basic knowledge above (<strong>even without it, AI proficiency is very high</strong>); <strong>the completion and accuracy are almost 100%,</strong> marking the end of the era of “AI coding from zero.”</p>

<h3 id="practical--turning-waste-into-treasure-with-ai--build-your-personal-desktop-dashboard-using-claude-design--claude-code">Practical — Turning Waste into Treasure with AI — Build Your Personal Desktop Dashboard Using Claude Design &amp; Claude Code</h3>

<h4 id="problem">Problem</h4>

<p>I have an old iPhone 8 Plus (iOS 16.7) that I replaced and left unused in a drawer. I thought I could turn it into a desktop dashboard to display real-time information I want to see.</p>

<h4 id="infrastructure">Infrastructure</h4>

<p>To enable an AI Agent to help you develop Google Apps Script, we need to complete some basic infrastructure.</p>

<blockquote>
  <p><em>Even if you’re not developing with AI, it’s highly recommended to use Clasp when working on medium to large scripts. The Web Editor can sometimes crash or accidentally overwrite new code with old code when multiple tabs are open. Clasp makes writing and managing source code more convenient and secure.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Other use cases:</em></strong> <em>Upload Google Apps Script projects to Git Repo for version control (excluding <code class="language-plaintext highlighter-rouge">.clasprc.json</code> and <code class="language-plaintext highlighter-rouge">.clasp.json</code>), add CI to automatically run Jest JS unit tests, use Clasp Token for CD to pull the latest code, and use .claspignore to exclude files that should not be uploaded to the Google Apps Script project.</em></p>
</blockquote>

<ol>
  <li>Install <a href="https://codelabs.developers.google.com/codelabs/clasp?hl=zh-tw#1" target="_blank"><strong>clasp Google Apps Script CLI</strong> ( official local development toolkit)</a></li>
</ol>

<ul>
  <li>
    <p>Node.js &gt;= 20.0.0 ( <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">If you haven’t installed the Node.js environment, please install it first</a> )</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">npm i @google/clasp -g</code></p>
  </li>
</ul>

<ol>
  <li>
    <p>Run <code class="language-plaintext highlighter-rouge">clasp login</code> to sign in and generate an authorization token ( <a href="https://script.google.com/home" target="_blank">choose the Google Apps Script corresponding account</a> )</p>
  </li>
  <li>
    <p>Save clasprc (Clasp Token) to Keychain to allow AI Agent secure access</p>
  </li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>security add-generic-password <span class="se">\</span>
  <span class="nt">-U</span> <span class="se">\</span>
  <span class="nt">-s</span> <span class="s2">"com.google.clasp"</span> <span class="se">\</span>
  <span class="nt">-a</span> <span class="s2">"</span><span class="nv">$USER</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-w</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">cat</span> ~/.clasprc.json<span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>

<p>If you want to copy the Clasp Token to put into CI/CD Secrets for running tasks, you can use <code class="language-plaintext highlighter-rouge">cat ~/.clasprc.json \\| base64 \\| pbcopy</code> (remember to add base64 encode). When using it, run <code class="language-plaintext highlighter-rouge">echo "$CLASPRC" \\| base64 --decode &gt; ~/.clasprc.json &amp;&amp; chmod 600 ~/.clasprc.json</code>.</p>

<ol>
  <li>Create Project Directory</li>
</ol>

<p><img src="/assets/35cc65327d28/1*W5Y_173DcbwNeNZ8aeSGsA.webp" alt="" loading="lazy" decoding="async" width="219" height="36" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTkiIGhlaWdodD0iMzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/35cc65327d28/1*W5Y_173DcbwNeNZ8aeSGsA.png" /></p>

<p>You must create a project directory and then enter it.</p>

<ol>
  <li>Create a Google Apps Script Project in the directory (using clasp)</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clasp create
</code></pre></div></div>

<blockquote>
  <p><em>Will directly use the folder name as the project name.</em></p>
</blockquote>

<p><img src="/assets/35cc65327d28/1*4OjgluUubwvz0cOrs3DZ9g.webp" alt="" loading="lazy" decoding="async" width="888" height="69" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODgiIGhlaWdodD0iNjkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/35cc65327d28/1*4OjgluUubwvz0cOrs3DZ9g.png" /></p>

<p>The current project is empty, containing only the <code class="language-plaintext highlighter-rouge">appsscript.json</code> configuration file.</p>

<h4 id="my-design">My Design</h4>

<p><a href="https://github.com/zhgchgli0718/GASDashboardExample" target="_blank"><img src="https://opengraph.githubassets.com/7f1fca54b1504ffaae1102433fb1baff9a4165f507554bb5b745851eefd4eb27/zhgchgli0718/GASDashboardExample" alt="" /></a></p>

<blockquote>
  <p><em>If you want to save tokens, you can directly git clone my GAS Web App to reuse my design and the developed GAS code.</em></p>
</blockquote>

<h4 id="designing-dashboard-with-claude-design-or-design-agent-skill">Designing Dashboard with Claude Design or Design Agent Skill</h4>

<p><strong>Method 1 — Claude Design</strong><br />
In the previous article “<a href="https://zhgchg.li/posts/zrealm-dev/jekyll-blog-%E8%87%AA%E8%A8%82%E4%B8%BB%E9%A1%8C%E8%A8%AD%E8%A8%88-%E7%94%A8-claude-design-claude-code-%E5%BF%AB%E9%80%9F%E6%89%93%E9%80%A0%E5%B0%88%E5%B1%AC%E9%A2%A8%E6%A0%BC-6bf79c5b4dab/#%E5%AF%A6%E7%8F%BE%E6%AD%A5%E9%A9%9F" target="_blank">Jekyll Blog Custom Theme Design｜Quickly Create a Unique Style with Claude Design + Claude Code</a>,” the same steps were introduced: adjust the design until satisfied, then export the Project and unzip it into the project’s <code class="language-plaintext highlighter-rouge">./Design</code> directory.</p>

<p><strong>Method 2 — Design Agent Skill</strong><br />
Use a third-party <a href="https://github.com/alchaincyf/huashu-skills" target="_blank">Design Skill</a> to complete the design. My prompt is roughly as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/huashu-design
Design a web dashboard that can be viewed in landscape mode on an iPhone 8 Plus, and save the design results in the ./Design directory
- It will be deployed later as a Google Apps Script Web App
- The top area fixedly displays the date, weekday, and a flipping clock
- The bottom tabs switch periodically and show a timed progress bar
- The bottom tabs include:
-- Weather conditions
-- Calendar: current events or upcoming events of the day, showing remaining events
-- Countdown/Count-up days
</code></pre></div></div>

<blockquote>
  <p><em>I use Model: Opus 4.7 (1M context) / Effort: Medium</em></p>
</blockquote>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
⏺ I first grasped the context: iPhone 8 Plus landscape = 736×414 CSS px, the content data structure (weather / calendar / countdown) is clearly defined in CLAUDE.md.  
  You haven't specified the style yet, so as your "design direction consultant," I'll give you 3 distinctly different style options. Please choose one (or mix and match, e.g., "font from A + colors from C"), then I'll create the full hi-fi design.  
<span class="p">  ---  </span>
  🅐 Option 1 · Massimo Vignelli Style Swiss Grid (Information Architecture)  
  ▎ Vignelli’s grid aesthetics for NYC subway — Helvetica + strict columns + red, black, white. The information itself is decoration, no illustrations, icons, or gradients.  
<span class="p">  -</span> Fonts: Neue Haas Grotesk / Inter Tight (display) + IBM Plex Mono (clock/numbers)  
<span class="p">  -</span> Colors: pure white background #FAFAFA · pure black ink #0A0A0A · single vermilion accent #E63946 (used for progress bar and current tab)  
<span class="p">  -</span> Layout: 12-column baseline grid, all elements aligned; flip clock with black background, white large text + thin horizontal separators; tabs are square color blocks  
<span class="p">  -</span> Keywords: rational / restrained / print-like / information density / timeless  
<span class="p">  -</span> Suitable for: you want a dashboard that won’t go out of style, like a high-quality newspaper front page  
<span class="p">  ---  </span>
  🅑 Option 2 · Dieter Rams × Braun Industrial Instruments (Minimalist Industrial)  
  ▎ Rams’ aesthetics for Braun ET66 calculator and audio systems — light beige-gray background + rounded hardware panels + single orange accent +  
  ▎ tactile physical buttons. Each section looks like an independent instrument panel.  
<span class="p">  -</span> Fonts: Söhne / Inter (labels) + JetBrains Mono (clock styled as LCD seven-segment)  
<span class="p">  -</span> Colors: warm beige-gray #E5E1DA · deep charcoal #2B2B2B · Braun orange #E85D2C accent · flip clock LCD yellow-green #A8B547 vintage LCD style  
<span class="p">  -</span> Layout: each card has subtle inner shadow and 1px hairline border for a recessed physical instrument feel; tab switching looks like button press  
<span class="p">  -</span> Keywords: industrial design / tactile / 1970s Braun / calm / engineer aesthetics  
<span class="p">  -</span> Suitable for: you like hardware feel and want comfort for long viewing  
<span class="p">  ---  </span>
  🅒 Option 3 · Teenage Engineering OP-1 / Terminal (Sporty Poetics)  
  ▎ TE’s OP-1 synth panel and vintage OLED terminal aesthetics — dark charcoal background + OLED teal glow + mono fonts + fine grid background. Flip clock is a large OLED  
  ▎ flip display, progress bar resembles sequencer step lights.  
<span class="p">  -</span> Fonts: JetBrains Mono / IBM Plex Mono (all) + Berkeley Mono (display large text)  
<span class="p">  -</span> Colors: background #0E0F0D · main text #E8E5DA off-white · OLED teal #9DEF8F accent · warning orange #FFB454  
<span class="p">  -</span> Layout: very faint 8px dot grid background; flip clock large text occupies 1/3 width with subtle scanlines; tab progress bar uses LED stepping; weather uses ASCII-style icons  
<span class="p">  -</span> Keywords: OLED / cyberdeck / analog synth / hacker chic / hacker culture  
<span class="p">  -</span> Suitable for: you want it to feel like a “device” on your desk, not just a “webpage”  
<span class="p">  ---  </span>
  Please tell me which one you choose (or which elements to mix), then I’ll proceed with the full hi-fi design into ./Design/. You can also say “none fit, show me more,” and I’ll suggest another style.
</code></pre></div></div>

<p>You can choose your preferred style or tell the AI the style you want, for example, “paper texture style, off-white rice paper + red seal stamp aesthetics.”</p>

<p>Waiting for AI to complete the design… <strong>AI might use Playwright to take a screenshot for verification after finishing. If you want to save tokens and verify the result yourself, you can add that instruction to the prompt.</strong></p>

<p><strong>Click the .html file directly in the directory to view the design result:</strong></p>

<p><img src="/assets/35cc65327d28/1*dTXDvk5dttT_RrCTNaSR5w.webp" alt="" loading="lazy" decoding="async" width="1200" height="681" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*dTXDvk5dttT_RrCTNaSR5w.png" /></p>

<blockquote>
  <p><em>Adjust until satisfied before moving on to the next development step.</em></p>
</blockquote>

<p><strong>Create the <a href="https://docs.google.com/spreadsheets/d/1R98OcfLMB3Dx9MSUsH849ZxLv0Hveh8Py_YxtJ1JljE/edit?usp=sharing" target="_blank">Countdown Data Source Google Sheet</a>:</strong></p>

<p><img src="/assets/35cc65327d28/1*Rb4yLjOhg9XwgICKCM7aww.webp" alt="" loading="lazy" decoding="async" width="1056" height="465" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDU2IiBoZWlnaHQ9IjQ2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*Rb4yLjOhg9XwgICKCM7aww.png" /></p>

<p>Finally, create a countdown data source Google Sheet. In the future, just add data to this Sheet when needed.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>title,date,repeat
Birthday,1994-07-18,yes
Japan Trip,2026-12-12,
</code></pre></div></div>

<blockquote>
  <p><em>repeat=yes: Annual recurring event</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Note the Sheet Id: the red-highlighted part in the URL above / Gid: 0 (the first Sheet).</em></strong></p>
</blockquote>

<p><strong>Register for the Central Weather Bureau API Key:</strong></p>

<ul>
  <li>
    <p>Register/Login: <a href="https://opendata.cwa.gov.tw/userLogin" target="_blank">https://opendata.cwa.gov.tw/userLogin</a></p>
  </li>
  <li>
    <p>Obtain Authorization Code</p>
  </li>
</ul>

<h4 id="developing-and-implementing-the-dashboard-with-claude-code">Developing and Implementing the Dashboard with Claude Code</h4>

<p>First, Init Claude Code Memory Markdown:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/init
⏺ Created CLAUDE.md, focusing on the current state of this repo:
  - Project stage: Early GAS project, currently only has appsscript.json / .clasp.json / Design/Paper Dashboard.html, no .gs code yet.
  - File purpose: manifest settings (V8, America/New_York, Stackdriver), clasp-bound scriptId, and a React+CDN visual prototype (without google.script.run).
  - Common commands: mainly clasp (push / pull / open / logs / deploy), no lint or test runner yet.
  - Notes before push: skipSubdirectories: false will upload Design/Paper Dashboard.html; manifest lacks oauthScopes declaration; timezone setting location.
  - Scope: Strictly use only this repo’s content, no reference to other projects (following your memory rules)
</code></pre></div></div>

<p>Execute the plan and schedule development tasks:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/plan
Implement the Dashboard web page features and design from ./Design, deploy to Google Apps Script Web App.
- Use the Central Weather Bureau API from the Ministry of Transportation and Communications for weather https://opendata.cwa.gov.tw/api/v1/rest/datastore/F-C0032-00
- Display the weather for Taipei City, show all available fields
- During development, you can generate a Token yourself and deploy the Web App to trigger necessary methods and test; be sure to remove this open access after development is complete
- Deploy the Web App so everyone can access it, but protect it with a custom Token; first-time users can enter the Token or pass it via URL
- Default calendar to read: primary
- Countdown days read from the Google Sheet I gave you, with title, date, repeat; if repeat is yes, it means an annual recurring countdown, also show how many years and months have passed (rounded down)
- API Key, Google Sheet ID/GID, etc. all stored in PropertiesService settings
- Use clasp push &amp; deploy for me, clasprc login token stored in keychain security add-generic-password \ -U \ -s "com.google.clasp" \ -a "$USER"
- If manual steps are needed from me, please tell me each step in Traditional Chinese
- "oauthScopes": [ "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/calendar", "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/script.scriptapp" ]
- Double-check if the google.script.run integration call format is correct
- The live GAS Web App HTML is rendered inside an iframe wrapper.
- Files like ./Design that do not need to be uploaded to Google Apps Script should be added to .claspignore
</code></pre></div></div>

<ul>
  <li>
    <p>Web Apps have four access levels: anyone, only yourself, only signed-in users, and signed-in users within the organization.</p>
  </li>
  <li>
    <p>The design in the above example is simple: everyone can access it but must enter the correct random Token to view the data. The weather can be changed to your own location, and multiple locations are also supported.</p>
  </li>
  <li>
    <p>The calendar defaults to handling only the primary calendar. If you have other calendars, you can get the Calendar ID from Calendar Settings and Sharing -&gt; Integrate calendar -&gt; Calendar ID.</p>
  </li>
  <li>
    <p><strong>Added a lazy development section here, directly asking AI to deploy and test the Web App entry so it can develop, test, and fix simultaneously, then notify me when it’s done</strong></p>
  </li>
</ul>

<blockquote>
  <p><em>If there are any issues with the plan, ask the AI; if not, you can start implementation!</em></p>
</blockquote>

<h4 id="making-coffee-while-waiting-for-ai-development">Making coffee while waiting for AI development…</h4>

<p><img src="/assets/35cc65327d28/1*TEYsNQzxxveclbMvaeAwAw.webp" alt="" loading="lazy" decoding="async" width="1246" height="1318" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjQ2IiBoZWlnaHQ9IjEzMTgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/35cc65327d28/1*TEYsNQzxxveclbMvaeAwAw.png" /></p>

<p><strong>AI Agent Question:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">Action</span> <span class="n">needed</span> <span class="n">from</span> <span class="nf">you</span> <span class="p">(</span><span class="n">one</span><span class="o">-</span><span class="n">time</span><span class="p">):</span>

  <span class="mf">1.</span> <span class="n">Open</span> <span class="n">the</span> <span class="n">editor</span><span class="p">:</span> <span class="n">https</span><span class="p">:</span><span class="c1">//script.google.com/d/1KEWgIql-gqTNI_Tv_w2-5t7VE_R0eBtjAtd3iqLqabYLOnzJZ8Ga8N4s/edit</span>
  <span class="mf">2.</span> <span class="n">Top</span><span class="o">-</span><span class="n">right</span> <span class="s">"Deploy"</span> <span class="err">→</span> <span class="s">"Manage deployments"</span>
  <span class="mf">3.</span> <span class="n">Delete</span> <span class="n">both</span> <span class="n">existing</span> <span class="nf">deployments</span> <span class="p">(</span><span class="n">the</span> <span class="n">bare</span> <span class="o">@</span><span class="n">HEAD</span> <span class="n">and</span> <span class="n">dev</span> <span class="n">v1</span> <span class="err">—</span> <span class="n">the</span> <span class="n">pencil</span><span class="o">-</span><span class="n">icon</span> <span class="n">edit</span> <span class="n">doesn</span><span class="nv">'t</span> <span class="k">let</span> <span class="n">you</span> <span class="n">change</span> <span class="n">access</span> <span class="k">type</span><span class="p">)</span>
  <span class="mf">4.</span> <span class="n">Then</span> <span class="s">"New deployment"</span> <span class="err">→</span> <span class="n">Type</span><span class="p">:</span> <span class="n">Web</span> <span class="n">app</span> <span class="err">→</span> <span class="n">Execute</span> <span class="k">as</span><span class="p">:</span> <span class="nf">Me</span> <span class="p">(</span><span class="n">your</span> <span class="n">account</span><span class="p">)</span> <span class="err">→</span> <span class="n">Who</span> <span class="n">has</span> <span class="n">access</span><span class="p">:</span> <span class="n">Anyone</span> <span class="err">→</span> <span class="n">Deploy</span>
  <span class="mf">5.</span> <span class="n">Grant</span> <span class="n">OAuth</span> <span class="n">on</span> <span class="n">the</span> <span class="n">consent</span> <span class="nf">screen</span> <span class="p">(</span><span class="n">calendar</span><span class="py">.readonly</span><span class="p">,</span> <span class="n">spreadsheets</span><span class="py">.readonly</span><span class="p">,</span> <span class="n">external_request</span><span class="p">)</span>
  <span class="mf">6.</span> <span class="nb">Copy</span> <span class="n">the</span> <span class="n">new</span> <span class="n">web</span> <span class="n">app</span> <span class="nf">URL</span> <span class="p">(</span><span class="n">looks</span> <span class="n">like</span> <span class="n">https</span><span class="p">:</span><span class="c1">//script.google.com/macros/s/AKfycb.../exec) and paste it here</span>

  <span class="n">Also</span> <span class="n">paste</span><span class="p">:</span>
  <span class="o">-</span> <span class="n">CWA</span> <span class="n">API</span> <span class="nf">Key</span> <span class="p">(</span><span class="n">Central</span> <span class="n">Weather</span> <span class="n">Bureau</span> <span class="n">Open</span> <span class="n">Data</span> <span class="n">Authorization</span><span class="p">)</span>
  <span class="o">-</span> <span class="n">Countdown</span> <span class="n">Sheet</span> <span class="nf">ID</span> <span class="p">(</span><span class="n">the</span> <span class="n">long</span> <span class="n">part</span> <span class="k">in</span> <span class="n">the</span> <span class="n">Sheet</span> <span class="n">URL</span> <span class="n">between</span> <span class="o">/</span><span class="n">d</span><span class="o">/</span> <span class="n">and</span> <span class="o">/</span><span class="n">edit</span><span class="p">)</span>
  <span class="o">-</span> <span class="n">Sheet</span> <span class="nf">GID</span> <span class="p">(</span><span class="n">the</span> <span class="n">number</span> <span class="n">after</span> <span class="n">gid</span><span class="o">=</span> <span class="k">in</span> <span class="n">the</span> <span class="n">URL</span> <span class="err">—</span> <span class="n">defaults</span> <span class="n">to</span> <span class="mi">0</span> <span class="k">for</span> <span class="n">the</span> <span class="n">first</span> <span class="n">tab</span><span class="p">)</span>

  <span class="n">Once</span> <span class="n">I</span> <span class="n">have</span> <span class="n">the</span> <span class="n">URL</span> <span class="o">+</span> <span class="n">secrets</span><span class="p">,</span> <span class="n">I</span><span class="nv">'ll</span> <span class="n">seed</span> <span class="n">Properties</span> <span class="n">via</span> <span class="n">the</span> <span class="n">dev</span> <span class="n">endpoint</span><span class="p">,</span> <span class="n">mint</span> <span class="n">the</span> <span class="n">access</span> <span class="n">token</span><span class="p">,</span> <span class="n">verify</span> <span class="n">each</span> <span class="n">handler</span><span class="p">,</span> <span class="n">and</span> <span class="n">wire</span> <span class="n">you</span> <span class="k">in</span><span class="err">.</span>
</code></pre></div></div>

<p>(1.) The first run still requires our authorization; afterward, the AI Agent can run on its own. Go to the Script Editor URL:</p>

<p><img src="/assets/35cc65327d28/1*qif-nDDBfttn-y6kR2I2VA.webp" alt="" loading="lazy" decoding="async" width="863" height="404" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjMiIGhlaWdodD0iNDA0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/35cc65327d28/1*qif-nDDBfttn-y6kR2I2VA.png" /></p>

<p>Randomly select a file with functions, then choose a function to debug:</p>

<p><img src="/assets/35cc65327d28/1*BgTkreP26mNx7nu9gkhedA.webp" alt="" loading="lazy" decoding="async" width="1412" height="769" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDEyIiBoZWlnaHQ9Ijc2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*BgTkreP26mNx7nu9gkhedA.png" /></p>

<ul>
  <li>Authorization is only required during the first run or when new permissions are added</li>
</ul>

<blockquote>
  <p><em>If AI keeps saying there is a permission issue, but no authorization window appears during execution, you can first go to the project settings and check “Show ‘appsscript.json’ manifest file in the editor.” Then return to the editor, edit <code class="language-plaintext highlighter-rouge">appsscript.json</code>, change the oauthScopes content, run once, and then change it back and run again. This should trigger the authorization process.</em></p>
</blockquote>

<p><strong>Only 1. needs to be handled by yourself; others can be done by AI:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">1. OK, please deploy it for me directly</span>
  <span class="s">CWA API Key</span><span class="err">:</span> <span class="s">CWA-XXXX-XXXX-XXXX-XXXX-XXXX</span>
  <span class="s">Countdown Sheet ID</span><span class="na">: XXXX / GID</span><span class="pi">:</span> <span class="m">0</span>
</code></pre></div></div>

<p><img src="/assets/35cc65327d28/1*E1UAPVqKXjYtmAsJRRABdw.webp" alt="" loading="lazy" decoding="async" width="1266" height="577" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY2IiBoZWlnaHQ9IjU3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*E1UAPVqKXjYtmAsJRRABdw.png" /></p>

<p><strong>Assist AI in Setting Up Google Apps Script..</strong></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">⏺</span> <span class="n">Scope</span> <span class="ow">is</span> <span class="n">updated</span> <span class="ow">and</span> <span class="n">pushed</span> <span class="n">but</span> <span class="n">the</span> <span class="n">deployer</span><span class="sh">'</span><span class="s">s OAuth grant needs to be re-authorized for the new scope. The web app runs under your account — newly added
  scopes require you to re-grant.

  Please do this once:

  1. Open the editor: https://script.google.com/d/1KEWgIql-gqTNI_Tv_w2-5t7VE_R0eBtjAtd3iqLqabYLOnzJZ8Ga8N4s/edit
  2. From the function dropdown at the top, pick setupConfig (or any function)
  3. Click Run — Google will pop an </span><span class="sh">"</span><span class="s">Authorization required</span><span class="sh">"</span><span class="s"> dialog
  4. Allow → grants the upgraded auth/spreadsheets scope
  5. Tell me when done and I</span><span class="sh">'</span><span class="n">ll</span> <span class="n">re</span><span class="o">-</span><span class="n">test</span> <span class="n">the</span> <span class="n">countdown</span> <span class="n">handler</span>
</code></pre></div></div>

<p>Although we have agreed with the AI, it may still get stuck in some places and need manual help, such as the message above; <strong>we just simply follow its steps to click and run the function once, then tell it OK, just like helping out. (If the method it mentioned doesn’t appear, just refresh the page)</strong></p>

<p><img src="/assets/35cc65327d28/1*oLIgKwY7YYbDZszAEMiSKQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="896" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*oLIgKwY7YYbDZszAEMiSKQ.png" /></p>

<h4 id="ai-development-complete-acceptance">AI Development Complete, Acceptance!</h4>

<p><img src="/assets/35cc65327d28/1*_lM4Az2SosWIYlp26KG8zQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="257" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjI1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*_lM4Az2SosWIYlp26KG8zQ.png" /></p>

<p>If any issues are found, you can ask the AI to fix them again.</p>

<h3 id="build-your-personal-desktop-dashboard-with-ai--results">Build Your Personal Desktop Dashboard with AI — Results</h3>

<h4 id="desktop-version">Desktop Version</h4>

<p><img src="/assets/35cc65327d28/1*a70ymWexht6E2RLr7v0PWQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="716" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*a70ymWexht6E2RLr7v0PWQ.png" /></p>

<h4 id="iphone-mobile-version">iPhone Mobile Version</h4>

<p><img src="/assets/35cc65327d28/1*o6rsj5YDo5x47eo4JpvbkQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*o6rsj5YDo5x47eo4JpvbkQ.png" /></p>

<blockquote>
  <p><em>GAS Web App cannot achieve full-screen PWA with hidden address bar. For a 100% experience, please refer to the advanced content.</em></p>
</blockquote>

<h4 id="final-code">Final Code</h4>

<p><a href="https://github.com/zhgchgli0718/GASDashboardExample" target="_blank"><img src="https://opengraph.githubassets.com/7f1fca54b1504ffaae1102433fb1baff9a4165f507554bb5b745851eefd4eb27/zhgchgli0718/GASDashboardExample" alt="" /></a></p>

<h4 id="follow-up-tasks">Follow-up Tasks</h4>

<ul>
  <li>
    <p>[required] Tell the AI: Remove all development-stage interfaces/endpoints after development is complete</p>
  </li>
  <li>
    <p>[optional] Google Apps Script Project -&gt; Project Settings -&gt; Script Properties -&gt; <code class="language-plaintext highlighter-rouge">ACCESS_TOKEN</code> -&gt; regenerate a random string, and replace the <code class="language-plaintext highlighter-rouge">?token=</code> in the URL with this string<br />
On macOS, you can generate a random string with this command: <code class="language-plaintext highlighter-rouge">openssl rand -hex 32</code></p>
  </li>
  <li>
    <p>[optional] <a href="https://opendata.cwa.gov.tw/userLogin" target="_blank">Regenerate the Central Weather Bureau API Key</a> and enter it into Google Apps Script Project -&gt; Project Settings -&gt; Script Properties -&gt; <code class="language-plaintext highlighter-rouge">CWA_API_KEY</code></p>
  </li>
</ul>

<blockquote>
  <p><strong><em>⚠️ According to Claude Code warning:</em></strong> <em>All tokens exposed in the conversation content may be leaked and should be considered compromised. For development convenience, tokens are entered directly first. After verification, they need to be regenerated and replaced.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>If you are familiar with GAS, you can also set up the script properties yourself at the beginning and then instruct the AI to use them directly.</em></strong></p>
</blockquote>

<h4 id="extension">Extension</h4>

<p>If this article sparks your creativity, consider integrating other data sources to display, such as connecting to Yahoo Finance to get real-time stock prices of your watched stocks, or linking AnalyticsData / AdSense to monitor your website traffic and ad performance.</p>

<h4 id="advanced">Advanced</h4>

<p>Because GAS Web App cannot achieve true RWD or full-screen PWA (hiding the address bar), I use the GAS Web App as an API service that only returns JSON data, and implement the frontend separately with GitHub Pages, deploying it as an official website; this way, a complete user experience can be achieved.</p>

<p><img src="/assets/35cc65327d28/1*Cx-dFPrSRjj8g2tL5Z_kJw.webp" alt="" loading="lazy" decoding="async" width="1160" height="695" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTYwIiBoZWlnaHQ9IjY5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*Cx-dFPrSRjj8g2tL5Z_kJw.png" /></p>

<p><img src="/assets/35cc65327d28/1*vtT3UhsGwmx15v4vNYS4vw.webp" alt="iOS Full-Screen PWA Web Settings -&gt; Open URL in Safari -&gt; Share -&gt; Add to Home Screen" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/35cc65327d28/1*vtT3UhsGwmx15v4vNYS4vw.jpeg" /></p>

<p>iOS Full-Screen PWA Web Setup -&gt; Open URL in Safari -&gt; Share -&gt; Add to Home Screen</p>

<p><strong>This approach is a bit more complex. You can tell the AI during the /plan stage:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- GAS Web App only serves as the backend, outputting JSON <span class="k">for </span>frontend use
- Frontend uses GitHub Pages <span class="k">for </span>design, presentation, and deployment, handling all development and deployment tasks
- A custom random string token protects communication between frontend and backend
</code></pre></div></div>

<h3 id="summary">Summary</h3>

<p>This concludes the introduction to using AI Agents directly for Google Apps Script development. Recently, I have also been using AI to refactor medium to large GAS projects I developed before, with great results. <strong>I can even ask it to add function unit tests locally (using Jest, MOCK Google APIs) to improve stability</strong> (running on CI/CD, combined with clasp pull &amp; backup GAS Project); the AI’s mastery and accuracy in producing GAS code are nearly 100%.</p>

<h4 id="if-it-were-2021">If it were 2021…</h4>

<p>If it were the pre-AI era, manually handling everything from design, slicing, programming logic development to deployment would take me about 30 hours; now with AI, it can be done within 3 hours.</p>

<blockquote>
  <p>Basically, I probably won’t develop GAS from scratch anymore, nor write AI code from zero. Instead, I’ll have AI build the complete product for me from the start (code? IDGAF).</p>
</blockquote>

<h4 id="case-2--personal-stock-portfolio-management">Case (2) — Personal Stock Portfolio Management</h4>

<p><img src="/assets/35cc65327d28/1*ouM-S6-JDJ8ddoUDVh8TVg.webp" alt="" loading="lazy" decoding="async" width="1147" height="1295" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ3IiBoZWlnaHQ9IjEyOTUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/35cc65327d28/1*ouM-S6-JDJ8ddoUDVh8TVg.png" /></p>

<p>Another recent AI case I tried was directly handling Google Apps Script. I asked it to convert my Google Sheet stock portfolio into a visual Web App, integrating Yahoo Finance to fetch the latest stock prices and display them in a table. Below the table, there’s a quick input area for buy/sell records, which then syncs back to the Google Sheet.</p>

<h4 id="further-reading">Further Reading</h4>

<blockquote>
  <p><em>TL;DR These are all previously hand-coded GAS services and tools</em></p>
</blockquote>

<ul>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E5%AF%A6%E7%8F%BE-google-%E6%9C%8D%E5%8B%99-rpa-%E8%87%AA%E5%8B%95%E5%8C%96-f6713ba3fee3?source=collection_home---6------3-----------------------" target="_blank"><strong>Automate Daily Data Reports with Google Apps Script RPA</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/"><strong>Google Apps Script x Google APIs Quick Integration Method</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>Using Google Apps Script Web App to Connect with GitHub Actions for Building a Free and Easy Packaging Tool Platform</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/">Simple 3 Steps — Create a Free GA4 Automated Data Notification Bot</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">Using Google Apps Script to Forward Gmail Emails to Slack</a></p>
  </li>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/slack-%E6%89%93%E9%80%A0%E5%85%A8%E8%87%AA%E5%8B%95-wfh-%E5%93%A1%E5%B7%A5%E5%81%A5%E5%BA%B7%E7%8B%80%E6%B3%81%E5%9B%9E%E5%A0%B1%E7%B3%BB%E7%B5%B1-d61062833c1a?source=collection_home---6------9-----------------------" target="_blank">Slack Builds a Fully Automated WFH Employee Health Status Reporting System</a></p>
  </li>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-big-query-%E6%89%93%E9%80%A0%E6%9B%B4%E5%8D%B3%E6%99%82%E4%BE%BF%E5%88%A9%E7%9A%84-crash-%E8%BF%BD%E8%B9%A4%E5%B7%A5%E5%85%B7-e77b80cc6f89?source=collection_home---6------7-----------------------" target="_blank">Crashlytics + Big Query: Building a More Real-Time and Convenient Crash Tracking Tool</a></p>
  </li>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-google-analytics-%E8%87%AA%E5%8B%95%E6%9F%A5%E8%A9%A2-app-crash-free-users-rate-793cb8f89b72?source=collection_home---6------6-----------------------" target="_blank">Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate</a></p>
  </li>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E4%B8%89%E6%AD%A5%E9%A9%9F%E5%85%8D%E8%B2%BB%E5%BB%BA%E7%AB%8B-github-repo-star-notifier-382218e15697?source=collection_home---6------5-----------------------" target="_blank">Use Google Apps Script in 3 Steps to Create a Free GitHub Repo Star Notifier</a></p>
  </li>
  <li>
    <p><a href="https://medium.com/zrealm-robotic-process-automation/10-%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E7%A7%BB%E8%BD%89-line-notify-%E5%88%B0-telegram-bot-%E9%80%9A%E7%9F%A5-6922e90ba90c?source=collection_home---6------0-----------------------" target="_blank">10-Minute Quick Migration from Line Notify to Telegram Bot Notifications</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">Create Your Unique Blog Style｜Claude Design + Claude Code for Custom Jekyll Themes</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/create-your-unique-blog-style-claude-design-claude-code-for-custom-jekyll-themes-6bf79c5b4dab/" rel="alternate" type="text/html" title="Create Your Unique Blog Style｜Claude Design + Claude Code for Custom Jekyll Themes" />
    <published>2026-04-27T23:00:32+08:00</published>
    <updated>2026-05-06T22:40:53+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/6bf79c5b4dab</id><summary type="html">Struggling to design a personalized blog? Learn how Claude Design and Claude Code simplify building your own Jekyll theme, giving you a unique blog style effortlessly.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="claude-code" /><category term="github-pages" /><category term="jekyll" /><category term="ai" /><category term="blog" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/6bf79c5b4dab/1*cQbgChJysDv6MjvNic7Hlw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/create-your-unique-blog-style-claude-design-claude-code-for-custom-jekyll-themes-6bf79c5b4dab/"><![CDATA[<h3 id="one-weekend-afternoon--claude-design--claude-code--create-a-blog-with-your-own-style">One Weekend Afternoon + Claude Design + Claude Code = Create a Blog with Your Own Style</h3>

<p>Create Your Own Jekyll Theme Blog Using Claude Design + Claude Code</p>

<h4 id="httpszhgchgli-20"><a href="https://zhgchg.li/" target="_blank">https://zhgchg.li/</a> 2.0!</h4>

<p><img src="/assets/6bf79c5b4dab/1*cQbgChJysDv6MjvNic7Hlw.webp" alt="&lt;https://zhgchg.li/&gt;" loading="lazy" decoding="async" width="1200" height="1057" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNTciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6bf79c5b4dab/1*cQbgChJysDv6MjvNic7Hlw.png" /></p>

<p><a href="https://zhgchg.li/" target="_blank">https://zhgchg.li/</a></p>

<blockquote>
  <p><em>Spent a weekend afternoon designing my preferred Blog style and features with Claude Design, then implemented and applied them back to my Jekyll Blog using Claude Code, perfectly replacing the 5-year-old Chirpy Theme interface with my ideal personal Blog style.</em></p>
</blockquote>

<h4 id="technology">Technology</h4>

<ul>
  <li>
    <p><strong>Blog Structure:</strong> Jekyll Static Website</p>
  </li>
  <li>
    <p><strong>Original Article File:</strong> Markdown file</p>
  </li>
  <li>
    <p><strong>Server/Web Hosting:</strong> GitHub Pages (free and reliable)</p>
  </li>
  <li>
    <p><strong>Engineering:</strong> Claude Code Max ($100 USD, 5-hour quota <strong>finished before fully used</strong>)</p>
  </li>
  <li>
    <p><strong>Design:</strong> Claude Design (The blog pages are few, so 2 hours of usage is enough)</p>
  </li>
</ul>

<h4 id="cost">Cost</h4>

<ul>
  <li>
    <p>Claude Code Max $100 USD</p>
  </li>
  <li>
    <p>Server/Web Hosting: $0 USD</p>
  </li>
  <li>
    <p>Jekyll Theme: $0 USD</p>
  </li>
  <li>
    <p>Time: One weekend afternoon</p>
  </li>
</ul>

<h3 id="implementation-steps">Implementation Steps</h3>

<h4 id="1-create-a-clean-jekyll-blog-locally">1. Create a clean Jekyll Blog locally</h4>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll new blog
</code></pre></div></div>

<h4 id="2-collect-theme-page-examples-and-upload-to-claude-design">2. Collect Theme Page Examples and Upload to Claude Design</h4>

<h4 id="20260506-update-21-design-agent-skill-using-third-party-design-skill-to-complete-design">[2026/05/06 Update] <a href="https://zhgchg.li/posts/zrealm-dev/%E5%88%A5%E5%86%8D%E5%BE%9E%E9%9B%B6%E9%96%8B%E5%A7%8B-ai-%E5%AF%AB%E7%A8%8B%E5%BC%8F-%E8%AE%93-ai-agent-%E7%9B%B4%E6%8E%A5%E5%B9%AB%E4%BD%A0%E6%90%9E%E5%AE%9A-google-apps-script-%E4%B8%B2%E6%8E%A5%E8%88%87%E9%96%8B%E7%99%BC-35cc65327d28/#%E4%BD%BF%E7%94%A8-claude-design-or-design-agent-skill-%E8%A8%AD%E8%A8%88-dashboard" target="_blank">2–1 Design Agent Skill Using Third-Party Design Skill to Complete Design</a></h4>

<p>For details, please refer to this article: “<a href="https://zhgchg.li/posts/zrealm-dev/%E5%88%A5%E5%86%8D%E5%BE%9E%E9%9B%B6%E9%96%8B%E5%A7%8B-ai-%E5%AF%AB%E7%A8%8B%E5%BC%8F-%E8%AE%93-ai-agent-%E7%9B%B4%E6%8E%A5%E5%B9%AB%E4%BD%A0%E6%90%9E%E5%AE%9A-google-apps-script-%E4%B8%B2%E6%8E%A5%E8%88%87%E9%96%8B%E7%99%BC-35cc65327d28/#%E4%BD%BF%E7%94%A8-claude-design-or-design-agent-skill-%E8%A8%AD%E8%A8%88-dashboard" target="_blank">Stop Starting from Scratch with AI Programming: Let AI Agents Handle Google Apps Script Integration and Development Directly</a>”</p>

<h4 id="22-claude-design">2–2 Claude Design</h4>

<p>I directly go to the current Blog and right-click “Save as” on each page, such as Home, Post, Page, PostList, Archives, Tags…</p>

<p><img src="/assets/6bf79c5b4dab/1*EWBgWOobhy5kFeNPq9-uqQ.webp" alt="" loading="lazy" decoding="async" width="482" height="359" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0ODIiIGhlaWdodD0iMzU5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*EWBgWOobhy5kFeNPq9-uqQ.png" /></p>

<p><strong>Create a Theme directory and put all pages and additional files inside:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*eo3d-J4ehZpBIZfx4bV4Wg.webp" alt="" loading="lazy" decoding="async" width="253" height="231" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTMiIGhlaWdodD0iMjMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*eo3d-J4ehZpBIZfx4bV4Wg.png" /></p>

<p><strong>Go to <a href="https://claude.ai/design" target="_blank">Claude Design</a> to create a project:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*AYCu31HSzfOHST8nberNWA.webp" alt="&lt;https://claude.ai/design&gt;" loading="lazy" decoding="async" width="377" height="441" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNzciIGhlaWdodD0iNDQxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*AYCu31HSzfOHST8nberNWA.png" /></p>

<p><a href="https://claude.ai/design" target="_blank">https://claude.ai/design</a></p>

<p><strong>Upload the Theme page example to Claude Design and select “Attach codebase”:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*1A08G_oNaieGA_8t17xPow.webp" alt="" loading="lazy" decoding="async" width="323" height="340" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjMiIGhlaWdodD0iMzQwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*1A08G_oNaieGA_8t17xPow.png" /></p>

<p><strong>Select the Theme directory you just organized:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*KbXyuIRuWnisFYbtnIUhzg.webp" alt="" loading="lazy" decoding="async" width="998" height="560" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5OTgiIGhlaWdodD0iNTYwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*KbXyuIRuWnisFYbtnIUhzg.png" /></p>

<p><strong>After making your selection, enter a prompt to ask Claude Design to create the page for you:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*bvO5_7XHaQSRdjbIcj_QsA.webp" alt="" loading="lazy" decoding="async" width="391" height="153" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTEiIGhlaWdodD0iMTUzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*bvO5_7XHaQSRdjbIcj_QsA.png" /></p>

<blockquote>
  <p><em>Redesign my Blog, communicate with me in Traditional Chinese.</em></p>
</blockquote>

<h4 id="3-redesign-using-claude-design">3. Redesign Using Claude Design</h4>

<p>After submitting the conversation, Claude Design will ask you questions about the design you want:</p>

<p><img src="/assets/6bf79c5b4dab/1*EvWpwlNUXDcLI5MHUz9Jug.webp" alt="" loading="lazy" decoding="async" width="646" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDYiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6bf79c5b4dab/1*EvWpwlNUXDcLI5MHUz9Jug.png" /></p>

<ul>
  <li>
    <p>Want to see several visual style variants? AI will generate multiple options for you to choose from</p>
  </li>
  <li>
    <p>Which tweaks would you like to adjust in real time?<br />
What toggle features do you want for the debug widget in the bottom right corner?</p>
  </li>
  <li>
    <p>Fill in the rest according to your own preferences.</p>
  </li>
</ul>

<h4 id="4-claude-design-cooking">4. Claude Design Cooking…</h4>

<p><img src="/assets/6bf79c5b4dab/1*pBQcJF9Bim581CsPHGdlxQ.webp" alt="" loading="lazy" decoding="async" width="1109" height="1302" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTA5IiBoZWlnaHQ9IjEzMDIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6bf79c5b4dab/1*pBQcJF9Bim581CsPHGdlxQ.png" /></p>

<p>It takes about 3–5 minutes for the AI to build a complete Design System (.css) and display the results using React.js/HTML.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The blog redesign is completed and saved in Blog Redesign.html.

Design System

Fonts: Noto Serif TC (Song typeface) + Inter + JetBrains Mono
Color scheme: Warm paper off-white + Red clay primary color (oklch warm tone)
Three category axes: Technology / Travel / Sharing, all using real Chinese sample content
Two directions, total 7 artboards (double-click to preview full screen)

Direction A · Magazine Style

Homepage — Header title + Featured story + Three-column secondary articles + Numbered list + Newsletter
Article page — Full single post with table of contents, drop cap, introduction, code blocks, comments, related posts
Category page — 2×2 large image index
Tag page — Visual bar statistics + tag cloud
Archive page — Timeline by year + month
About page — Personal introduction + Now section + Subscription
Direction B · Modern Publication (Substack Style) 7. Homepage — Large hero image + card grid + sidebar subscription wall

Tweaks panel (open at bottom right) allows instant switching of:

Title font (Noto Serif / Source Han Serif)
Wide layout / Standard layout
Primary color and paper color (includes 4 preset palettes: Red clay, Deep blue, Dark green, Wine red)
</code></pre></div></div>

<h4 id="5-adjusting-claude-design-results">5. Adjusting Claude Design Results</h4>

<p><img src="/assets/6bf79c5b4dab/1*fRHChM-U_a_dbtde2NcO3w.webp" alt="" loading="lazy" decoding="async" width="1200" height="769" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6bf79c5b4dab/1*fRHChM-U_a_dbtde2NcO3w.png" /></p>

<ul>
  <li>
    <p>You can click the Tweak button at the bottom right to quickly adjust parameters and see the results.</p>
  </li>
  <li>
    <p>You can click the top right corner of the page to enlarge and view the full webpage; if there are interactive features, you can also operate the mockup directly.</p>
  </li>
  <li>
    <p>If clicks are unresponsive, scrolling is disabled, the page displays incompletely, or content is cut off, you can ask the AI to make adjustments.</p>
  </li>
</ul>

<p>You can continue the conversation with the AI to add the features you want or adjust existing ones. For example, if I want to add a “buy me a coffee” donation link and comments at the end of the article, <strong>I can tell it:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*5IdYAz7JCerimasdQQl8lA.webp" alt="" loading="lazy" decoding="async" width="389" height="684" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzODkiIGhlaWdodD0iNjg0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*5IdYAz7JCerimasdQQl8lA.png" /></p>

<p><strong>Result:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*UdAS4J2B494L3RYk_Dpgcg.webp" alt="" loading="lazy" decoding="async" width="846" height="1184" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDYiIGhlaWdodD0iMTE4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6bf79c5b4dab/1*UdAS4J2B494L3RYk_Dpgcg.png" /></p>

<blockquote>
  <p>Keep interacting with AI to design the features and pages exactly as you want.</p>
</blockquote>

<h4 id="6-claude-design-completion">6. Claude Design Completion</h4>

<p>After finalizing the design, click the “Share” button at the top right → “Download project as .zip” to download the original design files:</p>

<p><img src="/assets/6bf79c5b4dab/1*HD6TIUrAQXeduhkUoRdC7g.webp" alt="" loading="lazy" decoding="async" width="477" height="450" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NzciIGhlaWdodD0iNDUwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*HD6TIUrAQXeduhkUoRdC7g.png" /></p>

<p>After downloading and extracting, place the folder into the Jekyll Blog directory:</p>

<p><img src="/assets/6bf79c5b4dab/1*ZUZcFtumkmeo9UuTACQakw.webp" alt="" loading="lazy" decoding="async" width="326" height="476" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjYiIGhlaWdodD0iNDc2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*ZUZcFtumkmeo9UuTACQakw.png" /></p>

<h4 id="7-ask-claude-code-to-implement-the-claude-design-mockup">7. Ask Claude Code to Implement the Claude Design Mockup</h4>

<p><img src="/assets/6bf79c5b4dab/1*npQOf0QkzG6qDJKwT1-f9A.webp" alt="" loading="lazy" decoding="async" width="1078" height="725" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDc4IiBoZWlnaHQ9IjcyNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6bf79c5b4dab/1*npQOf0QkzG6qDJKwT1-f9A.png" /></p>

<p>Remember to run <code class="language-plaintext highlighter-rouge">/init</code> once in the Jekyll Blog directory to let the AI recognize this is a Jekyll Blog structure.</p>

<p><img src="/assets/6bf79c5b4dab/1*sEtu11Zbw5ZkP5n69uyVmA.webp" alt="" loading="lazy" decoding="async" width="668" height="217" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NjgiIGhlaWdodD0iMjE3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*sEtu11Zbw5ZkP5n69uyVmA.png" /></p>

<blockquote>
  <p><em>Apply the theme design from ./MyBlogTheme to my blog</em></p>
</blockquote>

<h4 id="8-claude-code-baking">8. Claude Code baking…</h4>

<p><img src="/assets/6bf79c5b4dab/1*_iSl56RBPuHX9OqAjR4q-g.webp" alt="" loading="lazy" decoding="async" width="1192" height="1174" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTkyIiBoZWlnaHQ9IjExNzQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6bf79c5b4dab/1*_iSl56RBPuHX9OqAjR4q-g.png" /></p>

<p><img src="/assets/6bf79c5b4dab/1*D8RjjpPikNWEqe2G55PkhA.webp" alt="" loading="lazy" decoding="async" width="1192" height="1174" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTkyIiBoZWlnaHQ9IjExNzQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6bf79c5b4dab/1*D8RjjpPikNWEqe2G55PkhA.png" /></p>

<ul>
  <li>The initial build took about 20K Tokens (Opus 4.7 / effort: medium)</li>
</ul>

<h4 id="results">Results:</h4>

<p>Run <code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code> and open http://127.0.0.1:4000/ to see the result:</p>

<p><img src="/assets/6bf79c5b4dab/1*nvsTfFy1S2siBrAlHcbguQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="638" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYzOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6bf79c5b4dab/1*nvsTfFy1S2siBrAlHcbguQ.png" /></p>

<blockquote>
  <p>Continue to have the AI optimize and fix issues, but the basic completion level is already very high!</p>
</blockquote>

<h3 id="implementation-tips">Implementation Tips</h3>

<ul>
  <li>
    <p>You can tell the AI: <code class="language-plaintext highlighter-rouge">Your Blog will be deployed to GitHub Pages</code> or another platform, because each platform may have different features. For example, GitHub Pages only allows certain <a href="https://github.com/github/pages-gem/blob/master/lib/github-pages/plugins.rb#L21-L42" target="_blank">Jekyll Plugins</a>, and plugins not on the allowed list won’t work after deployment.</p>
  </li>
  <li>
    <p>You can tell the AI: <code class="language-plaintext highlighter-rouge">Prioritize using native Jekyll features, Jekyll Plugins, and free open-source projects to implement functionality.</code></p>
  </li>
  <li>
    <p>You can tell the AI: <code class="language-plaintext highlighter-rouge">Website design must prioritize SEO structure and RWD user experience.</code></p>
  </li>
  <li>
    <p>You can tell the AI: <code class="language-plaintext highlighter-rouge">Design a new Theme to replace the existing one</code>.</p>
  </li>
  <li>
    <p>The CI/CD YAML for deploying to GitHub Pages can also be written by AI, and it will guide you through the steps as well.</p>
  </li>
  <li>
    <p><strong>If you want to adjust the new page design, it is recommended to go back to Claude Design to modify the design draft. After adjusting, download it again and place it back in the directory; it is not recommended to ask Claude Code to do visual design directly, as it is not very good at aesthetics.</strong></p>
  </li>
</ul>

<h4 id="plugins-or-features-i-used"><strong>Plugins or Features I Used</strong></h4>

<p><strong>Jekyll Plugins:</strong></p>

<ul>
  <li>
    <p>jekyll 4.3 + kramdown (GFM) + kramdown-parser-gfm</p>
  </li>
  <li>
    <p>rouge — code highlight</p>
  </li>
  <li>
    <p>jekyll-feed — Atom feed (/feed.xml)</p>
  </li>
  <li>
    <p>jekyll-sitemap — /sitemap.xml</p>
  </li>
  <li>
    <p>jekyll-paginate-v2 — Article Pagination Feature</p>
  </li>
  <li>
    <p>jekyll-archives — Archive Pages</p>
  </li>
  <li>
    <p>jekyll-seo-tag — SEO Meta Tag / OG / Twitter Card meta</p>
  </li>
  <li>
    <p>jekyll-redirect-from — Short URL redirect</p>
  </li>
</ul>

<p><strong>CSS / Frontend Resources:</strong></p>

<ul>
  <li>
    <p>GLightbox v3 (CDN, MIT) — Image click to enlarge lightbox</p>
  </li>
  <li>
    <p>SCSS: assets/css/main.scss</p>
  </li>
  <li>
    <p><a href="https://fontawesome.com/" target="_blank">Font Awesome</a> icon svg</p>
  </li>
</ul>

<p><strong>JavaScript Features:</strong></p>

<ul>
  <li>
    <p>Drawer Menu (topbar menu / overlay / ESC to close)</p>
  </li>
  <li>
    <p>LQIP hydrator &amp; Lazy Load — Article Image Placeholder &amp; Lazy Loading</p>
  </li>
  <li>
    <p>Reading progress bar</p>
  </li>
  <li>
    <p>Client-side TOC</p>
  </li>
  <li>
    <p>Search: /search.json build-time + pure JS substring filter (no lunr)</p>
  </li>
</ul>

<p><strong>Third-Party Services:</strong></p>

<ul>
  <li>giscus — Comments (GitHub Discussions)</li>
</ul>

<h3 id="background">Background</h3>

<p>Since 2018, I have been writing articles on Medium to document programming and life. Later, I developed a small tool <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a> that can download Medium articles and convert them into Markdown files for backup. I also built an independent Blog website using Jekyll + Chirpy theme, deployed on the free GitHub Pages.</p>

<p><a href="https://github.com/jekyll/jekyll" target="_blank"><img src="https://repository-images.githubusercontent.com/65252/f2b7c780-70b6-11e9-85d2-f4bda8708a2d" alt="" /></a></p>

<p>Jekyll is a convenient static site generator that converts Markdown and templates into HTML, which can then be uploaded to a hosting service to publish content online.</p>

<ul>
  <li>
    <p>I also created my own Linktree using Jekyll: <a href="https://link.zhgchg.li" target="_blank">https://link.zhgchg.li</a></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">jekyll new blog</code> quickly creates a default Blog project</p>
  </li>
</ul>

<p><strong>The default Jekyll Blog project style is very basic:</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*SB1pZSaMsMvUG8AiUla62w.webp" alt="" loading="lazy" decoding="async" width="792" height="937" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTIiIGhlaWdodD0iOTM3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6bf79c5b4dab/1*SB1pZSaMsMvUG8AiUla62w.png" /></p>

<p>However, finding a satisfactory Jekyll Blog Theme is not easy. Some have beautiful homepages, some offer great article page experiences, and others have good list pages. Even paid themes rarely meet all expectations. Sometimes, after installing and setting everything up, you realize a feature or page is missing, forcing you to abandon it and start over, which is very frustrating.</p>

<p><a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank"><img src="https://repository-images.githubusercontent.com/165360641/257ebaa6-61dd-40a4-b075-d21b546026d6" alt="" /></a></p>

<p><strong>After several rounds of searching back and forth, I finally found a theme with a decent layout, responsive design, and complete blog features — <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Theme Chirpy</a> :</strong></p>

<p><img src="/assets/6bf79c5b4dab/1*Ayc3pF9qbahE5U2BnzixaQ.webp" alt="Live Demo" loading="lazy" decoding="async" width="1576" height="1143" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTc2IiBoZWlnaHQ9IjExNDMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6bf79c5b4dab/1*Ayc3pF9qbahE5U2BnzixaQ.png" /></p>

<p><a href="https://chirpy.cotes.page/posts/text-and-typography/" target="_blank">Live Demo</a></p>

<p>I have been using it for 5 years. There were times when I got tired of it and wanted to switch; but after going through a cycle of searching, I always ended up coming back to <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank"><strong>Theme Chirpy</strong></a>, as mentioned before, I couldn’t find a better-looking and more practical theme.</p>

<blockquote>
  <p>Until Claude Design + Claude Code appeared, I finally took the plunge and asked AI to design the Blog style I wanted and implement the functional pages.</p>
</blockquote>]]></content>
  </entry><entry>
    <title type="html">Hokkaido Sapporo 6-Day Trip｜City Tour, Jozankei Hot Springs &amp;amp; Furano-Biei Light-Up</title>
    <link href="https://en.zhgchg.li/posts/travel-journals/hokkaido-sapporo-6-day-trip-city-tour-jozankei-hot-springs-furano-biei-light-up-055527a739dd/" rel="alternate" type="text/html" title="Hokkaido Sapporo 6-Day Trip｜City Tour, Jozankei Hot Springs &amp; Furano-Biei Light-Up" />
    <published>2026-04-19T23:51:51+08:00</published>
    <updated>2026-04-20T23:33:43+08:00</updated>
    <id>https://en.zhgchg.li/posts/travel-journals/055527a739dd</id><summary type="html">Explore Sapporo city, rejuvenate at Jozankei Onsen, and experience stunning Furano and Biei light displays in one seamless 6-day itinerary designed for travelers seeking immersive Hokkaido adventures.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="Travel Journals" /><category term="japan" /><category term="life" /><category term="travel" /><category term="travel-writing" /><category term="hokkaido" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/055527a739dd/1*4N6zFGkzcM6QsIulPOwslQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/travel-journals/hokkaido-sapporo-6-day-trip-city-tour-jozankei-hot-springs-furano-biei-light-up-055527a739dd/"><![CDATA[<h3 id="travelogue-2026-hokkaido-sapporo-6-day-trip--sapporo-city-jozankei-onsen-hokkaido-light-up-furano-fairy-platform--biei-blue-pond-one-day-tour">[Travelogue] 2026 Hokkaido Sapporo 6-Day Trip — Sapporo City, Jozankei Onsen, Hokkaido Light-Up (Furano Fairy Platform &amp; Biei Blue Pond) One-Day Tour</h3>

<p>Complete Record of Walking in Sapporo City, Soaking in Jozankei, and Illuminations at Furano Fairy Hill and Biei Blue Pond</p>

<p><img src="/assets/055527a739dd/1*4N6zFGkzcM6QsIulPOwslQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="760" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc2MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*4N6zFGkzcM6QsIulPOwslQ.png" /></p>

<p>I have been working at my new job for almost half a year and just finished a project. Since I have never been to snowy Japan, I decided to take a trip to Hokkaido in mid-March.</p>

<h3 id="preparation-before-departure">Preparation Before Departure</h3>

<h4 id="plan">Plan</h4>

<ul>
  <li>
    <p>Day 1: After arriving in Sapporo, head straight to Jozankei Onsen for an overnight stay</p>
  </li>
  <li>
    <p>Day 2: Return to Sapporo City for sightseeing</p>
  </li>
  <li>
    <p>Day 3: Otaru, Otaru Aquarium</p>
  </li>
  <li>
    <p>Day 4: <a href="https://www.kkday.com/zh-tw/product/157133-hokkaido-sapporo-biei-blue-pond-illumination-tour-japan?cid=19365" target="_blank"><strong>KKday Hokkaido Illumination Tour ｜ Furano Fairy Platform &amp; Biei Blue Pond &amp; Shirahige Falls One-Day Tour ｜ Departure from Sapporo</strong></a></p>
  </li>
  <li>
    <p>Day 5: Shopping and Hokkaido Shrine</p>
  </li>
  <li>
    <p>Day 6: Head to the airport for departure</p>
  </li>
</ul>

<h4 id="line">Line</h4>

<h4 id="flight--china-airlines"><strong>Flight — China Airlines</strong></h4>

<ul>
  <li>
    <p>🛫 03/15 Departure CI130: TPE Taoyuan International Airport T2 <strong>08:35</strong> (actual delay 3 minutes) -&gt; CTS New Chitose Airport, Hokkaido <strong>13:15</strong> (actual delay 7 minutes)</p>
  </li>
  <li>
    <p>🛬 03/20 Return Flight CI131: CTS New Chitose Airport <strong>14:20</strong> (actual delay 8 minutes) -&gt; TPE Taoyuan International Airport <strong>18:15</strong> (actual arrival 3 minutes early)</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT $24,356 per person</em></p>
</blockquote>

<blockquote>
  <p>*Because the snow season is not over yet, prices are still high. Returning on Friday is better since weekends are even more expensive…</p>
</blockquote>

<h4 id="sapporo-station---jozankei-round-trip-bus-ticket"><strong>Sapporo Station &lt;-&gt; Jozankei Round-Trip Bus Ticket</strong></h4>

<p><img src="/assets/055527a739dd/1*WwGk0L6ccnJi3FweO4qr_g.webp" alt="" loading="lazy" decoding="async" width="848" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*WwGk0L6ccnJi3FweO4qr_g.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*i7zUMAOkPm7RwhcYbs3xVA.webp" alt="https://jozankei.jp/tw/news/newaccess/" loading="lazy" decoding="async" width="1200" height="848" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*i7zUMAOkPm7RwhcYbs3xVA.jpeg" /></p>

<p>https://jozankei.jp/tw/news/newaccess/</p>

<p>There are many JR trains from New Chitose Airport to Sapporo Station, and since the flight arrival time is uncertain, I didn’t buy tickets in advance. To get to Jozankei Onsen, you can only take a bus. According to the <a href="https://jozankei.jp/tw/news/newaccess/" target="_blank">latest transportation information as of December 2025</a>, there are three routes to choose from:</p>

<ol>
  <li>
    <p><strong>[Reservation required][About 60 minutes] Sapporo Station → Take the Kappa Liner bus → Jozankei Onsen.</strong><br />
<strong>Click here to view the timetable</strong></p>
  </li>
  <li>
    <p>[No reservation needed][About 70 minutes] Sapporo Station → Makomanai Station → Bus → Jozankei Onsen.</p>
  </li>
  <li>
    <p>[No reservation required][About 120 minutes] New Chitose Airport → <a href="https://www.hokto.co.jp/makomanai-chitose/" target="_blank">Airport Bus (Hokuto Kotsu)</a> → Makomanai Station → Jozankei Onsen.</p>
  </li>
</ol>

<p><strong>Route (1) — <a href="https://www.jotetsu.co.jp/bus/kappa_liner/index.html" target="_blank">かっぱライナー号 / Kappa Liner</a> is the most direct and convenient option</strong>, I checked the schedule and found that after the plane lands, clearing customs, and taking the JR to Sapporo Station, the 4:00 PM departure (winter season only) is more than enough time. So I booked it online in advance (foreigners can book too!).</p>

<p><strong>Outbound: Sapporo Station -&gt; Jozankei:</strong></p>

<p><img src="/assets/055527a739dd/1*cAKpufRPO7_kJuG-f3l74w.webp" alt="&lt;https://www.jotetsu.co.jp/bus/kappa_liner/index.html&gt;" loading="lazy" decoding="async" width="868" height="619" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjgiIGhlaWdodD0iNjE5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*cAKpufRPO7_kJuG-f3l74w.png" /></p>

<p><a href="https://www.jotetsu.co.jp/bus/kappa_liner/index.html" target="_blank">https://www.jotetsu.co.jp/bus/kappa_liner/index.html</a></p>

<ul>
  <li>Note that <strong>the trips marked in red only operate in autumn and winter</strong>, and the departures from Sapporo Station at 16:00 and 17:00 only go as far as the Jozankei Bus Garage.</li>
</ul>

<p><strong>Return Trip Jozankei -&gt; Sapporo Station:</strong></p>

<p><img src="/assets/055527a739dd/1*qVXeuuYsNv__vp_ye6jnvw.webp" alt="&lt;https://www.jotetsu.co.jp/bus/kappa_liner/index.html&gt;" loading="lazy" decoding="async" width="923" height="535" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjMiIGhlaWdodD0iNTM1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*qVXeuuYsNv__vp_ye6jnvw.png" /></p>

<p><a href="https://www.jotetsu.co.jp/bus/kappa_liner/index.html" target="_blank">https://www.jotetsu.co.jp/bus/kappa_liner/index.html</a></p>

<ul>
  <li>For the return trip, I planned to check out at 11:00 and stroll around the hot spring street before leaving, so I booked the 13:15 departure bus (boarding at Shiraito Falls at 13:31).</li>
</ul>

<p><strong>Reservation Method:</strong></p>

<ol>
  <li>Register as a member on the <a href="https://jotetsu.quicktrip.jp/buy" target="_blank">official reservation website</a></li>
</ol>

<p><img src="/assets/055527a739dd/1*CzG4MgskigUp8NXhe3xExw.webp" alt="" loading="lazy" decoding="async" width="767" height="484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjciIGhlaWdodD0iNDg0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*CzG4MgskigUp8NXhe3xExw.png" /></p>

<ul>
  <li>
    <p>Email / Taiwan phone number OK.</p>
  </li>
  <li>
    <p>Be sure to remember your account and password, as the e-ticket is activated online directly for the driver to verify.</p>
  </li>
</ul>

<ol>
  <li>Choose to purchase <a href="https://jotetsu.quicktrip.jp/buy/reservation?salesTicketId=1943" target="_blank">かっぱライナー号 / Kappa Liner</a> round-trip or one-way tickets</li>
</ol>

<p><img src="/assets/055527a739dd/1*HjF2Kh18Rm6CxchhV8rP7g.webp" alt="" loading="lazy" decoding="async" width="721" height="187" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjEiIGhlaWdodD0iMTg3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*HjF2Kh18Rm6CxchhV8rP7g.png" /></p>

<ol>
  <li>Select Number of People</li>
</ol>

<p><img src="/assets/055527a739dd/1*BuuwoHzpfc4R2XeDb-0Luw.webp" alt="" loading="lazy" decoding="async" width="745" height="279" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDUiIGhlaWdodD0iMjc5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*BuuwoHzpfc4R2XeDb-0Luw.png" /></p>

<ol>
  <li>Select the departure date and flight/train (times are shown as departure from Sapporo)</li>
</ol>

<p><img src="/assets/055527a739dd/1*6JVbtozEiw37fj0EqFDADw.webp" alt="" loading="lazy" decoding="async" width="779" height="1209" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzkiIGhlaWdodD0iMTIwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*6JVbtozEiw37fj0EqFDADw.png" /></p>

<ol>
  <li>Choose the boarding and alighting locations for the outbound trip</li>
</ol>

<p><img src="/assets/055527a739dd/1*apTSepE2RXgoJmbK_IoFjA.webp" alt="" loading="lazy" decoding="async" width="762" height="486" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjIiIGhlaWdodD0iNDg2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*apTSepE2RXgoJmbK_IoFjA.png" /></p>

<p>Boarding and alighting stations can be referred to in the chart below:</p>

<p><img src="/assets/055527a739dd/1*NyUAFnQb4KqMSoXiRiT6iw.webp" alt="&lt;https://www.jotetsu.co.jp/bus/kappa_liner/index.html&gt;" loading="lazy" decoding="async" width="1200" height="736" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjczNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*NyUAFnQb4KqMSoXiRiT6iw.png" /></p>

<p><a href="https://www.jotetsu.co.jp/bus/kappa_liner/index.html" target="_blank">https://www.jotetsu.co.jp/bus/kappa_liner/index.html</a></p>

<p>Our hotel is closer to <strong>(2) <a href="https://jozankei.jp/tw/spot/1449/" target="_blank">Shiraito Falls/白糸の滝/Shiraitonotaki</a></strong>, so we booked to get off at this stop.</p>

<blockquote>
  <p><em>Later, I found out you can continue riding the loop and get off at <strong>(7) Jozankei Onsen Higashi 2 Chome / 定山渓温泉東2丁目</strong> stop, which is right at the hotel entrance.</em></p>
</blockquote>

<ol>
  <li>
    <p>Select the return date and departure time (times are based on the departure station), as well as the boarding and alighting locations</p>
  </li>
  <li>
    <p>Complete payment online with credit card</p>
  </li>
</ol>

<blockquote>
  <p><em>Price: ¥2,920 JPY/adult.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*5flfur4c5dHTJsugsXkN0A.webp" alt="" loading="lazy" decoding="async" width="569" height="1013" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjkiIGhlaWdodD0iMTAxMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*5flfur4c5dHTJsugsXkN0A.png" /></p>

<p>You will receive an email notification once your reservation is confirmed. <strong>No need to exchange tickets in person; simply activate them online at <a href="https://jotetsu.quicktrip.jp/bought" target="_blank">the ticket website</a> and show it to the driver.</strong></p>

<h4 id="esim">eSIM</h4>

<p>Also purchased the 5G unlimited eSIM on KKday. This time in Hokkaido, the signal was only lost once in the remote mountains of Biei; otherwise, it worked fine. This time, I specifically chose a plan that supports ChatGPT for easy information lookup.</p>

<ul>
  <li>
    <p>KKday Purchase: <a href="https://www.kkday.com/zh-tw/product/137689-japan-unlimited-data-esim-card?cid=19365" target="_blank">Japan eSIM｜Docomo / KDDI / SoftBank / Rakuten｜ <strong>Supports ChatGPT and Gemini</strong></a></p>
  </li>
  <li>
    <p><strong>The eSIM must be activated while connected to the internet, preferably before boarding the plane at the airport in Taiwan.</strong></p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT $343/6 days, unlimited data (subject to fair usage policy)</em></p>
</blockquote>

<h4 id="visit-japan">Visit Japan</h4>

<p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E4%BA%AC%E9%98%AA%E7%A5%9E%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E4%BA%AC%E9%83%BD%E5%A4%A7%E9%98%AA%E7%A5%9E%E6%88%B68%E6%97%A5%E9%81%8A%E5%85%A8%E7%B4%80%E9%8C%84%E8%88%87%E5%AF%A6%E7%94%A8%E4%BA%A4%E9%80%9A%E4%BD%8F%E5%AE%BF%E6%8C%87%E5%8D%97-76d66c2e34af/#20241125-%E6%9B%B4%E6%96%B0%E5%85%A5%E5%A2%83%E5%AF%A9%E6%9F%A5%E8%88%87%E6%B5%B7%E9%97%9C%E7%94%B3%E5%A0%B1qr-code-%E5%B7%B2%E5%90%88%E4%BD%B5%E6%88%90%E5%90%8C%E4%B8%80%E5%80%8B%E5%85%A5%E5%A2%83%E5%AE%A1%E6%9F%A5%E5%8F%8A%E6%B5%B7%E5%85%B3%E7%94%B3%E6%8A%A5%E7%9A%84qr%E7%A0%81%E6%B2%92%E6%9C%89%E8%97%8D%E7%A2%BC%E9%BB%83%E7%A2%BC%E5%8D%80%E5%88%A5%E4%BB%A5%E4%B8%8B%E5%85%A7%E5%AE%B9%E5%8F%AA%E7%95%B6%E7%B4%80%E9%8C%84%E5%8F%AF%E4%BB%A5%E5%BF%BD%E7%95%A5" target="_blank">Please refer to the previous article and complete the registration in advance. Now, the immigration inspection and customs inspection use the same QR Code.</a></p>

<h3 id="stay">Stay</h3>

<h4 id="031516-1-night--jozankei-yurakusoan-ゆらく草庵-japanese-onsen-hotel"><strong>03/15–16 (1 night) — <a href="https://maps.app.goo.gl/mi1LRdo79NghFL3n7" target="_blank">Jozankei Yurakusoan ゆらく草庵 Japanese Onsen Hotel</a></strong></h4>

<p><img src="/assets/055527a739dd/1*NeSAUcKO7d4QbVw6OGaF6A.webp" alt="*3 Chome-228–1 Jozankeionsenhigashi, Minami Ward, Sapporo, Hokkaido 061–2301 Japan*" loading="lazy" decoding="async" width="512" height="362" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iMzYyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*NeSAUcKO7d4QbVw6OGaF6A.png" /></p>

<p><a href="https://maps.app.goo.gl/H3xB8zhQ7vNSeqtaA" target="_blank"><em>3 Chome-228–1 Jozankeionsenhigashi, Minami Ward, Sapporo, Hokkaido 061–2301 Japan</em></a></p>

<p>Located at the entrance of Jozankei Onsen Street.</p>

<blockquote>
  <p><em>Price: ¥52,150 JPY — 1 night double room / includes hearty Japanese breakfast / private hot spring bath / non-smoking (Hollywood Twin Room with Tatami — Non-Smoking)</em></p>
</blockquote>

<h4 id="031620-4-nights--keio-prelia-hotel-sapporo--京王プレリアホテル札幌--sapporo-keio-prelia-hotel"><strong>03/16–20 (4 nights) —</strong> <a href="https://maps.app.goo.gl/agffZjxdVngA5p5B9" target="_blank">Keio Prelia Hotel Sapporo / 京王プレリアホテル札幌 / Sapporo Keio Prelia Hotel</a></h4>

<p><img src="/assets/055527a739dd/1*3oY7bhsG8XdTjJQprMIvhQ.webp" alt="4 Chome-11–1 Kita 8 Jonishi, Kita Ward, Sapporo, Hokkaido 060–0808 Japan" loading="lazy" decoding="async" width="1144" height="832" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ0IiBoZWlnaHQ9IjgzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*3oY7bhsG8XdTjJQprMIvhQ.png" /></p>

<p><a href="https://maps.app.goo.gl/yko9ThsVPsdDj4WP6" target="_blank">4 Chome-11–1 Kita 8 Jonishi, Kita Ward, Sapporo, Hokkaido 060–0808 Japan</a></p>

<p>Located about a 5-minute walk from the back exit of Sapporo Station.</p>

<blockquote>
  <p><em>Price: ¥58,389 JPY — 4 nights in a comfortable queen room/non-smoking.</em></p>
</blockquote>

<h3 id="fun">Fun</h3>

<h4 id="kkday-one-day-tour-hokkaido-illumination-trip--furano-fairy-hill--biei-blue-pond--shirahige-falls-one-day-tour--departure-from-sapporo"><a href="https://www.kkday.com/zh-tw/product/157133-hokkaido-sapporo-biei-blue-pond-illumination-tour-japan?cid=19365" target="_blank">KKday One-Day Tour: Hokkaido Illumination Trip ｜ Furano Fairy Hill &amp; Biei Blue Pond &amp; Shirahige Falls One-Day Tour ｜ Departure from Sapporo</a></h4>

<p><img src="/assets/055527a739dd/1*5vq3ePeRaN3GcAHfFINuvA.webp" alt="Hokkaido Illumination Tour ｜ Furano Fairy Platform &amp; Biei Blue Pond &amp; Shirahige Waterfall One-Day Tour ｜ Departure from Sapporo" loading="lazy" decoding="async" width="1175" height="1075" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc1IiBoZWlnaHQ9IjEwNzUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*5vq3ePeRaN3GcAHfFINuvA.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/157133-hokkaido-sapporo-biei-blue-pond-illumination-tour-japan?cid=19365" target="_blank">Hokkaido Illumination Tour ｜ Furano Fairy Platform &amp; Biei Blue Pond &amp; Shirahige Falls One-Day Tour ｜ Departure from Sapporo</a></p>

<ul>
  <li><strong>[Relaxed 11:50 Departure Plan B]</strong> March &amp; April 2026 \| Daytime Furano Fairy Hill &amp; Christmas Tree &amp; Shirahige Falls &amp; Nighttime Blue Pond Illumination (No Shikisai-no-oka)</li>
</ul>

<blockquote>
  <p><em>Price: NT $1,807 TWD per person.</em></p>
</blockquote>

<h4 id="kkday---official-saleotaru-aquarium-admission-ticket-hokkaido-instant-usevalid-within-30-days-of-purchase"><a href="https://www.kkday.com/zh-tw/product/139823-otaru-aquarium-admission-ticket-hokkaido?cid=19365" target="_blank">KKday — 【 Official Sale】Otaru Aquarium Admission Ticket, Hokkaido (Instant Use/Valid Within 30 Days of Purchase)</a></h4>

<p><img src="/assets/055527a739dd/1*LV2OUgLymbyCMmfLjgsfpg.webp" alt="【Official Sale】Hokkaido Otaru Aquarium Ticket (Instant Use / Valid Within 30 Days After Purchase)" loading="lazy" decoding="async" width="1158" height="939" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTU4IiBoZWlnaHQ9IjkzOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*LV2OUgLymbyCMmfLjgsfpg.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/139823-otaru-aquarium-admission-ticket-hokkaido?cid=19365" target="_blank">【Official Sale】Hokkaido Otaru Aquarium Ticket (Instant Use / Valid for 30 Days After Purchase)</a></p>

<ul>
  <li>Features include outdoor port enclosures, many performances, and a penguin walking tour during winter.</li>
</ul>

<blockquote>
  <p><em>Ready! Set! Go!</em></p>
</blockquote>

<blockquote>
  <p><a href="https://calec.china-airlines.com/eCheckin/eCheckin_home.aspx" target="_blank"><em>Seat selection and online check-in can be completed three days before departure to speed up the process.</em></a></p>
</blockquote>

<h3 id="day-1-0315--arrival-in-sapporo-hokkaido-jozankei-onsen-hotel">Day 1 (03/15) — Arrival in Sapporo, Hokkaido, Jozankei Onsen Hotel</h3>

<h4 id="0500-airport-transfer-departure"><a href="https://www.kkday.com/zh-tw/trans/airport_transfer?cid=19365" target="_blank">05:00 Airport Transfer</a> Departure</h4>

<h4 id="0535-airport-transfer-arrive-at-tpe-taoyuan-international-airport-terminal-2-t2"><a href="https://www.kkday.com/zh-tw/trans/airport_transfer?cid=19365" target="_blank">05:35 Airport Transfer</a> Arrive at TPE Taoyuan International Airport Terminal 2 (T2)</h4>

<p><img src="/assets/055527a739dd/1*6_vxPCIXLx06F53yP4zS3g.webp" alt="" loading="lazy" decoding="async" width="1066" height="512" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDY2IiBoZWlnaHQ9IjUxMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*6_vxPCIXLx06F53yP4zS3g.png" /></p>

<p>Assigned to the far corners, the counter opens only at 06:10.</p>

<p><img src="/assets/055527a739dd/1*2LMJVQ9PSoJkWNLER6nBfA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*2LMJVQ9PSoJkWNLER6nBfA.jpeg" /></p>

<p>Activate the <a href="https://www.kkday.com/zh-tw/product/137689-japan-unlimited-data-esim-card?cid=19365" target="_blank">Japan eSIM</a> during your free time.</p>

<h4 id="0640-completed-check-in--baggage-drop--immigration-security-check">06:40 Completed Check-in + Baggage Drop + Immigration Security Check</h4>

<p>Check-in and baggage drop likely took little time because we arrived early. There were many people at immigration, but the process moved quickly. We cleared immigration around 06:40.</p>

<p><img src="/assets/055527a739dd/1*oOO3XXdFpobNN_KqNEtOyw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*oOO3XXdFpobNN_KqNEtOyw.jpeg" /></p>

<p>Enjoying a leisurely breakfast Zzzzz.</p>

<h4 id="0730-waiting-at-the-gate--d6-boarding-gate">07:30 Waiting at the gate — D6 Boarding Gate</h4>

<p><img src="/assets/055527a739dd/1*G4-ZNKS-LpmIN5tRNEHe6g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*G4-ZNKS-LpmIN5tRNEHe6g.jpeg" /></p>

<p>There is still some time before boarding, so first go to <a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E6%9D%B1%E4%BA%AC%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E5%B7%9D%E8%B6%8A%E5%B0%8F%E6%B1%9F%E6%88%B6%E4%B8%80%E6%97%A5%E9%81%8A%E8%88%87%E7%86%B1%E6%B5%B7%E6%B5%B7%E4%B8%8A%E8%8A%B1%E7%81%AB%E5%A4%A7%E6%9C%83%E5%85%A8%E7%B4%80%E9%8C%84-958599363857/#%E4%BA%8C%E8%88%AA%E5%87%BA%E5%A2%83%E5%85%8D%E7%A8%85%E5%BA%97-le-labo" target="_blank">D1 to use a free massage chair (tokens can be requested from nearby shops)</a>.</p>

<h4 id="0750-preparing-to-board">07:50 Preparing to Board</h4>

<p><img src="/assets/055527a739dd/1*TqzxK3cXR-j2tfGBVLspow.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*TqzxK3cXR-j2tfGBVLspow.jpeg" /></p>

<h4 id="0838-plane-departure-tpe---cts">08:38 Plane Departure TPE -&gt; CTS</h4>

<p><img src="/assets/055527a739dd/1*kGuZuIVYW2fYPb3clzIfXg.webp" alt="" loading="lazy" decoding="async" width="788" height="512" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODgiIGhlaWdodD0iNTEyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*kGuZuIVYW2fYPb3clzIfXg.png" /></p>

<p>The flight was on a newer China Airlines aircraft, the Airbus A330–300, which has a spacious cabin and well-equipped in-flight entertainment system.</p>

<p><img src="/assets/055527a739dd/1*jTKu8WMuRVfU9IZTxQvfpg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*jTKu8WMuRVfU9IZTxQvfpg.jpeg" /></p>

<p>The food was just… so-so.</p>

<blockquote>
  <p><em>Flight time: about 3 hours 50 minutes, snacks will also be provided.</em></p>
</blockquote>

<h4 id="1322-plane-lands-tpe---cts">13:22 Plane lands TPE -&gt; CTS</h4>

<p><img src="/assets/055527a739dd/1*TAHPtMwcNQIVPH7AohEhOg.webp" alt="" loading="lazy" decoding="async" width="786" height="512" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODYiIGhlaWdodD0iNTEyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*TAHPtMwcNQIVPH7AohEhOg.png" /></p>

<h4 id="1410-completed-immigration-and-baggage-claim">14:10 Completed immigration and baggage claim</h4>

<p>Immigration was quite fast, but the luggage took a long time to arrive. Not sure if it was because there were too many people (all seats were sold out).</p>

<p><img src="/assets/055527a739dd/1*b35AwJ_fwCxrOJqJGcwOTg.webp" alt="" loading="lazy" decoding="async" width="2112" height="878" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTEyIiBoZWlnaHQ9Ijg3OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*b35AwJ_fwCxrOJqJGcwOTg.png" /></p>

<p>Follow the signs for “Domestic Flights, JR Line” all the way from the airport.</p>

<p><img src="/assets/055527a739dd/1*UFM6K_85XwH7g19kKd6vRA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*UFM6K_85XwH7g19kKd6vRA.jpeg" /></p>

<p>After reaching the other building’s lobby, go down to B1F to find the JR station. (If using the escalator, you need to take a little detour.)</p>

<p><img src="/assets/055527a739dd/1*yTccLA73k_OJ6E5OKFMWQw.webp" alt="Timetable" loading="lazy" decoding="async" width="931" height="862" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzEiIGhlaWdodD0iODYyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*yTccLA73k_OJ6E5OKFMWQw.png" /></p>

<p><a href="https://jrhokkaidonorikae.com/vtime/vtime.php?s=9650&amp;d=20260419" target="_blank">Timetable</a></p>

<h4 id="1429-take-the-rapid-service-from-new-chitose-airport-station-to-sapporo-station">14:29 Take the Rapid Service from New Chitose Airport Station to Sapporo Station</h4>

<p><img src="/assets/055527a739dd/1*J47APGl3TjqzWKm7XQQJrA.webp" alt="&lt;https://www.jrhokkaido.co.jp/global/chinese/travel/airport.html&gt;" loading="lazy" decoding="async" width="1101" height="564" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTAxIiBoZWlnaHQ9IjU2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*J47APGl3TjqzWKm7XQQJrA.png" /></p>

<p><a href="https://www.jrhokkaido.co.jp/global/chinese/travel/airport.html" target="_blank">https://www.jrhokkaido.co.jp/global/chinese/travel/airport.html</a></p>

<ul>
  <li>
    <p><a href="https://www.jrhokkaido.co.jp/global/chinese/travel/airport.html" target="_blank">Sapporo &lt;-&gt; New Chitose Airport takes about 33 - 41 minutes; <strong>IC cards like Kitaca, Suica, PASMO can be used, no need to queue at ticket machines</strong>, with 163 trips per day, very frequent.</a></p>
  </li>
  <li>
    <p><a href="https://www.jrhokkaido.co.jp/global/chinese/travel/airport.html" target="_blank">5 Non-reserved Cars, 1 Reserved Car</a></p>
  </li>
</ul>

<p><img src="/assets/055527a739dd/1*6UaZZp6c43PLTj9EhJT36w.webp" alt="" loading="lazy" decoding="async" width="965" height="781" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjUiIGhlaWdodD0iNzgxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*6UaZZp6c43PLTj9EhJT36w.png" /></p>

<blockquote>
  <p><strong><em>You can enter the station directly with a Suica card,</em></strong> <em>but I wanted to break some bills, so I still bought a ticket with coins.</em></p>
</blockquote>

<h4 id="1506-arrive-at-sapporo-station">15:06 Arrive at Sapporo Station</h4>

<p><img src="/assets/055527a739dd/1*UgPpbSYwAcb02Kb74ML8lw.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*UgPpbSYwAcb02Kb74ML8lw.jpeg" /></p>

<ul>
  <li>Used the restroom and bought some food, then continued walking to bus stop 27.</li>
</ul>

<h4 id="1515-head-to-sapporo-station-bus-stop-no-27-kappa-line-boarding-point">15:15 Head to <a href="https://maps.app.goo.gl/MScVjZQHnW84qn626" target="_blank">Sapporo Station Bus Stop No. 27 (Kappa Line Boarding Point)</a></h4>

<p><img src="/assets/055527a739dd/1*8ARDa4cvMa0NvmAMXENJkQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="929" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkyOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*8ARDa4cvMa0NvmAMXENJkQ.png" /></p>

<h4 id="1525-arrive-at-sapporo-station-bus-stop-no27-kappa-line-boarding-point-waiting-for-the-bus">15:25 Arrive at <a href="https://maps.app.goo.gl/MScVjZQHnW84qn626" target="_blank">Sapporo Station Bus Stop No.27 (Kappa Line Boarding Point)</a> Waiting for the bus</h4>

<p><img src="/assets/055527a739dd/1*XQcnNbnu6N_IBLvKtW_5Fg.webp" alt="" loading="lazy" decoding="async" width="4696" height="2060" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0Njk2IiBoZWlnaHQ9IjIwNjAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*XQcnNbnu6N_IBLvKtW_5Fg.png" /></p>

<blockquote>
  <p><em>Exit Sapporo Station Front Exit, turn left, then turn right at the next intersection. You will see it, with a red building behind it.</em></p>
</blockquote>

<ul>
  <li>Japanese service is reliable; here, staff will also confirm that you are going to Jozankei and have a reservation.</li>
</ul>

<h4 id="1600-departure-to-jozankei-onsen">16:00 Departure to Jozankei Onsen</h4>

<p><img src="/assets/055527a739dd/1*mX62B7-0IYGOOkiCFard2g.webp" alt="" loading="lazy" decoding="async" width="2504" height="2036" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTA0IiBoZWlnaHQ9IjIwMzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*mX62B7-0IYGOOkiCFard2g.png" /></p>

<ul>
  <li>
    <p><a href="https://jotetsu.quicktrip.jp/bought" target="_blank">Show the mobile login reservation ticket activation to the staff, and you can board directly.</a></p>
  </li>
  <li>
    <p>Since this is a sightseeing bus, you can hand your suitcase directly to the staff to store it in the luggage compartment.</p>
  </li>
</ul>

<p><img src="/assets/055527a739dd/1*zDFW68P2S_0QYBuo7WJN8g.webp" alt="" loading="lazy" decoding="async" width="1200" height="794" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*zDFW68P2S_0QYBuo7WJN8g.png" /></p>

<ul>
  <li>The drive takes about 50 minutes. By mid-March, there is little snow in the city, but some snow remains in the outskirts.</li>
</ul>

<h4 id="1650-arrive-at-jozankei-onsen-2-shiraito-falls--白糸の滝-stop">16:50 Arrive at Jozankei Onsen (2) Shiraito Falls / 白糸の滝 Stop</h4>

<p><img src="/assets/055527a739dd/1*U1I_hwru8pgom6R3qG_9jA.webp" alt="" loading="lazy" decoding="async" width="1200" height="795" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*U1I_hwru8pgom6R3qG_9jA.png" /></p>

<ul>
  <li>
    <p>The Kappa Line bus design features the mascot of Jozankei Onsen — the kappa.</p>
  </li>
  <li>
    <p><strong>Show the electronic ticket to the driver when getting off and inform them that you need to collect your luggage</strong></p>
  </li>
  <li>
    <p>Get off the bus and walk straight across the street to reach the hot spring hotel where we will stay today!</p>
  </li>
</ul>

<h4 id="1700-arrive-at-jozankei-yurakusoan-ゆらく草庵-japanese-onsen-hotel">17:00 Arrive at <a href="https://maps.app.goo.gl/mi1LRdo79NghFL3n7" target="_blank">Jozankei Yurakusoan ゆらく草庵 Japanese Onsen Hotel</a></h4>

<p><img src="/assets/055527a739dd/1*jXijzCdM080XooCnRKifIQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="797" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*jXijzCdM080XooCnRKifIQ.png" /></p>

<p>Very traditional Japanese style, the entire hotel is made of wood. Upon entering the lobby, there is tatami flooring where you need to change shoes and put them in the shoe cabinet.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/aQBG5TI_Zzg" title="Jozankei Yurakusoan ゆらく草庵 日式溫泉飯店" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<ul>
  <li>房間很大有獨立的廁所、私人衛浴/湯屋、客廳跟床；厲害的是雖然是傳統木造榻榻米但是整體維護的非常好， <strong>沒有任何髒髒舊舊或是霉味的感覺</strong> 。</li>
  <li>也有公共的私人湯屋可以使用 (有好幾間，沒人就可以進去)</li>
</ul>

<h4 id="1800-head-out-for-dinner">18:00 Head out for dinner</h4>

<p>The hotel offers half board, but we booked a plan with only breakfast; we went out to find food for other meals.</p>

<h4 id="1810-kita-no-kunijozankei-manseikaku-hotel-milione-3rd-floor">18:10 <a href="https://maps.app.goo.gl/x1Wu1bSFXkk8PDbm7" target="_blank">Kita no Kuni｜Jozankei Manseikaku Hotel Milione 3rd Floor</a></h4>

<p>The hot spring street mostly offers lodging with meals, so there are few restaurants outside; finally, we walked to the izakaya on the 3rd floor of another hotel, <a href="https://maps.app.goo.gl/x1Wu1bSFXkk8PDbm7" target="_blank">万世閣</a>, for dinner.</p>

<p><img src="/assets/055527a739dd/1*iCfHKvgNp00NMpSeW_MnXw.webp" alt="" loading="lazy" decoding="async" width="1200" height="424" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*iCfHKvgNp00NMpSeW_MnXw.png" /></p>

<ul>
  <li>
    <p>First Drink: Draft — Sapporo Draft Beer</p>
  </li>
  <li>
    <p>Ate pork cutlet rice and the specialty fried crucian carp (very delicious).</p>
  </li>
</ul>

<h4 id="1930-return-to-the-hotel-for-hot-spring-bath-and-rest">19:30 Return to the hotel for hot spring bath and rest</h4>

<p><img src="/assets/055527a739dd/1*IqDyTM7yqweHmbUSLNZSRQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="576" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*IqDyTM7yqweHmbUSLNZSRQ.png" /></p>

<ul>
  <li>Fortunately, there were many convenience stores along the way, so I stopped by FamilyMart to buy a late-night snack.</li>
</ul>

<blockquote>
  <p><em>Although it stopped snowing, it still gets very cold after sunset. After a long day of traveling, I returned to the hotel early to soak in the hot spring and rest.</em></p>
</blockquote>

<h3 id="day-2-0316-mon--jozankei-onsen-street-sapporo-city-attractions">Day 2 (03/16 Mon) — Jozankei Onsen Street, Sapporo City Attractions</h3>

<h4 id="0900-breakfast">09:00 Breakfast</h4>

<p><img src="/assets/055527a739dd/1*ypWZE7fthJG5vBU8uC_F2A.webp" alt="" loading="lazy" decoding="async" width="1200" height="452" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*ypWZE7fthJG5vBU8uC_F2A.png" /></p>

<p>I happened to get a window seat, and the view outside was very beautiful.</p>

<p><img src="/assets/055527a739dd/1*zIdJFU-HuUUqDTW20dTX2Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="574" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*zIdJFU-HuUUqDTW20dTX2Q.png" /></p>

<blockquote>
  <p><em>Breakfast was very hearty: mainly rice/vegetable rice, miso soup, (1) steamed pork with vegetables in the center, (2) cold salmon — can be heated using the alcohol lamp and aluminum foil in the center, (3) tamagoyaki.</em></p>
</blockquote>

<h4 id="1100-checkout-store-luggage-walk-around-jozankei-onsen-street-nearby">11:00 Checkout, Store Luggage, Walk Around Jozankei Onsen Street Nearby</h4>

<p><img src="/assets/055527a739dd/1*RIvrEU6Nizz0EnOj-extDw.webp" alt="" loading="lazy" decoding="async" width="1456" height="1151" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDU2IiBoZWlnaHQ9IjExNTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*RIvrEU6Nizz0EnOj-extDw.png" /></p>

<blockquote>
  <p><em>There was still some time before boarding the return bus at 13:31, so I took a walk around the entire Jozankei Onsen area.</em></p>
</blockquote>

<h4 id="1110-jozankei-shrine">11:10 Jozankei Shrine</h4>

<p><img src="/assets/055527a739dd/1*otr7MA3QH-m__iTJI6AxWg.webp" alt="" loading="lazy" decoding="async" width="960" height="1280" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTI4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*otr7MA3QH-m__iTJI6AxWg.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*gOkeBho1WpoY-Bowj3e5qA.webp" alt="" loading="lazy" decoding="async" width="902" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*gOkeBho1WpoY-Bowj3e5qA.png" /></p>

<p>The entire shrine was frozen under heavy snow and ice.</p>

<h4 id="1130-kappa-familys-wishing-hand-bath"><a href="https://maps.app.goo.gl/q9rNER1WAPXw9DPH7" target="_blank">11:30 Kappa Family’s Wishing Hand Bath</a></h4>

<p><img src="/assets/055527a739dd/1*dBB1541WoB5lOVGSfaG44g.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*dBB1541WoB5lOVGSfaG44g.jpeg" /></p>

<p>Wash your hands briefly.</p>

<p>The slope beside leads down to the hot spring park.</p>

<h4 id="1140-jozankei-gensen-park"><a href="https://maps.app.goo.gl/j7K1pCCkWTsPX2uKA" target="_blank">11:40 Jozankei Gensen Park</a></h4>

<p><img src="/assets/055527a739dd/1*e4RVh1Mg-cxup8svIuUpPw.webp" alt="" loading="lazy" decoding="async" width="1200" height="792" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*e4RVh1Mg-cxup8svIuUpPw.png" /></p>

<p>The park has a public footbath and a hot spring outlet where you can boil eggs.</p>

<p><img src="/assets/055527a739dd/1*FVNqZAQ-8GVQsEcLza7uMg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*FVNqZAQ-8GVQsEcLza7uMg.jpeg" /></p>

<blockquote>
  <p><em>If you want to try cooking onsen eggs, you can cross the bridge to the opposite side at <a href="https://maps.app.goo.gl/JQ8MedX36trBSw7EA" target="_blank">㈱Jozankei Bussankan Central Store</a> to buy onsen eggs, cook them, and then bring them back to the shop to eat.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*JXKfDrv0HzbuwK7HENsOcA.webp" alt="" loading="lazy" decoding="async" width="3004" height="1996" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDA0IiBoZWlnaHQ9IjE5OTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*JXKfDrv0HzbuwK7HENsOcA.png" /></p>

<h4 id="1200-passing-by-ふる川果日-jglacée"><a href="https://maps.app.goo.gl/iUFZh6uSjqmqffw88" target="_blank">12:00 Passing by ふる川果日 J.glacée</a></h4>

<p><img src="/assets/055527a739dd/1*Ap1oNMTiOhNzn6JBFs6mcg.webp" alt="" loading="lazy" decoding="async" width="1200" height="798" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Ap1oNMTiOhNzn6JBFs6mcg.png" /></p>

<p>Continuing uphill, I passed a bakery that had freshly baked apple croissants. I bought one to try, and it tasted good.</p>

<h4 id="1215-passing-by-iwato-kannon-hall"><a href="https://maps.app.goo.gl/6Ymw95Ptk4BNpX1z6" target="_blank">12:15 Passing by Iwato Kannon Hall</a></h4>

<p><img src="/assets/055527a739dd/1*QxUH_qMGIGBPCNJNG16zBw.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*QxUH_qMGIGBPCNJNG16zBw.jpeg" /></p>

<p>After passing here, go forward and then down to return to the starting hotel.</p>

<p><img src="/assets/055527a739dd/1*BhscZ015lnl7cK7NxMlAPA.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*BhscZ015lnl7cK7NxMlAPA.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*qYBqoqgOiP2n19HP1pwXEg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*qYBqoqgOiP2n19HP1pwXEg.jpeg" /></p>

<p>You can imagine how heavy the snowfall was before—the suspension bridge over the stream was completely frozen with snow and ice!</p>

<h4 id="1230-walk-back-to-the-hotel">12:30 Walk back to the hotel</h4>

<p>With some time left, we continued to visit the nearby Shiraito Falls.</p>

<p><img src="/assets/055527a739dd/1*wOsNH9LlgSPzBv2LHxB3aA.webp" alt="" loading="lazy" decoding="async" width="3312" height="2210" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMzEyIiBoZWlnaHQ9IjIyMTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*wOsNH9LlgSPzBv2LHxB3aA.png" /></p>

<p>I had to take a detour downhill; unfortunately, it was also closed due to being frozen by snow and ice.</p>

<h4 id="1310-return-to-the-hotel-to-pick-up-luggage">13:10 Return to the hotel to pick up luggage</h4>

<h4 id="1315-arrive-at-2-shiraito-falls-bus-stop-opposite-side-from-arrival">13:15 Arrive at <a href="https://maps.app.goo.gl/XE4wcHVAofeew1DQ8" target="_blank">(2) Shiraito Falls Bus Stop (opposite side from arrival)</a></h4>

<p><img src="/assets/055527a739dd/1*6fAEfLztaLDeyirDO8CTmg.webp" alt="" loading="lazy" decoding="async" width="2956" height="1964" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTU2IiBoZWlnaHQ9IjE5NjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*6fAEfLztaLDeyirDO8CTmg.png" /></p>

<p><img src="/assets/055527a739dd/1*m8v0h-nb67YzZWgkldbdBQ.webp" alt="" loading="lazy" decoding="async" width="960" height="1280" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTI4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*m8v0h-nb67YzZWgkldbdBQ.jpeg" /></p>

<p>Our bus departs from the starting station at 13:15 and is expected to arrive at Shiraito Falls station at 13:31. The waiting time is boring, so we stepped on the snow.</p>

<h4 id="1340-board-the-bus-back-to-sapporo-station">13:40 Board the bus back to Sapporo Station</h4>

<ul>
  <li>Show the electronic ticket to the driver once when boarding and once when alighting.</li>
</ul>

<h4 id="1450-return-to-sapporo-check-in-at-keio-prelia-hotel-sapporo--京王プレリアホテル札幌--札幌京王普雷利亞飯店">14:50 Return to Sapporo, check in at <a href="https://maps.app.goo.gl/JP8qZhQiLpvaFaVq9" target="_blank">Keio Prelia Hotel Sapporo : 京王プレリアホテル札幌 : 札幌京王普雷利亞飯店</a></h4>

<p><img src="/assets/055527a739dd/1*ShHQszkicgDZYCPb-3VaHw.webp" alt="" loading="lazy" decoding="async" width="2912" height="1936" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTEyIiBoZWlnaHQ9IjE5MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*ShHQszkicgDZYCPb-3VaHw.png" /></p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/6ENDDDNTK8g" title="Keio Prelia Hotel Sapporo : 京王プレリアホテル札幌 : 札幌京王普雷利亞飯店" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<ul>
  <li>飯店很新、設備齊全，房間算大。</li>
  <li>地點在後站比較不熱鬧，要走一小段、札幌站也沒藥妝店； <strong>相較之下大通站周邊比較熱鬧。</strong></li>
</ul>

<blockquote>
  <p><em>Around 15:20, got ready and set off again. Didn’t have lunch, planning to have dinner near Odori Park earlier.</em></p>
</blockquote>

<h4 id="1530-yodobashi">15:30 <a href="https://maps.app.goo.gl/AGEuLosKQh1iSNE4A" target="_blank">Yodobashi</a></h4>

<p><img src="/assets/055527a739dd/1*6RC-qsV-DaGBnN-Yd4OSnw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*6RC-qsV-DaGBnN-Yd4OSnw.jpeg" /></p>

<p>On the way from the hotel to the JR/subway station, there is a Yodobashi (with a <a href="https://maps.app.goo.gl/3xn9SZskRd8Yzr2t9" target="_blank">McDonald’s</a> nearby). I went to check out the capsule toy machines.</p>

<h4 id="1610-arrive-at-susukino-station">16:10 Arrive at Susukino Station</h4>

<p><img src="/assets/055527a739dd/1*jdoeeUN6g1laKeVunxxbaQ.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*jdoeeUN6g1laKeVunxxbaQ.jpeg" /></p>

<p>Take the train to Susukino Station to enjoy the famous “lamb” barbecue in Sapporo.</p>

<h4 id="1615-genghis-khan-daruma-main-store-start-eating">16:15 <a href="https://maps.app.goo.gl/3TvfU2SAzF8beSFm6" target="_blank">Genghis Khan Daruma Main Store</a> Start Eating</h4>

<p><img src="/assets/055527a739dd/1*VKpLNNn6ryHdjU0c80J2Cw.webp" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*VKpLNNn6ryHdjU0c80J2Cw.png" /></p>

<ul>
  <li>
    <p>Ordered two signature dishes, one regular meat, white rice, and draft beer (large size… a bit too large).</p>
  </li>
  <li>
    <p>The meat was very delicious, with no gamey taste at all, and the texture was very tender.</p>
  </li>
  <li>
    <p>The cost for this amount was about 700 TWD per person.</p>
  </li>
</ul>

<blockquote>
  <p><em>The restaurant is small, and there are other branches nearby. By the time we finished eating close to 5 PM, there were already many people lining up outside.</em></p>
</blockquote>

<h4 id="1700-susukino-station-intersection"><a href="https://maps.app.goo.gl/kN9qw85rbk16KAWh6" target="_blank">17:00 Susukino Station Intersection</a></h4>

<p><img src="/assets/055527a739dd/1*0_Uo2gxSY01IJ7PS2jgWCg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*0_Uo2gxSY01IJ7PS2jgWCg.jpeg" /></p>

<p>After eating, we slowly walked back to Odori Station, intending to visit the <a href="https://maps.app.goo.gl/HdrBr15BZhTY9xoy5" target="_blank">Sapporo TV Tower</a>. On the way, we passed by Sapporo’s most famous intersection.</p>

<h4 id="1715-passing-by-tanukikoji-for-some-gacha-and-don-quijote-shopping">17:15 Passing by <a href="https://maps.app.goo.gl/t9UREfvkAkyNrCim6" target="_blank">Tanukikoji for some gacha and Don Quijote shopping</a></h4>

<p><img src="/assets/055527a739dd/1*Z0HU0h6iFvmVKFFxlFcrTA.webp" alt="" loading="lazy" decoding="async" width="2048" height="911" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjkxMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Z0HU0h6iFvmVKFFxlFcrTA.png" /></p>

<p>There is a whole building MEGA <a href="https://maps.app.goo.gl/t9UREfvkAkyNrCim6" target="_blank">Don Quijote</a> here.</p>

<h4 id="1741-sapporo-tv-tower">17:41 <a href="https://maps.app.goo.gl/HdrBr15BZhTY9xoy5" target="_blank">Sapporo TV Tower</a></h4>

<p><img src="/assets/055527a739dd/1*uW87zB98sSmv7KTGQBwWRw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*uW87zB98sSmv7KTGQBwWRw.jpeg" /></p>

<h4 id="1830-sapporo-tv-tower-night-view">18:30 <a href="https://maps.app.goo.gl/HdrBr15BZhTY9xoy5" target="_blank">Sapporo TV Tower</a> Night View</h4>

<p><img src="/assets/055527a739dd/1*66Xgue8l-kfCCv_N8iGa3Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="798" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*66Xgue8l-kfCCv_N8iGa3Q.png" /></p>

<p>Stayed at Sapporo Tower until about 18:30 and came down after enjoying the night view.</p>

<p><img src="/assets/055527a739dd/1*XgVlX-8dfSDPQzb3ajJzug.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*XgVlX-8dfSDPQzb3ajJzug.jpeg" /></p>

<p>You can also collect stamps while gathering postcards.</p>

<p><img src="/assets/055527a739dd/1*D5X9adov6oTZWgf4qOMZUQ.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*D5X9adov6oTZWgf4qOMZUQ.jpeg" /></p>

<h4 id="1840-parco-sapporo-shopping"><a href="https://maps.app.goo.gl/pexoh92RLGVdG9N36" target="_blank">18:40 PARCO Sapporo</a> Shopping</h4>

<p><img src="/assets/055527a739dd/1*f44BK8MmMCPBRjyRgD9N4g.webp" alt="" loading="lazy" decoding="async" width="3540" height="1560" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNTQwIiBoZWlnaHQ9IjE1NjAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*f44BK8MmMCPBRjyRgD9N4g.png" /></p>

<p>Right after exiting the TV Tower, the next intersection is Parco Sapporo store. Let’s go shopping.</p>

<h4 id="2030-buy-late-night-snacks-at-the-convenience-store-and-return-to-the-hotel-to-rest">20:30 Buy late-night snacks at the convenience store and return to the hotel to rest</h4>

<p><img src="/assets/055527a739dd/1*x52OxjlfB-Udv1DWwxrSVA.webp" alt="" loading="lazy" decoding="async" width="1200" height="449" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*x52OxjlfB-Udv1DWwxrSVA.png" /></p>

<p>Dinner was early, so I had to eat more for a late-night snack; I bought convenience store instant noodles, skewers, fried chicken, and strawberry fruit wine (delicious), plus <a href="https://the-sapporo-bar.susukino-brewing.com/" target="_blank"><strong>Hokkaido The Sapporo Bar collaboration whiskey chocolate ice cream</strong></a> (it really has alcohol flavor and tastes quite good).</p>

<h3 id="day-3-0317-tue--otaru-otaru-aquarium">Day 3 (03/17 Tue) — Otaru, Otaru Aquarium</h3>

<h4 id="0930-leave-the-house">09:30 Leave the house</h4>

<p><img src="/assets/055527a739dd/1*NCc4ExDsn14kx-uIpTxBWw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*NCc4ExDsn14kx-uIpTxBWw.jpeg" /></p>

<p>Bought a McDonald’s breakfast to eat in the car.</p>

<h4 id="1000-board-at-sapporo-station-to-otaru-station">10:00 Board at Sapporo Station to Otaru Station</h4>

<p><img src="/assets/055527a739dd/1*sVzzX65_gTfTTnwA5kG-XA.webp" alt="" loading="lazy" decoding="async" width="379" height="153" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNzkiIGhlaWdodD0iMTUzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*sVzzX65_gTfTTnwA5kG-XA.png" /></p>

<blockquote>
  <p><strong><em>The train was a local train, very crowded, so I had to stand,</em></strong> <em>there was no way to eat on the train.</em></p>
</blockquote>

<h4 id="1042-arrive-at-otaru-station">10:42 Arrive at Otaru Station</h4>

<p><img src="/assets/055527a739dd/1*pH7YPEhWohZ5L9mVmdhX7A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*pH7YPEhWohZ5L9mVmdhX7A.jpeg" /></p>

<p>You can tell many people come to Otaru by looking at the crowd.</p>

<h4 id="1045-otaru-station">10:45 Otaru Station</h4>

<p><img src="/assets/055527a739dd/1*0oQhRkSceYFsf3jHZwV9Bg.webp" alt="" loading="lazy" decoding="async" width="1074" height="810" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDc0IiBoZWlnaHQ9IjgxMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*0oQhRkSceYFsf3jHZwV9Bg.png" /></p>

<p>Exit Otaru Station from the front, cross the street, and the bus stop is on the right. Wait at platform 3 for the <strong>10 Otaru Aquarium Line:</strong></p>

<p><img src="/assets/055527a739dd/1*1iMFSEhrHNtnxkYzh8O30Q.webp" alt="Image Source: Google Map - K T (torute)" loading="lazy" decoding="async" width="935" height="907" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzUiIGhlaWdodD0iOTA3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*1iMFSEhrHNtnxkYzh8O30Q.png" /></p>

<p><a href="https://maps.app.goo.gl/p7mnP8KpppyUdtwu7" target="_blank">Image source: Google Map</a> - <a href="https://maps.google.com/maps/contrib/117333353874320617109" target="_blank">K T (torute)</a></p>

<p><img src="/assets/055527a739dd/1*irY-RRWlc8mKIiM6s6bdXw.webp" alt="" loading="lazy" decoding="async" width="734" height="826" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MzQiIGhlaWdodD0iODI2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*irY-RRWlc8mKIiM6s6bdXw.png" /></p>

<ul>
  <li>
    <p><strong>10 Otaru Aquarium Line</strong>: It is almost direct and takes about 20 minutes to reach Otaru Aquarium.</p>
  </li>
  <li>
    <p><strong>11 Shukutsu Line</strong>: Not direct, but it goes there; takes about 30 minutes.</p>
  </li>
</ul>

<h4 id="1110-arrive-at-otaru-aquarium">11:10 Arrive at Otaru Aquarium</h4>

<p><img src="/assets/055527a739dd/1*f3ZL6ho5OMt3wYHfVRT-TQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="452" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*f3ZL6ho5OMt3wYHfVRT-TQ.png" /></p>

<p>Simply show the QR code of the online purchased <a href="https://www.kkday.com/zh-tw/product/139823-otaru-aquarium-admission-ticket-hokkaido?cid=19365" target="_blank">【Official Sale】Hokkaido Otaru Aquarium Admission Ticket (Instant Use/Valid Within 30 Days After Purchase)</a> at the counter to enter directly.</p>

<h4 id="otaru-aquarium-homemade-map">Otaru Aquarium Homemade Map</h4>

<p><img src="/assets/055527a739dd/1*H0BTCoeqStQ7n4POyH6ymw.webp" alt="" loading="lazy" decoding="async" width="2522" height="1918" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTIyIiBoZWlnaHQ9IjE5MTgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*H0BTCoeqStQ7n4POyH6ymw.png" /></p>

<ul>
  <li>
    <p>The Ferris wheel is closed in winter. It is outside the aquarium area and requires a separate ticket.</p>
  </li>
  <li>
    <p>Although Otaru Aquarium is small, it has a rich variety of species and is well maintained; there are many shows, making the overall experience quite enjoyable.</p>
  </li>
  <li>
    <p>The red line marks the penguin walking route.</p>
  </li>
</ul>

<p><img src="/assets/055527a739dd/1*VpRX9psi2InntONIV365jA.webp" alt="The time varies by season. Please refer to the official website information." loading="lazy" decoding="async" width="3116" height="1400" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTE2IiBoZWlnaHQ9IjE0MDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*VpRX9psi2InntONIV365jA.png" /></p>

<p>Opening hours may vary by season. Please <a href="https://uu-beihaidao.tw/corporate/otaru-aq.shtml" target="_blank">refer to the official website</a>.</p>

<h4 id="turn-right-after-entering-and-walk-down--marine-mammal-park">Turn right after entering and walk down — Marine Mammal Park</h4>

<p><img src="/assets/055527a739dd/1*HjDF6BvQMKbWcx1HpFS4yA.webp" alt="" loading="lazy" decoding="async" width="1200" height="913" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkxMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*HjDF6BvQMKbWcx1HpFS4yA.png" /></p>

<p><img src="/assets/055527a739dd/1*frqiW_5h5tX81n9DqKUXlw.webp" alt="" loading="lazy" decoding="async" width="1200" height="685" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*frqiW_5h5tX81n9DqKUXlw.png" /></p>

<p>There is only an escalator going uphill here; the downhill is quite shaky.</p>

<p><strong>All outdoor performances are here:</strong></p>

<ul>
  <li>
    <p>Seal Activity: At 11:20 when we arrived, a show was in progress</p>
  </li>
  <li>
    <p>Sea Lion Show (Giant Sea Lion): 11:40</p>
  </li>
  <li>
    <p>Penguin Picnic: 12:00</p>
  </li>
  <li>
    <p>Penguin activity: Not seen</p>
  </li>
  <li>
    <p>Walrus feeding explanation: Not seen</p>
  </li>
</ul>

<p><img src="/assets/055527a739dd/1*DB-wHVOUcIjP73sMVSXeCA.webp" alt="" loading="lazy" decoding="async" width="2804" height="2350" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyODA0IiBoZWlnaHQ9IjIzNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*DB-wHVOUcIjP73sMVSXeCA.png" /></p>

<h4 id="1140-sea-lion-show">11:40 Sea Lion Show</h4>

<p><img src="/assets/055527a739dd/1*0cK0DaJLgqU8v2aqloSntg.webp" alt="" loading="lazy" decoding="async" width="3966" height="1756" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTY2IiBoZWlnaHQ9IjE3NTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*0cK0DaJLgqU8v2aqloSntg.png" /></p>

<p>Arrived early and stood in the front row. The sea lions are really huge!</p>

<h4 id="seals">Seals</h4>

<p><img src="/assets/055527a739dd/1*VlGMYBS8lHbzv--Fnoccxg.webp" alt="" loading="lazy" decoding="async" width="4086" height="1964" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDg2IiBoZWlnaHQ9IjE5NjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*VlGMYBS8lHbzv--Fnoccxg.png" /></p>

<p>Next to the show is the seal pool, where each seal looks very relaxed.</p>

<blockquote>
  <p><em>You can buy fish to feed the seals and sea lions for interaction. <strong>There is a limited quantity, and some pools do not allow feeding, so please check the signs.</strong></em></p>
</blockquote>

<h4 id="1200-penguin-walk">12:00 Penguin Walk</h4>

<p><img src="/assets/055527a739dd/1*OI0aGYtaim4YBadFYp91nQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="602" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*OI0aGYtaim4YBadFYp91nQ.png" /></p>

<blockquote>
  <p><em>The complete route is shown by the red line in the above image, ending at the penguin pool for a splash.</em></p>
</blockquote>

<p>After watching the sea lions, I went to the penguin walking route. There is a white line on the floor; stand outside the line and wait for the penguins to pass by. I stood at the corner here (where the person is in the photo above).</p>

<p><img src="/assets/055527a739dd/1*FxieyG5_jW2rSbBjlz4jPg.webp" alt="" loading="lazy" decoding="async" width="1928" height="1772" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTI4IiBoZWlnaHQ9IjE3NzIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*FxieyG5_jW2rSbBjlz4jPg.png" /></p>

<p>They are little penguins, somewhat like the ones in Nankan.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/iGPgTEcVJWc" title="小樽水族館 企鵝散步 (海獸公園) / おたる水族館 ペンギンの海まで遠足（海獣公園） / Otaru Aquarium Penguin Picnic (Marine mammal park)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><strong>Finally, we will walk to the penguin pond to splash some water:</strong></p>

<p><img src="/assets/055527a739dd/1*AC942rnUoH5eUTxG8hpdfQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*AC942rnUoH5eUTxG8hpdfQ.jpeg" /></p>

<h4 id="also-go-see-other-indoor-animals-seals-walruses">Also go see other indoor animals: (seals, walruses)</h4>

<p><img src="/assets/055527a739dd/1*L4Ow3BnB1I2VaSfm-HraCw.webp" alt="" loading="lazy" decoding="async" width="1200" height="794" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*L4Ow3BnB1I2VaSfm-HraCw.png" /></p>

<p>After watching, head towards the dolphinarium:</p>

<p><img src="/assets/055527a739dd/1*YJq8kpXFK_b7gwOu7fw9tQ.webp" alt="" loading="lazy" decoding="async" width="2808" height="1638" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyODA4IiBoZWlnaHQ9IjE2MzgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*YJq8kpXFK_b7gwOu7fw9tQ.png" /></p>

<p>Outside the dolphinarium, there is also a small penguin pool:</p>

<p><img src="/assets/055527a739dd/1*eSjHmjQ2Xb6J1mQjH93i1Q.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*eSjHmjQ2Xb6J1mQjH93i1Q.jpeg" /></p>

<h4 id="1240-sea-lion--dolphin-show">12:40 Sea Lion + Dolphin Show</h4>

<p><img src="/assets/055527a739dd/1*zApgCBreekd_oHyGNhuGmA.webp" alt="" loading="lazy" decoding="async" width="1200" height="798" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*zApgCBreekd_oHyGNhuGmA.png" /></p>

<p>After the visit, return to the main building to explore the marine life in the city:</p>

<p><img src="/assets/055527a739dd/1*7JOgPNrhoEEWZ9fvaDk7dw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*7JOgPNrhoEEWZ9fvaDk7dw.jpeg" /></p>

<p>The species are also quite diverse.</p>

<p><strong>Japan’s Oldest Asian Small-Clawed Otter:</strong></p>

<p><img src="/assets/055527a739dd/1*2TlrnSrYe31PT_qlk9RrMQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="577" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*2TlrnSrYe31PT_qlk9RrMQ.png" /></p>

<blockquote>
  <p><em>This otter is elderly and moves slowly and unsteadily. Please do not disturb it as it enjoys its later years.</em></p>
</blockquote>

<p>After visiting the souvenir shop, we returned to the bus stop where we arrived to wait for the bus.</p>

<h4 id="1400-take-the-bus-back-to-otaru-station">14:00 Take the bus back to Otaru Station</h4>

<h4 id="1420-return-to-otaru-station">14:20 Return to Otaru Station</h4>

<p>Walk along Ekimae Avenue toward the Otaru Canal.</p>

<p><img src="/assets/055527a739dd/1*eCDjh5Dp6zq7Lra8dZyQzg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*eCDjh5Dp6zq7Lra8dZyQzg.jpeg" /></p>

<p>Near the end of the street, you can see LeTAO Canal Shop:</p>

<p><img src="/assets/055527a739dd/1*j9S4vpv8COBT_jiAu_95wA.webp" alt="" loading="lazy" decoding="async" width="3360" height="1916" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMzYwIiBoZWlnaHQ9IjE5MTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*j9S4vpv8COBT_jiAu_95wA.png" /></p>

<p><img src="/assets/055527a739dd/1*sPnweRj4fwtR97NXNLchTw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*sPnweRj4fwtR97NXNLchTw.jpeg" /></p>

<p>Here, they sell souvenirs, ice cream desserts, and also have a deli café <a href="https://maps.app.goo.gl/K9PY2rFURawPitX38" target="_blank">ルタオ小樽運河店 三番庫カフェ</a>.</p>

<p><img src="/assets/055527a739dd/1*u-odR1T48phugsDA844MHQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="902" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*u-odR1T48phugsDA844MHQ.png" /></p>

<blockquote>
  <p><em>The cookies were quite tasty, but the other tin box with dried strawberry chocolate was a bit too sweet.</em></p>
</blockquote>

<h4 id="1440-letao-otaru-canal-store-third-warehouse-cafe-lunch"><a href="https://maps.app.goo.gl/K9PY2rFURawPitX38" target="_blank">14:40 LeTAO Otaru Canal Store Third Warehouse Cafe</a> Lunch</h4>

<p><img src="/assets/055527a739dd/1*Pm2hpXp23qw2J1hKJ0oixg.webp" alt="" loading="lazy" decoding="async" width="3998" height="1502" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTk4IiBoZWlnaHQ9IjE1MDIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*Pm2hpXp23qw2J1hKJ0oixg.png" /></p>

<p>I casually ordered a cooked Italian pasta to fill my stomach, along with a dessert set (the cake was delicious).</p>

<h4 id="1600-stroll-along-otaru-canal">16:00 Stroll along Otaru Canal</h4>

<p><img src="/assets/055527a739dd/1*64MslNJEByUEGfoSK0YTGA.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*64MslNJEByUEGfoSK0YTGA.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*6XxJGWGJCVSZGo-sualLdg.webp" alt="" loading="lazy" decoding="async" width="612" height="711" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MTIiIGhlaWdodD0iNzExIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*6XxJGWGJCVSZGo-sualLdg.png" /></p>

<p>After eating, continue walking along the Otaru Canal, heading towards the <a href="https://maps.app.goo.gl/fX5yyT2tVRV8Lo98A" target="_blank">Otaru Steam Clock</a>.</p>

<p><img src="/assets/055527a739dd/1*xCKpu7uLjK8VgfkbRuCSAQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*xCKpu7uLjK8VgfkbRuCSAQ.jpeg" /></p>

<p>There are many small shops, souvenir stores, and glassware shops around the <a href="https://maps.app.goo.gl/fX5yyT2tVRV8Lo98A" target="_blank">Otaru Steam Clock</a>.</p>

<p><img src="/assets/055527a739dd/1*kzVBtUi0gLMICty86NrsSA.webp" alt="" loading="lazy" decoding="async" width="3988" height="1746" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTg4IiBoZWlnaHQ9IjE3NDYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*kzVBtUi0gLMICty86NrsSA.png" /></p>

<p>Bought many cookie souvenirs here, especially the exclusive Hokkaido brand — Rokkatei.</p>

<p><img src="/assets/055527a739dd/1*skVsoy14UQ1TzJ26bBOShA.webp" alt="" loading="lazy" decoding="async" width="2920" height="1400" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTIwIiBoZWlnaHQ9IjE0MDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*skVsoy14UQ1TzJ26bBOShA.png" /></p>

<blockquote>
  <p><em>Must-buy at Rokkatei — Rum cookies (short shelf life, taste better chilled), liqueur chocolates (contain real alcohol)</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*6TcrcwJUKKGXrSHL1_vJSg.webp" alt="" loading="lazy" decoding="async" width="2724" height="1818" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNzI0IiBoZWlnaHQ9IjE4MTgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*6TcrcwJUKKGXrSHL1_vJSg.png" /></p>

<p>There is a <a href="https://maps.app.goo.gl/eq7F2zZ5P6RuUJ1H6" target="_blank">Kitaichi Glass Outlet</a> on the way that sells Hokkaido local sake with free tasting.</p>

<p><img src="/assets/055527a739dd/1*9KidHCaTaB9XSzch8ul8vA.webp" alt="" loading="lazy" decoding="async" width="4123" height="3100" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTIzIiBoZWlnaHQ9IjMxMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*9KidHCaTaB9XSzch8ul8vA.png" /></p>

<blockquote>
  <p><em>Bought a bottle of muskmelon liquor to enjoy later.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Has a thick texture with Hami melon pulp, and a strong Hami melon flavor. It’s better to drink with ice cubes and sparkling water.</em></strong></p>
</blockquote>

<h4 id="1710-walk-to-the-end-of-the-street-to-otaru-steam-clock">17:10 Walk to the end of the street to <a href="https://maps.app.goo.gl/fX5yyT2tVRV8Lo98A" target="_blank">Otaru Steam Clock</a></h4>

<p><img src="/assets/055527a739dd/1*F2amQ8v_lM3yYvDnzXta3A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*F2amQ8v_lM3yYvDnzXta3A.jpeg" /></p>

<p>It’s getting close to evening, so we started heading back.</p>

<p><img src="/assets/055527a739dd/1*4FRDy264prK1bVwCm2BpRw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*4FRDy264prK1bVwCm2BpRw.jpeg" /></p>

<h4 id="1810-return-to-otaru-station">18:10 Return to Otaru Station</h4>

<p><img src="/assets/055527a739dd/1*WI719YuzTLNf4fTMVWBOOg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*WI719YuzTLNf4fTMVWBOOg.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*hcCX6rn7zFpfD38KbgP-Jw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*hcCX6rn7zFpfD38KbgP-Jw.jpeg" /></p>

<p>Missed the 18:00 express airport bus to Sapporo, so took a random slower bus back.</p>

<h4 id="1900-return-to-sapporo-station">19:00 Return to Sapporo Station</h4>

<h4 id="1930-savoy-sapporo-station-kitaguchi-hokkaido-soup-curry">19:30 <a href="https://maps.app.goo.gl/1tAe4EyEFP2r5wC98" target="_blank">Savoy Sapporo Station Kitaguchi</a> Hokkaido Soup Curry</h4>

<p><img src="/assets/055527a739dd/1*uWJwZJH4ZnZq92cIDGUabg.webp" alt="" loading="lazy" decoding="async" width="2612" height="1482" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNjEyIiBoZWlnaHQ9IjE0ODIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*uWJwZJH4ZnZq92cIDGUabg.png" /></p>

<p>There is a highly-rated soup curry place near the hotel that offers takeout (too tired to dine out), so we bought it back to the hotel to eat.</p>

<blockquote>
  <p><em>The ingredients are very fresh, and the curry flavor does not overpower them. It is light and delicious.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*8MK6Ra5FKK9jKxpKwWrIvg.webp" alt="" loading="lazy" decoding="async" width="2356" height="1118" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMzU2IiBoZWlnaHQ9IjExMTgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*8MK6Ra5FKK9jKxpKwWrIvg.png" /></p>

<p>Late-night snack was a convenience store hot dog + melon liqueur (not bad) + Yakult 1000 reduced sugar version, to replenish probiotics (actually drink it every day, just too lazy to take photos).</p>

<h3 id="day-4-0318-wed--kkday-hokkaido-illumination-tour--furano-fairy-hill--biei-blue-pond--shirahige-waterfall-one-day-tourdeparting-from-sapporo">Day 4 (03/18 Wed) — <a href="https://www.kkday.com/zh-tw/product/157133-hokkaido-sapporo-biei-blue-pond-illumination-tour-japan?cid=19365" target="_blank"><strong>KKday Hokkaido Illumination Tour ｜ Furano Fairy Hill &amp; Biei Blue Pond &amp; Shirahige Waterfall One-Day Tour｜Departing from Sapporo</strong></a></h3>

<blockquote>
  <p><em>We chose the <strong>Relaxed 11:50 Departure Plan B</strong>, but it will be a late night, expected to return to Sapporo Odori Station at 21:40.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*r4yUA95KAdrxtt6SBKWZfA.webp" alt="" loading="lazy" decoding="async" width="872" height="783" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iNzgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*r4yUA95KAdrxtt6SBKWZfA.png" /></p>

<p>Meeting point: <a href="https://maps.app.goo.gl/dgPQLs75bVNTbVPR9" target="_blank">Odori Subway Station Exit 31</a>. You will receive a pre-trip notification email the day before with contact details for the guide/driver.</p>

<ul>
  <li>
    <p><strong>11:50 is the departure time, please arrive by 11:40</strong></p>
  </li>
  <li>
    <p><strong>11:50 is the departure time, please arrive by 11:40</strong></p>
  </li>
  <li>
    <p><strong>11:50 is the departure time, please arrive by 11:40</strong></p>
  </li>
</ul>

<h4 id="1000-leave-to-odori-station">10:00 Leave to Odori Station</h4>

<h4 id="1030-go-to-the-nearby-komeda-coffee-odori-nishi-2-chome-branch-for-breakfast">10:30 Go to the nearby <a href="https://maps.app.goo.gl/3m7TRSjJWUJAWDfi9" target="_blank">Komeda Coffee Odori Nishi 2-chome Branch</a> for breakfast</h4>

<p><img src="/assets/055527a739dd/1*HpkfNpNAwG8fjJ-Cm9BsUg.webp" alt="" loading="lazy" decoding="async" width="1200" height="451" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ1MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*HpkfNpNAwG8fjJ-Cm9BsUg.png" /></p>

<ul>
  <li>Order a drink and get a free meal bun + a la carte fried shrimp burger</li>
</ul>

<blockquote>
  <p><em>Because the trip is long, I wanted to save more energy.</em></p>
</blockquote>

<h4 id="arrive-around-1130-at-odori-subway-station-exit-31-to-wait-for-the-bus-at-the-park">Arrive around 11:30 at <a href="https://maps.app.goo.gl/dgPQLs75bVNTbVPR9" target="_blank">Odori Subway Station Exit 31</a> to wait for the bus at the park</h4>

<p><img src="/assets/055527a739dd/1*v4Yooh8ijGwkJw00MO1gMg.webp" alt="" loading="lazy" decoding="async" width="1200" height="452" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*v4Yooh8ijGwkJw00MO1gMg.png" /></p>

<p><img src="/assets/055527a739dd/1*mc74gZLhUIfqbW49JYgUXw.webp" alt="" loading="lazy" decoding="async" width="960" height="1280" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTI4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*mc74gZLhUIfqbW49JYgUXw.jpeg" /></p>

<blockquote>
  <p><em>Almost all day tours gather around this park. Make sure to confirm with the guide that you are in the right group, or you might get on the wrong bus.</em></p>
</blockquote>

<h4 id="1140-tour-guide-arrives-board-the-bus">11:40 Tour guide arrives, board the bus</h4>

<p><img src="/assets/055527a739dd/1*AFAqnskVajf2pY0py1Bn7w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*AFAqnskVajf2pY0py1Bn7w.jpeg" /></p>

<p>Last time, I joined a large group tour with a bus driver and guide for the <a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E4%B9%9D%E5%B7%9E%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E9%87%9C%E5%B1%B1%E6%96%B0%E5%B1%B1%E8%8C%B6%E8%8A%B1%E8%99%9F%E9%83%B5%E8%BC%AA%E5%85%A5%E5%A2%83%E6%97%A5%E6%9C%AC%E5%8D%9A%E5%A4%9A-%E6%B7%B1%E5%85%A5%E7%94%B1%E5%B8%83%E9%99%A2-%E5%A4%A7%E5%88%86-%E7%A6%8F%E5%B2%A1%E7%AD%89%E5%9C%B0-cb65fd5ab770/#day-9-kkday-%E9%AB%98%E5%8D%83%E7%A9%97%E4%B8%80%E6%97%A5%E9%81%8A%E9%AB%98%E5%8D%83%E7%A9%97%E7%A5%9E%E7%A4%BE%E9%AB%98%E5%8D%83%E7%A9%97%E5%B3%BD%E8%B0%B7%E5%A4%A9%E5%B2%A9%E6%88%B8%E7%A5%9E%E7%A4%BE%E5%A4%A9%E5%AE%89%E6%B2%B3%E5%8E%9F" target="_blank">KKDAY Takachiho One-Day Tour, Takachiho Shrine, Takachiho Gorge, Amano-Iwato Shrine, Amano-Yasugawara</a>. This time, I joined a small group of 6 people with only a driver-guide. The advantage is that with fewer people, moving around is faster.</p>

<p><img src="/assets/055527a739dd/1*GGf6O-gDlFpehOsdYEx-vw.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*GGf6O-gDlFpehOsdYEx-vw.jpeg" /></p>

<p>First the highway, then entering the mountain road deep in the forest—it’s really quite far.</p>

<blockquote>
  <p><strong><em>Travel time to the first attraction: about 1 hour 50 minutes.</em></strong></p>
</blockquote>

<h4 id="1330-furano-forest-elf-terrace---daytime"><a href="https://www.google.com/maps/search/?api=1&amp;query=43.3232668%2C142.3563319&amp;query_place_id=ChIJPbaM-V5Sc18RLOjzIysMtac" target="_blank">13:30 Furano Forest Elf Terrace - Daytime</a></h4>

<p><img src="/assets/055527a739dd/1*2ur7G9_kzOM1MD8eauR0fg.webp" alt="" loading="lazy" decoding="async" width="3122" height="1784" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTIyIiBoZWlnaHQ9IjE3ODQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*2ur7G9_kzOM1MD8eauR0fg.png" /></p>

<p>There is a tourist center here with restrooms, and right behind it is the Furano Ski Resort.</p>

<p><img src="/assets/055527a739dd/1*7M8YnUnPp4Srks7MyeFIHw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*7M8YnUnPp4Srks7MyeFIHw.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*aJdiFhYD9bY2-DjAjpJxog.webp" alt="" loading="lazy" decoding="async" width="3052" height="2036" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDUyIiBoZWlnaHQ9IjIwMzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*aJdiFhYD9bY2-DjAjpJxog.png" /></p>

<p><img src="/assets/055527a739dd/1*vBRe8LKhvuxUAPJ1YMuVPw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*vBRe8LKhvuxUAPJ1YMuVPw.jpeg" /></p>

<blockquote>
  <p><em>Furano Forest Fairy Terrace is a wooden boardwalk in the forest with many small cabins selling handmade goods. The area is small, and it takes less than 10 minutes to walk around. In winter, you can enjoy the snowy scenery during the day and illuminated night views in the evening.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Photography is prohibited at the store entrance and inside the store.</em></strong></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*Bh5lCi-uhSPsvKmRHxITag.webp" alt="Bought a wooden crescent moon souvenir" loading="lazy" decoding="async" width="903" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Bh5lCi-uhSPsvKmRHxITag.png" /></p>

<p>Bought a wooden moon-shaped handmade souvenir</p>

<blockquote>
  <p><em>Because we arrived early, we stayed here for 50 minutes.</em></p>
</blockquote>

<h4 id="1420-depart-for-herb-garden-furano-herb-garden-furano">14:20 Depart for <a href="https://maps.app.goo.gl/kYdVBB1XYsgGo45x7" target="_blank">Herb Garden Furano (HERB GARDEN FURANO)</a></h4>

<blockquote>
  <p><strong><em>Drive time: about 30 minutes.</em></strong></p>
</blockquote>

<blockquote>
  <p><em>Rest stop (shopping area) itinerary, <strong>no forced sales, the ice cream inside is delicious!</strong></em></p>
</blockquote>

<blockquote>
  <p><em>Stay for about 30 minutes.</em></p>
</blockquote>

<h4 id="1540-arrive-at-biei-station">15:40 Arrive at <a href="https://www.google.com/maps/search/?api=1&amp;query=43.5911991%2C142.4633615&amp;query_place_id=ChIJVzdU8tzFDF8RTXvFSGX1S8Y" target="_blank">Biei Station</a></h4>

<p><img src="/assets/055527a739dd/1*fEsxWXDi675dMyBw-cZnag.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*fEsxWXDi675dMyBw-cZnag.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*kquYhK10b7_hz5ASDgp5gQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="797" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*kquYhK10b7_hz5ASDgp5gQ.png" /></p>

<blockquote>
  <p><a href="https://www.google.com/maps/search/?api=1&amp;query=43.5911991%2C142.4633615&amp;query_place_id=ChIJVzdU8tzFDF8RTXvFSGX1S8Y" target="*blank"><em>Biei Station</em></a> <em>Very small and quite remote nearby, mainly for taking photos, using the restroom, and <strong>buying some food nearby to bring as dinner on the road</strong>.</em></p>
</blockquote>

<blockquote>
  <p><em>There is a 7–11 and a bakery nearby to choose from.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*e2sqcvDP_rl7hJpTK8FlWQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*e2sqcvDP_rl7hJpTK8FlWQ.jpeg" /></p>

<p>Bought a bread and convenience store ready-to-eat meal for dinner on the road.</p>

<blockquote>
  <p><em>Stay for about 50 minutes.</em></p>
</blockquote>

<h4 id="1630-depart-for-biei-christmas-tree">16:30 Depart for <a href="https://maps.app.goo.gl/5MbFa5QCD46tfjH67" target="_blank">Biei Christmas Tree</a></h4>

<blockquote>
  <p><em>About a 10-minute drive, roadside attraction.</em></p>
</blockquote>

<h4 id="1640-arrive-at-biei-christmas-tree">16:40 Arrive at <a href="https://maps.app.goo.gl/5MbFa5QCD46tfjH67" target="_blank">Biei Christmas Tree</a></h4>

<p><img src="/assets/055527a739dd/1*VNDn9fdGddz9VJpvUunO_g.webp" alt="" loading="lazy" decoding="async" width="3198" height="2130" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTk4IiBoZWlnaHQ9IjIxMzAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*VNDn9fdGddz9VJpvUunO_g.png" /></p>

<p><img src="/assets/055527a739dd/1*bmTIWwQmcsnzZoqe0ciDlg.webp" alt="" loading="lazy" decoding="async" width="1477" height="1108" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDc3IiBoZWlnaHQ9IjExMDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*bmTIWwQmcsnzZoqe0ciDlg.jpeg" /></p>

<p>Each season offers different scenery. Today features a snowy landscape with the sunset faintly visible.</p>

<blockquote>
  <p><em>Stay for about 20 minutes.</em></p>
</blockquote>

<h4 id="1700-depart-for-shirahige-falls">17:00 Depart for <a href="https://www.google.com/maps/search/?api=1&amp;query=43.474803813168%2C142.63934225235&amp;query_place_id=none" target="_blank">Shirahige Falls</a></h4>

<blockquote>
  <p><em>About a 30-minute drive, a bridge sightseeing spot.</em></p>
</blockquote>

<h4 id="1730-arrive-at-shirahige-falls">17:30 Arrive at <a href="https://www.google.com/maps/search/?api=1&amp;query=43.474803813168%2C142.63934225235&amp;query_place_id=none" target="_blank">Shirahige Falls</a></h4>

<p><img src="/assets/055527a739dd/1*dDpDKI44Mek6JN6zFChEhA.webp" alt="" loading="lazy" decoding="async" width="1200" height="683" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*dDpDKI44Mek6JN6zFChEhA.png" /></p>

<p>Looking down from this pedestrian iron bridge is the frozen Shirahige Waterfall.</p>

<p><img src="/assets/055527a739dd/1*tiviDULpwjoqXcl4kcncfw.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*tiviDULpwjoqXcl4kcncfw.jpeg" /></p>

<blockquote>
  <p><em>Stay for about 20 minutes.</em></p>
</blockquote>

<blockquote>
  <p><em>As evening approaches, the weather is cold; <strong>the bridge surface is icy, so walk carefully</strong>.</em></p>
</blockquote>

<h4 id="1750-depart-for-shirogane-blue-pond">17:50 Depart for <a href="https://www.google.com/maps/search/?api=1&amp;query=43.493606086454%2C142.61433241373&amp;query_place_id=none" target="_blank">Shirogane Blue Pond</a></h4>

<blockquote>
  <p><em>About a 5-minute drive.</em></p>
</blockquote>

<h4 id="1755-arrive-at-the-final-spot-shirogane-blue-pond">17:55 Arrive at the final spot <a href="https://www.google.com/maps/search/?api=1&amp;query=43.493606086454%2C142.61433241373&amp;query_place_id=none" target="_blank">Shirogane Blue Pond</a></h4>

<p><img src="/assets/055527a739dd/1*E9VIGAECznfFqG9QKyRqBA.webp" alt="" loading="lazy" decoding="async" width="2160" height="2172" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTYwIiBoZWlnaHQ9IjIxNzIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*E9VIGAECznfFqG9QKyRqBA.png" /></p>

<blockquote>
  <p><em>Stay for about 30 minutes, there is a visitor center restroom.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*l61TfdQcTz-adTlROZsmnA.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*l61TfdQcTz-adTlROZsmnA.jpeg" /></p>

<p>Walk up from the parking lot to see the Blue Pond.</p>

<p><img src="/assets/055527a739dd/1*1g4Qki4CDbE4WWOoNig2Rg.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*1g4Qki4CDbE4WWOoNig2Rg.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*68bEAmSijcpqQbGn8z58lA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*68bEAmSijcpqQbGn8z58lA.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*b92j-p7ghCGyP3peml3M3A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*b92j-p7ghCGyP3peml3M3A.jpeg" /></p>

<blockquote>
  <p><em>is a tranquil spot where the frozen, silent pond shows a hint of life through shifting light and shadows.</em></p>
</blockquote>

<h4 id="1830-return-odori-subway-station-exit-31">18:30 Return <a href="https://maps.app.goo.gl/dgPQLs75bVNTbVPR9" target="_blank">Odori Subway Station Exit 31</a></h4>

<p><img src="/assets/055527a739dd/1*VwEV_rxTwap-JzHlqs4kMw.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*VwEV_rxTwap-JzHlqs4kMw.jpeg" /></p>

<blockquote>
  <p><em>Drive time: about 2 hours 30 minutes, with a rest stop around 20:00 for everyone to use the restroom.</em></p>
</blockquote>

<h4 id="2055-arrive-at-odori-subway-station-exit-31">20:55 Arrive at <a href="https://maps.app.goo.gl/dgPQLs75bVNTbVPR9" target="_blank">Odori Subway Station Exit 31</a></h4>

<p>Overall, it was similar to my previous experience with “<a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E4%B9%9D%E5%B7%9E%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E9%87%9C%E5%B1%B1%E6%96%B0%E5%B1%B1%E8%8C%B6%E8%8A%B1%E8%99%9F%E9%83%B5%E8%BC%AA%E5%85%A5%E5%A2%83%E6%97%A5%E6%9C%AC%E5%8D%9A%E5%A4%9A-%E6%B7%B1%E5%85%A5%E7%94%B1%E5%B8%83%E9%99%A2-%E5%A4%A7%E5%88%86-%E7%A6%8F%E5%B2%A1%E7%AD%89%E5%9C%B0-cb65fd5ab770/#day-9-kkday-%E9%AB%98%E5%8D%83%E7%A9%97%E4%B8%80%E6%97%A5%E9%81%8A%E9%AB%98%E5%8D%83%E7%A9%97%E7%A5%9E%E7%A4%BE%E9%AB%98%E5%8D%83%E7%A9%97%E5%B3%BD%E8%B0%B7%E5%A4%A9%E5%B2%A9%E6%88%B8%E7%A5%9E%E7%A4%BE%E5%A4%A9%E5%AE%89%E6%B2%B3%E5%8E%9F" target="_blank">KKDAY Takachiho One-Day Tour: Takachiho Shrine, Takachiho Gorge, Ama-no-Iwato Shrine, Amano-Yasugawara</a>”: it’s mostly a quick bus tour. Visiting these spots on your own is quite troublesome since half the route is highway and the other half is mountain roads through unpopulated areas (which feel like bear country). This time, we were in a small group and the driver sped aggressively, so we returned to the city earlier than expected.</p>

<h4 id="2130-return-to-sapporo-station">21:30 Return to Sapporo Station</h4>

<p>After walking around near Odori Station, we returned to Sapporo Station. Most of the food places were already closed.</p>

<h4 id="sapporo-tsunagu-yokomachi"><a href="https://maps.app.goo.gl/kWrKsjCDcQXqZ7MW6" target="_blank"><strong>Sapporo Tsunagu Yokomachi</strong></a></h4>

<p>I checked and found there was a night market still open near the station, so I decided to walk over and take a look. Anything would do, just to grab a bite.</p>

<p><img src="/assets/055527a739dd/1*PthAxfmCvDvVAb1A96Xxcw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*PthAxfmCvDvVAb1A96Xxcw.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*chm5p8whKi2G8nLTCbNu1Q.webp" alt="" loading="lazy" decoding="async" width="3516" height="1996" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNTE2IiBoZWlnaHQ9IjE5OTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*chm5p8whKi2G8nLTCbNu1Q.png" /></p>

<p>Randomly picked a place with grilled skewers (mainly grilled eel) to have a quick bite.</p>

<blockquote>
  <p><em>This place is only open until March. I originally wanted to eat chicken wings but ended up choosing the wrong restaurant.</em></p>
</blockquote>

<h4 id="2230-return-to-the-hotel">22:30 Return to the hotel</h4>

<p><img src="/assets/055527a739dd/1*iVNNjGw0DokpzTnl6XKUyg.webp" alt="" loading="lazy" decoding="async" width="3038" height="1730" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDM4IiBoZWlnaHQ9IjE3MzAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*iVNNjGw0DokpzTnl6XKUyg.png" /></p>

<p>Still a bit hungry after eating, went to a nearby convenience store to buy some late-night snacks to eat back at the hotel.</p>

<blockquote>
  <p>S <em>eicomart sells some items that Lawson, 7-Eleven, and FamilyMart do not have.</em></p>
</blockquote>

<h3 id="day-5-0319-thu--shopping-and-hokkaido-shrine">Day 5 (03/19 Thu) — Shopping and Hokkaido Shrine</h3>

<h4 id="1000-head-out-for-breakfast-at-mcdonalds">10:00 Head out for breakfast at <a href="https://maps.app.goo.gl/3xn9SZskRd8Yzr2t9" target="_blank">McDonald’s</a></h4>

<p><img src="/assets/055527a739dd/1*38MT4SC9vjfjZBJua2NE1A.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*38MT4SC9vjfjZBJua2NE1A.jpeg" /></p>

<p><img src="/assets/055527a739dd/1*kODLJ4LXRV_ZXVa20qwZNg.webp" alt="" loading="lazy" decoding="async" width="1200" height="799" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*kODLJ4LXRV_ZXVa20qwZNg.png" /></p>

<ul>
  <li>
    <p>McGriddles Pancake Sandwich (not sold in Taiwan) — Bacon, cheese, and egg with a sweet and salty honey flavor</p>
  </li>
  <li>
    <p>Strawberry Cheesecake Pie (also later sold in Taiwan) — strawberry jam + cheese</p>
  </li>
</ul>

<h4 id="1000-odori-and-tanukikoji-continue-exploring-capsule-toy-machines-and-shopping-at-don-quijote">10:00 Odori and Tanukikoji continue exploring capsule toy machines and shopping at <a href="https://maps.app.goo.gl/t9UREfvkAkyNrCim6" target="_blank">Don Quijote</a></h4>

<p><img src="/assets/055527a739dd/1*7TSlQ_cHD2h9sBSh_w1XVQ.webp" alt="" loading="lazy" decoding="async" width="3654" height="2084" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNjU0IiBoZWlnaHQ9IjIwODQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*7TSlQ_cHD2h9sBSh_w1XVQ.png" /></p>

<p><img src="/assets/055527a739dd/1*B9w_5kkFC02FeDDpl49I_Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*B9w_5kkFC02FeDDpl49I_Q.jpeg" /></p>

<blockquote>
  <p><em>There’s not much to shop at Sapporo Station; there are more things to explore here.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*ATM-ffFeXSn1OU2MsF1VYw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*ATM-ffFeXSn1OU2MsF1VYw.jpeg" /></p>

<p>Twisted a fire alarm and some interesting capsule toys.</p>

<h4 id="1330-return-to-sapporo-station-hotel-to-drop-off-items-continue-shopping-and-eating-at-department-stores">13:30 Return to Sapporo Station hotel to drop off items, continue shopping and eating at department stores</h4>

<p><img src="/assets/055527a739dd/1*WlIAL89mRGqmZUHBO6zqTg.webp" alt="Lunch was casually a chirashi sushi and soba noodles from the department store" loading="lazy" decoding="async" width="4117" height="3093" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTE3IiBoZWlnaHQ9IjMwOTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*WlIAL89mRGqmZUHBO6zqTg.png" /></p>

<p>For lunch, I casually had chirashi sushi and soba noodles at the department store.</p>

<h4 id="le-labo-le-labo-sapporo-daimaru-store"><a href="https://maps.app.goo.gl/fdeQ5DFzfkGPGgaQ8" target="_blank">LE LABO Le Labo Sapporo Daimaru Store</a></h4>

<p>Quick glance at Le Labo 2026 / Sapporo prices:</p>

<p><img src="/assets/055527a739dd/1*Q039ga_Vw3MQgF0g0BRqwA.webp" alt="" loading="lazy" decoding="async" width="1086" height="1013" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDg2IiBoZWlnaHQ9IjEwMTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*Q039ga_Vw3MQgF0g0BRqwA.png" /></p>

<ul>
  <li>50ml ¥31,350 same as <a href="/posts/travel-journals/tokyo-area-travel-guide-kawagoe-little-edo-atami-fireworks-festival-5-day-itinerary-958599363857/">2025 Tokyo price</a></li>
</ul>

<blockquote>
  <p><em>Shopping and strolling, then depart around 15:00 to <a href="https://maps.app.goo.gl/sZDHVC7X3ctkwQzX9" target="_blank">Hokkaido Shrine</a>.</em></p>
</blockquote>

<blockquote>
  <p><em>From Sapporo, take the Namboku Line to Odori Station, then transfer to the Tozai Line to <a href="https://maps.app.goo.gl/rhiPPKBaugwZBxid6" target="_blank">Maruyama Park Station</a>. The ride takes about 30 minutes.</em></p>
</blockquote>

<h4 id="1540-arrive-at-maruyama-park-station">15:40 Arrive at <a href="https://maps.app.goo.gl/rhiPPKBaugwZBxid6" target="_blank">Maruyama Park Station</a></h4>

<p><img src="/assets/055527a739dd/1*UVfaLNsp7YdujXBEEzQHCQ.webp" alt="" loading="lazy" decoding="async" width="1858" height="2490" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxODU4IiBoZWlnaHQ9IjI0OTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*UVfaLNsp7YdujXBEEzQHCQ.png" /></p>

<blockquote>
  <p><em>If you go early, you can first take a seat at the nearby <a href="https://maps.app.goo.gl/jZe1NSebYoACZfEj8" target="_blank"><strong>famous Hokkaido seafood conveyor belt sushi Toriton</strong></a></em>.</p>
</blockquote>

<blockquote>
  <p><em>Exit Maruyama Koen Station at Exit 3, then walk straight into the park towards Hokkaido Shrine.</em></p>
</blockquote>

<blockquote>
  <p><em>Distance: about 850 meters / 15 minutes</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*S002OiQvL92tuOKH1IdGkg.webp" alt="" loading="lazy" decoding="async" width="1200" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*S002OiQvL92tuOKH1IdGkg.png" /></p>

<p>Just cross the street, and you’ll reach the park entrance:</p>

<p><img src="/assets/055527a739dd/1*D8BbsmFeID8pc06a0JjeTw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*D8BbsmFeID8pc06a0JjeTw.jpeg" /></p>

<blockquote>
  <p><em>When you meet your friends at Maruyama…</em></p>
</blockquote>

<blockquote>
  <p><em>Me: Maruyama Park 💁</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*xqB_Gf70oekdjB_7kAlyUw.webp" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*xqB_Gf70oekdjB_7kAlyUw.png" /></p>

<p>First, pass through Maruyama Park (there is still a lot of snow here), and you will see the entrance to the Jingu Torii gate:</p>

<p><img src="/assets/055527a739dd/1*cBmTSlECefOGTo6xrR-uLA.webp" alt="" loading="lazy" decoding="async" width="3092" height="4117" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDkyIiBoZWlnaHQ9IjQxMTciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*cBmTSlECefOGTo6xrR-uLA.png" /></p>

<h4 id="1550-jingu-chaya"><a href="https://maps.app.goo.gl/A4X2vpZic2F3onHD9" target="_blank">15:50 Jingu Chaya</a></h4>

<p><img src="/assets/055527a739dd/1*cDFq2pN_R-mdNkjb9J6M2Q.webp" alt="" loading="lazy" decoding="async" width="1128" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTI4IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*cDFq2pN_R-mdNkjb9J6M2Q.png" /></p>

<p>Continue walking and you will pass Jingu Chaya, where you can first enjoy some Hokkaido milk ice cream.</p>

<p><img src="/assets/055527a739dd/1*ak0-XC8XNs7U80Jv7w0SHw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*ak0-XC8XNs7U80Jv7w0SHw.jpeg" /></p>

<p>Rich and delicious.</p>

<blockquote>
  <p><strong><em>Eating is not allowed inside the shrine; please finish eating at the teahouse before leaving.</em></strong></p>
</blockquote>

<h4 id="1615-hokkaido-shrine"><a href="https://maps.app.goo.gl/sZDHVC7X3ctkwQzX9" target="_blank">16:15 Hokkaido Shrine</a></h4>

<p><img src="/assets/055527a739dd/1*geeJcHKwKdulE3ZAKdrdXw.webp" alt="" loading="lazy" decoding="async" width="1698" height="1236" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjk4IiBoZWlnaHQ9IjEyMzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*geeJcHKwKdulE3ZAKdrdXw.png" /></p>

<blockquote>
  <p><em>Worship: <strong>Throw a 5-yen coin (symbolizing good luck) -&gt; Bow twice -&gt; Clap twice -&gt; Make a wish -&gt; Finish with one bow</strong></em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*vIk2569WTqkQU_EOFagzkA.webp" alt="" loading="lazy" decoding="async" width="2826" height="1484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyODI2IiBoZWlnaHQ9IjE0ODQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*vIk2569WTqkQU_EOFagzkA.png" /></p>

<p><img src="/assets/055527a739dd/1*Mvgp7m0K3DkGJTemBXAIEw.webp" alt="" loading="lazy" decoding="async" width="1974" height="1484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTc0IiBoZWlnaHQ9IjE0ODQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*Mvgp7m0K3DkGJTemBXAIEw.png" /></p>

<p>Bought two omamori charms. The special one is the traffic safety charm, which can be hung inside a car, and it comes with a sticker that can be placed on a motorcycle, bicycle, or helmet.</p>

<h4 id="1630-leave-hokkaido-shrine">16:30 Leave Hokkaido Shrine</h4>

<p><img src="/assets/055527a739dd/1*uf7nqGUX9f_Sx-RWSieeSQ.webp" alt="" loading="lazy" decoding="async" width="3260" height="2170" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjYwIiBoZWlnaHQ9IjIxNzAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*uf7nqGUX9f_Sx-RWSieeSQ.png" /></p>

<p>Walked slowly back from the shrine and park to the subway station, preparing to return near Sapporo Station for dinner.</p>

<h3 id="1720-passing-by-sapporo-hokkaido-government-office-red-brick-building">17:20 <a href="https://maps.app.goo.gl/TPopSdeEizc4L3pt9" target="_blank">Passing by Sapporo Hokkaido Government Office Red Brick Building</a></h3>

<p><img src="/assets/055527a739dd/1*HepNYeLKdeZnUDG-5z9OjA.webp" alt="" loading="lazy" decoding="async" width="1536" height="2048" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTM2IiBoZWlnaHQ9IjIwNDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*HepNYeLKdeZnUDG-5z9OjA.jpeg" /></p>

<p>Just passing by, mainly heading to the neighboring Sapporo Mitsui for yakiniku:</p>

<p><img src="/assets/055527a739dd/1*0q_FfJh3NGkGSK2Qy3R0Yg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*0q_FfJh3NGkGSK2Qy3R0Yg.jpeg" /></p>

<h3 id="1730-yakiniku-bar-tamura-yakiniku-grilled-meat"><a href="https://maps.app.goo.gl/G8HjR4dNjvcoLA246" target="_blank">17:30 Yakiniku Bar Tamura</a> Yakiniku (Grilled Meat)</h3>

<p><img src="/assets/055527a739dd/1*VNc3gye9wK6y56eHPc2deA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*VNc3gye9wK6y56eHPc2deA.jpeg" /></p>

<p>Let’s try Hokkaido Wagyu beef.</p>

<p><img src="/assets/055527a739dd/1*AYwFzwBblluHK6-hmf_Cfw.webp" alt="" loading="lazy" decoding="async" width="3458" height="1640" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNDU4IiBoZWlnaHQ9IjE2NDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*AYwFzwBblluHK6-hmf_Cfw.png" /></p>

<blockquote>
  <p><em>Ordered the Hokkaido beef set + salad + beer + drink + cold noodles, totaling about NT$3,100; the wagyu beef was fatty and melted in the mouth, the steak was also very good, and the most impressive was the beef tongue, which was very chewy and tasty.</em></p>
</blockquote>

<h4 id="1900-sapporo-tokyu-department-store"><a href="https://maps.app.goo.gl/rR4a14JofuNjSBXT8" target="_blank">19:00 Sapporo Tokyu Department Store</a></h4>

<p><img src="/assets/055527a739dd/1*M1Jvj4MqsexEZInEN2CYiw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*M1Jvj4MqsexEZInEN2CYiw.jpeg" /></p>

<p>After eating, we took a final stroll to Tokyu Department Store.</p>

<p><img src="/assets/055527a739dd/1*ruyLfADnDmXwM1iObk1rig.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*ruyLfADnDmXwM1iObk1rig.jpeg" /></p>

<p>There are also toys and capsule toys upstairs here.</p>

<h4 id="2000-return-to-sapporo-station">20:00 Return to Sapporo Station</h4>

<p><img src="/assets/055527a739dd/1*Qm2l3HH2jAG788N4lvMRDA.webp" alt="" loading="lazy" decoding="async" width="901" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Qm2l3HH2jAG788N4lvMRDA.png" /></p>

<p>The department store is about to close, so let’s take one last look around.</p>

<h4 id="sapporo-station--sandwich-workshop-sandria"><a href="https://www.s-sandwich.com/" target="_blank">Sapporo Station — Sandwich Workshop Sandria</a></h4>

<p><img src="/assets/055527a739dd/1*6d-W6JOQaseQid4b7YrKow.webp" alt="" loading="lazy" decoding="async" width="1200" height="686" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*6d-W6JOQaseQid4b7YrKow.png" /></p>

<p>On the way back to the hotel, I bought sandwiches from a vending machine near the JR station that had a long line of people every day.</p>

<blockquote>
  <p><em>Limited to two per person. I bought one pork cutlet for a late-night snack and a potato egg salad for tomorrow’s breakfast.</em></p>
</blockquote>

<h4 id="2230-late-night-snack-on-the-last-night-in-sapporo">22:30 Late-night snack on the last night in Sapporo</h4>

<p><img src="/assets/055527a739dd/1*RUURJltr-9QnEkSuGXEsUA.webp" alt="" loading="lazy" decoding="async" width="3552" height="2020" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNTUyIiBoZWlnaHQ9IjIwMjAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*RUURJltr-9QnEkSuGXEsUA.png" /></p>

<blockquote>
  <p>Had strawberry wine and Hokkaido milk ice cream on the <em>first day.</em></p>
</blockquote>

<h3 id="day-6-0320-fri--new-chitose-airport-return-trip">Day 6 (03/20 Fri) — New Chitose Airport, Return Trip</h3>

<h4 id="1000-wake-up-and-have-breakfast-in-the-room">10:00 Wake up and have breakfast in the room</h4>

<p><img src="/assets/055527a739dd/1*O6AlKj2q5BGnpifN8xfb1Q.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*O6AlKj2q5BGnpifN8xfb1Q.jpeg" /></p>

<p>After eating and packing up, we checked out and headed straight to the airport.</p>

<h4 id="1105-waiting-for-the-bus-at-sapporo-station">11:05 Waiting for the bus at Sapporo Station</h4>

<p><img src="/assets/055527a739dd/1*NZDX273R7I57ua8K7dgQZg.webp" alt="" loading="lazy" decoding="async" width="1200" height="728" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcyOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*NZDX273R7I57ua8K7dgQZg.png" /></p>

<h4 id="1119-take-the-train-from-sapporo-station-to-new-chitose-airport">11:19 Take the train from Sapporo Station to New Chitose Airport</h4>

<blockquote>
  <p><em>Rapid Airport 68 Rapid New Chitose Airport, 38 minutes</em></p>
</blockquote>

<h4 id="1200-arrive-at-new-chitose-airport">12:00 Arrive at New Chitose Airport</h4>

<p><img src="/assets/055527a739dd/1*GY04A3vbPbQHK3yCnjUSyw.webp" alt="" loading="lazy" decoding="async" width="958" height="1115" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTgiIGhlaWdodD0iMTExNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*GY04A3vbPbQHK3yCnjUSyw.png" /></p>

<h4 id="jr-new-chitose-airport-domestic-terminal-lobby">JR New Chitose Airport, Domestic Terminal Lobby</h4>

<p><img src="/assets/055527a739dd/1*gh59koOOU8_OF3ONpsXV-g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*gh59koOOU8_OF3ONpsXV-g.jpeg" /></p>

<blockquote>
  <p><em>Take the subway up to the 1st floor lobby, where there are many food options and souvenirs. If you haven’t bought enough, you can find more here.</em></p>
</blockquote>

<h4 id="time-to-head-back-retracing-the-route-we-came-from">Time to head back… retracing the route we came from</h4>

<p><img src="/assets/055527a739dd/1*6W0gH8pkfaj0OJyXMEdLbQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*6W0gH8pkfaj0OJyXMEdLbQ.jpeg" /></p>

<h4 id="1217-arrive-at-the-international-terminal-hall">12:17 Arrive at the International Terminal Hall</h4>

<p><img src="/assets/055527a739dd/1*7WibQith1SFr80NzDbpVpw.webp" alt="" loading="lazy" decoding="async" width="1704" height="1179" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNzA0IiBoZWlnaHQ9IjExNzkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*7WibQith1SFr80NzDbpVpw.png" /></p>

<p>Assigned to the check-in counter at the far end, and you have to walk 165 meters to reach it:</p>

<p><img src="/assets/055527a739dd/1*s5JrKPKZCS7aalLWzJzQAQ.webp" alt="" loading="lazy" decoding="async" width="961" height="772" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjEiIGhlaWdodD0iNzcyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/055527a739dd/1*s5JrKPKZCS7aalLWzJzQAQ.png" /></p>

<p>Hokkaido Varied Tit:</p>

<p><img src="/assets/055527a739dd/1*vfgtjZcmirVzk2On2jCiaA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*vfgtjZcmirVzk2On2jCiaA.jpeg" /></p>

<h4 id="1222-arrive-at-the-check-in-counter">12:22 Arrive at the check-in counter</h4>

<blockquote>
  <p><em>The site is crowded, but online check-in is less busy. Therefore, be sure to complete seat selection and online check-in at least three days before the return trip to save queuing time.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*Kf8mo1c0_Q_dCgUF3cYV-g.webp" alt="" loading="lazy" decoding="async" width="1200" height="835" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Kf8mo1c0_Q_dCgUF3cYV-g.png" /></p>

<p>There is also a Don Quijote nearby for last-minute shopping before check-in:</p>

<p><img src="/assets/055527a739dd/1*Nh4v3mOEt0UkPi9ERBJ8yw.webp" alt="" loading="lazy" decoding="async" width="1200" height="833" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Nh4v3mOEt0UkPi9ERBJ8yw.png" /></p>

<h4 id="1303-check-in-and-luggage-drop-off-completed">13:03 Check-in and luggage drop-off completed</h4>

<h4 id="1317-completed-security-check-and-immigration-inspection">13:17 Completed Security Check and Immigration Inspection</h4>

<blockquote>
  <p><em>Airport security in Japan is stricter; if you wear boots, you must take them off for the X-ray.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*KEoEKjL5F66qscCtVKyLcw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*KEoEKjL5F66qscCtVKyLcw.jpeg" /></p>

<h4 id="1320-browse-while-waiting-for-the-flight">13:20 Browse While Waiting for the Flight</h4>

<p><img src="/assets/055527a739dd/1*jYqkVX8z5aFrCMr9LD7vlQ.webp" alt="" loading="lazy" decoding="async" width="3322" height="1064" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMzIyIiBoZWlnaHQ9IjEwNjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*jYqkVX8z5aFrCMr9LD7vlQ.png" /></p>

<p><img src="/assets/055527a739dd/1*vwC7ieofFNIOVl2L_0YjKA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*vwC7ieofFNIOVl2L_0YjKA.jpeg" /></p>

<blockquote>
  <p><em>There is a food street where you can eat. I only bought bread, fried chicken, and melon ice cream as light snacks.</em></p>
</blockquote>

<p><img src="/assets/055527a739dd/1*_83IZv2r6TiRQW-isqq3iw.webp" alt="" loading="lazy" decoding="async" width="1200" height="833" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*_83IZv2r6TiRQW-isqq3iw.png" /></p>

<p>The first floor has a whole row of souvenir shops where you can buy almost anything; however, there are fewer boutique stores.</p>

<p><img src="/assets/055527a739dd/1*5UHmAXCqA7_aR5EeHTvWzg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*5UHmAXCqA7_aR5EeHTvWzg.jpeg" /></p>

<p>There are also some souvenirs on the second floor.</p>

<h4 id="1430-boarding-completed">14:30 Boarding completed</h4>

<p><img src="/assets/055527a739dd/1*ggFHkcOkMXJO-u8Opfn1lw.webp" alt="" loading="lazy" decoding="async" width="1200" height="802" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*ggFHkcOkMXJO-u8Opfn1lw.png" /></p>

<p>Same as the flight here, it’s a new Airbus plane with spacious seats and updated entertainment systems.</p>

<h4 id="1500-departure-goodbye--sapporo">15:00 Departure, Goodbye — Sapporo</h4>

<p><img src="/assets/055527a739dd/1*NsbLIJ-RuRa4YOkVICnPGA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*NsbLIJ-RuRa4YOkVICnPGA.jpeg" /></p>

<p>The airplane meal was just so-so:</p>

<p><img src="/assets/055527a739dd/1*a20WxDinpEcoDIUd6Bi3xw.webp" alt="" loading="lazy" decoding="async" width="2048" height="1536" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDQ4IiBoZWlnaHQ9IjE1MzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*a20WxDinpEcoDIUd6Bi3xw.jpeg" /></p>

<h4 id="1815-landed-at-taiwan-taoyuan-international-airport">18:15 Landed at Taiwan Taoyuan International Airport</h4>

<h4 id="1915-immigration--baggage-claim-go-home-and-rest">19:15 Immigration + Baggage Claim, Go Home and Rest</h4>

<p>This time, the luggage took forever to arrive…</p>

<h3 id="thank-you-for-reading">Thank you for reading</h3>

<p>Thank you for reading this far and experiencing the various sights of Hokkaido with me. See you at the next destination!</p>

<h3 id="free-wifi-on-the-plane">Free WiFi on the Plane</h3>

<p><img src="/assets/055527a739dd/1*lK1NDVwT4V1iu0XiZhUtsw.webp" alt="" loading="lazy" decoding="async" width="1120" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTIwIiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*lK1NDVwT4V1iu0XiZhUtsw.png" /></p>

<p>This flight offers full in-flight WiFi service through China Airlines, allowing you to chat and message with friends.</p>

<blockquote>
  <p><strong><em>If your phone has AdGuard DNS or a similar DNS ad-blocking service installed, you need to go to Settings -&gt; General -&gt; VPN &amp; Device Management -&gt; Restrictions and Proxy Servers -&gt; DNS -&gt; change back to Automatic.</em></strong></p>
</blockquote>

<blockquote>
  <p><em>Otherwise, you won’t be able to connect to the onboard WiFi.</em></p>
</blockquote>

<h3 id="souvenirs">Souvenirs</h3>

<p><img src="/assets/055527a739dd/1*zSYwjAvHNp4zYYXixRVExA.webp" alt="" loading="lazy" decoding="async" width="3238" height="2432" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjM4IiBoZWlnaHQ9IjI0MzIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/055527a739dd/1*zSYwjAvHNp4zYYXixRVExA.png" /></p>

<ul>
  <li>Sapporo Agricultural School (milk cookies) and roasted corn crackers (puffed) are also good.</li>
</ul>

<h4 id="toys">Toys</h4>

<p><img src="/assets/055527a739dd/1*Q4kKEs40fKLCm6dfF0LG7Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="901" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/055527a739dd/1*Q4kKEs40fKLCm6dfF0LG7Q.png" /></p>

<ul>
  <li>
    <p>Twisted railroad crossing, signboard lights, car keys, fire alarm, Don Quijote figurine, Japan team jersey</p>
  </li>
  <li>
    <p>Wooden handmade musical instrument bought at Furano Fairy Hill</p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">iOS Certificates, Identifiers &amp;amp; Profiles Explained｜Fastlane Match for Unified Certificate Management in CI/CD</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ios-certificates-identifiers-profiles-explained-fastlane-match-for-unified-certificate-management-in-ci-cd-823ac523ccc8/" rel="alternate" type="text/html" title="iOS Certificates, Identifiers &amp; Profiles Explained｜Fastlane Match for Unified Certificate Management in CI/CD" />
    <published>2026-01-04T01:03:46+08:00</published>
    <updated>2026-01-05T08:55:47+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/823ac523ccc8</id><summary type="html">Discover how to streamline iOS certificate issuance and management using Fastlane Match, integrating Certificates, Identifiers &amp; Profiles seamlessly into your CI/CD pipeline for efficient app deployment.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="fastlane" /><category term="keychain" /><category term="github-actions" /><category term="macos" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/823ac523ccc8/1*-OS8G9xu0CFpnlAQRgnGAg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ios-certificates-identifiers-profiles-explained-fastlane-match-for-unified-certificate-management-in-ci-cd-823ac523ccc8/"><![CDATA[<h3 id="what-are-ios-certificates-identifiers--profiles-and-some-notes-on-unified-management-of-certificates-and-cicd-with-fastlane-match">What are iOS Certificates, Identifiers &amp; Profiles and <strong>Some Notes on Unified Management of Certificates and CI/CD with Fastlane Match</strong></h3>

<p>Introduction to the Relationship Between Certificates, Identifiers &amp; Profiles and Using Fastlane Match to Centralize Certificate Issuance Management and Integrate into CI/CD Workflows</p>

<p><img src="/assets/823ac523ccc8/1*-OS8G9xu0CFpnlAQRgnGAg.webp" alt="Photo by marcos mayer" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*-OS8G9xu0CFpnlAQRgnGAg.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@mmayyer?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank">marcos mayer</a></p>

<h4 id="preface">Preface</h4>

<p>In mid-2025, a series of articles was written about building a complete App CI/CD process from scratch using GitHub Actions:</p>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/">CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/">CI/CD Practical Guide (Part 2): Complete Guide to Using and Building GitHub Actions with Self-hosted Runner</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/">CI/CD Practical Guide (3): Implementing CI and CD Workflows for App Projects with GitHub Actions</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</a></p>
  </li>
</ul>

<p>Recently, I set up a new environment and went through the process again. Each time, I learn something new. This time, I focused on iOS Codesigning: the relationship between Certificates, Profiles, and Devices, and how I use Fastlane Match to connect certificate management with CI/CD.</p>

<h3 id="what-are-certificates-identifiers--profiles-and-devices"><a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">What are Certificates, Identifiers &amp; Profiles, and Devices?</a></h3>

<p>In the Apple ecosystem, app development is based on managing certificates and provisioning profiles, unlike Android where having the .apk file is enough to install and use. Apple enforces strict certificate matching rules; if these are not met, the app cannot be installed or used.</p>

<h4 id="main-components-and-their-functions"><strong>Main Components and Their Functions:</strong></h4>

<p><img src="/assets/823ac523ccc8/1*PyM0l-jXXcVBKubOwCiTsw.webp" alt="" loading="lazy" decoding="async" width="1200" height="439" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQzOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*PyM0l-jXXcVBKubOwCiTsw.png" /></p>

<h4 id="certificates-the-identity-used-to-sign-the-app-signing-identity"><strong>Certificates:</strong> The identity used to sign the App (Signing Identity)</h4>

<ul>
  <li>
    <p><strong>Development</strong> — Used on physical devices during development (no certificates needed for simulators), associated with a person or API Key (with quantity limits).</p>
  </li>
  <li>
    <p><strong>Distribution</strong> — Packaging for App Store, TestFlight, or Ad Hoc internal testing (limited to registered devices), with quantity limits, belonging to the Team.</p>
  </li>
  <li>
    <p><strong>Enterprise</strong> — Apps for internal use within the company.</p>
  </li>
</ul>

<blockquote>
  <p><strong><em>Format</em></strong> <em>: Requires both <code class="language-plaintext highlighter-rouge">Private Key</code> and <code class="language-plaintext highlighter-rouge">Certificate .cer</code> to use, or exporting from the original creating computer’s Keychain as a <code class="language-plaintext highlighter-rouge">.p12</code> file will include both.</em></p>
</blockquote>

<p><img src="/assets/823ac523ccc8/1*oP_p2kJ1Prqku4D8LIZ7yg.webp" alt="" loading="lazy" decoding="async" width="371" height="115" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNzEiIGhlaWdodD0iMTE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/823ac523ccc8/1*oP_p2kJ1Prqku4D8LIZ7yg.png" /></p>

<h4 id="identifiers-which-appextension-bundle-id"><strong>Identifiers: Which App/Extension (Bundle ID)</strong></h4>

<p>App’s Bundle ID registration and the required Capabilities to enable (such as Push Notifications, App Groups…), App Services.</p>

<p>Extensions also have their own Identifier.</p>

<h4 id="devices-registered-devices-iphoneipad"><strong>Devices: Registered Devices (iPhone/iPad..)</strong></h4>

<p>Development running on physical devices and Distribution Ad Hoc internal testing can only be used on registered devices.</p>

<blockquote>
  <p><strong><em>Limit: 100; including cancellations, the quota will only refresh after each annual billing cycle.</em></strong></p>
</blockquote>

<h4 id="provisioning-profile-hereafter-profile-description-file"><strong>Provisioning Profile (hereafter Profile): Description File</strong></h4>

<p>Combination of Certificates + Identifiers + Devices Relationships.</p>

<ul>
  <li>
    <p><strong>Development</strong> — Provisioning profiles used during the development phase. Required for testing on physical devices. Profiles include the relationship between Certificates, Identifiers, and Devices.</p>
  </li>
  <li>
    <p><strong>Ad Hoc</strong> — A provisioning profile for internal testing (e.g., Deploy to Firebase App Distribution), which includes the relationship between Certificates, Identifiers, and Devices.</p>
  </li>
  <li>
    <p><strong>App Store</strong> — Provisioning profiles used for packaging and uploading to the App Store / TestFlight, containing the relationship between Certificates and Identifiers.</p>
  </li>
</ul>

<blockquote>
  <p><em>Format: <code class="language-plaintext highlighter-rouge">.mobileprovision</code></em></p>
</blockquote>

<blockquote>
  <p><strong><em>Note: A Profile is simply a description file that defines relationships; it does not contain the actual certificate.</em></strong></p>
</blockquote>

<h4 id="summary">Summary</h4>

<p>In summary, to build for a physical device (Development Certificate) or perform Archive packaging (Distribution Certificate) on a clean machine without logging into the Xcode Apple Account, <strong>you must have two files</strong>:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.mobileprovision</code> Provisioning Profile: Describes the relationship between Certificates, Identifiers, and Devices.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.p12</code> Certificate: The actual certificate file (exported from the original machine where the certificate was created).</p>
  </li>
</ul>

<p><strong>Also, starting from macOS ≥ 15, the Keychain has moved to:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open /System/Library/CoreServices/Applications/Keychain<span class="se">\ </span>Access.app
</code></pre></div></div>

<p><em>Looked for it for a long time..</em></p>

<h4 id="common-errors">Common Errors</h4>

<p><strong>⚠️⚠️⚠️ Certificates and Profiles are confirmed correct but errors still occur:</strong></p>

<blockquote>
  <p><em>Most likely, you still have leftover old certificates or multiple certificates, causing Xcode to get confused.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>This issue is very common!</em></strong></p>
</blockquote>

<ol>
  <li>
    <p>Close Xcode</p>
  </li>
  <li>
    <p>Open macOS keychain: <code class="language-plaintext highlighter-rouge">open /System/Library/CoreServices/Applications/Keychain\ Access.app</code></p>
  </li>
  <li>
    <p>login keychain -&gt; All items -&gt; Search <code class="language-plaintext highlighter-rouge">apple development</code> -&gt; Delete all Certificates</p>
  </li>
  <li>
    <p>Finder -&gt; Go -&gt; Go to -&gt; <code class="language-plaintext highlighter-rouge">~/Library/MobileDevice/Provisioning\ Profiles</code> -&gt; Delete all Profiles files</p>
  </li>
  <li>
    <p>Re-pull Certificates</p>
  </li>
  <li>
    <p>Restarting Xcode should fix the issue.</p>
  </li>
</ol>

<p><strong>No signing certificate “iOS Distribution” found / No signing certificate “iOS Development” found:</strong></p>

<pre><code class="language-vbnet">No "iOS Distribution" signing certificate matching team ID "" with a private key was found.
No "iOS Development" signing certificate matching team ID "" with a private key was found.
</code></pre>

<p><strong>Reason:</strong></p>

<ul>
  <li>
    <p>Missing iOS Distribution/iOS Development Certificate</p>
  </li>
  <li>
    <p>Have an iOS Distribution/iOS Development Certificate <strong>but no corresponding Private Key</strong></p>
  </li>
</ul>

<p><strong>Solution:</strong></p>

<ul>
  <li>
    <p>Delete all old Certificates and Profiles in the Keychain</p>
  </li>
  <li>
    <p>Find the certificate in the Keychain of the computer where the Certificate was originally created, export it as a <code class="language-plaintext highlighter-rouge">.p12</code> file, and install it on the problematic computer.</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> to revoke the old Certificate and generate a new one<br />
(Don’t worry, this won’t affect the live App; it only impacts development and packaging stages)</p>
  </li>
</ul>

<p><strong>Provisioning profile “” doesn’t include signing certificate “Apple Development: XXX”. / Provisioning profile “” doesn’t include signing certificate “Apple Distribution: XXX”.:</strong></p>

<p><strong>Reason:</strong></p>

<ul>
  <li>The currently selected Provisioning Profile does not match the current Certificate.</li>
</ul>

<p><strong>Solution:</strong></p>

<ul>
  <li>
    <p>Delete all old Certificates and Profiles in the Keychain</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> Profiles to confirm that the provisioning profile has the corresponding certificate checked or recreate the profile for use.</p>
  </li>
</ul>

<p><strong>No profile for team ‘’ matching ‘’ found: Xcode couldn’t find any provisioning profiles matching ‘’.:</strong></p>

<p><strong>Reason:</strong></p>

<ul>
  <li>Provisioning profile not found</li>
</ul>

<p><strong>Solution:</strong></p>

<ul>
  <li>
    <p>Delete all old Certificates and Profiles in the Keychain</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> to download the corresponding Profile for use.</p>
  </li>
</ul>

<p><strong>Provisioning profile “” has app ID “”, which does not match the bundle ID “”. / Provisioning profile doesn’t match the bundle identifier:</strong></p>

<p><strong>Reason:</strong></p>

<ul>
  <li>Provisioning profile does not include the current Bundle Identifier</li>
</ul>

<p><strong>Solution:</strong></p>

<ul>
  <li>
    <p>Delete all old Certificates and Profiles in the Keychain</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> Identifiers to confirm the Bundle Identifier is registered.</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> to download the corresponding Profile for use.</p>
  </li>
</ul>

<p><strong>Provisioning profile “” doesn’t include the currently selected device “” (identifier ).:</strong></p>

<p><strong>Reason:</strong></p>

<ul>
  <li>Provisioning profile does not include the selected physical device’s Device Identifier</li>
</ul>

<p><strong>Solution:</strong></p>

<ul>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> Devices to verify that the physical device’s Identifier is registered.</p>
  </li>
  <li>
    <p>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> and make sure the corresponding Profile has the physical device enabled.</p>
  </li>
  <li>
    <p>Re-download the Profile for use</p>
  </li>
</ul>

<p><strong>Could not create another Development/Distribution certificate, reached the maximum number of available Development/Distribution certificates. :</strong></p>

<p><strong>Reason:</strong></p>

<p>Indicates that the created Development/Distribution Certificates have reached the maximum limit.</p>

<p><strong>Solution:</strong></p>

<ul>
  <li>Go to <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles</a> to delete unused certificates.</li>
</ul>

<p><strong>Xcode certificates are correct and packaging works fine, but signing errors occur when running packaging commands via CLI (Fastlane):</strong></p>

<p><strong>Reason:</strong></p>

<p>Here I also ran into another issue because I carelessly placed the project in the iCloud sync folder. For some reason, Fastlane kept showing certificate problems (suspected keychain access issues).</p>

<p><strong>Solution:</strong></p>

<p>Simply remove it from the iCloud sync directory.</p>

<h3 id="common-daily-usage-scenarios">Common Daily Usage Scenarios</h3>

<h4 id="development-certificate">Development Certificate</h4>

<p><img src="/assets/823ac523ccc8/1*uNICvLN0a9gdGAzi4EZ24g.webp" alt="" loading="lazy" decoding="async" width="1693" height="1029" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjkzIiBoZWlnaHQ9IjEwMjkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/823ac523ccc8/1*uNICvLN0a9gdGAzi4EZ24g.png" /></p>

<ul>
  <li>
    <p>Before adopting Match for unified certificate management, each developer creates their own <code class="language-plaintext highlighter-rouge">Development Certificate</code> and <code class="language-plaintext highlighter-rouge">Development Profile</code>; if there are 1,000+ developers in the organization, the backend of <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Certificates, Identifiers &amp; Profiles, Devices</a> becomes extremely chaotic and unmanageable.</p>
  </li>
  <li>
    <p>If there is an outsourced team responsible only for development, they still need to be added to the Apple Developer Program portal to generate their own development certificates and profiles.</p>
  </li>
</ul>

<h4 id="distribution-certificate">Distribution Certificate</h4>

<p><img src="/assets/823ac523ccc8/1*1JKlvoO8d89dlcpz2mJvjg.webp" alt="" loading="lazy" decoding="async" width="1200" height="1105" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjExMDUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/823ac523ccc8/1*1JKlvoO8d89dlcpz2mJvjg.png" /></p>

<ul>
  <li>
    <p>Distribution Certificates are created by the Team, so each development plan’s Team can only create a limited number of distribution certificates.</p>
  </li>
  <li>
    <p>A common practice is for one engineer to create the Distribution Certificate and then export the .p12 file to other developers who need to publish the app or to use it on the CI/CD machines.</p>
  </li>
  <li>
    <p>When the team is large and there are many apps, sending files back and forth becomes very troublesome, and <strong>they need to be renewed every year</strong>.</p>
  </li>
  <li>
    <p><strong>When packaging Ad Hoc builds, if a new device is registered, everyone and the CI/CD must re-download the Profile for the new device to take effect.</strong></p>
  </li>
</ul>

<h3 id="fastlane-match"><a href="https://docs.fastlane.tools/actions/match/" target="_blank">Fastlane Match</a></h3>

<p>To address the above issues, we want a platform to manage everything related to certificates on our behalf. All developers and CI/CD services will pull and update data uniformly from this platform. The storage of this platform must be secure, and that is — <a href="https://docs.fastlane.tools/actions/match/" target="_blank">Fastlane Match</a> .</p>

<blockquote>
  <p><em>Easily sync your certificates and profiles across your team</em></p>
</blockquote>

<blockquote>
  <p><em>A new approach to iOS and macOS code signing: Share one code signing identity across your development team to simplify your code signing setup and prevent code signing issues.</em></p>
</blockquote>

<blockquote>
  <p><em>match is the implementation of the <a href="https://codesigning.guide/" target="_blank">codesigning.guide concept</a>. match creates all required certificates &amp; provisioning profiles and stores them in a separate git repository, Google Cloud, or Amazon S3. Every team member with access to the selected storage can use those credentials for code signing. match also automatically repairs broken and expired credentials. It’s the easiest way to share signing credentials across teams</em></p>
</blockquote>

<p><img src="/assets/823ac523ccc8/1*UEL7Abx3PFrYbAc2yVlq8A.webp" alt="" loading="lazy" decoding="async" width="1883" height="777" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxODgzIiBoZWlnaHQ9Ijc3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*UEL7Abx3PFrYbAc2yVlq8A.png" /></p>

<p><strong>Fastlane Match:</strong></p>

<ul>
  <li>
    <p>Interact with App Store Connect (via App Store Connect API or Apple Developer Login Session) to generate or update certificates</p>
  </li>
  <li>
    <p>Encrypt and upload the three certificate files (<code class="language-plaintext highlighter-rouge">.mobileprovision</code> Profiles, <code class="language-plaintext highlighter-rouge">.p12</code> Certificate, <code class="language-plaintext highlighter-rouge">.cer</code> Certificate) to the Git Repo (or other storage).<br />
The <code class="language-plaintext highlighter-rouge">.cer</code> Certificate is also stored separately to verify the certificate’s validity.</p>
  </li>
  <li>
    <p>If you want to separate permissions, you can use two Repos: one to manage Development certificates and another to manage Distribution certificates.</p>
  </li>
</ul>

<p><a href="https://docs.fastlane.tools/actions/match/" target="_blank"><strong>Folder structure:</strong></a></p>

<ul>
  <li>
    <p>The <code class="language-plaintext highlighter-rouge">certs</code> folder contains all certificates along with their private keys</p>
  </li>
  <li>
    <p>The <code class="language-plaintext highlighter-rouge">profiles</code> folder contains all provisioning profiles</p>
  </li>
</ul>

<p><img src="/assets/823ac523ccc8/1*mStp0t3Ty3xGTKvbiBGtqw.webp" alt="&lt;https://docs.fastlane.tools/actions/match/&gt;" loading="lazy" decoding="async" width="683" height="508" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODMiIGhlaWdodD0iNTA4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/823ac523ccc8/1*mStp0t3Ty3xGTKvbiBGtqw.png" /></p>

<p><a href="https://docs.fastlane.tools/actions/match/" target="_blank">https://docs.fastlane.tools/actions/match/</a></p>

<p>Encryption Algorithm: <a href="https://github.com/fastlane/fastlane/blob/master/match/lib/match/encryption/encryption.rb" target="_blank">AES-256-GCM</a></p>

<p><strong>Developers, CI/CD Services:</strong></p>

<ul>
  <li>
    <p>Use the Fastlane match command consistently to manage certificates.</p>
  </li>
  <li>
    <p>Fastlane Match will first retrieve the certificate files from the Git Repo and decrypt them for use. If it detects any expired or invalid certificates and has Create/Write permissions, it will automatically regenerate and push them back to the Repo; if it only has Read permission, it will report an error.</p>
  </li>
</ul>

<p><strong>Create/Write:</strong></p>

<ul>
  <li>
    <p>Only the person responsible for managing certificates can generate, update, and push certificates.</p>
  </li>
  <li>
    <p><strong>It is best to store important Distribution Certificates in a separate repo accessible only to CI/CD services or administrators</strong></p>
  </li>
</ul>

<p><strong>Read:</strong></p>

<ul>
  <li>
    <p>Other developers and CI/CD services have <strong>read-only</strong> permission to pull certificates.</p>
  </li>
  <li>
    <p>The CI/CD service pulls the latest certificates before each task execution.</p>
  </li>
  <li>
    <p>Everyone shares the same Development &amp; Distribution Certificate.</p>
  </li>
  <li>
    <p>After personnel changes, access to the Match Repo will be lost. You can revoke the old certificates and regenerate them; others just need to pull again (it’s even smoother if integrated into the make project script).</p>
  </li>
</ul>

<h3 id="fastlane-match-setup-and-usage">Fastlane Match Setup and Usage</h3>

<p>Let’s take Git Storage as an example for all certificates.</p>

<p><strong>1. Create an Empty Git Private Match Repo to Store Certificates</strong><br />
Although all Certificates and Profiles are stored encrypted, you still need to set proper access permissions for the repo.</p>

<ol>
  <li>Confirm that Git SSH access is set up locally by running <code class="language-plaintext highlighter-rouge">git clone git@github.com:xxx/certificates.git</code></li>
</ol>

<blockquote>
  <p><em>It is recommended to <strong>consistently use SSH Git Clone Repo</strong>, as CI/CD will also use the same method.</em></p>
</blockquote>

<blockquote>
  <p><em>If running fastlane match gets stuck at <code class="language-plaintext highlighter-rouge">If cloning the repo takes too long, you can use the </code>clone_branch_directly<code class="language-plaintext highlighter-rouge"> option in match.</code>, it is most likely an SSH permission issue.</em></p>
</blockquote>

<ol>
  <li>Run <code class="language-plaintext highlighter-rouge">bundle exec fastlane match init</code> in the project directory to complete the setup</li>
</ol>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="mi">21</span><span class="p">:</span><span class="mi">54</span><span class="p">:</span><span class="mi">32</span><span class="p">]:</span> <span class="n">fastlane</span> <span class="n">match</span> <span class="n">supports</span> <span class="n">multiple</span> <span class="n">storage</span> <span class="n">modes</span><span class="p">,</span> <span class="n">please</span> <span class="nb">select</span> <span class="n">the</span> <span class="n">one</span> <span class="n">you</span> <span class="n">want</span> <span class="n">to</span> <span class="n">use</span><span class="p">:</span>
<span class="mi">1</span><span class="p">.</span> <span class="n">git</span>
<span class="mi">2</span><span class="p">.</span> <span class="n">google_cloud</span>
<span class="mi">3</span><span class="p">.</span> <span class="n">s3</span>
<span class="mi">4</span><span class="p">.</span> <span class="n">gitlab_secure_files</span>
<span class="o">#</span> <span class="n">Enter</span> <span class="mi">1</span> <span class="n">to</span> <span class="n">use</span> <span class="n">git</span>
<span class="err">?</span>   <span class="mi">1</span>

<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">40</span><span class="p">]:</span> <span class="n">Please</span> <span class="n">create</span> <span class="n">a</span> <span class="n">new</span><span class="p">,</span> <span class="n">private</span> <span class="n">git</span> <span class="n">repository</span> <span class="n">to</span> <span class="n">store</span> <span class="n">the</span> <span class="n">certificates</span> <span class="ow">and</span> <span class="n">profiles</span> <span class="n">there</span>
<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">40</span><span class="p">]:</span> <span class="n">URL</span> <span class="n">of</span> <span class="n">the</span> <span class="n">Git</span> <span class="n">Repo</span><span class="p">:</span> <span class="n">git</span><span class="err">@</span><span class="n">github</span><span class="p">.</span><span class="n">com</span><span class="p">:</span><span class="n">xxx</span><span class="o">/</span><span class="n">certificates</span><span class="p">.</span><span class="n">git</span>
<span class="o">#</span> <span class="n">Enter</span> <span class="n">the</span> <span class="n">SSH</span> <span class="n">URL</span> <span class="n">of</span> <span class="n">your</span> <span class="n">created</span> <span class="n">Git</span> <span class="n">Private</span> <span class="n">Match</span> <span class="n">Repo</span>

<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">47</span><span class="p">]:</span> <span class="n">Successfully</span> <span class="n">created</span> <span class="s1">'./fastlane/Matchfile'</span><span class="p">.</span> <span class="n">You</span> <span class="n">can</span> <span class="n">open</span> <span class="n">the</span> <span class="n">file</span> <span class="n">using</span> <span class="n">a</span> <span class="n">code</span> <span class="n">editor</span><span class="p">.</span>
<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">47</span><span class="p">]:</span> <span class="n">You</span> <span class="n">can</span> <span class="n">now</span> <span class="n">run</span> <span class="err">`</span><span class="n">fastlane</span> <span class="n">match</span> <span class="n">development</span><span class="err">`</span><span class="p">,</span> <span class="err">`</span><span class="n">fastlane</span> <span class="n">match</span> <span class="n">adhoc</span><span class="err">`</span><span class="p">,</span> <span class="err">`</span><span class="n">fastlane</span> <span class="n">match</span> <span class="n">enterprise</span><span class="err">`</span> <span class="ow">and</span> <span class="err">`</span><span class="n">fastlane</span> <span class="n">match</span> <span class="n">appstore</span><span class="err">`</span>
<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">47</span><span class="p">]:</span> <span class="n">On</span> <span class="n">the</span> <span class="n">first</span> <span class="n">run</span> <span class="k">for</span> <span class="n">each</span> <span class="n">environment</span> <span class="n">it</span> <span class="n">will</span> <span class="n">create</span> <span class="n">the</span> <span class="n">provisioning</span> <span class="n">profiles</span> <span class="ow">and</span>
<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">47</span><span class="p">]:</span> <span class="n">certificates</span> <span class="k">for</span> <span class="n">you</span><span class="p">.</span> <span class="n">From</span> <span class="k">then</span> <span class="n">on</span><span class="p">,</span> <span class="n">it</span> <span class="n">will</span> <span class="n">automatically</span> <span class="n">import</span> <span class="n">the</span> <span class="n">existing</span> <span class="n">profiles</span><span class="p">.</span>
<span class="p">[</span><span class="mi">22</span><span class="p">:</span><span class="mi">04</span><span class="p">:</span><span class="mi">47</span><span class="p">]:</span> <span class="n">For</span> <span class="n">more</span> <span class="n">information</span> <span class="n">visit</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">docs</span><span class="p">.</span><span class="n">fastlane</span><span class="p">.</span><span class="n">tools</span><span class="o">/</span><span class="n">actions</span><span class="o">/</span><span class="n">match</span><span class="o">/</span>
</code></pre></div></div>

<ol>
  <li>After completion, a <strong><code class="language-plaintext highlighter-rouge">fastlane/Matchfile</code> :</strong> will be generated.</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Your Git Private Match Repo SSH URL</span>
git_url<span class="o">(</span><span class="s2">"git@github.com:xxxx/certificates.git"</span><span class="o">)</span>

storage_mode<span class="o">(</span><span class="s2">"git"</span><span class="o">)</span>

<span class="nb">type</span><span class="o">(</span><span class="s2">"development"</span><span class="o">)</span> <span class="c"># The default type, can be: appstore, adhoc, enterprise or development</span>

<span class="c"># app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])</span>
<span class="c"># username("user@fastlane.tools") # Your Apple Developer Portal username</span>

<span class="c"># For all available options run `fastlane match --help`</span>
<span class="c"># Remove the # at the beginning of the line to enable the other options</span>

<span class="c"># The docs are available on https://docs.fastlane.tools/actions/match</span>
</code></pre></div></div>

<p><strong>5. Generate the <a href="https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api" target="_blank">App Store Connect API .p8 Key</a></strong>, <strong>and use the API Key uniformly to create certificates:</strong></p>

<p><img src="/assets/823ac523ccc8/1*RFhS_XtZ3TfIJBAeoE-laA.webp" alt="" loading="lazy" decoding="async" width="1181" height="349" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTgxIiBoZWlnaHQ9IjM0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*RFhS_XtZ3TfIJBAeoE-laA.png" /></p>

<blockquote>
  <p><em>⚠️️️️ <a href="https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api" target="_blank"><strong>App Store Connect API .p8 Key</strong></a> <strong>has extensive permissions</strong>, enabling management of certificates, app submissions, reviews, users, and financial reports.</em></p>
</blockquote>

<blockquote>
  <p><em>Whether to include it in the team’s .git for everyone to access can be decided based on the team’s situation.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>A higher security approach is to allow only authorized personnel to manage the certificates, while storing them encrypted for use in the CI/CD system (which will be introduced later in the article).</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>For demo convenience, it is placed directly under the fastlane directory for access.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Additionally, the Fastlane script in this article is simplified for demonstration purposes, without considering code repetition or structure.</em></strong></p>
</blockquote>

<h4 id="certificate-management">Certificate Management</h4>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>platform :ios <span class="k">do
  </span>lane :match_development <span class="k">do</span> <span class="se">\\</span>|options<span class="se">\\</span>|
    <span class="c"># Replace with your App Identifier ID</span>
    app_identifier <span class="o">=</span> <span class="s2">"li.zhgchg.myApp"</span>

    <span class="nb">type</span> <span class="o">=</span> options.fetch<span class="o">(</span>:type, <span class="s2">"development"</span><span class="o">)</span>
    isRead <span class="o">=</span> options.fetch<span class="o">(</span>:isRead, <span class="nb">true</span><span class="o">)</span>
    <span class="k">if </span>isRead 
      <span class="nb">readonly</span> <span class="o">=</span> <span class="nb">true
      </span>force <span class="o">=</span> <span class="nb">false
    </span><span class="k">else
      </span><span class="nb">readonly</span> <span class="o">=</span> <span class="nb">false
      </span>force <span class="o">=</span> <span class="nb">true</span>

      <span class="c"># App Store Connect API Key is required to manage certificates on Apple's backend</span>
      <span class="c"># Assuming the App Store Connect API .p8 Key is in the ./fastlane/ directory</span>
      app_store_connect_api_key<span class="o">(</span>
        key_id: <span class="s2">"XXXXXX"</span>,
        issuer_id: <span class="s2">"XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX"</span>,
        key_filepath: <span class="s2">"./fastlane/AuthKey_XXXXXX.p8"</span>,
      <span class="o">)</span>
    end

    <span class="c"># Fetch Development certificates for the app_identifier</span>
    match<span class="o">(</span>
      <span class="nb">type</span>: <span class="nb">type</span>,
      app_identifier: app_identifier, 
      <span class="nb">readonly</span>: <span class="nb">readonly</span>, <span class="c"># Whether to update/upload cert/profile when needed</span>
      force: force <span class="c"># Whether to forcibly recreate the provisioning profile</span>
    <span class="o">)</span>
  end
end
</code></pre></div></div>

<p><strong>Create Development Certificate &amp; Profile and Upload to Match Repo:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle exec fastlane match_development type:development isRead:false
[22:22:38]: Creating new provisioning profile for 'li.zhgchg.myApp' with name 'match Development li.zhgchg.myApp' for 'ios' platform
[22:22:39]: Downloading provisioning profile...
[22:22:39]: Successfully downloaded provisioning profile...
[22:22:39]: Installing provisioning profile...
/var/folders/pk/978f3gws7ml0bkmrw245cg_c0000gn/T/d20260103-5010-v6r6w2/profiles/development/Development_li.zhgchg.myApp.mobileprovision
[22:22:39]: Installing provisioning profile...
[22:22:39]: 🔒  Successfully encrypted certificates repo
[22:22:39]: Pushing changes to remote git repo...
[22:22:42]: Finished uploading files to Git Repo [git@github.com:xxxx/certificates.git]
</code></pre></div></div>

<ul>
  <li>No errors mean the Development Certificate &amp; Profile were successfully created, installed, and pushed to the Match Repo.</li>
</ul>

<p><strong>The first time you set it up, you will need to configure a <code class="language-plaintext highlighter-rouge">Passphrase</code>:</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="ss">23:29:12</span><span class="p">]:</span> <span class="sx">Enter</span> the passphrase that should be used to encrypt/decrypt your certificates
<span class="p">[</span><span class="ss">23:29:12</span><span class="p">]:</span> <span class="sx">This</span> passphrase is specific per repository and will be stored in your local keychain
<span class="p">[</span><span class="ss">23:29:12</span><span class="p">]:</span> <span class="sx">Make</span> sure to remember the password, as you'll need it when you run match on a different machine
<span class="p">[</span><span class="ss">23:29:12</span><span class="p">]:</span> <span class="sx">Passphrase</span> for Match storage:
</code></pre></div></div>

<ul>
  <li>
    <p>This value is used as a reference to encrypt all the files in your Match Repo (passphrase + random salt).</p>
  </li>
  <li>
    <p>It is recommended to generate a <a href="https://www.random.org/strings/?num=1&amp;len=32&amp;digits=on&amp;upperalpha=on&amp;loweralpha=on&amp;unique=on&amp;format=html&amp;rnd=new" target="_blank">random string</a>, then set and record it.</p>
  </li>
  <li>
    <p>In future updates or when others pull the Match Repo certificates, this string is required to decrypt back to the original files.</p>
  </li>
</ul>

<p><strong>Other team members uniformly pull Development Certificate &amp; Profile from Match Repo:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>fastlane match_development <span class="nb">type</span>:development
</code></pre></div></div>

<p><strong>The first time you use it, you will be asked for your login keychain password because the certificate needs to be installed into the keychain. Enter it twice to confirm:</strong></p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="ss">16:52:59</span><span class="p">]:</span> <span class="sx">Installing</span> certificate...
<span class="p">[</span><span class="ss">16:53:00</span><span class="p">]:</span> <span class="sx">There</span> are no local code signing identities found.
You can run security find-identity -v -p codesigning to get this output.
This Stack Overflow thread has more information: https://stackoverflow.com/q/35390072/774.
(Check in Keychain Access for an expired WWDR certificate: https://stackoverflow.com/a/35409835/774 has more info.)
<span class="p">[</span><span class="ss">16:53:00</span><span class="p">]:</span> <span class="sx">Enter</span> the password for /Users/zhgchgli/Library/Keychains/login.keychain-db
<span class="p">[</span><span class="ss">16:53:00</span><span class="p">]:</span> <span class="sx">This</span> passphrase will be stored in your local keychain with the name fastlane_keychain_login and used in future runs
<span class="p">[</span><span class="ss">16:53:00</span><span class="p">]:</span> <span class="sx">This</span> prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable
<span class="p">[</span><span class="ss">16:53:00</span><span class="p">]:</span> <span class="sx">Password</span> for login keychain: <span class="ge">********</span>
<span class="p">[</span><span class="ss">16:53:24</span><span class="p">]:</span> <span class="sx">Type</span> password for login keychain again: <span class="ge">********</span>
</code></pre></div></div>

<p><strong>If Match Development finishes pulling certificates, but Xcode keeps showing errors or invalid status:</strong></p>

<p>You can refer to the common errors mentioned earlier. Most are caused by old, corrupted certificates. Clearing all certificates and re-downloading them should resolve the issue.</p>

<p>—</p>

<p><strong>Create AdHoc Distribution Certificate &amp; Profile and Upload to Match Repo:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>fastlane match_development <span class="nb">type</span>:adhoc isRead:false
</code></pre></div></div>

<p><strong>Create AppStore Distribution Certificate &amp; Profile and Upload to Match Repo:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">exec </span>fastlane match_development <span class="nb">type</span>:appstore isRead:false
</code></pre></div></div>

<p><strong>CI/CD services uniformly pull Distribution Certificate &amp; Profile ( <code class="language-plaintext highlighter-rouge">isRead:true</code> ) from the Match Repo, then execute the build and release tasks.</strong></p>

<h4 id="register-a-new-device">Register a New Device</h4>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">platform</span> <span class="o">:</span><span class="n">ios</span> <span class="k">do</span>  
  <span class="n">desc</span> <span class="s2">"Register a new device and refresh profiles"</span>
  <span class="n">lane</span> <span class="o">:</span><span class="n">registerDevice</span> <span class="k">do</span> <span class="err">\\</span><span class="o">|</span><span class="n">options</span><span class="err">\\</span><span class="o">|</span>
    <span class="c1"># Replace with your App Identifier ID</span>
    <span class="n">app_identifier</span> <span class="o">=</span> <span class="s2">"li.zhgchg.myApp"</span>

    <span class="c1"># App Store Connect API Key is required to manage certificates on Apple backend</span>
    <span class="c1"># Assume the App Store Connect API .p8 Key is in the ./fastlane/ directory</span>
    <span class="nf">app_store_connect_api_key</span><span class="p">(</span>
      <span class="n">key_id</span><span class="o">:</span> <span class="s2">"XXXXXX"</span><span class="p">,</span>
      <span class="n">issuer_id</span><span class="o">:</span> <span class="s2">"XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX"</span><span class="p">,</span>
      <span class="n">key_filepath</span><span class="o">:</span> <span class="s2">"./fastlane/AuthKey_XXXXXX.p8"</span><span class="p">,</span>
    <span class="p">)</span>
    <span class="c1"># Input: UDID and device name</span>
    <span class="n">udid</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">udid</span><span class="p">]</span> <span class="err">\\</span><span class="o">|</span><span class="err">\\</span><span class="o">|</span> <span class="no">UI</span><span class="mf">.</span><span class="nf">input</span><span class="p">(</span><span class="s2">"Enter device UDID:"</span><span class="p">)</span>
    <span class="n">device_name</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">name</span><span class="p">]</span> <span class="err">\\</span><span class="o">|</span><span class="err">\\</span><span class="o">|</span> <span class="no">UI</span><span class="mf">.</span><span class="nf">input</span><span class="p">(</span><span class="s2">"Enter device name:"</span><span class="p">)</span>
    <span class="no">UI</span><span class="mf">.</span><span class="nf">message</span><span class="p">(</span><span class="s2">"📱 Registering device #</span><span class="si">{</span><span class="nv">device_name</span><span class="si">}</span><span class="s2"> (#</span><span class="si">{</span><span class="nv">udid</span><span class="si">}</span><span class="s2">)"</span><span class="p">)</span>
    <span class="nf">register_device</span><span class="p">(</span>
      <span class="n">name</span><span class="o">:</span> <span class="n">device_name</span><span class="p">,</span>
      <span class="n">udid</span><span class="o">:</span> <span class="n">udid</span><span class="p">,</span>
      <span class="n">platform</span><span class="o">:</span> <span class="s1">'ios'</span>
    <span class="p">)</span>

    <span class="c1"># Update Development certificates for app_identifier</span>
    <span class="k">match</span><span class="p">(</span>
      <span class="n">type</span><span class="o">:</span> <span class="s2">"development"</span><span class="p">,</span>
      <span class="n">app_identifier</span><span class="o">:</span> <span class="n">app_identifier</span><span class="p">,</span> 
      <span class="k">readonly</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1"># Update/upload cert/profile if needed</span>
      <span class="n">force_for_new_devices</span><span class="o">:</span> <span class="kc">true</span> <span class="c1"># Recreate provisioning profile for new devices</span>
    <span class="p">)</span>

    <span class="c1"># Update AdHoc certificates for app_identifier</span>
    <span class="k">match</span><span class="p">(</span>
      <span class="n">type</span><span class="o">:</span> <span class="s2">"adhoc"</span><span class="p">,</span>
      <span class="n">app_identifier</span><span class="o">:</span> <span class="n">app_identifier</span><span class="p">,</span> 
      <span class="k">readonly</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1"># Update/upload cert/profile if needed</span>
      <span class="n">force_for_new_devices</span><span class="o">:</span> <span class="kc">true</span> <span class="c1"># Recreate provisioning profile for new devices</span>
    <span class="p">)</span>
  <span class="n">end</span>
<span class="n">end</span>
</code></pre></div></div>

<p>After registration, other developers or CI/CD services pulling the Profile (Development or AdHoc) from the Match Repo will include the new devices.</p>

<h3 id="fastlane-match-x-cicd-workflow-integration">Fastlane Match x CI/CD Workflow Integration</h3>

<p>After a general introduction to how Fastlane Match generates and pulls certificates, next is to explain how to integrate it into the CI/CD process.</p>

<h4 id="question-1--how-to-clone-private-match-repo">Question 1 — How to Clone Private Match Repo</h4>

<p>The most common issue is how to clone the Private Match Repo project on CI/CD. During local development, we use ssh git clone with our own account’s ssh key, so there is no problem. However, CI/CD does not have this key. Although you can use a personal key, it is not secure.</p>

<p><strong>GitHub — Repo Deploy Key:</strong></p>

<ol>
  <li>
    <p>First, generate the private/public key locally: <code class="language-plaintext highlighter-rouge">ssh-keygen -t rsa -b 4096 -f ./id_rsa</code> ( <strong>do NOT enter a passphrase</strong> )</p>
  </li>
  <li>
    <p>Go to <strong>Match Private Repo</strong> -&gt; Settings -&gt; Security -&gt; Deploy keys -&gt; Add deploy key:</p>
  </li>
</ol>

<p><img src="/assets/823ac523ccc8/1*nzo-oPRi7DDxPvtJIWBspw.webp" alt="" loading="lazy" decoding="async" width="1149" height="774" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ5IiBoZWlnaHQ9Ijc3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*nzo-oPRi7DDxPvtJIWBspw.png" /></p>

<ol>
  <li>Open <code class="language-plaintext highlighter-rouge">id_rsa.pub</code> with a text editor, copy the content, and paste it into Key -&gt; “Add key”</li>
</ol>

<p><img src="/assets/823ac523ccc8/1*mbwtijzM-2iAzfgFiT32rw.webp" alt="" loading="lazy" decoding="async" width="1496" height="780" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDk2IiBoZWlnaHQ9Ijc4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*mbwtijzM-2iAzfgFiT32rw.png" /></p>

<ol>
  <li>Go back to the main Repo -&gt; Settings -&gt; Security -&gt; Secrets and variables</li>
</ol>

<p><img src="/assets/823ac523ccc8/1*jvY8Yg6tI85LNKHgIaHVVQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="869" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*jvY8Yg6tI85LNKHgIaHVVQ.png" /></p>

<ol>
  <li>Add SSH Private Key Content to Secret:</li>
</ol>

<p><img src="/assets/823ac523ccc8/1*8WiPfOz21rJWD6yiMwP3aw.webp" alt="" loading="lazy" decoding="async" width="1200" height="689" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/823ac523ccc8/1*8WiPfOz21rJWD6yiMwP3aw.png" /></p>

<ul>
  <li>Name: <code class="language-plaintext highlighter-rouge">MATCH_REPO_DEPLOY_PRIVATE_KEY</code></li>
</ul>

<ol>
  <li>Return to the main Repo’s GitHub Actions to set up the SSH Key:</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI - Deploy</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">main</span> <span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout current repo (Repo A)</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup SSH for Deploy Key</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">mkdir -p ~/.ssh</span>
          <span class="s">echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" &gt; ~/.ssh/id_ed25519</span>
          <span class="s">chmod 600 ~/.ssh/id_ed25519</span>
          <span class="s">ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Test Clone</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">git clone git@github.com:xxxx/match-certificates.git</span>
<span class="c1"># Success!</span>
<span class="c1"># .. do deploy job...</span>
</code></pre></div></div>

<ol>
  <li>Setup Successful!</li>
</ol>

<h4 id="issue-2--secure-storage-and-use-of-app-store-connect-api-p8-key">Issue 2 — Secure Storage and Use of App Store Connect API .p8 Key</h4>

<p>Because GitHub Actions cannot store files directly, you must first save it as a string and then write it to a file.</p>

<ol>
  <li>
    <p>For the same issue 1 step, add an <code class="language-plaintext highlighter-rouge">APP_STORE_CONNECT_API_KEY_CONTENT</code> Secret in the main Repo.</p>
  </li>
  <li>
    <p>Open the <code class="language-plaintext highlighter-rouge">AuthKey_XXXXXX.p8</code> file with a text editor, then copy and paste its content.</p>
  </li>
  <li>
    <p><strong>Add a step in the main repo’s GitHub Actions to read content and write to a file:</strong></p>
  </li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI - Deploy</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">main</span> <span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout current repo (Repo A)</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup SSH for Deploy Key</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">mkdir -p ~/.ssh</span>
          <span class="s">echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" &gt; ~/.ssh/id_ed25519</span>
          <span class="s">chmod 600 ~/.ssh/id_ed25519</span>
          <span class="s">ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span>  
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Write Secret Key to File</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">APP_STORE_CONNECT_API_KEY_CONTENT</span><span class="pi">:</span> <span class="s">${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># ensure fastlane directory exists</span>
          <span class="s">mkdir -p ./fastlane</span>
      
          <span class="s"># create file path</span>
          <span class="s">APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8</span>
      
          <span class="s"># write content to file (keep newline)</span>
          <span class="s">echo "$APP_STORE_CONNECT_API_KEY_CONTENT" &gt; "$APP_STORE_CONNECT_API_KEY_PATH"</span>
      
          <span class="s"># (optional) restrict permissions</span>
          <span class="s">chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Firebase</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">MATCH_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MATCH_PASSWORD</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane deploy_to_firebase</span>
</code></pre></div></div>

<h4 id="issue-3--setting-a-passphrase-for-the-private-match-repo-to-prevent-prompt-interruptions-during-match">Issue 3 — Setting a Passphrase for the Private Match Repo to Prevent Prompt Interruptions During Match</h4>

<ol>
  <li>
    <p>For Step 1 of the same issue, add <code class="language-plaintext highlighter-rouge">MATCH_PASSWORD</code> to Secrets in the main repo.</p>
  </li>
  <li>
    <p>Enter the <code class="language-plaintext highlighter-rouge">Passphrase</code> for the Fastlane Match Repo you configured.</p>
  </li>
  <li>
    <p><strong>Add</strong> <code class="language-plaintext highlighter-rouge">env: secret.MATCH_PASSWORD</code> <strong>to the main Repo’s GitHub Actions:</strong></p>
  </li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI - Deploy</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">main</span> <span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout current repo (Repo A)</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup SSH for Deploy Key</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">mkdir -p ~/.ssh</span>
          <span class="s">echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" &gt; ~/.ssh/id_ed25519</span>
          <span class="s">chmod 600 ~/.ssh/id_ed25519</span>
          <span class="s">ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span>  
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Write Secret Key to File</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">APP_STORE_CONNECT_API_KEY_CONTENT</span><span class="pi">:</span> <span class="s">${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># ensure fastlane directory exists</span>
          <span class="s">mkdir -p ./fastlane</span>
      
          <span class="s"># create file path</span>
          <span class="s">APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8</span>
      
          <span class="s"># write content to file (keep newline)</span>
          <span class="s">echo "$APP_STORE_CONNECT_API_KEY_CONTENT" &gt; "$APP_STORE_CONNECT_API_KEY_PATH"</span>
      
          <span class="s"># (optional) restrict permissions</span>
          <span class="s">chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Firebase</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">MATCH_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MATCH_PASSWORD</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane deploy_to_firebase</span>
</code></pre></div></div>

<h4 id="question-4--keychain-handling-on-self-hosted-runner">Question 4 — Keychain Handling on Self-hosted Runner</h4>

<p>Unlike cloud machines that are always clean and new each time, with Self-hosted Runners we can specify <code class="language-plaintext highlighter-rouge">derived_data_path</code>, <code class="language-plaintext highlighter-rouge">output_directory</code>, <code class="language-plaintext highlighter-rouge">buildlog_path</code>, and <code class="language-plaintext highlighter-rouge">reinstall_app</code> in Fastlane to ensure a clean environment for each run; but since Certificates and Profiles are installed into the system Keychain, how should we handle them?</p>

<p>Fastlane actually considers this for us and allows creating a clean Keychain before running Match:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">CI - Deploy</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span> <span class="nv">main</span> <span class="pi">]</span>
  <span class="na">pull_request</span><span class="pi">:</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout current repo (Repo A)</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup SSH for Deploy Key</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">mkdir -p ~/.ssh</span>
          <span class="s">echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" &gt; ~/.ssh/id_ed25519</span>
          <span class="s">chmod 600 ~/.ssh/id_ed25519</span>
          <span class="s">ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span>  
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Write Secret Key to File</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">APP_STORE_CONNECT_API_KEY_CONTENT</span><span class="pi">:</span> <span class="s">${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># ensure fastlane directory exists</span>
          <span class="s">mkdir -p ./fastlane</span>
      
          <span class="s"># create file path</span>
          <span class="s">APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8</span>
      
          <span class="s"># write content to file (keep newline)</span>
          <span class="s">echo "$APP_STORE_CONNECT_API_KEY_CONTENT" &gt; "$APP_STORE_CONNECT_API_KEY_PATH"</span>
      
          <span class="s"># (optional) restrict permissions</span>
          <span class="s">chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create fastlane keychain</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">KEYCHAIN_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">runner.name</span><span class="nv"> </span><span class="s">}}"</span>
          <span class="na">MATCH_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MATCH_PASSWORD</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec fastlane run create_keychain \</span>
            <span class="s">name:"$KEYCHAIN_NAME" \</span>
            <span class="s">password:"$MATCH_PASSWORD" \</span>
            <span class="s">unlock:true \</span>
            <span class="s">timeout:0 \</span>
            <span class="s">lock_when_sleeps:false</span>
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Firebase</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">MATCH_PASSWORD</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MATCH_PASSWORD</span><span class="nv"> </span><span class="s">}}"</span>
          <span class="na">KEYCHAIN_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">runner.name</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane deploy_to_firebase</span>

      <span class="c1"># 🔥 This will run regardless of previous success or failure</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Delete fastlane keychain</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">always()</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">KEYCHAIN_NAME</span><span class="pi">:</span> <span class="s">${{ runner.name }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec fastlane run delete_keychain \</span>
            <span class="s">name:"$KEYCHAIN_NAME"</span>
</code></pre></div></div>

<ul>
  <li>
    <p>Each Runner only executes one task at a time, so we use the Runner Name as the Keychain Name. Each Runner will have its own Keychain.</p>
  </li>
  <li>
    <p>Deleted after execution regardless of success or failure.</p>
  </li>
  <li>
    <p>I didn’t set a separate <code class="language-plaintext highlighter-rouge">keychain_password</code>; I just used <code class="language-plaintext highlighter-rouge">MATCH_PASSWORD</code> uniformly.</p>
  </li>
</ul>

<p>Add keychain parameters to the match method in <code class="language-plaintext highlighter-rouge">Fastlane/Fastfile</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#...</span>
    <span class="n">match</span><span class="p">(</span>
      <span class="ss">type: </span><span class="s2">"adhoc"</span><span class="p">,</span>
      <span class="ss">app_identifier: </span><span class="n">app_identifier</span><span class="p">,</span> 
      <span class="ss">readonly: </span><span class="kp">false</span><span class="p">,</span> <span class="c1"># Update/upload cert/profile if needed</span>
      <span class="ss">force_for_new_devices: </span><span class="kp">true</span><span class="p">,</span> <span class="c1"># Recreate provisioning profile for new devices</span>
      <span class="ss">keychain_name: </span><span class="no">ENV</span><span class="p">[</span><span class="s1">'KEYCHAIN_NAME'</span><span class="p">],</span> <span class="c1"># default value: nil</span>
      <span class="ss">keychain_password: </span><span class="no">ENV</span><span class="p">[</span><span class="s1">'MATCH_PASSWORD'</span><span class="p">]</span> <span class="c1"># default value: nil</span>
    <span class="p">)</span>
<span class="c1">#...</span>
</code></pre></div></div>

<p>This way, Match will save the pulled certificates to the specified Keychain instead of the shared login keychain.</p>

<h3 id="conclusion">Conclusion</h3>

<p>The scope of Fastlane is very broad, so here I will only document the process of using Fastlane Match. Other lanes for testing, packaging, publishing, etc., will be covered in a new supplementary article when there’s a chance. If I think of any other Match-related cases, I will add them later! Feel free to leave questions or comments!</p>

<h3 id="further-reading">Further Reading</h3>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/">CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/">CI/CD Practical Guide (Part 2): Complete Guide to Using and Building GitHub Actions with Self-hosted Runner</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/">CI/CD Practical Guide (3): Implementing CI and CD Workflows for App Projects with GitHub Actions</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</a></p>
  </li>
</ul>

<p><strong>Please kindly proceed to read. 🤞🏻</strong></p>]]></content>
  </entry><entry>
    <title type="html">Medium API Data Scraping｜Bypass Cloudflare Verification Efficiently</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/medium-api-data-scraping-bypass-cloudflare-verification-efficiently-88f0fb935120/" rel="alternate" type="text/html" title="Medium API Data Scraping｜Bypass Cloudflare Verification Efficiently" />
    <published>2025-12-31T12:36:53+08:00</published>
    <updated>2026-01-02T22:43:48+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/88f0fb935120</id><summary type="html">Discover how to efficiently scrape data from Medium&apos;s GraphQL Private API, backup articles, and bypass Cloudflare verification blocks to ensure uninterrupted data access and reliable content backup.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="cloudflare" /><category term="medium" /><category term="api" /><category term="graphql" /><category term="medium-api" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/88f0fb935120/1*Lh9a_XR4zvvsaP3y58x9Aw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/medium-api-data-scraping-bypass-cloudflare-verification-efficiently-88f0fb935120/"><![CDATA[<h3 id="the-journey-of-crawling-medium-api-data-and-battling-cloudflare-defense">The Journey of Crawling Medium API Data and Battling Cloudflare Defense</h3>

<p>Using Medium Graphql Private API to Crawl Data, Backup Articles, and Bypass Cloudflare Verification Blocks.</p>

<h4 id="background">Background</h4>

<p>Since I started writing on Medium in 2018, by around 2022 I had accumulated over 50 articles, nearly 60,000 words, and about 3 GB of article images (by 2025, over 120 articles/100,000+ words/6 GB+ images); <strong>no backups! no backups! no backups!</strong> Because I always wrote directly on Medium (the editor is really good), <strong>I published immediately after writing without any local backups.</strong></p>

<p>At that time, a sense of unease arose—what if one day Medium shuts down and my articles disappear? (And Medium had been struggling for years, not making money.) Or what if my account suddenly gets suspended, would all my hard work be lost?</p>

<p>So that year, I started researching how to scrape Medium data, especially article content and how to download backups.</p>

<h4 id="medium-html-">Medium HTML ❌</h4>

<p>The structure is very complex, prone to changes, and slow to crawl. Directly scraping content via the browser is the worst approach.</p>

<h4 id="medium-official-api-">Medium Official API ❌</h4>

<p><a href="https://github.com/Medium/medium-api-docs" target="_blank"><img src="https://opengraph.githubassets.com/e5dab4a39df5809789f4fa59924d1eda04c658aa27cd430b543a80ff7f74a935/Medium/medium-api-docs" alt="" /></a></p>

<blockquote>
  <p><strong><em>Warning The Medium API is no longer supported. We do not recommend using it.</em></strong></p>
</blockquote>

<p>Medium once provided an official API, but it was likely discontinued shortly after its launch. It is presumed that this was due to different product goals—Medium aims to keep content within its own platform rather than allowing external access through APIs.</p>

<h4 id="medium-unofficial-api-">Medium Unofficial API 🤔</h4>

<p><a href="https://mediumapi.com/" target="_blank"><img src="https://mediumapi.com/imgs/mediumapi-website-image.jpg" alt="" /></a></p>

<p>Because the official API is not provided, some skilled developers have created external API interfaces for users in need; however, I suspect these also call the Medium Private API, just adding a layer of encapsulation to bridge and convert requests into the Private API to fetch the original data.</p>

<blockquote>
  <p><strong><em>The fee is not cheap, about $5 USD per 2,500 requests</em></strong> <em>; this service has been running for quite a while, but since it ultimately calls the Medium Private API, I might as well sniff and call it myself.</em></p>
</blockquote>

<h3 id="medium-private-api-">Medium Private API ✅</h3>

<p>What is a Private API? It is an API interface <strong>not intended for external use, undocumented, may be disabled at any time, and could pose legal risks</strong>; essentially, it means <strong>observing the website/App behavior to find the corresponding API calls</strong> and writing your own code to reuse this API to fetch data for your own service.</p>

<h4 id="httpsmediumcom_graphql"><a href="https://medium.com/_/graphql" target="_blank">https://medium.com/_/graphql</a></h4>

<p>By sniffing network requests, we found that all Medium APIs use the <a href="https://medium.com/_/graphql" target="_blank">https://medium.com/_/graphql</a> endpoint. As long as we can call this endpoint in our own program and get the same data, this approach will work.</p>

<h4 id="medium-private-api--sniffing-medium-graphql-example-getting-follower-count">Medium Private API — Sniffing Medium Graphql (Example: Getting Follower Count)</h4>

<p>My first application was to crawl my follower count and display it on my portal website.</p>

<p><a href="https://link.zhgchg.li/" target="_blank"><strong>link.zhgchg.li</strong></a> <strong>:</strong></p>

<p><img src="/assets/88f0fb935120/1*Lh9a_XR4zvvsaP3y58x9Aw.webp" alt="" loading="lazy" decoding="async" width="963" height="526" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjMiIGhlaWdodD0iNTI2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/88f0fb935120/1*Lh9a_XR4zvvsaP3y58x9Aw.png" /></p>

<p>Therefore, I had to scrape the follower count of my account on Medium.</p>

<ul>
  <li>
    <p>First, open the browser’s “Developer Mode” (Chrome -&gt; Right-click -&gt; Inspect), switch to the “Network” tab, check “Preserve log,” and enter “<code class="language-plaintext highlighter-rouge">graphql</code>” in the search box.</p>
  </li>
  <li>
    <p>Go to the Medium account followers page: <code class="language-plaintext highlighter-rouge">https://medium.com/@YOUR_USER_NAME/followers</code><br />
Only this follower URL format can be sniffed; the format <code class="language-plaintext highlighter-rouge">https://YOUR_USER_NAME.medium.com/followers</code> does not call the Followers API.</p>
  </li>
</ul>

<p><img src="/assets/88f0fb935120/1*jmGbo-BQK9amNmoRneJRPw.webp" alt="" loading="lazy" decoding="async" width="1200" height="848" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/88f0fb935120/1*jmGbo-BQK9amNmoRneJRPw.png" /></p>

<ul>
  <li>
    <p>Check each “graphql” request on the left, search for the keyword “<code class="language-plaintext highlighter-rouge">followerCount</code>” in the “Response” data, and find the one where the <code class="language-plaintext highlighter-rouge">username</code> matches the webpage’s username and the follower count matches the number shown on the page. This is the target request.</p>
  </li>
  <li>
    <p>After finding the target API, switch to Payload -&gt; View Source -&gt; copy all the content below</p>
  </li>
</ul>

<p><img src="/assets/88f0fb935120/1*CGyScjCEe9Cf_3VIJrUJzQ.webp" alt="" loading="lazy" decoding="async" width="1437" height="633" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDM3IiBoZWlnaHQ9IjYzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/88f0fb935120/1*CGyScjCEe9Cf_3VIJrUJzQ.png" /></p>

<p>Now that we have obtained the Medium followers API &amp; payload, let’s test it using <a href="https://www.postman.com/" target="_blank">Postman</a>!</p>

<h4 id="medium-follower-count-endpoint--request--response">Medium Follower Count Endpoint — Request &amp; Response</h4>

<p><strong>Endpoint:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST https://medium.com/_/graphql
</code></pre></div></div>

<p>Authorization: No authorization required (No need to include Token or set cookies)</p>

<p><strong>Payload:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"operationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UserFollowers"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
            </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query UserFollowers($username: ID, $id: ID, $paging: PagingOptions) {</span><span class="se">\n</span><span class="s2">  userResult(username: $username, id: $id) {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    ... on User {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      followersUserConnection(paging: $paging) {</span><span class="se">\n</span><span class="s2">        pagingInfo {</span><span class="se">\n</span><span class="s2">          next {</span><span class="se">\n</span><span class="s2">            from</span><span class="se">\n</span><span class="s2">            limit</span><span class="se">\n</span><span class="s2">            __typename</span><span class="se">\n</span><span class="s2">          }</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        users {</span><span class="se">\n</span><span class="s2">          ...FollowList_publisher</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      ...UserCanonicalizer_user</span><span class="se">\n</span><span class="s2">      ...FollowersHeader_publisher</span><span class="se">\n</span><span class="s2">      ...NoFollows_publisher</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionUrl_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionAvatar_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  avatar {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiModal_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...SignInOptions_collection</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublicationFollowButton_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...SusiModal_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublicationFollowRow_collection on Collection {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  description</span><span class="se">\n</span><span class="s2">  ...CollectionAvatar_collection</span><span class="se">\n</span><span class="s2">  ...PublicationFollowButton_collection</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment userUrl_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserAvatar_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  membership {</span><span class="se">\n</span><span class="s2">    tier</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment isUserVerifiedBookAuthor_user on User {</span><span class="se">\n</span><span class="s2">  verifications {</span><span class="se">\n</span><span class="s2">    isBookAuthor</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiModal_user on User {</span><span class="se">\n</span><span class="s2">  ...SignInOptions_user</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  user {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    newsletterV3 {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useAuthorFollowSubscribeButton_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useAuthorFollowSubscribeButton_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment AuthorFollowSubscribeButton_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  ...SusiModal_user</span><span class="se">\n</span><span class="s2">  ...useAuthorFollowSubscribeButton_user</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...useAuthorFollowSubscribeButton_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserFollowRow_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  ...UserAvatar_user</span><span class="se">\n</span><span class="s2">  ...isUserVerifiedBookAuthor_user</span><span class="se">\n</span><span class="s2">  ...AuthorFollowSubscribeButton_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowsHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowList_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...PublicationFollowRow_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...UserFollowRow_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserCanonicalizer_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowersHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  ...FollowsHeader_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    subscriberCount</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    socialStats {</span><span class="se">\n</span><span class="s2">      followerCount</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NoFollows_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n</span><span class="s2">"</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">username</code> : Replace with the username you want to query.</p>

<p><a href="https://www.postman.com/" target="_blank"><strong>Postman</strong></a> <strong>:</strong></p>

<p><img src="/assets/88f0fb935120/1*MuntmkeqjFqqD_ma_td3VA.webp" alt="" loading="lazy" decoding="async" width="1200" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/88f0fb935120/1*MuntmkeqjFqqD_ma_td3VA.png" /></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">POST https://medium.com/_/graphql</code></p>
  </li>
  <li>
    <p>Body -&gt; raw -&gt; JSON</p>
  </li>
</ul>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
            </span><span class="nl">"userResult"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"User"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8854784154b8"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"followersUserConnection"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"pagingInfo"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"next"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                            </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1140babdfef7"</span><span class="p">,</span><span class="w">
                            </span><span class="nl">"limit"</span><span class="p">:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
                            </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PageParams"</span><span class="w">
                        </span><span class="p">},</span><span class="w">
                        </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Paging"</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="nl">"users"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                        </span><span class="err">///..</span><span class="w"> </span><span class="err">omitted</span><span class="w">
                    </span><span class="p">],</span><span class="w">
                    </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UserConnection"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"hasSubdomain"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                </span><span class="nl">"customDomainState"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"live"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"domain"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli.medium.com"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CustomDomain"</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"CustomDomainState"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ZhgChgLi"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"socialStats"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"followerCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">1050</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"__typename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"SocialStats"</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>Key info: <code class="language-plaintext highlighter-rouge">response[0]["data"]["userResult"]["socialStats"]["followerCount"]</code> is the target follower count!</p>

<p><strong>Ruby Demo Code:</strong></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">load_medium_followers</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
    <span class="k">begin</span>
        <span class="n">url</span> <span class="o">=</span> <span class="s2">"https://medium.com/_/graphql"</span>
        <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

        <span class="n">payload</span> <span class="o">=</span> <span class="p">[</span>
            <span class="p">{</span>
            <span class="s2">"operationName"</span><span class="p">:</span> <span class="s2">"UserFollowers"</span><span class="p">,</span>
            <span class="s2">"variables"</span><span class="p">:</span> <span class="p">{</span>
                <span class="s2">"id"</span><span class="p">:</span> <span class="kp">nil</span><span class="p">,</span>
                <span class="s2">"username"</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span>
                <span class="s2">"paging"</span><span class="p">:</span> <span class="kp">nil</span>
            <span class="p">},</span>
            <span class="s2">"query"</span><span class="p">:</span> <span class="s2">"query UserFollowers($username: ID, $id: ID, $paging: PagingOptions) {</span><span class="se">\n</span><span class="s2">  userResult(username: $username, id: $id) {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    ... on User {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      followersUserConnection(paging: $paging) {</span><span class="se">\n</span><span class="s2">        pagingInfo {</span><span class="se">\n</span><span class="s2">          next {</span><span class="se">\n</span><span class="s2">            from</span><span class="se">\n</span><span class="s2">            limit</span><span class="se">\n</span><span class="s2">            __typename</span><span class="se">\n</span><span class="s2">          }</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        users {</span><span class="se">\n</span><span class="s2">          ...FollowList_publisher</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      ...UserCanonicalizer_user</span><span class="se">\n</span><span class="s2">      ...FollowersHeader_publisher</span><span class="se">\n</span><span class="s2">      ...NoFollows_publisher</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionUrl_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionAvatar_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  avatar {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiModal_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...SignInOptions_collection</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublicationFollowButton_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...SusiModal_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublicationFollowRow_collection on Collection {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  description</span><span class="se">\n</span><span class="s2">  ...CollectionAvatar_collection</span><span class="se">\n</span><span class="s2">  ...PublicationFollowButton_collection</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment userUrl_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserAvatar_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  membership {</span><span class="se">\n</span><span class="s2">    tier</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment isUserVerifiedBookAuthor_user on User {</span><span class="se">\n</span><span class="s2">  verifications {</span><span class="se">\n</span><span class="s2">    isBookAuthor</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiModal_user on User {</span><span class="se">\n</span><span class="s2">  ...SignInOptions_user</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  user {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    newsletterV3 {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useAuthorFollowSubscribeButton_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useAuthorFollowSubscribeButton_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment AuthorFollowSubscribeButton_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  ...SusiModal_user</span><span class="se">\n</span><span class="s2">  ...useAuthorFollowSubscribeButton_user</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...useAuthorFollowSubscribeButton_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserFollowRow_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  ...UserAvatar_user</span><span class="se">\n</span><span class="s2">  ...isUserVerifiedBookAuthor_user</span><span class="se">\n</span><span class="s2">  ...AuthorFollowSubscribeButton_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowsHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowList_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...PublicationFollowRow_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...UserFollowRow_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserCanonicalizer_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowersHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  ...FollowsHeader_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    subscriberCount</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    socialStats {</span><span class="se">\n</span><span class="s2">      followerCount</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NoFollows_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n</span><span class="s2">"</span>
            <span class="p">}</span>
        <span class="p">];</span>

        <span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
            <span class="s2">"User-Agent"</span> <span class="o">=&gt;</span> <span class="s2">"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0"</span><span class="p">,</span>
            <span class="s2">"Content-Type"</span> <span class="o">=&gt;</span> <span class="s2">"application/json"</span>
        <span class="p">}</span>

        <span class="n">https</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">host</span><span class="p">,</span> <span class="n">uri</span><span class="p">.</span><span class="nf">port</span><span class="p">)</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">read_timeout</span> <span class="o">=</span> <span class="mi">30</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">open_timeout</span> <span class="o">=</span> <span class="mi">10</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">use_ssl</span> <span class="o">=</span> <span class="kp">true</span>

        <span class="c1"># --- TLS / Certificate verification setup ---</span>
        <span class="c1"># Some OpenSSL builds/configs enable CRL checking, which can fail with:</span>
        <span class="c1"># "certificate verify failed (unable to get certificate CRL)".</span>
        <span class="c1"># Net::HTTP/OpenSSL does not automatically fetch CRLs, so we use a default</span>
        <span class="c1"># cert store and clear CRL-related flags to avoid hard failures while still</span>
        <span class="c1"># verifying the peer certificate.</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">verify_mode</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">SSL</span><span class="o">::</span><span class="no">VERIFY_PEER</span>

        <span class="n">store</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">X509</span><span class="o">::</span><span class="no">Store</span><span class="p">.</span><span class="nf">new</span>
        <span class="n">store</span><span class="p">.</span><span class="nf">set_default_paths</span>
        <span class="c1"># Ensure no CRL-check flags are enabled by default</span>
        <span class="n">store</span><span class="p">.</span><span class="nf">flags</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">cert_store</span> <span class="o">=</span> <span class="n">store</span>

        <span class="c1"># Allow overriding CA bundle paths via environment variables if needed.</span>
        <span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_FILE'</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_FILE'</span><span class="p">].</span><span class="nf">empty?</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">ca_file</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_FILE'</span><span class="p">]</span>
        <span class="k">end</span>
        <span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_DIR'</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_DIR'</span><span class="p">].</span><span class="nf">empty?</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">ca_path</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'SSL_CERT_DIR'</span><span class="p">]</span>
        <span class="k">end</span>

        <span class="c1"># (Optional) timeouts to avoid hanging on network issues</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">open_timeout</span> <span class="o">=</span> <span class="mi">10</span>
        <span class="n">https</span><span class="p">.</span><span class="nf">read_timeout</span> <span class="o">=</span> <span class="mi">30</span>
        <span class="c1"># --- end TLS setup ---</span>


        <span class="n">req</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Post</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">request_uri</span><span class="p">,</span> <span class="n">headers</span><span class="p">)</span>
        <span class="n">req</span><span class="p">.</span><span class="nf">body</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">dump</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>

        <span class="n">res</span> <span class="o">=</span> <span class="n">https</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>

        <span class="n">json</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span>
        <span class="n">count</span> <span class="o">=</span> <span class="n">json</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s2">"data"</span><span class="p">,</span> <span class="s2">"userResult"</span><span class="p">,</span> <span class="s2">"socialStats"</span><span class="p">,</span> <span class="s2">"followerCount"</span><span class="p">)</span>
        <span class="n">count</span> <span class="p">?</span> <span class="n">count</span><span class="p">.</span><span class="nf">to_i</span> <span class="p">:</span> <span class="mi">0</span>
        <span class="k">return</span> <span class="s2">"1K+"</span> <span class="k">unless</span> <span class="n">count</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">&gt;</span> <span class="mi">0</span>
        <span class="k">return</span> <span class="s2">"</span><span class="si">#{</span><span class="n">count</span><span class="p">.</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">reverse</span><span class="p">.</span><span class="nf">gsub</span><span class="p">(</span><span class="sr">/(\d{3})(?=\d)/</span><span class="p">,</span> <span class="s1">'\\1,'</span><span class="p">).</span><span class="nf">reverse</span><span class="si">}</span><span class="s2">+"</span>
    <span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">e</span>
        <span class="s2">"1K+"</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<h4 id="medium-private-api--fetch-all-articles-of-an-account">Medium Private API — Fetch All Articles of an Account</h4>

<p>In the same way, we can also get the target account’s article list.</p>

<p>Graphql Query Body:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"operationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"UserProfileQuery"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"homepagePostsFrom"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
          </span><span class="nl">"includeDistributedResponses"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8854784154b8"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"homepagePostsLimit"</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query UserProfileQuery($id: ID, $username: ID, $homepagePostsLimit: PaginationLimit, $homepagePostsFrom: String = null, $includeDistributedResponses: Boolean = true) {</span><span class="se">\n</span><span class="s2">  userResult(id: $id, username: $username) {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    ... on User {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      name</span><span class="se">\n</span><span class="s2">      viewerIsUser</span><span class="se">\n</span><span class="s2">      viewerEdge {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        isFollowing</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {</span><span class="se">\n</span><span class="s2">        posts {</span><span class="se">\n</span><span class="s2">          id</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      ...UserCanonicalizer_user</span><span class="se">\n</span><span class="s2">      ...UserProfileScreen_user</span><span class="se">\n</span><span class="s2">      ...EntityDrivenSubscriptionLandingPageScreen_writer</span><span class="se">\n</span><span class="s2">      ...useShouldShowEntityDrivenSubscription_creator</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserCanonicalizer_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserProfileScreen_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  viewerIsUser</span><span class="se">\n</span><span class="s2">  ...PublisherHeader_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHomePosts_publisher</span><span class="se">\n</span><span class="s2">  ...UserSubdomainFlow_user</span><span class="se">\n</span><span class="s2">  ...UserProfileMetadata_user</span><span class="se">\n</span><span class="s2">  ...SuspendedBannerLoader_user</span><span class="se">\n</span><span class="s2">  ...ExpandablePost_user</span><span class="se">\n</span><span class="s2">  ...useAnalytics_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderBackground_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderNameplate_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderActions_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderNav_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderBackground_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    ...PublisherHeaderBackground_customStyleSheet</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">        backgroundColor</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    legacyHeaderBackgroundImage {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      focusPercentX</span><span class="se">\n</span><span class="s2">      focusPercentY</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    ...collectionTintBackgroundTheme_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...publisherUrl_publisher</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderBackground_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      background {</span><span class="se">\n</span><span class="s2">        rgb</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    headerScale</span><span class="se">\n</span><span class="s2">    backgroundImageDisplayMode</span><span class="se">\n</span><span class="s2">    backgroundImageVerticalAlignment</span><span class="se">\n</span><span class="s2">    backgroundColorDisplayMode</span><span class="se">\n</span><span class="s2">    backgroundColor {</span><span class="se">\n</span><span class="s2">      alpha</span><span class="se">\n</span><span class="s2">      rgb</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      ...getOpaqueHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    secondaryBackgroundColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    postBackgroundColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    backgroundImage {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderBackground_imageMetadata</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getHexFromColorValue_colorValue on ColorValue {</span><span class="se">\n</span><span class="s2">  rgb</span><span class="se">\n</span><span class="s2">  alpha</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getOpaqueHexFromColorValue_colorValue on ColorValue {</span><span class="se">\n</span><span class="s2">  rgb</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderBackground_imageMetadata on ImageMetadata {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  originalWidth</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionTintBackgroundTheme_collection on Collection {</span><span class="se">\n</span><span class="s2">  colorPalette {</span><span class="se">\n</span><span class="s2">    ...collectionTintBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...collectionTintBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionTintBackgroundTheme_colorPalette on ColorPalette {</span><span class="se">\n</span><span class="s2">  ...customTintBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment customTintBackgroundTheme_colorPalette on ColorPalette {</span><span class="se">\n</span><span class="s2">  tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">    ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ThemeUtil_colorSpectrum on ColorSpectrum {</span><span class="se">\n</span><span class="s2">  backgroundColor</span><span class="se">\n</span><span class="s2">  ...ThemeUtilInterpolateHelpers_colorSpectrum</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ThemeUtilInterpolateHelpers_colorSpectrum on ColorSpectrum {</span><span class="se">\n</span><span class="s2">  colorPoints {</span><span class="se">\n</span><span class="s2">    ...ThemeUtil_colorPoint</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ThemeUtil_colorPoint on ColorPoint {</span><span class="se">\n</span><span class="s2">  color</span><span class="se">\n</span><span class="s2">  point</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionTintBackgroundTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...customTintBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment customTintBackgroundTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      primary {</span><span class="se">\n</span><span class="s2">        colorPalette {</span><span class="se">\n</span><span class="s2">          ...customTintBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment publisherUrl_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionUrl_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment userUrl_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      domain</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  hasSubdomain</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderNameplate_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  ...PublisherAvatar_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderLogo_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherFollowerCount_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherAvatar_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...CollectionAvatar_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...UserAvatar_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionAvatar_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  avatar {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserAvatar_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  mediumMemberAt</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderLogo_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    header {</span><span class="se">\n</span><span class="s2">      logoImage {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        originalHeight</span><span class="se">\n</span><span class="s2">        originalWidth</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      appNameColor {</span><span class="se">\n</span><span class="s2">        ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      appNameTreatment</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    logo {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...CustomHeaderTooltip_publisher</span><span class="se">\n</span><span class="s2">  ...publisherUrl_publisher</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CustomHeaderTooltip_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    header {</span><span class="se">\n</span><span class="s2">      appNameTreatment</span><span class="se">\n</span><span class="s2">      nameTreatment</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherFollowerCount_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    subscriberCount</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    socialStats {</span><span class="se">\n</span><span class="s2">      followerCount</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderActions_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ...MetaHeaderPubMenu_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...CollectionFollowButton_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...FollowAndSubscribeButtons_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderPubMenu_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...MetaHeaderPubMenu_publisher_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...MetaHeaderPubMenu_publisher_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderPubMenu_publisher_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...MutePopoverOptions_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MutePopoverOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderPubMenu_publisher_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  ...MutePopoverOptions_creator</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MutePopoverOptions_creator on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionFollowButton_collection on Collection {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">  ...SusiClickable_collection</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiClickable_collection on Collection {</span><span class="se">\n</span><span class="s2">  ...SusiContainer_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiContainer_collection on Collection {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...SignInOptions_collection</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment FollowAndSubscribeButtons_user on User {</span><span class="se">\n</span><span class="s2">  ...UserFollowButton_user</span><span class="se">\n</span><span class="s2">  ...UserSubscribeButton_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserFollowButton_user on User {</span><span class="se">\n</span><span class="s2">  ...UserFollowButtonSignedIn_user</span><span class="se">\n</span><span class="s2">  ...UserFollowButtonSignedOut_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserFollowButtonSignedIn_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserFollowButtonSignedOut_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...SusiClickable_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiClickable_user on User {</span><span class="se">\n</span><span class="s2">  ...SusiContainer_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiContainer_user on User {</span><span class="se">\n</span><span class="s2">  ...SignInOptions_user</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserSubscribeButton_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  isPartnerProgramEnrolled</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  viewerEdge {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    isFollowing</span><span class="se">\n</span><span class="s2">    isUser</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  viewerIsUser</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_user</span><span class="se">\n</span><span class="s2">  ...MembershipUpsellModal_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  user {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    newsletterV3 {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useNewsletterV3Subscription_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MembershipUpsellModal_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  postSubscribeMembershipUpsellShownAt</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderNav_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    navigation {</span><span class="se">\n</span><span class="s2">      navItems {</span><span class="se">\n</span><span class="s2">        name</span><span class="se">\n</span><span class="s2">        ...PublisherHeaderNavLink_headerNavigationItem</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...PublisherHeaderNavLink_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    domain</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    navItems {</span><span class="se">\n</span><span class="s2">      tagSlug</span><span class="se">\n</span><span class="s2">      title</span><span class="se">\n</span><span class="s2">      url</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    customDomainState {</span><span class="se">\n</span><span class="s2">      live {</span><span class="se">\n</span><span class="s2">        domain</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    hasSubdomain</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    about</span><span class="se">\n</span><span class="s2">    homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {</span><span class="se">\n</span><span class="s2">      posts {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderNavLink_headerNavigationItem on HeaderNavigationItem {</span><span class="se">\n</span><span class="s2">  href</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  tags {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    normalizedTagSlug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHeaderNavLink_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHomePosts_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  homepagePostsConnection(</span><span class="se">\n</span><span class="s2">    paging: {limit: $homepagePostsLimit, from: $homepagePostsFrom}</span><span class="se">\n</span><span class="s2">    includeDistributedResponses: $includeDistributedResponses</span><span class="se">\n</span><span class="s2">  ) {</span><span class="se">\n</span><span class="s2">    posts {</span><span class="se">\n</span><span class="s2">      ...PublisherHomePosts_post</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    pagingInfo {</span><span class="se">\n</span><span class="s2">      next {</span><span class="se">\n</span><span class="s2">        from</span><span class="se">\n</span><span class="s2">        limit</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...CardByline_publisher</span><span class="se">\n</span><span class="s2">  ...NewsletterV3Promo_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherHomePosts_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHomePosts_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...ExpandablePost_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePost_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    ...ExpandablePost_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    ...CardByline_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...InteractivePostBody_postPreview</span><span class="se">\n</span><span class="s2">  firstPublishedAt</span><span class="se">\n</span><span class="s2">  isLocked</span><span class="se">\n</span><span class="s2">  isSeries</span><span class="se">\n</span><span class="s2">  isShortform</span><span class="se">\n</span><span class="s2">  latestPublishedAt</span><span class="se">\n</span><span class="s2">  inResponseToCatalogResult {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  postResponses {</span><span class="se">\n</span><span class="s2">    count</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  previewImage {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    focusPercentX</span><span class="se">\n</span><span class="s2">    focusPercentY</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  readingTime</span><span class="se">\n</span><span class="s2">  sequence {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  title</span><span class="se">\n</span><span class="s2">  uniqueSlug</span><span class="se">\n</span><span class="s2">  visibility</span><span class="se">\n</span><span class="s2">  ...CardByline_post</span><span class="se">\n</span><span class="s2">  ...ExpandablePostFooter_post</span><span class="se">\n</span><span class="s2">  ...InResponseToEntityPreview_post</span><span class="se">\n</span><span class="s2">  ...PostScrollTracker_post</span><span class="se">\n</span><span class="s2">  ...ReadMore_post</span><span class="se">\n</span><span class="s2">  ...HighDensityPreview_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePost_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  ...CardByline_user</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CardByline_user on User {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  mediumMemberAt</span><span class="se">\n</span><span class="s2">  socialStats {</span><span class="se">\n</span><span class="s2">    followerCount</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">  ...UserMentionTooltip_user</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserMentionTooltip_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  mediumMemberAt</span><span class="se">\n</span><span class="s2">  ...UserAvatar_user</span><span class="se">\n</span><span class="s2">  ...UserFollowButton_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CardByline_collection on Collection {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment InteractivePostBody_postPreview on Post {</span><span class="se">\n</span><span class="s2">  extendedPreviewContent(</span><span class="se">\n</span><span class="s2">    truncationConfig: {previewParagraphsWordCountThreshold: 400, minimumWordLengthForTruncation: 150, truncateAtEndOfSentence: true, showFullImageCaptions: true, shortformPreviewParagraphsWordCountThreshold: 30, shortformMinimumWordLengthForTruncation: 30}</span><span class="se">\n</span><span class="s2">  ) {</span><span class="se">\n</span><span class="s2">    bodyModel {</span><span class="se">\n</span><span class="s2">      ...PostBody_bodyModel</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    isFullContent</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBody_bodyModel on RichText {</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    textLayout</span><span class="se">\n</span><span class="s2">    imageLayout</span><span class="se">\n</span><span class="s2">    backgroundImage {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    videoLayout</span><span class="se">\n</span><span class="s2">    backgroundVideo {</span><span class="se">\n</span><span class="s2">      videoId</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      previewImageId</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...PostBodySection_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...normalizedBodyModel_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBodySection_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...PostBodyParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBodyParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  ...ImageParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...TextParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...IframeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...MixtapeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ImageParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  href</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  metadata {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    originalHeight</span><span class="se">\n</span><span class="s2">    originalWidth</span><span class="se">\n</span><span class="s2">    focusPercentX</span><span class="se">\n</span><span class="s2">    focusPercentY</span><span class="se">\n</span><span class="s2">    alt</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  ...ParagraphRefsMapContext_paragraph</span><span class="se">\n</span><span class="s2">  ...PostAnnotationsMarker_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment Markups_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  hasDropCap</span><span class="se">\n</span><span class="s2">  dropCapImage {</span><span class="se">\n</span><span class="s2">    ...MarkupNode_data_dropCapImage</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  markups {</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    start</span><span class="se">\n</span><span class="s2">    end</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    anchorType</span><span class="se">\n</span><span class="s2">    userId</span><span class="se">\n</span><span class="s2">    linkMetadata {</span><span class="se">\n</span><span class="s2">      httpStatus</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MarkupNode_data_dropCapImage on ImageMetadata {</span><span class="se">\n</span><span class="s2">  ...DropCap_image</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment DropCap_image on ImageMetadata {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  originalHeight</span><span class="se">\n</span><span class="s2">  originalWidth</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ParagraphRefsMapContext_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostAnnotationsMarker_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  ...PostViewNoteCard_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostViewNoteCard_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment TextParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  hasDropCap</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  ...ParagraphRefsMapContext_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment IframeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  iframe {</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      iframeSrc</span><span class="se">\n</span><span class="s2">      iframeHeight</span><span class="se">\n</span><span class="s2">      iframeWidth</span><span class="se">\n</span><span class="s2">      title</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  ...getEmbedlyCardUrlParams_paragraph</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getEmbedlyCardUrlParams_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  iframe {</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      iframeSrc</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MixtapeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  mixtapeMetadata {</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      mediumCatalog {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...GenericMixtapeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment GenericMixtapeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  mixtapeMetadata {</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    thumbnailImageId</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  markups {</span><span class="se">\n</span><span class="s2">    start</span><span class="se">\n</span><span class="s2">    end</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment normalizedBodyModel_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    markups {</span><span class="se">\n</span><span class="s2">      type</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    ...getParagraphHighlights_paragraph</span><span class="se">\n</span><span class="s2">    ...getParagraphPrivateNotes_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    ...getSectionEndIndex_section</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...getParagraphStyles_richText</span><span class="se">\n</span><span class="s2">  ...getParagraphSpaces_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphHighlights_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphPrivateNotes_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getSectionEndIndex_section on Section {</span><span class="se">\n</span><span class="s2">  startIndex</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphStyles_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    text</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    ...getSectionEndIndex_section</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphSpaces_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    layout</span><span class="se">\n</span><span class="s2">    metadata {</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    ...paragraphExtendsImageGrid_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...getSeriesParagraphTopSpacings_richText</span><span class="se">\n</span><span class="s2">  ...getPostParagraphTopSpacings_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment paragraphExtendsImageGrid_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getSeriesParagraphTopSpacings_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getPostParagraphTopSpacings_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    layout</span><span class="se">\n</span><span class="s2">    text</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CardByline_post on Post {</span><span class="se">\n</span><span class="s2">  ...DraftStatus_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment DraftStatus_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  pendingCollection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    creator {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    ...BoldCollectionName_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  statusForCollection</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isPublished</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment BoldCollectionName_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePostFooter_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  allowResponses</span><span class="se">\n</span><span class="s2">  postResponses {</span><span class="se">\n</span><span class="s2">    count</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isLimitedState</span><span class="se">\n</span><span class="s2">  ...ExpandablePostCardOverflowButton_post</span><span class="se">\n</span><span class="s2">  ...BookmarkButton_post</span><span class="se">\n</span><span class="s2">  ...PostFooterSocialPopover_post</span><span class="se">\n</span><span class="s2">  ...MultiVote_post</span><span class="se">\n</span><span class="s2">  ...OverflowMenuButtonWithNegativeSignal_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePostCardOverflowButton_post on Post {</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...ExpandablePostCardEditorWriterButton_post</span><span class="se">\n</span><span class="s2">  ...ExpandablePostCardReaderButton_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePostCardEditorWriterButton_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  allowResponses</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  visibility</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  responseDistribution</span><span class="se">\n</span><span class="s2">  ...useIsPinnedInContext_post</span><span class="se">\n</span><span class="s2">  ...CopyFriendLinkMenuItem_post</span><span class="se">\n</span><span class="s2">  ...NewsletterV3EmailToSubscribersMenuItem_post</span><span class="se">\n</span><span class="s2">  ...OverflowMenuItemUndoClaps_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useIsPinnedInContext_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  pendingCollection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  pinnedAt</span><span class="se">\n</span><span class="s2">  pinnedByCreatorAt</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CopyFriendLinkMenuItem_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3EmailToSubscribersMenuItem_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    newsletterV3 {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      subscribersCount</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isNewsletter</span><span class="se">\n</span><span class="s2">  isAuthorNewsletter</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment OverflowMenuItemUndoClaps_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  ...ClapMutation_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ClapMutation_post on Post {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  ...MultiVoteCount_post</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MultiVoteCount_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...PostVotersNetwork_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostVotersNetwork_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  voterCount</span><span class="se">\n</span><span class="s2">  recommenders {</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ExpandablePostCardReaderButton_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  ...ClapMutation_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment BookmarkButton_post on Post {</span><span class="se">\n</span><span class="s2">  visibility</span><span class="se">\n</span><span class="s2">  ...SusiClickable_post</span><span class="se">\n</span><span class="s2">  ...AddToCatalogBookmarkButton_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiClickable_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  ...SusiContainer_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiContainer_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment AddToCatalogBookmarkButton_post on Post {</span><span class="se">\n</span><span class="s2">  ...AddToCatalogBase_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment AddToCatalogBase_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostFooterSocialPopover_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  title</span><span class="se">\n</span><span class="s2">  ...SharePostButton_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SharePostButton_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MultiVote_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...SusiClickable_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isPublished</span><span class="se">\n</span><span class="s2">  ...SusiClickable_post</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isLimitedState</span><span class="se">\n</span><span class="s2">  ...MultiVoteCount_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment OverflowMenuButtonWithNegativeSignal_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...OverflowMenuWithNegativeSignal_post</span><span class="se">\n</span><span class="s2">  ...CreatorActionOverflowPopover_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment OverflowMenuWithNegativeSignal_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...OverflowMenuItemUndoClaps_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CreatorActionOverflowPopover_post on Post {</span><span class="se">\n</span><span class="s2">  allowResponses</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  statusForCollection</span><span class="se">\n</span><span class="s2">  isLocked</span><span class="se">\n</span><span class="s2">  isPublished</span><span class="se">\n</span><span class="s2">  clapCount</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  pinnedAt</span><span class="se">\n</span><span class="s2">  pinnedByCreatorAt</span><span class="se">\n</span><span class="s2">  curationEligibleAt</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  responseDistribution</span><span class="se">\n</span><span class="s2">  visibility</span><span class="se">\n</span><span class="s2">  inResponseToPostResult {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  inResponseToCatalogResult {</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  pendingCollection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    creator {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    avatar {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    domain</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...MutePopoverOptions_creator</span><span class="se">\n</span><span class="s2">    ...auroraHooks_publisher</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    creator {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    avatar {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    domain</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    ...MutePopoverOptions_collection</span><span class="se">\n</span><span class="s2">    ...auroraHooks_publisher</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...useIsPinnedInContext_post</span><span class="se">\n</span><span class="s2">  ...NewsletterV3EmailToSubscribersMenuItem_post</span><span class="se">\n</span><span class="s2">  ...OverflowMenuItemUndoClaps_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment auroraHooks_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    isAuroraEligible</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    viewerEdge {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      isEditor</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    isAuroraVisible</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment InResponseToEntityPreview_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  inResponseToEntityType</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostScrollTracker_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sequence {</span><span class="se">\n</span><span class="s2">    sequenceId</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ReadMore_post on Post {</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  readingTime</span><span class="se">\n</span><span class="s2">  ...usePostUrl_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment usePostUrl_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    domain</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  isSeries</span><span class="se">\n</span><span class="s2">  mediumUrl</span><span class="se">\n</span><span class="s2">  sequence {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  uniqueSlug</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment HighDensityPreview_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  title</span><span class="se">\n</span><span class="s2">  previewImage {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    focusPercentX</span><span class="se">\n</span><span class="s2">    focusPercentY</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  extendedPreviewContent(</span><span class="se">\n</span><span class="s2">    truncationConfig: {previewParagraphsWordCountThreshold: 400, minimumWordLengthForTruncation: 150, truncateAtEndOfSentence: true, showFullImageCaptions: true, shortformPreviewParagraphsWordCountThreshold: 30, shortformMinimumWordLengthForTruncation: 30}</span><span class="se">\n</span><span class="s2">  ) {</span><span class="se">\n</span><span class="s2">    subtitle</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...HighDensityFooter_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment HighDensityFooter_post on Post {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  readingTime</span><span class="se">\n</span><span class="s2">  tags {</span><span class="se">\n</span><span class="s2">    ...TopicPill_tag</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...BookmarkButton_post</span><span class="se">\n</span><span class="s2">  ...ExpandablePostCardOverflowButton_post</span><span class="se">\n</span><span class="s2">  ...OverflowMenuButtonWithNegativeSignal_post</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment TopicPill_tag on Tag {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  displayTitle</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CardByline_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...CardByline_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...CardByline_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3Promo_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...NewsletterV3Promo_publisher_User</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...NewsletterV3Promo_publisher_Collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3Promo_publisher_User on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  viewerIsUser</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...NewsletterV3Promo_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3Promo_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  description</span><span class="se">\n</span><span class="s2">  promoHeadline</span><span class="se">\n</span><span class="s2">  promoBody</span><span class="se">\n</span><span class="s2">  ...NewsletterV3AmpButton_newsletterV3</span><span class="se">\n</span><span class="s2">  ...NewsletterV3SubscribeButton_newsletterV3</span><span class="se">\n</span><span class="s2">  ...NewsletterV3SubscribeByEmail_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3AmpButton_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    ...collectionDefaultBackgroundTheme_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionDefaultBackgroundTheme_collection on Collection {</span><span class="se">\n</span><span class="s2">  colorPalette {</span><span class="se">\n</span><span class="s2">    ...collectionDefaultBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...collectionDefaultBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionDefaultBackgroundTheme_colorPalette on ColorPalette {</span><span class="se">\n</span><span class="s2">  ...customDefaultBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment customDefaultBackgroundTheme_colorPalette on ColorPalette {</span><span class="se">\n</span><span class="s2">  highlightSpectrum {</span><span class="se">\n</span><span class="s2">    ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  defaultBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">    ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">    ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment collectionDefaultBackgroundTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...customDefaultBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment customDefaultBackgroundTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      primary {</span><span class="se">\n</span><span class="s2">        colorPalette {</span><span class="se">\n</span><span class="s2">          ...customDefaultBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      background {</span><span class="se">\n</span><span class="s2">        colorPalette {</span><span class="se">\n</span><span class="s2">          ...customDefaultBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3SubscribeButton_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  user {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    ...SusiClickable_collection</span><span class="se">\n</span><span class="s2">    ...collectionDefaultBackgroundTheme_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...SusiClickable_newsletterV3</span><span class="se">\n</span><span class="s2">  ...useNewsletterV3Subscription_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiClickable_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  ...SusiContainer_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SusiContainer_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  ...SignInOptions_newsletterV3</span><span class="se">\n</span><span class="s2">  ...SignUpOptions_newsletterV3</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignInOptions_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SignUpOptions_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3SubscribeByEmail_newsletterV3 on NewsletterV3 {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  user {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  collection {</span><span class="se">\n</span><span class="s2">    ...collectionDefaultBackgroundTheme_collection</span><span class="se">\n</span><span class="s2">    ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment NewsletterV3Promo_publisher_Collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...NewsletterV3Promo_newsletterV3</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherHomePosts_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...useShowAuthorNewsletterV3Promo_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useShowAuthorNewsletterV3Promo_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    showPromo</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserSubdomainFlow_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  hasCompletedProfile</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  ...UserCompleteProfileDialog_user</span><span class="se">\n</span><span class="s2">  ...UserSubdomainOnboardingDialog_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserCompleteProfileDialog_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  hasCompletedProfile</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserSubdomainOnboardingDialog_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  customDomainState {</span><span class="se">\n</span><span class="s2">    pending {</span><span class="se">\n</span><span class="s2">      status</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    live {</span><span class="se">\n</span><span class="s2">      status</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserProfileMetadata_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  bio</span><span class="se">\n</span><span class="s2">  socialStats {</span><span class="se">\n</span><span class="s2">    followerCount</span><span class="se">\n</span><span class="s2">    followingCount</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">  ...UserProfileMetadataHelmet_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserProfileMetadataHelmet_user on User {</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  twitterScreenName</span><span class="se">\n</span><span class="s2">  navItems {</span><span class="se">\n</span><span class="s2">    title</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SuspendedBannerLoader_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  isSuspended</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useAnalytics_user on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment EntityDrivenSubscriptionLandingPageScreen_writer on User {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  imageId</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  username</span><span class="se">\n</span><span class="s2">  isPartnerProgramEnrolled</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    ...CustomThemeProvider_customStyleSheet</span><span class="se">\n</span><span class="s2">    ...CustomBackgroundWrapper_customStyleSheet</span><span class="se">\n</span><span class="s2">    ...MetaHeader_customStyleSheet</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...MetaHeader_publisher</span><span class="se">\n</span><span class="s2">  ...userUrl_user</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CustomThemeProvider_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...customDefaultBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...customStyleSheetFontTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment customStyleSheetFontTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    fonts {</span><span class="se">\n</span><span class="s2">      font1 {</span><span class="se">\n</span><span class="s2">        name</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      font2 {</span><span class="se">\n</span><span class="s2">        name</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      font3 {</span><span class="se">\n</span><span class="s2">        name</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CustomBackgroundWrapper_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      background {</span><span class="se">\n</span><span class="s2">        ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeader_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    headerScale</span><span class="se">\n</span><span class="s2">    horizontalAlignment</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...MetaHeaderBackground_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...MetaHeaderEngagement_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...MetaHeaderLogo_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavVertical_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...MetaHeaderTagline_customStyleSheet</span><span class="se">\n</span><span class="s2">  ...MetaHeaderThemeProvider_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderBackground_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    headerScale</span><span class="se">\n</span><span class="s2">    backgroundImageDisplayMode</span><span class="se">\n</span><span class="s2">    backgroundImageVerticalAlignment</span><span class="se">\n</span><span class="s2">    backgroundColorDisplayMode</span><span class="se">\n</span><span class="s2">    backgroundColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      ...getOpaqueHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    secondaryBackgroundColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    postBackgroundColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    backgroundImage {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderBackground_imageMetadata</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderEngagement_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNav_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNav_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  navigation {</span><span class="se">\n</span><span class="s2">    navItems {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderNav_headerNavigationItem</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNav_headerNavigationItem on HeaderNavigationItem {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  tagSlugs</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavLink_headerNavigationItem</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNavLink_headerNavigationItem on HeaderNavigationItem {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...getNavItemHref_headerNavigationItem</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getNavItemHref_headerNavigationItem on HeaderNavigationItem {</span><span class="se">\n</span><span class="s2">  href</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  tags {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    normalizedTagSlug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderLogo_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    nameColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    nameTreatment</span><span class="se">\n</span><span class="s2">    postNameTreatment</span><span class="se">\n</span><span class="s2">    logoImage {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderLogo_imageMetadata</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    logoScale</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderLogo_imageMetadata on ImageMetadata {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  originalWidth</span><span class="se">\n</span><span class="s2">  originalHeight</span><span class="se">\n</span><span class="s2">  ...PublisherLogo_image</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherLogo_image on ImageMetadata {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  originalHeight</span><span class="se">\n</span><span class="s2">  originalWidth</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNavVertical_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  navigation {</span><span class="se">\n</span><span class="s2">    navItems {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderNavLink_headerNavigationItem</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNav_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderTagline_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    taglineColor {</span><span class="se">\n</span><span class="s2">      ...getHexFromColorValue_colorValue</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    taglineTreatment</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderThemeProvider_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...useMetaHeaderTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useMetaHeaderTheme_customStyleSheet on CustomStyleSheet {</span><span class="se">\n</span><span class="s2">  ...customDefaultBackgroundTheme_customStyleSheet</span><span class="se">\n</span><span class="s2">  global {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      primary {</span><span class="se">\n</span><span class="s2">        colorPalette {</span><span class="se">\n</span><span class="s2">          tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">            ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">            __typename</span><span class="se">\n</span><span class="s2">          }</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  header {</span><span class="se">\n</span><span class="s2">    backgroundColor {</span><span class="se">\n</span><span class="s2">      colorPalette {</span><span class="se">\n</span><span class="s2">        tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">          ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    postBackgroundColor {</span><span class="se">\n</span><span class="s2">      colorPalette {</span><span class="se">\n</span><span class="s2">        tintBackgroundSpectrum {</span><span class="se">\n</span><span class="s2">          ...ThemeUtil_colorSpectrum</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    backgroundImage {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeader_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...MetaHeaderEngagement_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderLogo_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavVertical_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderTagline_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderThemeProvider_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderActions_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderTop_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavLink_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    favicon {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    tagline</span><span class="se">\n</span><span class="s2">    ...CollectionNavigationContextProvider_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    bio</span><span class="se">\n</span><span class="s2">    ...UserProfileCatalogsLink_publisher</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderEngagement_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNav_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherAboutLink_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherFollowButton_publisher</span><span class="se">\n</span><span class="s2">  ...PublisherFollowerCount_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    creator {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    customStyleSheet {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      ...CustomThemeProvider_customStyleSheet</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...UserProfileCatalogsLink_publisher</span><span class="se">\n</span><span class="s2">    ...UserSubscribeButton_user</span><span class="se">\n</span><span class="s2">    customStyleSheet {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      ...CustomThemeProvider_customStyleSheet</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNav_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavLink_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNavLink_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...getNavItemHref_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getNavItemHref_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...publisherUrl_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherAboutLink_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PublisherFollowButton_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    ...CollectionFollowButton_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...UserFollowButton_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment UserProfileCatalogsLink_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    ...userUrl_user</span><span class="se">\n</span><span class="s2">    homePostsPublished: homepagePostsConnection(paging: {limit: 1}) {</span><span class="se">\n</span><span class="s2">      posts {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderLogo_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    logo {</span><span class="se">\n</span><span class="s2">      ...MetaHeaderLogo_imageMetadata</span><span class="se">\n</span><span class="s2">      ...PublisherLogo_image</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...auroraHooks_publisher</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderNavVertical_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  ...PublisherAboutLink_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNav_publisher</span><span class="se">\n</span><span class="s2">  ...MetaHeaderNavLink_publisher</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderTagline_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    tagline</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    bio</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderThemeProvider_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  customStyleSheet {</span><span class="se">\n</span><span class="s2">    ...MetaHeaderThemeProvider_customStyleSheet</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    colorPalette {</span><span class="se">\n</span><span class="s2">      ...customDefaultBackgroundTheme_colorPalette</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderActions_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ...MetaHeaderPubMenu_publisher</span><span class="se">\n</span><span class="s2">  ...SearchWidget_publisher</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    creator {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    customStyleSheet {</span><span class="se">\n</span><span class="s2">      navigation {</span><span class="se">\n</span><span class="s2">        navItems {</span><span class="se">\n</span><span class="s2">          name</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    ...CollectionAvatar_collection</span><span class="se">\n</span><span class="s2">    ...CollectionMetabarActionsPopover_collection</span><span class="se">\n</span><span class="s2">    ...MetaHeaderActions_collection_common</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...UserAvatar_user</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment SearchWidget_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    domain</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...algoliaSearch_publisher</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment algoliaSearch_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionMetabarActionsPopover_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  isAuroraEligible</span><span class="se">\n</span><span class="s2">  isAuroraVisible</span><span class="se">\n</span><span class="s2">  newsletterV3 {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...collectionUrl_collection</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderActions_collection_common on Collection {</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderTop_publisher on Publisher {</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  ... on Collection {</span><span class="se">\n</span><span class="s2">    slug</span><span class="se">\n</span><span class="s2">    ...CollectionMetabarActionsPopover_collection</span><span class="se">\n</span><span class="s2">    ...CollectionAvatar_collection</span><span class="se">\n</span><span class="s2">    ...MetaHeaderTop_collection</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ... on User {</span><span class="se">\n</span><span class="s2">    username</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MetaHeaderTop_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  creator {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CollectionNavigationContextProvider_collection on Collection {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  domain</span><span class="se">\n</span><span class="s2">  slug</span><span class="se">\n</span><span class="s2">  isAuroraVisible</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment useShouldShowEntityDrivenSubscription_creator on User {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n</span><span class="s2">"</span><span class="w">
      </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">id</code> : The User Id corresponding to the account, provided by the follower retrieval Endpoint above</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">homepagePostsFrom</code> : Pagination parameter, refer to the <code class="language-plaintext highlighter-rouge">from</code> in the response to fetch all paginated data</p>
  </li>
</ul>

<p><a href="https://www.postman.com/" target="_blank"><strong>Postman</strong></a> <strong>:</strong></p>

<p><img src="/assets/88f0fb935120/1*dw61NwISVGRbFLlie4Na2w.webp" alt="" loading="lazy" decoding="async" width="1200" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/88f0fb935120/1*dw61NwISVGRbFLlie4Na2w.png" /></p>

<p><strong>Response:</strong></p>

<p><img src="/assets/88f0fb935120/1*FEt2xWEjJMqpWjxYVUgVWg.webp" alt="" loading="lazy" decoding="async" width="1070" height="998" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDcwIiBoZWlnaHQ9Ijk5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/88f0fb935120/1*FEt2xWEjJMqpWjxYVUgVWg.png" /></p>

<p>The article list information can be obtained at <code class="language-plaintext highlighter-rouge">response[0]["data"]["userResult"]["homepagePostsConnection"]["posts"]</code>.</p>

<h4 id="medium-private-api--fetching-article-content">Medium Private API — Fetching Article Content</h4>

<p>Next is the most crucial part: scraping the original content of the articles.</p>

<p>Graphql Query Body:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"operationName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PostViewerEdgeContentQuery"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"variables"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"postId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"c008a9e8ceca"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"query PostViewerEdgeContentQuery($postId: ID!, $postMeteringOptions: PostMeteringOptions) {</span><span class="se">\n</span><span class="s2">  post(id: $postId) {</span><span class="se">\n</span><span class="s2">    ... on Post {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      viewerEdge {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        fullContent(postMeteringOptions: $postMeteringOptions) {</span><span class="se">\n</span><span class="s2">          isLockedPreviewOnly</span><span class="se">\n</span><span class="s2">          validatedShareKey</span><span class="se">\n</span><span class="s2">          bodyModel {</span><span class="se">\n</span><span class="s2">            ...PostBody_bodyModel</span><span class="se">\n</span><span class="s2">            __typename</span><span class="se">\n</span><span class="s2">          }</span><span class="se">\n</span><span class="s2">          __typename</span><span class="se">\n</span><span class="s2">        }</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBody_bodyModel on RichText {</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    name</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    textLayout</span><span class="se">\n</span><span class="s2">    imageLayout</span><span class="se">\n</span><span class="s2">    backgroundImage {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    videoLayout</span><span class="se">\n</span><span class="s2">    backgroundVideo {</span><span class="se">\n</span><span class="s2">      videoId</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      previewImageId</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    ...PostBodySection_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...normalizedBodyModel_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBodySection_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  ...PostBodyParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostBodyParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  ...ImageParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...TextParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...IframeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...MixtapeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  ...CodeBlockParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ImageParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  href</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  metadata {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    originalHeight</span><span class="se">\n</span><span class="s2">    originalWidth</span><span class="se">\n</span><span class="s2">    focusPercentX</span><span class="se">\n</span><span class="s2">    focusPercentY</span><span class="se">\n</span><span class="s2">    alt</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  ...ParagraphRefsMapContext_paragraph</span><span class="se">\n</span><span class="s2">  ...PostAnnotationsMarker_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment Markups_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  hasDropCap</span><span class="se">\n</span><span class="s2">  dropCapImage {</span><span class="se">\n</span><span class="s2">    ...MarkupNode_data_dropCapImage</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  markups {</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    start</span><span class="se">\n</span><span class="s2">    end</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    anchorType</span><span class="se">\n</span><span class="s2">    userId</span><span class="se">\n</span><span class="s2">    linkMetadata {</span><span class="se">\n</span><span class="s2">      httpStatus</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MarkupNode_data_dropCapImage on ImageMetadata {</span><span class="se">\n</span><span class="s2">  ...DropCap_image</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment DropCap_image on ImageMetadata {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  originalHeight</span><span class="se">\n</span><span class="s2">  originalWidth</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment ParagraphRefsMapContext_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostAnnotationsMarker_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  ...PostViewNoteCard_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment PostViewNoteCard_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment TextParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  hasDropCap</span><span class="se">\n</span><span class="s2">  codeBlockMetadata {</span><span class="se">\n</span><span class="s2">    mode</span><span class="se">\n</span><span class="s2">    lang</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  ...ParagraphRefsMapContext_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment IframeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  iframe {</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      iframeSrc</span><span class="se">\n</span><span class="s2">      iframeHeight</span><span class="se">\n</span><span class="s2">      iframeWidth</span><span class="se">\n</span><span class="s2">      title</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  ...getEmbedlyCardUrlParams_paragraph</span><span class="se">\n</span><span class="s2">  ...Markups_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getEmbedlyCardUrlParams_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  iframe {</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      iframeSrc</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment MixtapeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  mixtapeMetadata {</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    mediaResource {</span><span class="se">\n</span><span class="s2">      mediumCatalog {</span><span class="se">\n</span><span class="s2">        id</span><span class="se">\n</span><span class="s2">        __typename</span><span class="se">\n</span><span class="s2">      }</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...GenericMixtapeParagraph_paragraph</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment GenericMixtapeParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  text</span><span class="se">\n</span><span class="s2">  mixtapeMetadata {</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    thumbnailImageId</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  markups {</span><span class="se">\n</span><span class="s2">    start</span><span class="se">\n</span><span class="s2">    end</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    href</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment CodeBlockParagraph_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  codeBlockMetadata {</span><span class="se">\n</span><span class="s2">    lang</span><span class="se">\n</span><span class="s2">    mode</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment normalizedBodyModel_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    markups {</span><span class="se">\n</span><span class="s2">      type</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    codeBlockMetadata {</span><span class="se">\n</span><span class="s2">      lang</span><span class="se">\n</span><span class="s2">      mode</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    ...getParagraphHighlights_paragraph</span><span class="se">\n</span><span class="s2">    ...getParagraphPrivateNotes_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    ...getSectionEndIndex_section</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...getParagraphStyles_richText</span><span class="se">\n</span><span class="s2">  ...getParagraphSpaces_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphHighlights_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphPrivateNotes_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  name</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getSectionEndIndex_section on Section {</span><span class="se">\n</span><span class="s2">  startIndex</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphStyles_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    text</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    ...getSectionEndIndex_section</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getParagraphSpaces_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    layout</span><span class="se">\n</span><span class="s2">    metadata {</span><span class="se">\n</span><span class="s2">      originalHeight</span><span class="se">\n</span><span class="s2">      originalWidth</span><span class="se">\n</span><span class="s2">      id</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    type</span><span class="se">\n</span><span class="s2">    ...paragraphExtendsImageGrid_paragraph</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  ...getSeriesParagraphTopSpacings_richText</span><span class="se">\n</span><span class="s2">  ...getPostParagraphTopSpacings_richText</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment paragraphExtendsImageGrid_paragraph on Paragraph {</span><span class="se">\n</span><span class="s2">  layout</span><span class="se">\n</span><span class="s2">  type</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">  id</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getSeriesParagraphTopSpacings_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    id</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n\n</span><span class="s2">fragment getPostParagraphTopSpacings_richText on RichText {</span><span class="se">\n</span><span class="s2">  paragraphs {</span><span class="se">\n</span><span class="s2">    layout</span><span class="se">\n</span><span class="s2">    text</span><span class="se">\n</span><span class="s2">    codeBlockMetadata {</span><span class="se">\n</span><span class="s2">      lang</span><span class="se">\n</span><span class="s2">      mode</span><span class="se">\n</span><span class="s2">      __typename</span><span class="se">\n</span><span class="s2">    }</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  sections {</span><span class="se">\n</span><span class="s2">    startIndex</span><span class="se">\n</span><span class="s2">    __typename</span><span class="se">\n</span><span class="s2">  }</span><span class="se">\n</span><span class="s2">  __typename</span><span class="se">\n</span><span class="s2">}</span><span class="se">\n</span><span class="s2">"</span><span class="w">
      </span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">posdId</code> : Article ID, provided by the API used to get the article list above.</li>
</ul>

<p><a href="https://www.postman.com/" target="_blank"><strong>Postman</strong></a> <strong>:</strong></p>

<p><img src="/assets/88f0fb935120/1*K7ldG92qvdNIMG89NJC3nw.webp" alt="" loading="lazy" decoding="async" width="1200" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/88f0fb935120/1*K7ldG92qvdNIMG89NJC3nw.png" /></p>

<p><strong>Response:</strong></p>

<p><img src="/assets/88f0fb935120/1*r3va7Mjc0DTD3pAYkDsozQ.webp" alt="" loading="lazy" decoding="async" width="987" height="1060" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODciIGhlaWdodD0iMTA2MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/88f0fb935120/1*r3va7Mjc0DTD3pAYkDsozQ.png" /></p>

<p>Article source as shown in the image:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">response[0]["data"]["post"]["viewerEdge"]["fullContent"]["bodyModel"]["paragraphs"]</code> : The entire article’s paragraphs, combined to form the full article content</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">["markups"]</code> : Text styles in the paragraph, such as bold, hyperlinks…</p>
  </li>
</ul>

<blockquote>
  <p><strong><em>Regarding how to convert this JSON description format into Markdown, you can directly refer to or use my previously developed open-source tool — <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a></em></strong></p>
</blockquote>

<p><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank"><img src="https://repository-images.githubusercontent.com/493527574/9b5b7025-cc95-4e81-84a9-b38706093c27" alt="" /></a></p>

<ul>
  <li>If you want to crawl your own paid articles, you need to include the sid and uid login token information (see below).</li>
</ul>

<h3 id="medium-x-cloudflare-offense-and-defense">Medium x Cloudflare Offense and Defense</h3>

<p>The second topic of this article is Cloudflare. Starting around the second half of 2025, Medium’s Cloudflare protection was set to nearly the highest level, blocking all requests from cloud services (my requests from Google Apps Script and GitHub Actions were all blocked), causing data scraping to fail.</p>

<p><strong>Block Message: 403</strong></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span><span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en-US"</span><span class="nt">&gt;&lt;head&gt;&lt;title&gt;</span>Just a moment...<span class="nt">&lt;/title&gt;&lt;meta</span> <span class="na">http-equiv=</span><span class="s">"Content-Type"</span> <span class="na">content=</span><span class="s">"text/html; charset=UTF-8"</span><span class="nt">&gt;</span>....
</code></pre></div></div>

<p>This was very frustrating for me because my mirror site ( <a href="https://zhgchg.li" target="_blank">https://zhgchg.li</a> ) has been running steadily for several years. Whenever Medium publishes a new article, a script automatically runs there regularly to sync it over (via the Private API Graphql). <strong>If cloud services get blocked, I will have to run the script manually on my local computer</strong>.</p>

<h4 id="adding-sid-uid-to-header-cookies-"><strong>Adding sid, uid to Header Cookies ❌</strong></h4>

<p><strong>At first, I added sid and uid to the Header Cookies, which acted as Medium login credentials and worked smoothly. However, after a few months, even this method stopped working.</strong></p>

<p><img src="/assets/88f0fb935120/1*nKrcxHJP80VHkDUnQLEFFg.webp" alt="" loading="lazy" decoding="async" width="1200" height="401" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/88f0fb935120/1*nKrcxHJP80VHkDUnQLEFFg.png" /></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">uid</code> : your user ID</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">sid</code> : your access token <strong>(keep it confidential)</strong></p>
  </li>
</ul>

<h4 id="cloudflare-worker-proxy-request-">Cloudflare Worker Proxy Request ✅</h4>

<p>After trying everything without success, I had a sudden idea to use Cloudflare’s magic to defeat magic. Using Cloudflare, it surprisingly worked! It was not blocked by Cloudflare Bot protection.</p>

<p><strong>Demo Code:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="p">{</span>

  <span class="k">async</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">env</span><span class="p">,</span> <span class="nx">ctx</span><span class="p">)</span> <span class="p">{</span>

    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span><span class="p">;</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">path</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">/graphql</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="kd">let</span> <span class="nx">body</span><span class="p">;</span>
      <span class="k">try</span> <span class="p">{</span>
        <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span>
      <span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">Invalid JSON body</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">400</span> <span class="p">});</span>
      <span class="p">}</span>

      <span class="kd">let</span> <span class="nx">apiURL</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://medium.com/_/graphql</span><span class="dl">"</span><span class="p">;</span>

      <span class="kd">const</span> <span class="nx">apiResponse</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="nx">apiURL</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
          <span class="dl">"</span><span class="s2">Accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
          <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
          <span class="dl">"</span><span class="s2">User-Agent</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0</span><span class="dl">"</span><span class="p">,</span>
          <span class="dl">"</span><span class="s2">Cookie</span><span class="dl">"</span><span class="p">:</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">cookie</span><span class="dl">"</span><span class="p">)</span>
        <span class="p">},</span>
        <span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">body</span><span class="p">),</span>
      <span class="p">});</span>

      <span class="kd">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">apiResponse</span><span class="p">.</span><span class="nf">json</span><span class="p">();</span>
      <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">json</span><span class="p">),</span> <span class="p">{</span>
        <span class="na">status</span><span class="p">:</span> <span class="nx">apiResponse</span><span class="p">.</span><span class="nx">status</span><span class="p">,</span>
        <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
          <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json; charset=utf-8</span><span class="dl">"</span><span class="p">,</span>
        <span class="p">},</span>
      <span class="p">});</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">Not Found</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">status</span><span class="p">:</span> <span class="mi">404</span> <span class="p">});</span>
  <span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Request:</p>

<p>Change from <code class="language-plaintext highlighter-rouge">https://medium.com/_/graphql</code> to your deployed Cloudflare Worker URL.</p>

<h4 id="summary">Summary</h4>

<p>This sums up my years-long battle with the Medium API. Currently, all articles (Markdown files) and images are backed up at <a href="https://zhgchg.li" target="_blank">https://zhgchg.li</a>, including all attached image files. There is a copy in the GitHub repo and one on my computer’s hard drive, ensuring high security.</p>

<blockquote>
  <p>Still hope Medium lasts for a long time! This content is for experimental reference only; the author takes no responsibility for its use.</p>
</blockquote>]]></content>
  </entry><entry>
    <title type="html">App Store Connect API Webhook｜Automate CI/CD Workflows Seamlessly</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/app-store-connect-api-webhook-automate-ci-cd-workflows-seamlessly-7c0974856393/" rel="alternate" type="text/html" title="App Store Connect API Webhook｜Automate CI/CD Workflows Seamlessly" />
    <published>2025-12-27T17:02:01+08:00</published>
    <updated>2025-12-27T17:05:14+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/7c0974856393</id><summary type="html">Developers facing manual deployment delays can integrate App Store Connect API Webhooks to automate CI/CD pipelines, reducing errors and accelerating app releases efficiently.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="cicd" /><category term="webhooks" /><category term="fastlane" /><category term="app-store" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/7c0974856393/1*IqytjX72CmAx9WOx4Rm3gg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/app-store-connect-api-webhook-automate-ci-cd-workflows-seamlessly-7c0974856393/"><![CDATA[<h3 id="cicd-using-app-store-connect-api-webhook-to-integrate-automated-workflows">[CI/CD] Using App Store Connect API Webhook to Integrate Automated Workflows</h3>

<p>App Store Connect Webhook Use Case Analysis and Practical Integration Tutorial.</p>

<p><img src="/assets/7c0974856393/1*IqytjX72CmAx9WOx4Rm3gg.webp" alt="Photo by Volodymyr Hryshchenko" loading="lazy" decoding="async" width="5472" height="3648" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NDcyIiBoZWlnaHQ9IjM2NDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/7c0974856393/1*IqytjX72CmAx9WOx4Rm3gg.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@lunarts?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank">Volodymyr Hryshchenko</a></p>

<h4 id="introduction">Introduction</h4>

<p>Apple has continuously expanded the App Store Connect API in recent years, which is great news for developers. Previously, even certificate management relied on “hardcore” web sessions (with expiration and SMS verification), making CI/CD integration difficult. Features like store reviews could only depend on unstable RSS feeds.</p>

<p>In recent years, new features have been added almost every year, covering development, testing, and deployment processes, as well as native support for post-release reviews, finance, and data reports. Additionally, user management, groups, and TestFlight capabilities have been enhanced, allowing <strong>App Store Connect API to better improve the Apple developer experience</strong>.</p>

<blockquote>
  <p><em>Further reading: “<a href="/posts/zrealm-dev/app-store-connect-api-manage-customer-reviews-and-subscriptions-efficiently-f1365e51902c/">App Store Connect API Now Supports Reading and Managing Customer Reviews</a>”</em></p>
</blockquote>

<h4 id="wwdc-2025-automate-your-development-process-with-the-app-store-connect-api"><a href="https://developer.apple.com/videos/play/wwdc2025/324/" target="_blank">WWDC 2025 Automate your development process with the App Store Connect API</a></h4>

<p><a href="https://developer.apple.com/videos/play/wwdc2025/324/" target="_blank"><img src="https://devimages-cdn.apple.com/wwdc-services/images/3055294D-836B-4513-B7B0-0BC5666246B0/9859/9859_wide_250x141_2x.jpg" alt="" /></a></p>

<p><strong>The 2025 WWDC also brings the highly anticipated major feature — Webhook Notifications:</strong></p>

<ul>
  <li>
    <p><strong>Build Upload Status (The <a href="https://developer.apple.com/help/app-store-connect/reference/app-uploads/build-upload-statuses" target="_blank">status of a build upload</a> changes.)</strong><br />
Receive related data when the build upload status changes.<br />
<code class="language-plaintext highlighter-rouge">Complete / Failed / Processing</code></p>
  </li>
  <li>
    <p><strong>App Version Status (The <a href="https://developer.apple.com/help/app-store-connect/reference/app-information/app-and-submission-statuses#app-statuses" target="_blank">status of an app version</a> changes.)</strong><br />
Receive related data when the app version status changes.<br />
<code class="language-plaintext highlighter-rouge">Prepare for Submission / Ready for Review / Waiting for Review / Ready for Distribution / Rejected…</code></p>
  </li>
  <li>
    <p><strong>TestFlight Version Status (New <a href="https://developer.apple.com/help/app-store-connect/test-a-beta-version/view-tester-feedback" target="_blank">TestFlight feedback</a> is submitted by a tester.)</strong><br />
Receive related data when a tester leaves feedback (crash reports/screenshots).</p>
  </li>
  <li>
    <p><strong>Apple-hosted Asset Pack Status Changes (The <a href="https://developer.apple.com/help/app-store-connect/reference/app-uploads/apple-hosted-asset-pack-statuses" target="_blank">status of an Apple-hosted asset pack version</a> changes.)</strong><br />
Receive related data when specific changes occur to an Apple-hosted asset pack version.</p>
  </li>
</ul>

<h4 id="app-store-connect-api--webhook-notifications-"><a href="https://developer.apple.com/documentation/AppStoreConnectAPI/webhook-notifications" target="_blank">App Store Connect API / Webhook notifications</a> :</h4>

<blockquote>
  <p><em>Webhooks allow a system to send real-time data to another system over the internet.</em></p>
</blockquote>

<blockquote>
  <p><em>Webhook allows a system to instantly send data to another system over the internet.</em></p>
</blockquote>

<blockquote>
  <p><em>Unlike traditional APIs, where one system must request data, a webhook allows you to push data to the receiving system immediately when an event happens.</em></p>
</blockquote>

<blockquote>
  <p><em>Unlike traditional APIs, which require the receiver to actively send requests, Webhooks can instantly push data to the receiver as soon as the event happens.</em></p>
</blockquote>

<blockquote>
  <p><em>Webhooks are event-driven, meaning they are triggered by a specific action or event and immediately send the relevant data to a predefined URL, also called the “webhook URL” or “callback URL.”</em></p>
</blockquote>

<blockquote>
  <p><em>Webhooks are event-driven, meaning they instantly send relevant data to a pre-configured URL, called the “Webhook URL” or “Callback URL,” when a specific action or event occurs.</em></p>
</blockquote>

<blockquote>
  <p><em>A notification webhook is an endpoint you create on your server.</em></p>
</blockquote>

<blockquote>
  <p><em>Notification Webhook is an endpoint you create on your own server.</em></p>
</blockquote>

<blockquote>
  <p><em>This webhook endpoint receives HTTP POST requests from App Store Connect.</em></p>
</blockquote>

<blockquote>
  <p><em>This Webhook endpoint receives HTTP POST requests from App Store Connect.</em></p>
</blockquote>

<blockquote>
  <p><em>The POST requests describe important events related to your app.</em></p>
</blockquote>

<blockquote>
  <p><em>These POST requests describe important events related to your App.</em></p>
</blockquote>

<blockquote>
  <p><em>Use the webhooks notifications endpoint to set up notifications for events occurring in your apps.</em></p>
</blockquote>

<blockquote>
  <p><em>You can use Webhook notification endpoints to set up notifications for various events related to your App.</em></p>
</blockquote>

<h3 id="5-use-cases">5 Use Cases</h3>

<h4 id="1-trigger-app-submission-after-build-completion">1. Trigger App Submission After Build Completion</h4>

<p><strong>Before:</strong></p>

<p><img src="/assets/7c0974856393/1*usYZtMkzAu-bA4fVX8ZbUQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="465" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*usYZtMkzAu-bA4fVX8ZbUQ.png" /></p>

<p>In the past, when implementing App CI/CD packaging and submission, you <strong>had to wait for Apple to finish processing</strong> before continuing with the submission. Fastlane’s default method is to poll App Store Connect to check the uploaded build status, and only proceed with the submission lane once it is Complete.</p>

<blockquote>
  <p><strong><em>The waiting time is about 20 minutes</em></strong>. <em>This doesn’t matter much for self-hosted CI/CD, but if using cloud services, this 20-minute wait wastes a lot of resources. For example, GitHub Runner macOS costs US$0.062 per minute, so <strong>each waiting time for submission causes an unnecessary expense of US$1.24.</strong></em></p>
</blockquote>

<p><img src="/assets/7c0974856393/1*2-Zn2ApgVYd5S5KzxMLEMQ.webp" alt="Ref: Build Completed Processing Notification Email with Gmail Filter + Google Apps Script" loading="lazy" decoding="async" width="694" height="340" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTQiIGhlaWdodD0iMzQwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/7c0974856393/1*2-Zn2ApgVYd5S5KzxMLEMQ.png" /></p>

<p>Ref: <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">Build Completed Processing 通知信 搭配 Gmail Filter + Google Apps Script</a></p>

<p>In the era without Webhook active notifications, I previously used “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">Build Completed Processing notification emails combined with Gmail Filter + Google Apps Script to trigger</a>” to achieve the effect, but the process was a bit hardcore.</p>

<p><strong>After:</strong></p>

<p><img src="/assets/7c0974856393/1*ADkNoScFn4M_dIgv3sAKaw.webp" alt="" loading="lazy" decoding="async" width="1200" height="301" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjMwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*ADkNoScFn4M_dIgv3sAKaw.png" /></p>

<ul>
  <li>
    <p>With Webhook, the packaging and uploading steps can finish immediately after the upload is complete.</p>
  </li>
  <li>
    <p><strong>A Webhook notification is sent after the App Store Connect Build Process completes. We proceed with the submission step upon receiving the notification.</strong></p>
  </li>
  <li>
    <p><strong>Zero Waiting Cost</strong></p>
  </li>
</ul>

<h4 id="2-align-gitflow-release-process-with-app-release-timing">2. Align GitFlow Release Process with App Release Timing</h4>

<p><strong>Before:</strong></p>

<blockquote>
  <p><em>The final step in GitFlow deployment requires merging the develop branch back into the master branch, which corresponds to the current live version.</em></p>
</blockquote>

<p>Previously, tasks could only be executed manually or automatically on a fixed schedule, such as releasing the app every Monday afternoon and merging develop back to master on Monday. Manual execution is tedious, and with automatic execution, what if there’s a delay? Or if Monday is a holiday? The app might not actually be released, but develop has already been merged back to master.</p>

<p>In most cases, this is not very important. Only in extreme situations, such as needing to insert a hotfix during this time, might there be discrepancies. However, if you aim for a complete and stable CI/CD development process, this is a case worth exploring.</p>

<p><strong>Another approach, similar to above, is using the “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">App is Ready for Sale notification email with Gmail Filter + Google Apps Script to trigger</a>,” which is feasible.</strong></p>

<p><strong>After:</strong></p>

<p><img src="/assets/7c0974856393/1*6JjHC6GKc4fKXrh-WWCJVA.webp" alt="" loading="lazy" decoding="async" width="1200" height="224" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjIyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*6JjHC6GKc4fKXrh-WWCJVA.png" /></p>

<ul>
  <li>
    <p>With Webhook, you can directly trigger CI/CD actions (Master to Develop) upon receiving app release notifications.</p>
  </li>
  <li>
    <p>Ensure the App is truly live before merging back to Master</p>
  </li>
</ul>

<h4 id="3-app-release-messages">3. App Release Messages</h4>

<blockquote>
  <p><em>After the app is released to users, another common internal workflow is sending release notifications to relevant teams, listing tasks included in the version, and completing related tasks.</em></p>
</blockquote>

<p><strong>Before:</strong></p>

<p>Only manual, scheduled automation, or using email <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/"><strong>combined with Gmail Filter + Google Apps Script to trigger</strong></a>.</p>

<p><strong>After:</strong></p>

<p><img src="/assets/7c0974856393/1*UuGPKonNhXRUMZ71rDQcdw.webp" alt="" loading="lazy" decoding="async" width="1200" height="443" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*UuGPKonNhXRUMZ71rDQcdw.png" /></p>

<ul>
  <li>With Webhook, after receiving an App release notification, you can connect to the Jira/Asana API to mark the corresponding version’s batch as Complete and send a release completion message for the included tasks to Slack.</li>
</ul>

<h4 id="4-build-failed--review-rejected-notifier">4. Build Failed / Review Rejected Notifier</h4>

<blockquote>
  <p><em>In sections 1 and 2, it was mentioned that previously there was a chance to trigger workflows via email notifications. However, in large teams with strict permission controls, <strong>iOS developers only have “developer” backend access and cannot release or manage the app, so they cannot receive any email notifications about app status changes; this includes rejection notices for uploaded builds or app review rejections.</strong></em></p>
</blockquote>

<p><strong>Before:</strong></p>

<p>In the past, we could only rely on kind-hearted people (a.k.a PMs) to forward emails to the engineers. If those kind-hearted people missed it, we might only find out the app was rejected and couldn’t go live right before the launch!</p>

<p><strong>After:</strong></p>

<p><img src="/assets/7c0974856393/1*unKq9zdc8tJEqlNmfXiRYA.webp" alt="" loading="lazy" decoding="async" width="1200" height="582" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*unKq9zdc8tJEqlNmfXiRYA.png" /></p>

<ul>
  <li>This case is relatively simple: after receiving the Webhook notification, it forwards the message to Slack.</li>
</ul>

<h4 id="5-testflight-feedback-notifier">5. Testflight Feedback Notifier</h4>

<blockquote>
  <p><em>Similar to 4, but connects to TestFlight Feedback Webhook notifications.</em></p>
</blockquote>

<p><strong>Before:</strong></p>

<p>In the past, developers could only check tester feedback or crash issues by logging into the App Store Connect TestFlight backend themselves, <strong>which was very easy to overlook (there were even cases where feedback reported a year ago was only seen a year later)</strong>.</p>

<p><strong>After:</strong></p>

<ul>
  <li>Forward Testflight Feedback Webhook notifications to Slack after receiving them.</li>
</ul>

<p>— — —</p>

<blockquote>
  <p><em>歡迎其他創意使用案例。接下來，我們將介紹如何整合與使用它。</em></p>
</blockquote>

<h3 id="app-store-connect-api-webhook-setup">App Store Connect API Webhook Setup</h3>

<blockquote>
  <p><em>Permission Requirements: <strong>Admin or Account Holder privileges are required to configure</strong>.</em></p>
</blockquote>

<h4 id="setting-up-app-store-connect-api-webhook-notifications">Setting Up App Store Connect API Webhook Notifications</h4>

<ol>
  <li>
    <p><a href="https://appstoreconnect.apple.com/access/users" target="_blank">Go to App Store Connect Dashboard</a></p>
  </li>
  <li>
    <p>Go to “Users and Access” -&gt; “Integrations”</p>
  </li>
  <li>
    <p>Click “Webhooks” under “Additional”</p>
  </li>
  <li>
    <p>Click the “Create Webhook” button</p>
  </li>
</ol>

<p><img src="/assets/7c0974856393/1*e_6oin7tJjAB2gXl3txJow.webp" alt="" loading="lazy" decoding="async" width="716" height="767" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MTYiIGhlaWdodD0iNzY3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/7c0974856393/1*e_6oin7tJjAB2gXl3txJow.jpeg" /></p>

<ul>
  <li>
    <p>Name: Enter the Webhook name</p>
  </li>
  <li>
    <p>Payload URL: Enter the URL of your Webhook notification receiver service</p>
  </li>
  <li>
    <p>Secret String: Webhook request verification key (you can <a href="https://www.random.org/strings/?num=1&amp;len=32&amp;digits=on&amp;upperalpha=on&amp;loweralpha=on&amp;unique=on&amp;format=html&amp;rnd=new" target="_blank">generate a random string</a> for use)</p>
  </li>
  <li>
    <p>App: Select the App to receive Webhook notifications in advance</p>
  </li>
  <li>
    <p><strong>Trigger Events:</strong></p>
  </li>
</ul>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">TestFlight</span> <span class="nt">Feedback</span>
<span class="nt">Receive</span> <span class="nt">related</span> <span class="nt">data</span> <span class="nt">when</span> <span class="nt">testers</span> <span class="nt">leave</span> <span class="nt">feedback</span><span class="o">.</span>
<span class="o">[]</span> <span class="nt">Crash</span> <span class="nt">Feedback</span>
<span class="o">[]</span> <span class="nt">Screenshot</span> <span class="nt">Feedback</span>

<span class="o">[]</span> <span class="nt">TestFlight</span> <span class="nt">Version</span> <span class="nt">Status</span>
<span class="nt">Receive</span> <span class="nt">related</span> <span class="nt">data</span> <span class="nt">when</span> <span class="nt">the</span> <span class="nt">TestFlight</span> <span class="nt">version</span> <span class="nt">status</span> <span class="nt">changes</span><span class="o">.</span> <span class="nt">Learn</span> <span class="nt">more</span>

<span class="o">[]</span> <span class="nt">App</span> <span class="nt">Version</span> <span class="nt">Status</span>
<span class="nt">Receive</span> <span class="nt">related</span> <span class="nt">data</span> <span class="nt">when</span> <span class="nt">the</span> <span class="nt">App</span> <span class="nt">version</span> <span class="nt">status</span> <span class="nt">changes</span><span class="o">.</span> <span class="nt">Learn</span> <span class="nt">more</span>

<span class="o">[]</span> <span class="nt">Build</span> <span class="nt">Upload</span> <span class="nt">Status</span>
<span class="nt">Receive</span> <span class="nt">related</span> <span class="nt">data</span> <span class="nt">when</span> <span class="nt">the</span> <span class="nt">build</span> <span class="nt">upload</span> <span class="nt">status</span> <span class="nt">changes</span><span class="o">.</span> <span class="nt">Learn</span> <span class="nt">more</span>

<span class="nt">Background</span> <span class="nt">Assets</span>
<span class="nt">Receive</span> <span class="nt">related</span> <span class="nt">data</span> <span class="nt">when</span> <span class="nt">specific</span> <span class="nt">changes</span> <span class="nt">occur</span> <span class="nt">in</span> <span class="nt">Apple-hosted</span> <span class="nt">asset</span> <span class="nt">pack</span> <span class="nt">versions</span><span class="o">.</span> <span class="nt">Learn</span> <span class="nt">more</span>

<span class="o">[]</span> <span class="nt">Update</span> <span class="nt">App</span> <span class="nt">Store</span> <span class="nt">Release</span> <span class="nt">Version</span>
<span class="o">[]</span> <span class="nt">Update</span> <span class="nt">External</span> <span class="nt">TestFlight</span> <span class="nt">Release</span> <span class="nt">Version</span>
<span class="o">[]</span> <span class="nt">Create</span> <span class="nt">Internal</span> <span class="nt">TestFlight</span> <span class="nt">Release</span> <span class="nt">Version</span>
<span class="o">[]</span> <span class="nt">Update</span> <span class="nt">Asset</span> <span class="nt">Pack</span> <span class="nt">Version</span>
</code></pre></div></div>

<p>Select according to your needs, or choose all notifications and decide whether to process them afterward.</p>

<p><strong>Finally, click “Add” to create the Webhook.</strong></p>

<h4 id="testing-app-store-connect-api-webhook-notifications">Testing App Store Connect API Webhook Notifications</h4>

<p>Go to the Webhook page.</p>

<p><img src="/assets/7c0974856393/1*-pEVZ24cpSPBabtzMwll4Q.webp" alt="" loading="lazy" decoding="async" width="979" height="548" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzkiIGhlaWdodD0iNTQ4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/7c0974856393/1*-pEVZ24cpSPBabtzMwll4Q.png" /></p>

<p>Click the “Test” button at the top right to receive a test notification.</p>

<p><strong>The test notification content is as follows:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">Headers:</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"content-type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"application/json"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"x-apple-jingle-correlation-key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PNSCHDQW3MY2AX6VSRFHYYNUL4"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"x-apple-request-uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"7b64238e-16db-31a0-5fd5-944a7c61b45f"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"x-apple-signature"</span><span class="p">:</span><span class="w"> </span><span class="s2">"hmacsha256=cf50020f0bbd3c5274860594f616f1806965c1f9fb765d8d278f512dff5b4c0e"</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="err">Body:</span><span class="w">
</span><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"webhookPingCreated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"65726e27-cb79-47f2-a3e4-c8ced9f356e8"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-26T15:47:38.472168681Z"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="app-store-connect-api-webhook-notification-sending-records">App Store Connect API Webhook Notification Sending Records</h4>

<p>The “Recent Deliveries” section at the bottom of the Webhook page shows the most recently sent Webhook events.</p>

<p><img src="/assets/7c0974856393/1*lacj5lpaDwJtN0hgA5EAVg.webp" alt="" loading="lazy" decoding="async" width="1200" height="502" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*lacj5lpaDwJtN0hgA5EAVg.png" /></p>

<h4 id="verify-app-store-connect-api-webhook-notifications">Verify App Store Connect API Webhook Notifications</h4>

<p>When creating a Webhook, we input a “Secret” string. It is recommended to verify requests to prevent the Webhook URL from leaking, which could allow malicious actors to forge and send Webhook events to your service.</p>

<p><strong>Verification Method:</strong></p>

<blockquote>
  <p><em>Perform HMAC-SHA256 on the Request Body using your set secret key and output a HEX string. Compare this string with the value after <code class="language-plaintext highlighter-rouge">hmacsha256=</code> in the <code class="language-plaintext highlighter-rouge">x-apple-signature</code> field of the Request Headers.</em></p>
</blockquote>

<p><strong>Implementation — Nodejs:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">crypto</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">crypto</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">function</span> <span class="nf">verifyAppleWebhook</span><span class="p">(</span><span class="nx">rawBody</span><span class="p">,</span> <span class="nx">appleSignature</span><span class="p">,</span> <span class="nx">secret</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">hex</span> <span class="o">=</span> <span class="nx">crypto</span>
    <span class="p">.</span><span class="nf">createHmac</span><span class="p">(</span><span class="dl">'</span><span class="s1">sha256</span><span class="dl">'</span><span class="p">,</span> <span class="nx">secret</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="nx">rawBody</span><span class="p">,</span> <span class="dl">'</span><span class="s1">utf8</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">.</span><span class="nf">digest</span><span class="p">(</span><span class="dl">'</span><span class="s1">hex</span><span class="dl">'</span><span class="p">);</span>

  <span class="k">return</span> <span class="s2">`hmacsha256=</span><span class="p">${</span><span class="nx">hex</span><span class="p">}</span><span class="s2">`</span> <span class="o">===</span> <span class="nx">appleSignature</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Implementation — Cloudflare Worker:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">bufferToHex</span><span class="p">(</span><span class="nx">buffer</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[...</span><span class="k">new</span> <span class="nc">Uint8Array</span><span class="p">(</span><span class="nx">buffer</span><span class="p">)]</span>
    <span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">b</span> <span class="o">=&gt;</span> <span class="nx">b</span><span class="p">.</span><span class="nf">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">).</span><span class="nf">padStart</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">))</span>
    <span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nf">hmacSha256Hex</span><span class="p">(</span><span class="nx">secret</span><span class="p">,</span> <span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">enc</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TextEncoder</span><span class="p">();</span>

  <span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">subtle</span><span class="p">.</span><span class="nf">importKey</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">raw</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">enc</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="nx">secret</span><span class="p">),</span>
    <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">HMAC</span><span class="dl">'</span><span class="p">,</span> <span class="na">hash</span><span class="p">:</span> <span class="dl">'</span><span class="s1">SHA-256</span><span class="dl">'</span> <span class="p">},</span>
    <span class="kc">false</span><span class="p">,</span>
    <span class="p">[</span><span class="dl">'</span><span class="s1">sign</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">);</span>

  <span class="kd">const</span> <span class="nx">signature</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">crypto</span><span class="p">.</span><span class="nx">subtle</span><span class="p">.</span><span class="nf">sign</span><span class="p">(</span>
    <span class="dl">'</span><span class="s1">HMAC</span><span class="dl">'</span><span class="p">,</span>
    <span class="nx">key</span><span class="p">,</span>
    <span class="nx">enc</span><span class="p">.</span><span class="nf">encode</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span>
  <span class="p">);</span>

  <span class="k">return</span> <span class="nf">bufferToHex</span><span class="p">(</span><span class="nx">signature</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">async</span> <span class="kd">function</span> <span class="nf">verifyAppleWebhook</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">secret</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">appleSignature</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">X-Apple-Signature</span><span class="dl">'</span><span class="p">);</span>

  <span class="kd">const</span> <span class="nx">rawBody</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">request</span><span class="p">.</span><span class="nf">clone</span><span class="p">().</span><span class="nf">text</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">calculated</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">hmacSha256Hex</span><span class="p">(</span><span class="nx">secret</span><span class="p">,</span> <span class="nx">rawBody</span><span class="p">);</span>

  <span class="k">return</span> <span class="dl">"</span><span class="s2">hmacsha256=</span><span class="dl">"</span><span class="o">+</span><span class="nx">calculated</span> <span class="o">===</span> <span class="nx">appleSignature</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>Cloudflare Worker does not have the crypto module, so you need to use the Web Crypto API (crypto.subtle)</li>
</ul>

<p><strong>Implementation — Google Apps Script Web App ❌</strong></p>

<p>Due to technical limitations, Google Apps Script Web App <code class="language-plaintext highlighter-rouge">doGet(e)/doPost(e)</code> cannot access Request Headers, so this method cannot be used to verify the request source.</p>

<p>You can only add some key parameters in the URL query for simple validation and protection.</p>

<h3 id="app-store-connect-api-webhook-notification-payload">App Store Connect API Webhook Notification Payload</h3>

<p>Here are some collected event Payloads received during the App upload and review process, provided for your direct reference in automation development.</p>

<blockquote>
  <p><em>Webhooks only send event and status names, without other detailed information such as version number or rejection reasons; <strong>we need to call the App Store Connect API ourselves using the Relationships Link in the Event Payload to get the full details.</strong></em></p>
</blockquote>

<h4 id="build-version-upload--process-complete">Build Version Upload — Process Complete</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploadStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"oldState"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PROCESSING"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"newState"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"COMPLETE"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploads"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="build-version-upload--process-failed">Build Version Upload — Process Failed</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploadStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"oldState"</span><span class="p">:</span><span class="w"> </span><span class="s2">"PROCESSING"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"newState"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FAILED"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploads"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Mostly due to binary rejections, such as using the microphone without declaring it.</p>

<h4 id="app-version-status--prepare-for-submission">App Version Status — Prepare For Submission</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PREPARE_FOR_SUBMISSION"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"DEVELOPER_REJECTED"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T05:01:47.118Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>When a new version number is created and ready for submission, this stage allows you to fill in version information, update details, and select the Build to submit for review.</p>

<h4 id="app-version-status--ready-for-review"><strong>App Version Status — Ready For Review</strong></h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"READY_FOR_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PREPARE_FOR_SUBMISSION"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T03:41:12.516Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>When the submission data is confirmed and ready for review.</p>

<h4 id="app-version-status--waiting-for-review-submitted-awaiting-review">App Version Status — Waiting For Review (Submitted, Awaiting Review)</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"WAITING_FOR_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"READY_FOR_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T03:41:21.179Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The app has been submitted for review and is awaiting approval.</p>

<h4 id="app-version-status--developer-rejected">App Version Status — Developer Rejected</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"DEVELOPER_REJECTED"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"WAITING_FOR_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T03:50:30.552Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The developer withdraws the version that is currently under review.</p>

<h4 id="app-version-status--in-review-official-review-in-progress">App Version Status — In Review (Official Review in Progress)</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"IN_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"WAITING_FOR_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T22:05:50.038Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="app-version-status--pending-developer-release-approved-and-waiting-for-release">App Version Status — Pending Developer Release (Approved and Waiting for Release)</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PENDING_DEVELOPER_RELEASE"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"IN_REVIEW"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-18T22:34:18.785Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p><em>The time of the Pending Developer Release event minus the time of the Waiting For Review event equals the waiting time between app submission and being ready for release.</em></p>
</blockquote>

<h4 id="app-version-status--ready-for-distribution-app-ready-to-publish-aka-ready-for-sale">App Version Status — Ready for Distribution (App Ready to Publish) a.k.a Ready For Sale</h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersionAppVersionStateUpdated"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"version"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"newValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"READY_FOR_DISTRIBUTION"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"oldValue"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"PENDING_DEVELOPER_RELEASE"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"timestamp"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-23T06:03:50.925Z"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"instance"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"data"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"type"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"id"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"links"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>App is ready for release (almost equivalent to Ready For Sale, but there is no Ready For Sale event).</p>

<blockquote>
  <p><em>Your app has been accepted and is ready for distribution.</em></p>
</blockquote>

<blockquote>
  <p><em>To distribute your app, your <a href="https://developer.apple.com/help/app-store-connect/manage-agreements/view-agreements-status" target="_blank">agreements</a> must be active. The Account Holder can accept the latest agreements in the Business section.</em></p>
</blockquote>

<h3 id="app-store-connect-api-webhook-workflow-integration">App Store Connect API Webhook Workflow Integration</h3>

<h4 id="method-1--using-fastlane-to-connect-with-app-store-connect-api">Method 1 — Using Fastlane to Connect with App Store Connect API</h4>

<p>The fastest way here is to trigger it directly with a CI/CD service, then use Fastlane’s built-in Spaceship to connect to the App Store Connect API.</p>

<blockquote>
  <p><em>If you already use the <a href="https://docs.fastlane.tools/app-store-connect-api/" target="_blank">App Store Connect API with Fastlane</a> to manage Match certificates and submissions, this method can be used directly without hassle; if not, first <a href="https://docs.fastlane.tools/app-store-connect-api/" target="_blank">refer to the official documentation</a> to generate an API Key and securely store it in your CI/CD service secrets.</em></p>
</blockquote>

<p><img src="/assets/7c0974856393/1*FrJoXtV2lrk09KnAt2Hydg.webp" alt="" loading="lazy" decoding="async" width="2141" height="577" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTQxIiBoZWlnaHQ9IjU3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*FrJoXtV2lrk09KnAt2Hydg.png" /></p>

<ul>
  <li><strong>App Store Connect</strong></li>
</ul>

<ol>
  <li>
    <p>When the App status changes</p>
  </li>
  <li>
    <p>Trigger Webhook</p>
  </li>
</ol>

<ul>
  <li><strong>Webhook Endpoint</strong><br />
<code class="language-plaintext highlighter-rouge">Can be a self-hosted service/API or simply use FAAS services (Cloudflare Worker / AWS Lambda / Cloud Functions / Google Apps Script)</code></li>
</ul>

<ol>
  <li>
    <p>Verify Webhook (optional)</p>
  </li>
  <li>
    <p>Handling Webhook events and forwarding event requests to CI/CD services for execution<br />
<code class="language-plaintext highlighter-rouge">e.g. Triggering GitHub Actions via GitHub API..</code></p>
  </li>
</ol>

<ul>
  <li><strong>CI/CD Service</strong><br />
<code class="language-plaintext highlighter-rouge">GitHub Actions / Bitbucket Pipeline / Gitlab Runner…</code></li>
</ul>

<ol>
  <li>
    <p>Trigger Action</p>
  </li>
  <li>
    <p>Run Fastlane Script, Reuse Fastlane Spaceship Authentication</p>
  </li>
</ol>

<ul>
  <li><strong>App Store Connect</strong></li>
</ul>

<ol>
  <li>Using App Store Connect API to Obtain Complete Information</li>
</ol>

<ul>
  <li><strong>CI/CD Service</strong></li>
</ul>

<ol>
  <li>Subsequent steps, such as sending notifications or triggering another action</li>
</ol>

<p><strong>Fastlane Example:</strong></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="c1"># Usage:</span>
  <span class="c1">#   bundle exec fastlane appStoreConnectWebhookHandler \</span>
  <span class="c1">#     data:'{"data":{"type":"buildUploadStateUpdated","id":"xxx-xxx-xxx-xx-xxx","version":1,"attributes":{"oldState":"PROCESSING","newState":"COMPLETE"},"relationships":{"instance":{"data":{"type":"buildUploads","id":"xxx-xxx-xxx-xx-xxx"},"links":{"self":"https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xxx-xxx-xx-xxx"}}}}}'</span>
  <span class="c1"># Notes:</span>
  <span class="c1"># - `data:` must be a JSON string.</span>
  <span class="c1"># - This lane is intended for local debugging (it prints the GET response).</span>
  <span class="n">desc</span> <span class="s2">"[Automation] Handle App Store Connect webhook payload and fetch related instance via ASC API"</span>
  <span class="n">lane</span> <span class="ss">:appStoreConnectWebhookHandler</span> <span class="k">do</span> <span class="p">\\</span><span class="o">|</span><span class="n">options</span><span class="p">\\</span><span class="o">|</span>
    <span class="k">begin</span>
      <span class="n">data</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="ss">:data</span><span class="p">]</span>
      <span class="no">UI</span><span class="p">.</span><span class="nf">user_error!</span><span class="p">(</span><span class="s2">"Missing data"</span><span class="p">)</span> <span class="k">if</span> <span class="n">data</span><span class="p">.</span><span class="nf">empty?</span>
      <span class="n">data</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
      <span class="n">url</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="s2">"data"</span><span class="p">,</span> <span class="s2">"relationships"</span><span class="p">,</span> <span class="s2">"instance"</span><span class="p">,</span> <span class="s2">"links"</span><span class="p">,</span> <span class="s2">"self"</span><span class="p">).</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">strip</span>
      <span class="no">UI</span><span class="p">.</span><span class="nf">user_error!</span><span class="p">(</span><span class="s2">"Missing instance self url in JSON"</span><span class="p">)</span> <span class="k">if</span> <span class="n">url</span><span class="p">.</span><span class="nf">empty?</span>

      <span class="n">api_key</span> <span class="o">=</span> <span class="n">app_store_connect_api_key</span><span class="p">(</span>
        <span class="ss">key_id: </span><span class="s2">"xxxx"</span><span class="p">,</span>
        <span class="ss">issuer_id: </span><span class="s2">"xxxx-xxxx-xxxx-xxxx-165aa6465141"</span><span class="p">,</span>
        <span class="ss">key_filepath: </span><span class="s2">"./AuthKey_xxxx.p8"</span><span class="p">,</span>
        <span class="ss">duration: </span><span class="mi">1200</span><span class="p">,</span> <span class="c1"># optional (maximum 1200)</span>
        <span class="ss">in_house: </span><span class="kp">false</span> <span class="c1"># optional but may be required if using match/sigh</span>
      <span class="p">)</span>

      <span class="n">loadAppStoreConnectAPIKey</span>
      <span class="c1">#</span>
      <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
      <span class="n">http</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">host</span><span class="p">,</span> <span class="n">uri</span><span class="p">.</span><span class="nf">port</span><span class="p">)</span>
      <span class="n">http</span><span class="p">.</span><span class="nf">use_ssl</span> <span class="o">=</span> <span class="kp">true</span>
      <span class="n">http</span><span class="p">.</span><span class="nf">verify_mode</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">SSL</span><span class="o">::</span><span class="no">VERIFY_PEER</span>
      <span class="n">store</span> <span class="o">=</span> <span class="no">OpenSSL</span><span class="o">::</span><span class="no">X509</span><span class="o">::</span><span class="no">Store</span><span class="p">.</span><span class="nf">new</span>
      <span class="n">store</span><span class="p">.</span><span class="nf">set_default_paths</span>
      <span class="n">http</span><span class="p">.</span><span class="nf">cert_store</span> <span class="o">=</span> <span class="n">store</span>

      <span class="n">request</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Get</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">request_uri</span><span class="p">)</span>
      <span class="n">token</span> <span class="o">=</span> <span class="no">Spaceship</span><span class="o">::</span><span class="no">ConnectAPI</span><span class="p">.</span><span class="nf">token</span>
      <span class="no">UI</span><span class="p">.</span><span class="nf">user_error!</span><span class="p">(</span><span class="s2">"App Store Connect API token is not available. Make sure app_store_connect_api_key is configured correctly."</span><span class="p">)</span> <span class="k">if</span> <span class="n">token</span><span class="p">.</span><span class="nf">nil?</span>
      <span class="n">request</span><span class="p">[</span><span class="s1">'Authorization'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Bearer </span><span class="si">#{</span><span class="n">token</span><span class="p">.</span><span class="nf">text</span><span class="si">}</span><span class="s2">"</span>

      <span class="n">request</span><span class="p">[</span><span class="s1">'Content-Type'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'application/json'</span>
      <span class="n">request</span><span class="p">[</span><span class="s1">'Accept'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'application/json'</span>

      <span class="n">response</span> <span class="o">=</span> <span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
      <span class="no">UI</span><span class="p">.</span><span class="nf">message</span><span class="p">(</span><span class="s2">"📡 GET </span><span class="si">#{</span><span class="n">url</span><span class="si">}</span><span class="s2"> Response: [</span><span class="si">#{</span><span class="n">response</span><span class="p">.</span><span class="nf">code</span><span class="si">}</span><span class="s2">] </span><span class="si">#{</span><span class="n">response</span><span class="p">.</span><span class="nf">message</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
      <span class="no">UI</span><span class="p">.</span><span class="nf">message</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span>
      <span class="c1">#</span>

      <span class="n">response</span>
      <span class="c1">## handle response...do next actions...</span>
      
    <span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">e</span>
        <span class="no">UI</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="s2">"❌ Failed handle App Store Connect API Webhook: </span><span class="si">#{</span><span class="n">e</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
    <span class="k">end</span>

  <span class="k">end</span>
</code></pre></div></div>

<h4 id="method-2--handle-it-yourself-at-the-webhook-endpoint">Method 2 — Handle It Yourself at the Webhook Endpoint</h4>

<p>The second method is to handle everything directly on the Webhook Endpoint service, but <strong>the drawback is that you need to put the App Store Connect API Key on the service and handle Token verification yourself</strong>.</p>

<p><img src="/assets/7c0974856393/1*AUw59sLt97RquLhoRBp5Rw.webp" alt="" loading="lazy" decoding="async" width="1491" height="590" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDkxIiBoZWlnaHQ9IjU5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7c0974856393/1*AUw59sLt97RquLhoRBp5Rw.png" /></p>

<p><strong>Ruby Example:</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">require</span> <span class="s1">'jwt'</span>
<span class="k">require</span> <span class="s1">'net/http'</span>
<span class="k">require</span> <span class="s1">'time'</span>

<span class="n">keyFile</span> <span class="o">=</span> <span class="nc">File</span><span class="mf">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'./AuthKey_XXXX.p8'</span><span class="p">)</span> <span class="c1"># replace with your .p8 private key file (download from App Store Connect)</span>
<span class="n">privateKey</span> <span class="o">=</span> <span class="nc">OpenSSL</span><span class="o">::</span><span class="nc">PKey</span><span class="o">::</span><span class="no">EC</span><span class="mf">.</span><span class="k">new</span><span class="p">(</span><span class="n">keyFile</span><span class="p">)</span>

<span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
            <span class="n">iss</span><span class="o">:</span> <span class="s1">'YOUR_ISSUE_ID'</span><span class="p">,</span> <span class="c1"># replace with your issuer ID (found in App Store Connect user access -&gt; keys -&gt; App Store Connect API page)</span>
            <span class="n">iat</span><span class="o">:</span> <span class="nc">Time</span><span class="mf">.</span><span class="n">now</span><span class="mf">.</span><span class="n">to_i</span><span class="p">,</span>
            <span class="n">exp</span><span class="o">:</span> <span class="nc">Time</span><span class="mf">.</span><span class="n">now</span><span class="mf">.</span><span class="n">to_i</span> <span class="o">+</span> <span class="mi">60</span><span class="o">*</span><span class="mi">20</span><span class="p">,</span>
            <span class="n">aud</span><span class="o">:</span> <span class="s1">'appstoreconnect-v1'</span>
          <span class="p">}</span>

<span class="n">token</span> <span class="o">=</span> <span class="no">JWT</span><span class="mf">.</span><span class="n">encode</span> <span class="n">payload</span><span class="p">,</span> <span class="n">privateKey</span><span class="p">,</span> <span class="s1">'ES256'</span><span class="p">,</span> <span class="n">header_fields</span><span class="o">=</span><span class="p">{</span><span class="n">kid</span><span class="o">:</span><span class="s2">"YOUR_KEY_ID"</span><span class="p">,</span> <span class="n">typ</span><span class="o">:</span><span class="s2">"JWT"</span><span class="p">}</span> <span class="c1"># replace with your key ID (found in App Store Connect user access -&gt; keys -&gt; App Store Connect API page)</span>
<span class="n">puts</span> <span class="n">token</span>

<span class="n">decoded_token</span> <span class="o">=</span> <span class="no">JWT</span><span class="mf">.</span><span class="n">decode</span> <span class="n">token</span><span class="p">,</span> <span class="n">privateKey</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="p">{</span> <span class="n">algorithm</span><span class="o">:</span> <span class="s1">'ES256'</span> <span class="p">}</span>
<span class="n">puts</span> <span class="n">decoded_token</span>

<span class="c1"># Replace with relationships link from Webhook Payload</span>
<span class="n">uri</span> <span class="o">=</span> <span class="nf">URI</span><span class="p">(</span><span class="s2">"https://api.appstoreconnect.apple.com/v1/apps/APPID/customerReviews"</span><span class="p">)</span> <span class="c1"># replace APPID with your app ID in App Store Connect -&gt; your app -&gt; app information -&gt; Apple ID</span>
<span class="n">https</span> <span class="o">=</span> <span class="nc">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="mf">.</span><span class="k">new</span><span class="p">(</span><span class="n">uri</span><span class="mf">.</span><span class="n">host</span><span class="p">,</span> <span class="n">uri</span><span class="mf">.</span><span class="n">port</span><span class="p">)</span>
<span class="n">https</span><span class="mf">.</span><span class="n">use_ssl</span> <span class="o">=</span> <span class="kc">true</span>

<span class="n">request</span> <span class="o">=</span> <span class="nc">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="nc">Get</span><span class="mf">.</span><span class="k">new</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
<span class="n">request</span><span class="p">[</span><span class="s1">'Authorization'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"Bearer #</span><span class="si">{</span><span class="nv">token</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>

<span class="n">response</span> <span class="o">=</span> <span class="n">https</span><span class="mf">.</span><span class="nf">request</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="n">puts</span> <span class="n">response</span><span class="mf">.</span><span class="n">read_body</span>
</code></pre></div></div>

<blockquote>
  <p><em>For generating App Store Connect API Keys, creating Tokens, and using the API, please refer to “<a href="/posts/zrealm-dev/app-store-connect-api-manage-customer-reviews-and-subscriptions-efficiently-f1365e51902c/">App Store Connect API now supports reading and managing Customer Reviews</a>”.</em></p>
</blockquote>

<h3 id="app-store-connect-api-response">App Store Connect API Response</h3>

<p>Here are some example responses received after getting the Relationships Link to obtain complete information following a Webhook Event.</p>

<h4 id="build-version-upload--process-complete-1">Build Version Upload — Process Complete</h4>

<p><code class="language-plaintext highlighter-rouge">https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploads"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xx-xx-xx-xxx-xx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"cfBundleShortVersionString"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.101.0"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"cfBundleVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"createdDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-25T08:26:43-08:00"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"warnings"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"infos"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"COMPLETE"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"platform"</span><span class="p">:</span><span class="w"> </span><span class="s2">"IOS"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"uploadedDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-25T08:28:35-08:00"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"buildUploadFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx/relationships/buildUploadFiles"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx/buildUploadFiles"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xxx-xx"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="build-version-upload--process-failed-1">Build Version Upload — Process Failed</h4>

<p><code class="language-plaintext highlighter-rouge">https://api.appstoreconnect.apple.com/v1/buildUploads/xxx-xx-xx-xx-xxx</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"buildUploads"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xx-xx-xx-xx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"cfBundleShortVersionString"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.101.0"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"cfBundleVersion"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"createdDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-12T09:03:32-08:00"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"errors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
            </span><span class="nl">"code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"90683"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Missing purpose string in Info.plist. Your app’s code references one or more APIs that access sensitive user data, or the app has one or more entitlements that permit such access. The Info.plist file for the “My.app” bundle should contain a NSMicrophoneUsageDescription key with a user-facing purpose string explaining clearly and completely why your app needs the data. If you’re using external libraries or SDKs, they may reference APIs that require a purpose string. While your app might not use these APIs, a purpose string is still required. For details, visit: https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy/requesting_access_to_protected_resources."</span><span class="w">
          </span><span class="p">}</span><span class="w">
        </span><span class="p">],</span><span class="w">
        </span><span class="nl">"warnings"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"infos"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FAILED"</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"platform"</span><span class="p">:</span><span class="w"> </span><span class="s2">"IOS"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"uploadedDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-12T09:05:26-08:00"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"buildUploadFiles"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx/relationships/buildUploadFiles"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx/buildUploadFiles"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/buildUploads/xx-xx-xx-xx-xxx"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Taking ITMS-90683 as an example.</p>

<h4 id="app-version-status">App Version Status</h4>

<p><code class="language-plaintext highlighter-rouge">https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xx-xx-xx-xxx</code></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"appStoreVersions"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xxx-xxx-xxx-xxx"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"attributes"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"platform"</span><span class="p">:</span><span class="w"> </span><span class="s2">"IOS"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"versionString"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.101.0"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"appStoreState"</span><span class="p">:</span><span class="w"> </span><span class="s2">"READY_FOR_SALE"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"appVersionState"</span><span class="p">:</span><span class="w"> </span><span class="s2">"READY_FOR_DISTRIBUTION"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"copyright"</span><span class="p">:</span><span class="w"> </span><span class="s2">"© 2025 ZhgChgLi."</span><span class="p">,</span><span class="w">
      </span><span class="nl">"reviewType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"APP_STORE"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"releaseType"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MANUAL"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"earliestReleaseDate"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"usesIdfa"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
      </span><span class="nl">"downloadable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"createdDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2025-12-15T19:12:55-08:00"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"relationships"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"ageRatingDeclaration"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/ageRatingDeclaration"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/ageRatingDeclaration"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreVersionLocalizations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionLocalizations"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionLocalizations"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/build"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/build"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreVersionPhasedRelease"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionPhasedRelease"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionPhasedRelease"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"gameCenterAppVersion"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/gameCenterAppVersion"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/gameCenterAppVersion"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"routingAppCoverage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/routingAppCoverage"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/routingAppCoverage"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreReviewDetail"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreReviewDetail"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreReviewDetail"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreVersionSubmission"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionSubmission"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionSubmission"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appClipDefaultExperience"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appClipDefaultExperience"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appClipDefaultExperience"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreVersionExperiments"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionExperiments"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionExperiments"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"appStoreVersionExperimentsV2"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/appStoreVersionExperimentsV2"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/appStoreVersionExperimentsV2"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"customerReviews"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/customerReviews"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/customerReviews"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="nl">"alternativeDistributionPackage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/relationships/alternativeDistributionPackage"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"related"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx/alternativeDistributionPackage"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"links"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"self"</span><span class="p">:</span><span class="w"> </span><span class="s2">"https://api.appstoreconnect.apple.com/v1/appStoreVersions/xxx-xxx-xxx-xxx"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p><em>As mentioned earlier, detailed app information, version number, and error reasons can only be obtained by calling the API.</em></p>
</blockquote>

<h3 id="done">Done</h3>

<p>By now, we can use the App Store Connect API Webhook to better enhance App CI/CD and automation workflows, improving team development efficiency.</p>

<h4 id="further-reading">Further Reading</h4>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/app-reviews-monitoring-robot-zreviewtender-free-open-source-tool-for-real-time-feedback-e36e48bb9265/">ZReviewTender — Free Open Source App Reviews Monitoring Bot</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/app-store-connect-api-manage-customer-reviews-and-subscriptions-efficiently-f1365e51902c/">App Store Connect API Now Supports Reading and Managing Customer Reviews</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">iOS Timer vs DispatchSourceTimer｜Safe Usage with Finite State Machine &amp;amp; Design Patterns</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ios-timer-vs-dispatchsourcetimer-safe-usage-with-finite-state-machine-design-patterns-62f68ebeecd3/" rel="alternate" type="text/html" title="iOS Timer vs DispatchSourceTimer｜Safe Usage with Finite State Machine &amp; Design Patterns" />
    <published>2025-12-14T16:17:06+08:00</published>
    <updated>2025-12-15T00:24:36+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/62f68ebeecd3</id><summary type="html">Discover how to enhance iOS timer safety by encapsulating DispatchSourceTimer using finite state machines and design patterns, solving common concurrency issues for reliable app performance.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="design-patterns" /><category term="timer" /><category term="swift" /><category term="finite-state-machine" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/62f68ebeecd3/1*2CWJW1fPOAZdU3nqeJ2JeQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ios-timer-vs-dispatchsourcetimer-safe-usage-with-finite-state-machine-design-patterns-62f68ebeecd3/"><![CDATA[<h3 id="ios-how-to-choose-and-safely-use-timer-and-dispatchsourcetimer">[iOS] How to Choose and Safely Use Timer and DispatchSourceTimer?</h3>

<p>Encapsulating DispatchSourceTimer with Finite-State Machine and Design Patterns for Safer and Easier Use.</p>

<p><img src="/assets/62f68ebeecd3/1*2CWJW1fPOAZdU3nqeJ2JeQ.webp" alt="Photo by Ralph Hutter" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/62f68ebeecd3/1*2CWJW1fPOAZdU3nqeJ2JeQ.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@pixelfreund?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank">Ralph Hutter</a></p>

<h4 id="about-timer">About Timer</h4>

<p>In iOS development, the “Timer trigger” scenario is inevitable; from UI-level countdown displays and banner carousels to data logic-level timed event sending and periodic data cleanup, we all need Timers to help achieve these goals.</p>

<h3 id="foundation--timer-nstimer"><a href="https://developer.apple.com/documentation/foundation/timer" target="_blank">Foundation — Timer (NSTimer)</a></h3>

<p>Timer is probably the most intuitive API that comes to mind first, but there are several points to consider when choosing and using Timer.</p>

<h4 id="advantages-and-disadvantages">Advantages and Disadvantages</h4>

<p><strong>Advantages of Timer:</strong></p>

<ul>
  <li>
    <p>Default integration with UI tasks, no need to explicitly switch to Main Thread execution</p>
  </li>
  <li>
    <p>Automatically adjusts trigger timing to optimize power usage</p>
  </li>
  <li>
    <p>Lower complexity in usage; may cause retain cycles or forgetting to stop the Timer, but will not directly cause a crash</p>
  </li>
</ul>

<p><strong>Disadvantages of Timer:</strong></p>

<ul>
  <li>
    <p>Accuracy is affected by the RunLoop state and may be delayed during high UI interaction or mode switching.</p>
  </li>
  <li>
    <p>Does not support advanced operations like <code class="language-plaintext highlighter-rouge">suspend</code>, <code class="language-plaintext highlighter-rouge">resume</code>, <code class="language-plaintext highlighter-rouge">activate</code>, etc.</p>
  </li>
</ul>

<h4 id="suitable-scenarios">Suitable Scenarios</h4>

<p>For UI-level needs, such as carousel banners (auto-scrolling ScrollView) or countdown timers for coupon claims, where the user only needs to interact with the current foreground screen, I choose to use Timer directly. It is convenient, fast, and safe to achieve the goal.</p>

<h4 id="lifecycle">Lifecycle</h4>

<p><img src="/assets/62f68ebeecd3/1*rQrIoCxCdwoFCgqZc1zE8A.webp" alt="" loading="lazy" decoding="async" width="1187" height="1237" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTg3IiBoZWlnaHQ9IjEyMzciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/62f68ebeecd3/1*rQrIoCxCdwoFCgqZc1zE8A.png" /></p>

<p>Creating a Timer on the UI Main Thread means the Timer is strongly held by the Main Thread’s RunLoop and is periodically triggered by the RunLoop’s polling mechanism. It will only be released after calling Timer’s invalidate(). Therefore, we need to strongly hold the Timer in the ViewController and call Timer invalidate() in deinit to properly stop and release the Timer when the view is dismissed.</p>

<ul>
  <li>
    <p>⭐️️️ View Controller strongly holds Timer, <strong>the Timer’s execution block (handler/closure) must use weak self; otherwise, it will cause a retain cycle.</strong></p>
  </li>
  <li>
    <p>⭐️️️ Be sure to call Timer invalidate() when the View Controller lifecycle ends, otherwise the RunLoop will still hold the Timer and keep running it.</p>
  </li>
</ul>

<blockquote>
  <p><em>RunLoop is an event processing loop within a Thread that polls and handles events; <strong>the system automatically creates a RunLoop for the Main Thread (RunLoop.main)</strong>, but other Threads may not have a RunLoop.</em></p>
</blockquote>

<h4 id="usage">Usage</h4>

<p>We can directly use <code class="language-plaintext highlighter-rouge">Timer.scheduledTimer</code> to declare a Timer (it will automatically add to RunLoop.main &amp; <strong>Mode: .default</strong>):</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">HomeViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
    
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="kt">Timer</span><span class="p">?</span>
    
    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">invalidate</span><span class="p">()</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="kc">nil</span>
    <span class="p">}</span>

    <span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
        <span class="nf">startCarousel</span><span class="p">()</span>
    <span class="p">}</span>
    
    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">startCarousel</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="kt">Timer</span><span class="o">.</span><span class="nf">scheduledTimer</span><span class="p">(</span><span class="nv">withTimeInterval</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nv">block</span><span class="p">:</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">_</span> <span class="k">in</span>
            <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="nf">doSomething</span><span class="p">()</span>
        <span class="p">})</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">doSomething</span><span class="p">()</span> <span class="p">{</span>
        <span class="nf">print</span><span class="p">(</span><span class="s">"Hello World!"</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can also declare a Timer object yourself and add it to the RunLoop:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="kt">Timer</span><span class="p">(</span><span class="nv">timeInterval</span><span class="p">:</span> <span class="mf">1.0</span><span class="p">,</span> <span class="nv">repeats</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="n">_</span> <span class="k">in</span>
  <span class="c1">// do something..</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">timer</span>
<span class="c1">// Adding to RunLoop will start the timer</span>
<span class="kt">RunLoop</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">timer</span><span class="p">,</span> <span class="nv">forMode</span><span class="p">:</span> <span class="o">.</span><span class="k">default</span><span class="p">)</span>
</code></pre></div></div>

<h4 id="how-to-use-timer">How to Use Timer</h4>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">invalidate()</code> stops the Timer</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">fire()</code> triggers immediately once</p>
  </li>
</ul>

<h4 id="the-impact-of-runloop-mode">The Impact of RunLoop <strong>Mode</strong></h4>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.default</code>: The default added Mode, mainly handles UI display.<br />
<strong>Will pause first when switching to <code class="language-plaintext highlighter-rouge">.tracking</code> Mode</strong></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.tracking</code>: Handles ScrollView scrolling and Gesture recognition.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">.common</code>: Handles both <code class="language-plaintext highlighter-rouge">.default</code> and <code class="language-plaintext highlighter-rouge">.tracking</code>.</p>
  </li>
</ul>

<blockquote>
  <p><em>⭐️️️⭐️️️⭐️️️Therefore, by default, our Timer is added to the <code class="language-plaintext highlighter-rouge">.default</code> mode, <strong>which will automatically pause when the user scrolls a ScrollView or performs gesture actions</strong>, and will only resume after the operation ends. This may cause the Timer to trigger late or fire fewer times than expected.</em></p>
</blockquote>

<p><strong>For this, we can add the Timer to the .common Mode to solve the above issue:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">RunLoop</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">timer</span><span class="p">,</span> <span class="nv">forMode</span><span class="p">:</span> <span class="o">.</span><span class="n">common</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="grand-central-dispatch--dispatchsourcetimer"><a href="https://developer.apple.com/documentation/dispatch/dispatchsourcetimer" target="_blank">Grand Central Dispatch — DispatchSourceTimer</a></h3>

<p>Besides Timer, GCD also offers another option called DispatchSourceTimer.</p>

<h4 id="advantages-and-disadvantages-1">Advantages and Disadvantages</h4>

<p><strong>Advantages of DispatchSourceTimer:</strong></p>

<ul>
  <li>
    <p>Better operational flexibility (supports <code class="language-plaintext highlighter-rouge">suspend</code> and <code class="language-plaintext highlighter-rouge">resume</code>)</p>
  </li>
  <li>
    <p>Higher accuracy and reliability: relies on GCD Queue</p>
  </li>
  <li>
    <p>Leeway can be set to control power consumption</p>
  </li>
  <li>
    <p>Stable resident task (GCD Queue)</p>
  </li>
</ul>

<p><strong>Disadvantages of DispatchSourceTimer:</strong></p>

<ul>
  <li>
    <p>UI operations require manually switching back to the Main Thread</p>
  </li>
  <li>
    <p>The API is complex and order-dependent; <strong>using it incorrectly will cause a crash</strong></p>
  </li>
  <li>
    <p>Encapsulation is needed for safe usage</p>
  </li>
</ul>

<h4 id="suitable-scenarios-1">Suitable Scenarios</h4>

<p>Compared to Timer, which is suitable for UI-related scenarios, DispatchSourceTimer is better for tasks unrelated to the UI or the user’s current screen. The most common use cases include sending tracking events regularly, where user actions are sent to the server periodically, or cleaning up unused CoreData data at set intervals. These tasks are well suited for DispatchSourceTimer.</p>

<h4 id="lifecycle-1">Lifecycle</h4>

<p><img src="/assets/62f68ebeecd3/1*2hIwImzm6ubpc5zK_roI3Q.webp" alt="" loading="lazy" decoding="async" width="1289" height="914" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjg5IiBoZWlnaHQ9IjkxNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/62f68ebeecd3/1*2hIwImzm6ubpc5zK_roI3Q.png" /></p>

<p>The lifecycle of a DispatchSourceTimer depends on whether it is still retained by an external object; the GCD queue itself does not strongly retain the timer’s owner and only handles scheduling and executing events.</p>

<h4 id="crash-issues">Crash Issues</h4>

<p>Although DispatchSourceTimer offers more control methods: <code class="language-plaintext highlighter-rouge">activate</code>, <code class="language-plaintext highlighter-rouge">suspend</code>, <code class="language-plaintext highlighter-rouge">resume</code>, <code class="language-plaintext highlighter-rouge">cancel</code>; it is extremely sensitive. Calling them in the wrong order can cause immediate crashes (EXC_BREAKPOINT/DispatchSourceTimer), which is very dangerous.</p>

<p><img src="/assets/62f68ebeecd3/1*04cApJDt29a-98zgb9Wd2A.webp" alt="" loading="lazy" decoding="async" width="683" height="185" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODMiIGhlaWdodD0iMTg1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/62f68ebeecd3/1*04cApJDt29a-98zgb9Wd2A.png" /></p>

<p><strong>The app will crash immediately in the following cases:</strong></p>

<ul>
  <li>
    <p>❌ Calling suspend() and resume() not in pairs<br />
Calling suspend() twice in a row<br />
Calling resume() twice in a row</p>
  </li>
  <li>
    <p>❌ Calling cancel() after suspend()<br />
You need to call resume() before cancel()</p>
  </li>
  <li>
    <p>❌ Timer Released (nil) While in suspend() State</p>
  </li>
  <li>
    <p>❌ Calling other operations after cancel()</p>
  </li>
</ul>

<h4 id="using-finite-state-machine-to-encapsulate-operations">Using Finite-State Machine to Encapsulate Operations</h4>

<p>Moving on to another key point of this article: How to safely use DispatchSourceTimer?</p>

<p><img src="/assets/62f68ebeecd3/1*S33K4OWFUPMocZvM4dLWvw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1151" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExNTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/62f68ebeecd3/1*S33K4OWFUPMocZvM4dLWvw.png" /></p>

<p>As shown in the above figure, we use a finite-state machine to encapsulate DispatchSourceTimer operations, making it safer and easier to use:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">DispatchSourceTimerMachine</span> <span class="p">{</span>
    <span class="c1">// States of the finite state machine</span>
    <span class="kd">private</span> <span class="kd">enum</span> <span class="kt">TimerState</span> <span class="p">{</span>
        <span class="c1">// Initial state</span>
        <span class="k">case</span> <span class="n">idle</span>
        <span class="c1">// Running</span>
        <span class="k">case</span> <span class="n">running</span>
        <span class="c1">// Suspended</span>
        <span class="k">case</span> <span class="n">suspended</span>
        <span class="c1">// Cancelled</span>
        <span class="k">case</span> <span class="n">cancelled</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="kt">DispatchSourceTimer</span><span class="p">?</span>
    <span class="kd">private</span> <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">timerQueue</span><span class="p">:</span> <span class="kt">DispatchQueue</span> <span class="o">=</span> <span class="p">{</span>
        <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"li.zhgchg.DispatchSourceTimerMachine"</span><span class="p">,</span> <span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">)</span>
    <span class="p">}()</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">_state</span><span class="p">:</span> <span class="kt">TimerState</span> <span class="o">=</span> <span class="o">.</span><span class="n">idle</span>
    
    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="c1">// When the owner object deallocates, synchronously cancel the timer</span>
        <span class="c1">// Not mandatory since the handler is weak, but ensures expected flow</span>
        <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">suspended</span> <span class="p">{</span>
            <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
        <span class="p">}</span>
        <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">running</span> <span class="p">{</span>
            <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
            <span class="n">timer</span> <span class="o">=</span> <span class="kc">nil</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">cancelled</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Start the Timer</span>
    <span class="kd">func</span> <span class="nf">activate</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="p">{</span>
        <span class="c1">// Timer can only be activated from idle or cancelled states</span>
        <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">idle</span><span class="p">,</span> <span class="o">.</span><span class="n">cancelled</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        
        <span class="c1">// Create Timer and activate()</span>
        <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">timer</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">activate</span><span class="p">()</span>
        
        <span class="c1">// Switch to running state</span>
        <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
    <span class="p">}</span>

    <span class="c1">// Suspend the Timer</span>
    <span class="kd">func</span> <span class="nf">suspend</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Timer can only be suspended when running</span>
        <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">running</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        
        <span class="c1">// Suspend Timer</span>
        <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">suspend</span><span class="p">()</span>
        
        <span class="c1">// Switch to suspended state</span>
        <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">suspended</span>
    <span class="p">}</span>

    <span class="c1">// Resume the Timer</span>
    <span class="kd">func</span> <span class="nf">resume</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Timer can only be resumed when suspended</span>
        <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">suspended</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        
        <span class="c1">// Resume Timer</span>
        <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
        
        <span class="c1">// Switch to running state</span>
        <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
    <span class="p">}</span>

    <span class="c1">// Cancel the Timer</span>
    <span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// Timer can only be cancelled when suspended or running</span>
        <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">suspended</span><span class="p">,</span> <span class="o">.</span><span class="n">running</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
        
        <span class="c1">// If currently suspended, resume before cancelling</span>
        <span class="c1">// This is a DispatchSourceTimer limitation; cancel only allowed when running</span>
        <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">suspended</span> <span class="p">{</span>
            <span class="k">self</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
        <span class="p">}</span>

        <span class="c1">// Cancel Timer</span>
        <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
        <span class="n">timer</span> <span class="o">=</span> <span class="kc">nil</span>
        
        <span class="c1">// Switch to cancelled state</span>
        <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">cancelled</span>
    <span class="p">}</span>
    
    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">DispatchSourceTimer</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="nf">makeTimerSource</span><span class="p">(</span><span class="nv">queue</span><span class="p">:</span> <span class="n">timerQueue</span><span class="p">)</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">schedule</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">(),</span> <span class="nv">repeating</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">)</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">timer</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We simply used a finite-state machine to encapsulate the logic of “which states can transition to which” and “what each state needs to do.” Calls made in the wrong state are ignored (no crashes). We also added some optimizations, such as allowing cancel in the suspended state and re-activating from the cancelled state.</p>

<blockquote>
  <p><strong><em>Further Reading:</em></strong></p>
</blockquote>

<blockquote>
  <p><em>Previously, I wrote another article “<a href="https://zhgchg.li/posts/pinkoi-engineering/design-patterns-%E5%AF%A6%E6%88%B0%E6%87%89%E7%94%A8-%E5%B0%81%E8%A3%9D-socket-io-%E5%8D%B3%E6%99%82%E9%80%9A%E8%A8%8A%E6%9E%B6%E6%A7%8B%E8%88%87%E4%B8%83%E5%A4%A7%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F%E8%A7%A3%E6%9E%90-78507a8de6a5/#%E9%9C%80%E6%B1%82%E5%A0%B4%E6%99%AF-3" target="_blank">Design Patterns Practical Application｜Encapsulating Socket.IO Real-Time Communication Architecture</a>,” which also uses a finite-state machine and applies the State Pattern.</em></p>
</blockquote>

<blockquote>
  <p><em>Finite-State Machine: Focuses on controlling transitions between states and the actions to perform.</em></p>
</blockquote>

<blockquote>
  <p><em>State Pattern: Focuses on the behavior logic within each state.</em></p>
</blockquote>

<h4 id="using-serial-queue-to-handle-finite-state-machine-state-transitions">Using Serial Queue to Handle Finite-State Machine State Transitions</h4>

<p>Ensuring safe use of DispatchSourceTimer with a state machine is not the end. We cannot guarantee that calls to DispatchSourceTimerMachine from outside occur on the same thread. If different threads operate on this object, it can cause race conditions and lead to crashes.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">DispatchSourceTimerMachine</span> <span class="p">{</span>
    <span class="c1">// States of the finite state machine</span>
    <span class="kd">private</span> <span class="kd">enum</span> <span class="kt">TimerState</span> <span class="p">{</span>
        <span class="c1">// Initial state</span>
        <span class="k">case</span> <span class="n">idle</span>
        <span class="c1">// Running</span>
        <span class="k">case</span> <span class="n">running</span>
        <span class="c1">// Suspended</span>
        <span class="k">case</span> <span class="n">suspended</span>
        <span class="c1">// Cancelled</span>
        <span class="k">case</span> <span class="n">cancelled</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="kt">DispatchSourceTimer</span><span class="p">?</span>
    <span class="kd">private</span> <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">timerQueue</span><span class="p">:</span> <span class="kt">DispatchQueue</span> <span class="o">=</span> <span class="p">{</span>
        <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"li.zhgchg.DispatchSourceTimerMachine"</span><span class="p">,</span> <span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">)</span>
    <span class="p">}()</span>

    <span class="kd">private</span> <span class="k">var</span> <span class="nv">_state</span><span class="p">:</span> <span class="kt">TimerState</span> <span class="o">=</span> <span class="o">.</span><span class="n">idle</span>

    <span class="kd">private</span> <span class="kd">static</span> <span class="k">let</span> <span class="nv">operationQueueSpecificKey</span> <span class="o">=</span> <span class="kt">DispatchSpecificKey</span><span class="o">&lt;</span><span class="kt">ObjectIdentifier</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="kd">private</span> <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">operationQueueSpecificValue</span><span class="p">:</span> <span class="kt">ObjectIdentifier</span> <span class="o">=</span> <span class="kt">ObjectIdentifier</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
    <span class="kd">private</span> <span class="kd">lazy</span> <span class="k">var</span> <span class="nv">operationQueue</span><span class="p">:</span> <span class="kt">DispatchQueue</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">queue</span> <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="s">"li.zhgchg.DispatchSourceTimerMachine.operationQueue"</span><span class="p">)</span>
        <span class="n">queue</span><span class="o">.</span><span class="nf">setSpecific</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="k">Self</span><span class="o">.</span><span class="n">operationQueueSpecificKey</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">operationQueueSpecificValue</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">queue</span>
    <span class="p">}()</span>
    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">operation</span><span class="p">(</span><span class="nv">async</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">true</span><span class="p">,</span> <span class="n">_</span> <span class="nv">work</span><span class="p">:</span> <span class="kd">@escaping</span> <span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">Void</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="kt">DispatchQueue</span><span class="o">.</span><span class="nf">getSpecific</span><span class="p">(</span><span class="nv">key</span><span class="p">:</span> <span class="k">Self</span><span class="o">.</span><span class="n">operationQueueSpecificKey</span><span class="p">)</span> <span class="o">==</span> <span class="n">operationQueueSpecificValue</span> <span class="p">{</span>
            <span class="nf">work</span><span class="p">()</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">if</span> <span class="k">async</span> <span class="p">{</span>
                <span class="n">operationQueue</span><span class="o">.</span><span class="nf">async</span><span class="p">(</span><span class="nv">execute</span><span class="p">:</span> <span class="n">work</span><span class="p">)</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">operationQueue</span><span class="o">.</span><span class="nf">sync</span><span class="p">(</span><span class="nv">execute</span><span class="p">:</span> <span class="n">work</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">deinit</span> <span class="p">{</span>
        <span class="c1">// When the owner object is deallocated, synchronously cancel the timer</span>
        <span class="c1">// Although not necessary (handler is weak), this ensures the flow is as expected</span>
        <span class="c1">// Ensure sync finishes execution</span>
        <span class="nf">operation</span><span class="p">(</span><span class="nv">async</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">suspended</span> <span class="p">{</span>
                <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
                <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">running</span> <span class="p">{</span>
                <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
                <span class="n">timer</span> <span class="o">=</span> <span class="kc">nil</span>
                <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">cancelled</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Start the Timer</span>
    <span class="kd">func</span> <span class="nf">activate</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="p">{</span>
        <span class="n">operation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="o">=</span> <span class="k">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="c1">// Timer can only be activated in idle or cancelled states</span>
            <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">idle</span><span class="p">,</span> <span class="o">.</span><span class="n">cancelled</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            
            <span class="c1">// Create Timer and activate()</span>
            <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
            <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">timer</span>
            <span class="n">timer</span><span class="o">.</span><span class="nf">activate</span><span class="p">()</span>
            
            <span class="c1">// Switch to running state</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Suspend the Timer</span>
    <span class="kd">func</span> <span class="nf">suspend</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">operation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="o">=</span> <span class="k">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="c1">// Timer can only be suspended when running</span>
            <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">running</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            
            <span class="c1">// Suspend the Timer</span>
            <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">suspend</span><span class="p">()</span>
            
            <span class="c1">// Switch to suspended state</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">suspended</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Resume the Timer</span>
    <span class="kd">func</span> <span class="nf">resume</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">operation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="o">=</span> <span class="k">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="c1">// Timer can only be resumed when suspended</span>
            <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">suspended</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            
            <span class="c1">// Resume the Timer</span>
            <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
            
            <span class="c1">// Switch to running state</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Cancel the Timer</span>
    <span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">operation</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="o">=</span> <span class="k">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="c1">// Timer can only be cancelled when suspended or running</span>
            <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">suspended</span><span class="p">,</span> <span class="o">.</span><span class="n">running</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            
            <span class="c1">// If currently suspended, resume first before cancelling</span>
            <span class="c1">// This is a DispatchSourceTimer limitation; it can only be cancelled when running</span>
            <span class="k">if</span> <span class="n">_state</span> <span class="o">==</span> <span class="o">.</span><span class="n">suspended</span> <span class="p">{</span>
                <span class="k">self</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
            <span class="p">}</span>
            
            <span class="c1">// Cancel the Timer</span>
            <span class="n">timer</span><span class="p">?</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
            <span class="n">timer</span> <span class="o">=</span> <span class="kc">nil</span>
            
            <span class="c1">// Switch to cancelled state</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">cancelled</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="kd">private</span> <span class="kd">func</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">DispatchSourceTimer</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="nf">makeTimerSource</span><span class="p">(</span><span class="nv">queue</span><span class="p">:</span> <span class="n">timerQueue</span><span class="p">)</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">schedule</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">(),</span> <span class="nv">repeating</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">)</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">timer</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, we can safely use the <code class="language-plaintext highlighter-rouge">DispatchSourceTimerMachine</code> object as a Timer without any worries:</p>

<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">final</span> <span class="kd">class</span> <span class="nc">TrackingEventSender</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">let</span> <span class="nx">timerMachine</span> <span class="o">=</span> <span class="nc">DispatchSourceTimerMachine</span><span class="p">()</span>
    <span class="k">public</span> <span class="kd">var</span> <span class="nx">events</span><span class="p">:</span> <span class="p">[</span><span class="nb">String</span><span class="p">:</span> <span class="nb">String</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>

    <span class="c1">// Start periodic tracking</span>
    <span class="nx">func</span> <span class="nf">startTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">timerMachine</span><span class="p">.</span><span class="nf">activate</span><span class="p">(</span><span class="nx">repeatTimeInterval</span><span class="p">:</span> <span class="p">.</span><span class="nf">seconds</span><span class="p">(</span><span class="mi">30</span><span class="p">))</span> <span class="p">{</span> <span class="p">[</span><span class="nx">weak</span> <span class="nb">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="nb">self</span><span class="p">?.</span><span class="nf">sendTrackingEvent</span><span class="p">()</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Pause tracking (e.g., when App goes to background)</span>
    <span class="nx">func</span> <span class="nf">pauseTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">timerMachine</span><span class="p">.</span><span class="nf">suspend</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Resume tracking (e.g., when App returns to foreground)</span>
    <span class="nx">func</span> <span class="nf">resumeTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">timerMachine</span><span class="p">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Stop tracking (e.g., when leaving the page)</span>
    <span class="nx">func</span> <span class="nf">stopTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="nx">timerMachine</span><span class="p">.</span><span class="nf">cancel</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="nx">func</span> <span class="nf">sendTrackingEvent</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// send events to server...</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The section on how to safely use DispatchSourceTimer has concluded. Next, we will extend to several Design Patterns to help us abstract objects for testing and abstract the execution logic of DispatchSourceHandler.</p>

<h4 id="extension--using-adapter-pattern--factory-pattern-to-create-dispatchsourcetimer-facilitates-abstract-testing">Extension — Using Adapter Pattern + Factory Pattern to Create DispatchSourceTimer (Facilitates Abstract Testing)</h4>

<p>DispatchSourceTimer is a GCD Objective-C object, making it difficult to mock during testing (no Protocol available); therefore, we need to define a Protocol + Factory Pattern layer ourselves so that TimerStateMachine can be testable.</p>

<p><strong>Adapter Pattern— Encapsulating DispatchSourceTimer Operations:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">TimerAdapter</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">schedule</span><span class="p">(</span><span class="nv">repeating</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">)</span>
    <span class="kd">func</span> <span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span>
    <span class="kd">func</span> <span class="nf">activate</span><span class="p">()</span>
    <span class="kd">func</span> <span class="nf">suspend</span><span class="p">()</span>
    <span class="kd">func</span> <span class="nf">resume</span><span class="p">()</span>
    <span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span>
<span class="p">}</span>

<span class="c1">// Adapter implementation for DispatchSourceTimer</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">DispatchSourceTimerAdapter</span><span class="p">:</span> <span class="kt">TimerAdapter</span> <span class="p">{</span>
    <span class="c1">// The original DispatchSourceTimer</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">timer</span><span class="p">:</span> <span class="kt">DispatchSourceTimer</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">"li.zhgchg.DispatchSourceTimerAdapter"</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">queue</span> <span class="o">=</span> <span class="kt">DispatchQueue</span><span class="p">(</span><span class="nv">label</span><span class="p">:</span> <span class="n">label</span><span class="p">,</span> <span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="nf">makeTimerSource</span><span class="p">(</span><span class="nv">queue</span><span class="p">:</span> <span class="n">queue</span><span class="p">)</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">timer</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">schedule</span><span class="p">(</span><span class="nv">repeating</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">schedule</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">(),</span> <span class="nv">repeating</span><span class="p">:</span> <span class="n">repeating</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">qos</span><span class="p">:</span> <span class="o">.</span><span class="n">background</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">activate</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">activate</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">suspend</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">suspend</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">resume</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">cancel</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Factory Pattern — Abstracting the creation method of TimerAdapter:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">DispatchSourceTimerAdapterFactorySpec</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">TimerAdapter</span>
<span class="p">}</span>

<span class="c1">// Encapsulates the creation steps of DispatchSourceTimerAdapter</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">DispatchSourceTimerAdapterFactory</span><span class="p">:</span> <span class="kt">DispatchSourceTimerAdapterFactorySpec</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">func</span> <span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSourceProtocol</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="o">-&gt;</span> <span class="kt">TimerAdapter</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="kt">DispatchSourceTimerAdapter</span><span class="p">()</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">schedule</span><span class="p">(</span><span class="nv">repeating</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">)</span>
        <span class="n">timer</span><span class="o">.</span><span class="nf">setEventHandler</span><span class="p">(</span><span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">timer</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Combined Usage:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">stateMachine</span> <span class="o">=</span> <span class="kt">DispatchSourceTimerMachine</span><span class="p">(</span><span class="nv">timerFactory</span><span class="p">:</span> <span class="kt">DispatchSourceTimerAdapterFactory</span><span class="p">())</span>

<span class="c1">//</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">DispatchSourceTimerMachine</span> <span class="p">{</span>
    <span class="c1">// Omitted..</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">timer</span><span class="p">:</span> <span class="kt">TimerAdapter</span><span class="p">?</span>
    <span class="kd">private</span> <span class="k">let</span> <span class="nv">timerFactory</span><span class="p">:</span> <span class="kt">DispatchSourceTimerAdapterFactorySpec</span>
    <span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="nv">timerFactory</span><span class="p">:</span> <span class="kt">DispatchSourceTimerAdapterFactorySpec</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">timerFactory</span> <span class="o">=</span> <span class="n">timerFactory</span>
    <span class="p">}</span>
    <span class="c1">// Omitted..</span>

    <span class="kd">func</span> <span class="nf">activate</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="kt">DispatchTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="kt">DispatchSource</span><span class="o">.</span><span class="kt">DispatchSourceHandler</span><span class="p">?)</span> <span class="p">{</span>
        <span class="n">onQueue</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">guard</span> <span class="k">let</span> <span class="nv">self</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="k">guard</span> <span class="p">[</span><span class="o">.</span><span class="n">idle</span><span class="p">,</span> <span class="o">.</span><span class="n">cancelled</span><span class="p">]</span><span class="o">.</span><span class="nf">contains</span><span class="p">(</span><span class="n">_state</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
            <span class="c1">// Use Factory to make timer</span>
            <span class="k">let</span> <span class="nv">timer</span> <span class="o">=</span> <span class="n">timerFactory</span><span class="o">.</span><span class="nf">makeTimer</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="n">repeatTimeInterval</span><span class="p">,</span> <span class="nv">handler</span><span class="p">:</span> <span class="n">handler</span><span class="p">)</span>
            <span class="k">self</span><span class="o">.</span><span class="n">timer</span> <span class="o">=</span> <span class="n">timer</span>

            <span class="n">timer</span><span class="o">.</span><span class="nf">activate</span><span class="p">()</span>
            <span class="n">_state</span> <span class="o">=</span> <span class="o">.</span><span class="n">running</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Omitted..</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This allows us to write Mock Objects for <code class="language-plaintext highlighter-rouge">TimerAdapter</code> / <code class="language-plaintext highlighter-rouge">DispatchSourceTimerAdapterFactorySpec</code> during testing to run unit tests.</p>

<h4 id="extension--using-the-strategy-pattern-to-encapsulate-dispatchsourcehandler-tasks">Extension — Using the <strong><em>Strategy</em></strong> Pattern to Encapsulate DispatchSourceHandler Tasks</h4>

<p>Assuming we want the DispatchSourceHandler to execute tasks that can change dynamically, we can use the Strategy Pattern to encapsulate the work content.</p>

<p><strong>TrackingHandlerStrategy:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protocol</span> <span class="kt">TrackingHandlerStrategy</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">target</span><span class="p">:</span> <span class="kt">String</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
    <span class="kd">func</span> <span class="nf">execute</span><span class="p">()</span>
<span class="p">}</span>

<span class="c1">// Home Event</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">HomeTrackingHandlerStrategy</span><span class="p">:</span> <span class="kt">TrackingHandlerStrategy</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">target</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">"home"</span>
    <span class="kd">func</span> <span class="nf">execute</span><span class="p">()</span> <span class="p">{</span>
       <span class="c1">// fetch home event logs..and send</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Product Event</span>
<span class="kd">final</span> <span class="kd">class</span> <span class="kt">ProductTrackingHandlerStrategy</span><span class="p">:</span> <span class="kt">TrackingHandlerStrategy</span> <span class="p">{</span>
    <span class="kd">static</span> <span class="k">var</span> <span class="nv">target</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">"product"</span>
    <span class="kd">func</span> <span class="nf">execute</span><span class="p">()</span> <span class="p">{</span>
       <span class="c1">// fetch product event logs..and send</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Combined Usage:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">sender</span> <span class="o">=</span> <span class="kt">TrackingEventSender</span><span class="p">()</span>
<span class="n">sender</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="kt">HomeTrackingHandlerStrategy</span><span class="p">())</span>
<span class="n">sender</span><span class="o">.</span><span class="nf">register</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="kt">ProductTrackingHandlerStrategy</span><span class="p">())</span>
<span class="n">sender</span><span class="o">.</span><span class="nf">startTracking</span><span class="p">()</span>

<span class="c1">// ...</span>

<span class="c1">//</span>

<span class="kd">final</span> <span class="kd">class</span> <span class="kt">TrackingEventSender</span> <span class="p">{</span>

    <span class="kd">private</span> <span class="k">let</span> <span class="nv">timerMachine</span> <span class="o">=</span> <span class="kt">DispatchSourceTimerMachine</span><span class="p">()</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">events</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">TrackingHandlerStrategy</span><span class="p">]</span> <span class="o">=</span> <span class="p">[:]</span>

    <span class="c1">// Register required Event strategies</span>
    <span class="kd">func</span> <span class="nf">register</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="kt">TrackingHandlerStrategy</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">events</span><span class="p">[</span><span class="nf">type</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">event</span><span class="p">)</span><span class="o">.</span><span class="n">target</span><span class="p">]</span> <span class="o">=</span> <span class="n">event</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="n">retrive</span><span class="o">&lt;</span><span class="kt">T</span><span class="p">:</span> <span class="kt">TrackingHandlerStrategy</span><span class="o">&gt;</span><span class="p">(</span><span class="nv">event</span><span class="p">:</span> <span class="kt">T</span><span class="o">.</span><span class="k">Type</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">T</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">events</span><span class="p">[</span><span class="n">event</span><span class="o">.</span><span class="n">target</span><span class="p">]</span> <span class="k">as?</span> <span class="kt">T</span>
    <span class="p">}</span>

    <span class="c1">// Start periodic tracking</span>
    <span class="kd">func</span> <span class="nf">startTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timerMachine</span><span class="o">.</span><span class="nf">activate</span><span class="p">(</span><span class="nv">repeatTimeInterval</span><span class="p">:</span> <span class="o">.</span><span class="nf">seconds</span><span class="p">(</span><span class="mi">30</span><span class="p">))</span> <span class="p">{</span> <span class="p">[</span><span class="k">weak</span> <span class="k">self</span><span class="p">]</span> <span class="k">in</span>
            <span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="n">events</span><span class="o">.</span><span class="n">values</span><span class="o">.</span><span class="n">forEach</span> <span class="p">{</span> <span class="n">event</span> <span class="k">in</span>
                <span class="n">event</span><span class="o">.</span><span class="nf">execute</span><span class="p">()</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="c1">// Pause tracking (e.g., when App goes to background)</span>
    <span class="kd">func</span> <span class="nf">pauseTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timerMachine</span><span class="o">.</span><span class="nf">suspend</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Resume tracking (e.g., when App comes to foreground)</span>
    <span class="kd">func</span> <span class="nf">resumeTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timerMachine</span><span class="o">.</span><span class="nf">resume</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="c1">// Stop tracking (e.g., when leaving the page)</span>
    <span class="kd">func</span> <span class="nf">stopTracking</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">timerMachine</span><span class="o">.</span><span class="nf">cancel</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="acknowledgments">Acknowledgments</h4>

<p>Thanks to <a href="https://medium.com/u/e13f6afcf9b9" target="_blank">Ethan Huang</a> for donating <strong>5 Beers</strong>:</p>

<p><a href="https://www.patreon.com/cw/ethanhuang13/home?vanity=ethanhuang13" target="_blank"><img src="https://c7.patreon.com/https%3A%2F%2Fwww.patreon.com%2F%2Fcard-teaser-image%2Fcreator%2F6512681%3Fc=6796995314395033145/selector/%23creator-teaser%2C.png" alt="" /></a></p>

<blockquote>
  <p><em>Indeed, I haven’t written much for almost half a year. Just started a new job and am still searching for inspiration! 💪</em></p>
</blockquote>

<blockquote>
  <p>The next article might share the process of managing certificates with Fastlane Match and setting up a Self-hosted Runner… or Bitbucket Pipeline… or the AppStoreConnect API…</p>
</blockquote>

<h3 id="further-reading">Further Reading</h3>

<ul>
  <li>
    <p><a href="/posts/pinkoi-engineering/design-patterns-practical-solutions-for-socket-io-client-library-challenges-78507a8de6a5/"><strong>Practical Application of Design Patterns (Encapsulating Socket.io)</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/kkday-tech-blog/design-patterns-in-wkwebview-builder-strategy-chain-of-responsibility-explained-f4b02ee342a4/">Practical Application of Design Patterns (Encapsulating WKWebView)</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/visitor-pattern-in-ios-swift-design-pattern-practical-applications-ba5773a7bfea/">Visitor Pattern in Swift</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/visitor-pattern-enhance-tableview-readability-and-extensibility-60473cb47550/">Visitor Pattern in TableView</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">Japan Overseas Shopping｜Rakuten Consolidated Shipping for Taiwan｜Buy from Rakuten &amp;amp; Amazon</title>
    <link href="https://en.zhgchg.li/posts/zrealm-life/japan-overseas-shopping-rakuten-consolidated-shipping-for-taiwan-buy-from-rakuten-amazon-bea62c251f1b/" rel="alternate" type="text/html" title="Japan Overseas Shopping｜Rakuten Consolidated Shipping for Taiwan｜Buy from Rakuten &amp; Amazon" />
    <published>2025-10-26T17:58:35+08:00</published>
    <updated>2025-10-27T08:41:53+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-life/bea62c251f1b</id><summary type="html">Taiwan shoppers struggle with direct Japan purchases; learn how to use Rakuten consolidation to buy from Rakuten and Amazon, ensuring smooth delivery and saving costs with a step-by-step guide featuring Horie titanium cup.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Life." /><category term="lifestyle" /><category term="unboxing" /><category term="rakuten" /><category term="titanium-cup" /><category term="consolidated-shipping" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/bea62c251f1b/1*bXj9-XVgjfsVta6cZg8Fpw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-life/japan-overseas-shopping-rakuten-consolidated-shipping-for-taiwan-buy-from-rakuten-amazon-bea62c251f1b/"><![CDATA[<h3 id="how-to-shop-from-japan-and-ship-to-taiwan--using-rakuten-shipping-to-buy-from-rakuten-auctions-and-amazon-horie-titanium-cup-with-delivery-in-as-fast-as-4-days">How to Shop from Japan and Ship to Taiwan — Using Rakuten Shipping to Buy from Rakuten Auctions and Amazon (Horie Titanium Cup) with Delivery in as Fast as 4 Days</h3>

<p>Buying what you want yourself: A record and guide for using Rakuten forwarding to purchase Japanese products and ship them back to Taiwan, using the Horie titanium cup as an example.</p>

<h3 id="horie-titanium-cup"><a href="https://a.r10.to/hkPx9m" target="_blank">Horie Titanium Cup</a></h3>

<p>Last time I went to Japan, I got hooked on the Horie Titanium Cup. Its main features are:</p>

<p><img src="/assets/bea62c251f1b/1*7m-tH8lB3Sx0yxtdc9Bn_g.webp" alt="Official Horie Rakuten Store" loading="lazy" decoding="async" width="776" height="970" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzYiIGhlaWdodD0iOTcwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*7m-tH8lB3Sx0yxtdc9Bn_g.png" /></p>

<p><a href="https://a.r10.to/hkPx9m" target="_blank">Official Horie Rakuten Store</a></p>

<ul>
  <li>
    <p>Exclusive oxidation coloring pattern technology, offering vibrant and brilliant colors with multiple options available</p>
  </li>
  <li>
    <p>Exclusive double-layer insulation structure, keeps drinks cold without condensation and hot without burning your hands</p>
  </li>
  <li>
    <p>Exclusive special inner cup design that automatically creates long-lasting fine bubbles when drinking beer</p>
  </li>
  <li>
    <p>The cup rim is tightly sealed to prevent mouth scratches</p>
  </li>
  <li>
    <p>Pure titanium material, very lightweight, antibacterial, and odor-free</p>
  </li>
</ul>

<p>What attracted me the most were the first three exclusive technologies <strong>＋ it is a thermos cup</strong>. Besides, I don’t have any titanium cups yet, and I am troubled by the issue of lingering odors in thermos cups, so I really want to buy one to try.</p>

<h4 id="product-to-purchase"><strong>Product to Purchase:</strong></h4>

<blockquote>
  <p><em>Brand: Horie Titanium Cup</em></p>
</blockquote>

<blockquote>
  <p><em>Model: Kiln-Made Booster / 400ml</em></p>
</blockquote>

<blockquote>
  <p><em>Style: G4B</em></p>
</blockquote>

<p><a href="https://a.r10.to/hkPx9m" target="_blank">Official Price</a> <strong>¥19,800 yen (about NT $4,080 TWD)</strong>, free shipping within Japan.</p>

<p>I checked the prices in Taiwan; whether on e-commerce platforms or from individual sellers, they are around NT$5,500. Occasionally, group buys or exhibition events offer prices around NT$4,200, which is a good deal to consider.</p>

<h3 id="total-time-and-cost-for-rakuten-forwarding-consolidation-shipping-to-taiwan">Total Time and Cost for Rakuten Forwarding Consolidation Shipping to Taiwan</h3>

<p>The result is presented first.</p>

<p><img src="/assets/bea62c251f1b/1*n3ahp0WYgvO36vendy3GEQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="569" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*n3ahp0WYgvO36vendy3GEQ.png" /></p>

<ul>
  <li>
    <p>Because I wanted engraving, it required 7–10 days to produce. Additionally, I bought a magazine and had to wait for it to arrive at Rakuten forwarding.</p>
  </li>
  <li>
    <p>Looking at the shipping time alone, the forwarding center took only 1 day to receive the goods, and the entire process from packing to delivery took just 3 days, which is very fast!</p>
  </li>
  <li>
    <p>Total cost including shipping: NT $4,744. (See details in the text; shipping is more expensive due to the small purchase quantity.)</p>
  </li>
</ul>

<h3 id="rakuten-global-express"><a href="https://secure.globalexpress.rakuten.co.jp/mypage/register/invite?key=a6nn9r3fq6j4sskcsOrXE%2Bgdygp0zER%2FMe83%2FUDPw6k7LFkMFZBfgN0hX48E%3D" target="_blank">Rakuten Global Express</a></h3>

<p>Since I was going to buy items from Japan’s Rakuten Market anyway, I decided to try Rakuten’s forwarding/ consolidation service; of course, Rakuten forwarding is not limited to Rakuten purchases—it also supports other e-commerce platforms like Amazon Japan (Amazon JP), Yahoo Shopping / Yahoo Auctions Japan, Mercari, Biccamera, and more.</p>

<ul>
  <li>You can earn Rakuten points! And use points to pay!</li>
</ul>

<h4 id="special-protection-when-using-rakuten-shipping-to-purchase-rakuten-products">Special Protection When Using Rakuten Shipping to Purchase Rakuten Products</h4>

<p>In addition to providing forwarding/consolidation services, <strong>using Rakuten</strong> forwarding to <strong>purchase items from Rakuten Ichiba</strong> also offers the following guarantees:</p>

<ul>
  <li>
    <p>Compensation Service: Paid but Item Not Delivered</p>
  </li>
  <li>
    <p>Compensation Service: Product Damage (Please obtain a damage report issued by the logistics company)</p>
  </li>
  <li>
    <p>Compensation Service: For receiving items that clearly differ from the product description or are defective</p>
  </li>
  <li>
    <p>Compensation Service: If the received branded product is a counterfeit, infringing trademark rights (covered under the Safe Shopping Guarantee for over 1,000 brands)</p>
  </li>
  <li>
    <p>Compensation amount: up to 300,000 Japanese yen</p>
  </li>
  <li>
    <p>No handling fees or annual fees</p>
  </li>
</ul>

<p>For details, please <a href="https://event.rakuten.co.jp/anshin/anshinshopping/zh-tw/top/" target="_blank">refer to the official instructions</a>.</p>

<h4 id="purchase-protection-outside-rakuten">Purchase Protection Outside Rakuten</h4>

<p>If the purchased item is not from Rakuten Ichiba, basic forwarding shipping protection is provided:</p>

<ul>
  <li>
    <p>Compensation Service: Paid but Item Not Delivered</p>
  </li>
  <li>
    <p>Compensation Service: Product Damage (Please obtain a damage report issued by the logistics company)</p>
  </li>
</ul>

<p>For details, please refer to the <a href="https://globalexpress.rakuten.co.jp/help/insurance?l-id=help_tw_insurance" target="_blank">official explanation</a>.</p>

<h4 id="shipping-fee-calculation-and-quotation">Shipping Fee Calculation and Quotation</h4>

<p>You can use the <a href="https://globalexpress.rakuten.co.jp/estimate/?l-id=top_estimate" target="_blank">official tool</a> to estimate the shipping cost.</p>

<p><img src="/assets/bea62c251f1b/1*4mfK-p6n2FUgWpDCq8xisQ.webp" alt="&lt;https://globalexpress.rakuten.co.jp/estimate/?l-id=top_estimate&gt;" loading="lazy" decoding="async" width="949" height="596" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDkiIGhlaWdodD0iNTk2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*4mfK-p6n2FUgWpDCq8xisQ.png" /></p>

<p><a href="https://globalexpress.rakuten.co.jp/estimate/?l-id=top_estimate" target="_blank">https://globalexpress.rakuten.co.jp/estimate/?l-id=top_estimate</a></p>

<p><strong>Shipping Methods and Taiwan Shipping Rates</strong></p>

<p><img src="/assets/bea62c251f1b/1*Bi9oS0C32l8Mhvtwfdg-zg.webp" alt="&lt;https://globalexpress.rakuten.co.jp/help/delivery&gt;" loading="lazy" decoding="async" width="947" height="670" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iNjcwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*Bi9oS0C32l8Mhvtwfdg-zg.png" /></p>

<p><a href="https://globalexpress.rakuten.co.jp/help/delivery" target="_blank">https://globalexpress.rakuten.co.jp/help/delivery</a></p>

<p><img src="/assets/bea62c251f1b/1*k4M0QBRRDbPQZ7BSyB6c4A.webp" alt="&lt;https://globalexpress.rakuten.co.jp/help/country/fee?country=TW&gt;" loading="lazy" decoding="async" width="980" height="943" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODAiIGhlaWdodD0iOTQzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*k4M0QBRRDbPQZ7BSyB6c4A.png" /></p>

<p><a href="https://globalexpress.rakuten.co.jp/help/country/fee?country=TW" target="_blank">https://globalexpress.rakuten.co.jp/help/country/fee?country=TW</a></p>

<blockquote>
  <p><em>Please note that the size and weight here are the final numbers after consolidation.</em></p>
</blockquote>

<h3 id="registering-a-rakuten-japan-account">Registering a Rakuten Japan Account</h3>

<p>Go to the <a href="https://login.account.rakuten.com/sso/register?client_id=rakuten_ichiba_top_web&amp;service_id=s245&amp;response_type=code&amp;scope=openid&amp;redirect_uri=https%3A%2F%2Fwww.rakuten.co.jp%2F#/registration/1" target="_blank"><strong>Registration Page</strong></a>:</p>

<p><img src="/assets/bea62c251f1b/1*b3acAhAfqIrsIe7-hHuQLA.webp" alt="" loading="lazy" decoding="async" width="1307" height="1467" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzA3IiBoZWlnaHQ9IjE0NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*b3acAhAfqIrsIe7-hHuQLA.png" /></p>

<p>Enter your email address, password, and English name. Submit to complete the registration.</p>

<blockquote>
  <p><em>A random website issue was found here: <strong>if a server error keeps appearing on the Rakuten site, try switching the language to Japanese</strong> to use it normally. (Likely a multi-language error)</em></p>
</blockquote>

<h4 id="first-complete-the-real-name-verification-on-rakuten-transport">First, complete the real-name verification on Rakuten Transport</h4>

<p><img src="/assets/bea62c251f1b/1*roub7uHzS2bUtIP5qIg8pA.webp" alt="" loading="lazy" decoding="async" width="1330" height="920" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzMwIiBoZWlnaHQ9IjkyMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*roub7uHzS2bUtIP5qIg8pA.png" /></p>

<p>After registering on Rakuten, you will be automatically logged in to <a href="https://secure.globalexpress.rakuten.co.jp/mypage/register/invite?key=aanar03e62e0ckog4nWPIFcV7FTuy6JGEeCL1HwxrPgcSjKRlGHs1GY6PkH0%3D" target="_blank">Rakuten Global Express</a>.</p>

<ol>
  <li>
    <p>First, click to change the language to Traditional Chinese for easier operation.</p>
  </li>
  <li>
    <p>Click My Page to activate the forwarding service.</p>
  </li>
</ol>

<p><img src="/assets/bea62c251f1b/1*LtS88oCvn5XKCHImq_8Twg.webp" alt="" loading="lazy" decoding="async" width="537" height="715" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MzciIGhlaWdodD0iNzE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*LtS88oCvn5XKCHImq_8Twg.png" /></p>

<p><img src="/assets/bea62c251f1b/1*EBb5vLI574judf0zb4up9Q.webp" alt="" loading="lazy" decoding="async" width="522" height="701" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MjIiIGhlaWdodD0iNzAxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*EBb5vLI574judf0zb4up9Q.png" /></p>

<p>Go to your email inbox to retrieve the verification code and complete the email verification.</p>

<p><img src="/assets/bea62c251f1b/1*caABYLKqpF995bAS9QULpQ.webp" alt="" loading="lazy" decoding="async" width="530" height="692" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MzAiIGhlaWdodD0iNjkyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*caABYLKqpF995bAS9QULpQ.png" /></p>

<p>Next, fill in your name information.</p>

<ul>
  <li>
    <p>Surname (Kanji), Given Name (Kanji): Enter your name in Chinese characters</p>
  </li>
  <li>
    <p>Last Name (Katakana), First Name (Katakana): Enter your name in Katakana. You can <a href="https://dokochina.com/katakana.php" target="_blank">use this website for translation</a>, or use a nickname, for example: リー, ハリー.</p>
  </li>
</ul>

<h4 id="activate-rakuten-forwarding-address-and-complete-identity-verification">Activate Rakuten forwarding address and complete identity verification</h4>

<p><img src="/assets/bea62c251f1b/1*XH92Vc8EmKSvvF3RH7hYJg.webp" alt="" loading="lazy" decoding="async" width="1200" height="716" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*XH92Vc8EmKSvvF3RH7hYJg.png" /></p>

<p>Step one is to agree to the user terms. <strong>Some items, such as flammable goods, sprays, and fresh foods, are prohibited from shipping</strong>.</p>

<p><img src="/assets/bea62c251f1b/1*WOxQ8TpaXdOS_BJqyWeZYg.webp" alt="" loading="lazy" decoding="async" width="892" height="1109" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTIiIGhlaWdodD0iMTEwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*WOxQ8TpaXdOS_BJqyWeZYg.png" /></p>

<p>Step 2: Enter your personal information. Be sure to input your real passport name in English and your date of birth.</p>

<h4 id="get-your-exclusive-rakuten-forwarding-address">Get Your Exclusive Rakuten Forwarding Address</h4>

<p><img src="/assets/bea62c251f1b/1*u8ObmbBY2rsVw93cS8ADDA.webp" alt="" loading="lazy" decoding="async" width="880" height="1180" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODAiIGhlaWdodD0iMTE4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*u8ObmbBY2rsVw93cS8ADDA.png" /></p>

<ul>
  <li>
    <p>Be sure to enter the forwarding warehouse address including the final <code class="language-plaintext highlighter-rouge">白石町6–1 RGXセンター XXXXXXX</code> English and number code, as this is your unique identification address.</p>
  </li>
  <li>
    <p>You can click “Add Now” to directly add the address to your Rakuten account.</p>
  </li>
</ul>

<blockquote>
  <p><em>With this address and phone number, you can shop and ship from major Japanese e-commerce sites! However, to ensure your package is safely forwarded to you, <strong>please complete the “identity verification process” before purchasing and shipping items from shopping websites.</strong></em></p>
</blockquote>

<h4 id="continue-with-the-identity-verification-process">Continue with the identity verification process</h4>

<p><img src="/assets/bea62c251f1b/1*Fv2V7TPlqTBQHTeo3bPuWg.webp" alt="" loading="lazy" decoding="async" width="860" height="941" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjAiIGhlaWdodD0iOTQxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*Fv2V7TPlqTBQHTeo3bPuWg.png" /></p>

<ul>
  <li>Prepare your original ID card and mobile phone, then proceed with facial recognition verification. The entire process can be completed online 24/7 through self-service.</li>
</ul>

<blockquote>
  <p><em>But my ID photo looked quite different from my current appearance, so the online verification failed and was automatically switched to manual review. I received the official notification that the manual review was completed the next day.</em></p>
</blockquote>

<h4 id="my-page">My Page</h4>

<p>Click on “My Page” or the top of “My Page” above to go to the My Page homepage:</p>

<p><img src="/assets/bea62c251f1b/1*MhXfS0YfDj4TVZC_h1uBmA.webp" alt="" loading="lazy" decoding="async" width="950" height="584" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTAiIGhlaWdodD0iNTg0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*MhXfS0YfDj4TVZC_h1uBmA.png" /></p>

<p><img src="/assets/bea62c251f1b/1*_qPCZpRDpfbrNfytEsjBGA.webp" alt="" loading="lazy" decoding="async" width="1322" height="596" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzIyIiBoZWlnaHQ9IjU5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*_qPCZpRDpfbrNfytEsjBGA.png" /></p>

<p>If the verification process is not completed, the following warning will appear:</p>

<p><img src="/assets/bea62c251f1b/1*qkgvVofPd8uI6CLH_0rnhw.webp" alt="" loading="lazy" decoding="async" width="694" height="200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTQiIGhlaWdodD0iMjAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*qkgvVofPd8uI6CLH_0rnhw.png" /></p>

<blockquote>
  <p><strong><em>Please complete the verification before making any purchases.</em></strong></p>
</blockquote>

<h4 id="your-personal-shipping-address-and-information">Your Personal Shipping Address and Information</h4>

<p>Your personalized information will be displayed at the bottom right of the page.</p>

<p><img src="/assets/bea62c251f1b/1*_qsR1A94i83sEDAVpGxgbg.webp" alt="" loading="lazy" decoding="async" width="1313" height="1083" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzEzIiBoZWlnaHQ9IjEwODMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*_qsR1A94i83sEDAVpGxgbg.png" /></p>

<h3 id="prohibited-items-for-shipping-to-taiwan">Prohibited Items for Shipping to Taiwan</h3>

<p>Please check whether the item is prohibited before purchasing and shipping it back to Taiwan.</p>

<p><img src="/assets/bea62c251f1b/1*c7qamfTN89ipGwM9LZUblQ.webp" alt="&lt;https://globalexpress.rakuten.co.jp/help/country/detail?country=TW&gt;" loading="lazy" decoding="async" width="959" height="873" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTkiIGhlaWdodD0iODczIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*c7qamfTN89ipGwM9LZUblQ.png" /></p>

<p><a href="https://globalexpress.rakuten.co.jp/help/country/detail?country=TW" target="_blank">https://globalexpress.rakuten.co.jp/help/country/detail?country=TW</a></p>

<p>Different shipping methods have different restrictions. Please <a href="https://globalexpress.rakuten.co.jp/help/country/detail?country=TW" target="_blank">read carefully</a>.</p>

<blockquote>
  <p><em>If you want to buy alcohol or cigarettes, be sure to check other guides online first. From what I understand, after the items arrive in Taiwan, you need to bring the notification letter to pay the additional tax before release.</em></p>
</blockquote>

<h3 id="shopping-on-rakuten-ichiba--horie-titanium-cup-order-guide"><a href="https://a.r10.to/hkPx9m" target="_blank">Shopping on Rakuten Ichiba — Horie Titanium Cup</a> Order Guide</h3>

<blockquote>
  <p><em>High insulation double-walled titanium tumbler fired in a 1055℃ kiln. Allergy-free, hygienic, rust-resistant, lightweight, durable, and long-lasting. Ideal as a gift for loved ones or a treat for yourself. [Free gift wrapping available] HORIE official shop / Kiln-made personalized pure titanium double tumbler horie titanium tumbler Mother’s Day Father’s Day Respect for the Aged Day gift beer glass Horie Tsubame-Sanjo birthday present farewell retirement celebration thank-you wedding celebration SDGs Christmas</em></p>
</blockquote>

<blockquote>
  <p><em>Free shipping for orders between 13,200 and 19,800 yen</em></p>
</blockquote>

<h4 id="go-to-product-page-"><a href="https://a.r10.to/hkPx9m" target="_blank">Go to Product Page</a> ：</h4>

<p><img src="/assets/bea62c251f1b/1*8aYksm9WZ5UUkmSKSI_5EA.webp" alt="Product Page" loading="lazy" decoding="async" width="938" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*8aYksm9WZ5UUkmSKSI_5EA.png" /></p>

<p><a href="https://a.r10.to/hkPx9m" target="_blank">Product Page</a></p>

<p>This is the product page for the Kiln Creation series. To choose other series, <a href="https://a.r10.to/hkG3CK" target="_blank">you can click HORIE store homepage</a> to view.</p>

<p>Scroll to the bottom to select product specifications (商品詳細を選択).</p>

<ul>
  <li>
    <p><strong>Shape/Size</strong>: Choose the style you want (please refer to the product page images; options include wide mouth, narrow mouth, and various capacities).</p>
  </li>
  <li>
    <p><strong>Color</strong>: Choose the style you want (please refer to the product page images)</p>
  </li>
</ul>

<p><strong>I bought: Kiln-made Booster/400ml &amp; G4B:</strong></p>

<p><img src="/assets/bea62c251f1b/1*rwCNpways8AGJZ3DglI0qw.webp" alt="" loading="lazy" decoding="async" width="727" height="1016" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjciIGhlaWdodD0iMTAxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*rwCNpways8AGJZ3DglI0qw.png" /></p>

<p><strong>Name Engraving Option</strong>: Whether to have engraving (online store exclusive, <strong>free engraving</strong>), requires 7–10 business days:</p>

<p><img src="/assets/bea62c251f1b/1*8Zfq6b9q2X2jORa51cjm1w.webp" alt="" loading="lazy" decoding="async" width="657" height="825" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTciIGhlaWdodD0iODI1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*8Zfq6b9q2X2jORa51cjm1w.png" /></p>

<p><strong>Wrapping option</strong>: Gift wrap style, A or B or no style.</p>

<p><img src="/assets/bea62c251f1b/1*TeJNyUkr6nsAapTC9H504A.webp" alt="" loading="lazy" decoding="async" width="640" height="623" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNjIzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*TeJNyUkr6nsAapTC9H504A.png" /></p>

<p><strong>Choice of Paulownia Box Design</strong>: Packaging box style, WakuWaku Surprise Edition (left, single item only) or Regular Edition (right)</p>

<p><strong>Paulownia Wooden Box for 2-piece Set (※Check only if ordering 2 or more items and needed):</strong> Check this box to place two cups in the same box (see bottom right of the image).<br />
Skip if buying only one.</p>

<p><strong>Combination for 2 sets (example: Premium and Light) (※Please specify if ordering 2 or more sets):</strong> How to combine the boxes when buying two or more sets. If it’s troublesome, it’s faster to add them separately to the cart.<br />
If buying only one, you can skip this.</p>

<p><img src="/assets/bea62c251f1b/1*49fEtjiRRBvNyEGxdFeE3A.webp" alt="" loading="lazy" decoding="async" width="645" height="621" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDUiIGhlaWdodD0iNjIxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*49fEtjiRRBvNyEGxdFeE3A.png" /></p>

<p><strong>Name Engraving Type ※If ordering 2 or more single name-engraved items, please add them to the cart one by one:</strong> No engraving (no inscription), you can skip the fields below. If engraving is needed, please choose the style and fill in the text for each line according to the product page images:</p>

<ul>
  <li>
    <p><strong>Name engraving line 1</strong></p>
  </li>
  <li>
    <p><strong>Name engraving line 2</strong></p>
  </li>
  <li>
    <p><strong>Name engraving line 3</strong></p>
  </li>
  <li>
    <p><strong>Name engraving line 4</strong></p>
  </li>
</ul>

<blockquote>
  <p><strong><em>Please note that if you want engraving, add each item to the cart separately. Do not write the engraving text in the product details.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>I chose style B for the engraving (see the image at the end).</em></strong></p>
</blockquote>

<p><img src="/assets/bea62c251f1b/1*i50V97FHogvQ4PVCFTLX7Q.webp" alt="" loading="lazy" decoding="async" width="625" height="630" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjUiIGhlaWdodD0iNjMwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*i50V97FHogvQ4PVCFTLX7Q.png" /></p>

<p><strong>Gift Wrapping and Greeting Card Styles, Content:</strong></p>

<ul>
  <li>
    <p>Noshi content: The type of congratulatory message, such as “御祝” (celebration), “祝壽” (birthday wishes), etc. If not needed, select “No noshi.”</p>
  </li>
  <li>
    <p>Type of Noshi: Gift label style, as shown in the picture below. If not needed, select “No Noshi.”</p>
  </li>
  <li>
    <p>Types of Mizuhiki (Decorative Cord) for Noshi: Styles of celebratory cord bindings, as shown below. If not needed, select “No Noshi.”</p>
  </li>
</ul>

<p><img src="/assets/bea62c251f1b/1*uLCkqmso0Ad9MTynZL0T-w.webp" alt="" loading="lazy" decoding="async" width="626" height="854" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjYiIGhlaWdodD0iODU0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*uLCkqmso0Ad9MTynZL0T-w.png" /></p>

<p><strong>Name to be written on the gift wrapping (sender) ※For joint names, please insert “・” between names. If you want furigana for occasions like thank-you gifts, please enter it as well. If over 20 characters, please write in the remarks section:</strong> Please fill in the sender’s name to be printed on the gift wrapping (noshi). For multiple senders, insert “・” between names; leave blank if not applicable.</p>

<h4 id="add-to-cart-and-complete-checkout">Add to Cart and Complete Checkout</h4>

<p><img src="/assets/bea62c251f1b/1*UCbosHBXeND4C0Q7hoX38A.webp" alt="" loading="lazy" decoding="async" width="627" height="711" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjciIGhlaWdodD0iNzExIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*UCbosHBXeND4C0Q7hoX38A.png" /></p>

<p>After filling in all the specifications, click “かごに追加” to add the item to your cart. For the delivery address (お届け先), select our Rakuten forwarding service in “Kanagawa Prefecture.” Below, it will show the estimated delivery time to Rakuten forwarding and free shipping. Once everything is added, click the cart icon above to proceed to checkout:</p>

<p><img src="/assets/bea62c251f1b/1*XP6gv2xNgplYY_IvQyKNCQ.webp" alt="" loading="lazy" decoding="async" width="1048" height="615" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ4IiBoZWlnaHQ9IjYxNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*XP6gv2xNgplYY_IvQyKNCQ.png" /></p>

<p>After confirming the product specifications are correct, click the “Proceed to Purchase” button on the right.</p>

<p><img src="/assets/bea62c251f1b/1*lsPBGTQGkyJ_RAG0VjayAw.webp" alt="" loading="lazy" decoding="async" width="1049" height="989" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ5IiBoZWlnaHQ9Ijk4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*lsPBGTQGkyJ_RAG0VjayAw.png" /></p>

<ul>
  <li>
    <p><strong>Delivery Address:</strong> For the shipping information, you can directly select the forwarding address you just added in Rakuten Shipping, or add a new forwarding address yourself (please be sure to enter the full forwarding address including the final number).</p>
  </li>
  <li>
    <p><strong>Payment Method:</strong> I paid directly by credit card (since it’s a purchase from Japan, check which banks offer discounts).</p>
  </li>
</ul>

<p>After confirming everything is correct, click “注文を確定する” to complete the order.</p>

<h4 id="horie-engraving-confirmation">HORIE Engraving Confirmation</h4>

<p><img src="/assets/bea62c251f1b/1*Q8t3tiej00RrQqsEBVsOLg.webp" alt="" loading="lazy" decoding="async" width="921" height="1190" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjEiIGhlaWdodD0iMTE5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*Q8t3tiej00RrQqsEBVsOLg.png" /></p>

<p>If you choose to add engraving, HORIE will send you a confirmation after receiving the order. If everything is correct, you can reply with “ご送付いただいたレイアウトを確認しました。問題ございませんので製作を進めてください。” to let them start production.</p>

<p>He will also send a message informing the shipping time.</p>

<h4 id="merchant-shipping-and-rakuten-forwarding-arrival-notification">Merchant Shipping and Rakuten Forwarding Arrival Notification</h4>

<p><img src="/assets/bea62c251f1b/1*DGN-R9yxdnrjEMlSR5W0Bw.webp" alt="" loading="lazy" decoding="async" width="850" height="761" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NTAiIGhlaWdodD0iNzYxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*DGN-R9yxdnrjEMlSR5W0Bw.png" /></p>

<p><img src="/assets/bea62c251f1b/1*j5pKRcNpLfuiToRzoNG-bg.webp" alt="" loading="lazy" decoding="async" width="894" height="642" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTQiIGhlaWdodD0iNjQyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*j5pKRcNpLfuiToRzoNG-bg.png" /></p>

<p>After receiving the HORIE shipping notification, the package arrives at the Rakuten forwarding warehouse in about 1–3 days.</p>

<h3 id="shipping-from-amazon-jp-to-rakuten-forwarding-service">Shipping from Amazon JP to Rakuten Forwarding Service</h3>

<p>This time, besides buying the HORIE titanium cup, I also purchased a magazine from Amazon Japan (Amazon JP) and plan to ship them together back to Taiwan.</p>

<p><img src="/assets/bea62c251f1b/1*70bmfM40fgui1EptFuLylA.webp" alt="" loading="lazy" decoding="async" width="1400" height="707" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjcwNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*70bmfM40fgui1EptFuLylA.png" /></p>

<p>On the shopping website, just remember to fill in the shipping address and contact phone number with your Rakuten forwarding information.</p>

<h4 id="non-rakuten-market-shopping-pre-registration-of-incoming-packages">(Non-Rakuten Market Shopping) Pre-Registration of Incoming Packages</h4>

<p>For items not purchased directly from Rakuten, it is recommended to register the shipment with the forwarding service after placing the order.</p>

<p><img src="/assets/bea62c251f1b/1*hbc4AK4PyGSScxgg8Q9OWg.webp" alt="" loading="lazy" decoding="async" width="1217" height="716" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjE3IiBoZWlnaHQ9IjcxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*hbc4AK4PyGSScxgg8Q9OWg.png" /></p>

<p>Click “Cargo List” → “Register/Confirm”</p>

<p><img src="/assets/bea62c251f1b/1*IYs0xH6f-y55OFHDGmWpHQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="574" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjU3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*IYs0xH6f-y55OFHDGmWpHQ.png" /></p>

<p>Enter store, product information, and amount purchased from other platforms.</p>

<h4 id="amazon-japan-purchase---rakuten-shipping-arrival-notification">Amazon Japan Purchase - Rakuten Shipping Arrival Notification</h4>

<p><img src="/assets/bea62c251f1b/1*eWxfcNY_Wsr7VT9-5ZuFtQ.webp" alt="" loading="lazy" decoding="async" width="610" height="437" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MTAiIGhlaWdodD0iNDM3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*eWxfcNY_Wsr7VT9-5ZuFtQ.png" /></p>

<p>After completing the order, wait for the seller to ship. You will also receive a notification when the package arrives at the Rakuten forwarding center.</p>

<h3 id="rakuten-forwarding-to-taiwan">Rakuten Forwarding to Taiwan</h3>

<p><a href="https://globalexpress.rakuten.co.jp/faq/detail?id=99" target="_blank">Rakuten Global Express offers free storage for up to 30 days</a>. Once all items have arrived and are consolidated, you can request packaging and shipment.</p>

<p><img src="/assets/bea62c251f1b/1*m3TCoxNu_7ltuthkRAXFBg.webp" alt="" loading="lazy" decoding="async" width="1200" height="663" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY2MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*m3TCoxNu_7ltuthkRAXFBg.png" /></p>

<p>Delivered items: A total of three.</p>

<p>Click “Cargo List”:</p>

<p><img src="/assets/bea62c251f1b/1*4YRX_1LsNWvrExJAeoQ0nQ.webp" alt="" loading="lazy" decoding="async" width="795" height="831" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTUiIGhlaWdodD0iODMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*4YRX_1LsNWvrExJAeoQ0nQ.png" /></p>

<p>Click “Submit Packaging Request”</p>

<p><img src="/assets/bea62c251f1b/1*gUgWUivN-7jH9wNcFn-Y6g.webp" alt="" loading="lazy" decoding="async" width="1400" height="637" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjYzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*gUgWUivN-7jH9wNcFn-Y6g.png" /></p>

<p>Confirm the contents of the consolidated package for shipment, and if correct, click “Confirm Shipment Package.”</p>

<p><img src="/assets/bea62c251f1b/1*PVcM448g7pTwBjOeKh3n0Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="561" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjU2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*PVcM448g7pTwBjOeKh3n0Q.png" /></p>

<p>Click “Select other paid packaging service.”</p>

<p><img src="/assets/bea62c251f1b/1*5tmccDXzlPpxIvo1CMNIow.webp" alt="" loading="lazy" decoding="async" width="1400" height="603" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjYwMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*5tmccDXzlPpxIvo1CMNIow.png" /></p>

<p>You can choose the packaging service: (Click the “i” for illustrated instructions)</p>

<ul>
  <li>
    <p>Remove packaging boxes, shoe boxes, flyers, catalogs, delivery notes, etc.: to reduce weight and volume, saving shipping costs.</p>
  </li>
  <li>
    <p>Package Waterproofing Treatment</p>
  </li>
  <li>
    <p>Use reinforced boxes for international shipping: I checked this option, which means using a larger cardboard box plus cushioning paper to protect your items.</p>
  </li>
</ul>

<h4 id="packaging-request-choose-shipping-location">Packaging Request (Choose Shipping Location)</h4>

<p><img src="/assets/bea62c251f1b/1*uJDDeUEF1kAT3thtzpzwDw.webp" alt="" loading="lazy" decoding="async" width="1400" height="333" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjMzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*uJDDeUEF1kAT3thtzpzwDw.png" /></p>

<p><img src="/assets/bea62c251f1b/1*yrnzSlMcUafXAIhIaJy4Hw.webp" alt="" loading="lazy" decoding="async" width="743" height="797" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDMiIGhlaWdodD0iNzk3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*yrnzSlMcUafXAIhIaJy4Hw.png" /></p>

<p>Enter the shipping address:</p>

<ul>
  <li>Passport English name, <a href="https://www.post.gov.tw/post/internet/SearchZone/index.jsp?ID=130112" target="_blank">English address</a>, <strong>your phone number (replace the leading 0 with 886)</strong></li>
</ul>

<p><img src="/assets/bea62c251f1b/1*3o3wRZOd4t8lldmdU-NObw.webp" alt="" loading="lazy" decoding="async" width="1200" height="676" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*3o3wRZOd4t8lldmdU-NObw.png" /></p>

<p>After submission, the status will change to: Packaging in progress. Please wait for the process to complete before making payment and shipping.</p>

<p><img src="/assets/bea62c251f1b/1*5ogwtSaFBNCO3r2gJIA48w.webp" alt="" loading="lazy" decoding="async" width="1200" height="537" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*5ogwtSaFBNCO3r2gJIA48w.png" /></p>

<p>I received the packaging completion notification the next morning.</p>

<h4 id="pay-forwarding-shipping-fee-and-request-shipment">Pay Forwarding Shipping Fee and Request Shipment</h4>

<p><img src="/assets/bea62c251f1b/1*lA5p3upIWTdp6FTDrOdtqA.webp" alt="" loading="lazy" decoding="async" width="1160" height="725" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTYwIiBoZWlnaHQ9IjcyNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*lA5p3upIWTdp6FTDrOdtqA.png" /></p>

<p>Status: Pending Payment.</p>

<p><img src="/assets/bea62c251f1b/1*psFozqxO72KXbbEKY0k37g.webp" alt="" loading="lazy" decoding="async" width="1098" height="887" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDk4IiBoZWlnaHQ9Ijg4NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*psFozqxO72KXbbEKY0k37g.png" /></p>

<p>Click “Cargo List” → “Pending Payment” cargo → “Proceed to Payment”.</p>

<p><img src="/assets/bea62c251f1b/1*XC-tScyTBFqb-XGTedttWA.webp" alt="" loading="lazy" decoding="async" width="1200" height="530" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUzMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*XC-tScyTBFqb-XGTedttWA.png" /></p>

<blockquote>
  <p><strong><em>Final size and weight: 2,330 g, 38 x 30 x 21 cm</em></strong></p>
</blockquote>

<p>Confirm the package contents and recipient information. If correct, click “Select Shipping Method.”</p>

<p><img src="/assets/bea62c251f1b/1*q9ImvJKflcvx1uHFSxS4pw.webp" alt="" loading="lazy" decoding="async" width="1200" height="577" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*q9ImvJKflcvx1uHFSxS4pw.png" /></p>

<p>You can choose:</p>

<ul>
  <li>
    <p>DHL Express (Super Express Air): 1–3 days, 9,100 yen<br />
According to a friend’s advice, if you use DHL, remember to <a href="https://roo.cash/blog/dhl-introduction/#DHL_%E5%85%88%E7%A8%85%E5%BE%8C%E6%94%BE%E6%98%AF%E4%BB%80%E9%BA%BC%EF%BC%9F" target="_blank">apply for tax payment before delivery</a> to avoid extra handling fees if customs inspects and charges additional tax.</p>
  </li>
  <li>
    <p><strong>EMS Express Air Shipping: 3–8 days, 6,550 JPY (I chose this, actually received in 2 days, very fast)</strong></p>
  </li>
  <li>
    <p>ECMS Economy Air Shipping: 6–11 days, 6,280 JPY</p>
  </li>
  <li>
    <p>Sea Shipping: 20–40 days, 5,620 JPY</p>
  </li>
</ul>

<blockquote>
  <p><em>The price is similar to the quote from the <a href="https://globalexpress.rakuten.co.jp/estimate/" target="_blank">Cost Estimation Tool</a>.</em></p>
</blockquote>

<p>Next, enter your credit card or PayPal information to complete the payment:</p>

<p><img src="/assets/bea62c251f1b/1*uPqI-nncfu_uToP6427_Jw.webp" alt="" loading="lazy" decoding="async" width="1200" height="561" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*uPqI-nncfu_uToP6427_Jw.png" /></p>

<p><img src="/assets/bea62c251f1b/1*ydX1HyUTXvumBk__QH4UJw.webp" alt="" loading="lazy" decoding="async" width="1200" height="569" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*ydX1HyUTXvumBk__QH4UJw.png" /></p>

<p>Shipping fees also earn Rakuten points (1%)!</p>

<h4 id="shipping-entrustment-completed">Shipping Entrustment Completed</h4>

<p><img src="/assets/bea62c251f1b/1*2jZefbTEBHYcnc8szKlx6Q.webp" alt="" loading="lazy" decoding="async" width="1208" height="492" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjA4IiBoZWlnaHQ9IjQ5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*2jZefbTEBHYcnc8szKlx6Q.png" /></p>

<p><img src="/assets/bea62c251f1b/1*_f1zLXqHYGcWXdgzxCfL_g.webp" alt="" loading="lazy" decoding="async" width="1161" height="777" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTYxIiBoZWlnaHQ9Ijc3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*_f1zLXqHYGcWXdgzxCfL_g.png" /></p>

<p>Status will change to: Shipping in progress.</p>

<h4 id="complete-ezway-registration">Complete EZWay Registration</h4>

<p>If you have no experience buying items from overseas, <a href="https://www.rakuten.com.tw/magazine/life/2022/012802/#D" target="_blank">be sure to complete EZWay real-name verification by downloading the EZWay app, registering, and completing the verification</a>.</p>

<h4 id="rakuten-forwarding-shipment">Rakuten Forwarding Shipment</h4>

<p><img src="/assets/bea62c251f1b/1*ZNzn3Zpuk_HN0kbmo_zEzg.webp" alt="" loading="lazy" decoding="async" width="849" height="698" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDkiIGhlaWdodD0iNjk4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/bea62c251f1b/1*ZNzn3Zpuk_HN0kbmo_zEzg.png" /></p>

<p>Rakuten will also send a notification after shipping is completed, including a tracking number to check the shipment status.</p>

<blockquote>
  <p><em>The shipment point may not be found immediately; please check again later and it will appear.</em></p>
</blockquote>

<p><img src="/assets/bea62c251f1b/1*U0L23fpYbBKTeoPtZcLsBA.webp" alt="" loading="lazy" decoding="async" width="904" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*U0L23fpYbBKTeoPtZcLsBA.png" /></p>

<h4 id="waiting-for-the-package">Waiting for the package!</h4>

<p>Up to this point, registration on the Japanese Rakuten site, using Rakuten forwarding, placing orders, shipping, sending back to Taiwan, and EZWay are all completed. Now just wait for the goods to arrive!</p>

<h3 id="rakuten-forwarding-and-horie-titanium-cup-unboxing">Rakuten Forwarding and <a href="https://a.r10.to/hkPx9m" target="_blank">Horie Titanium Cup</a> Unboxing</h3>

<p>10/14 Requested Rakuten forwarding shipment, received it on 10/16!</p>

<p><img src="/assets/bea62c251f1b/1*LnzDj0-qp5JrhXe40RiewQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1088" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwODgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*LnzDj0-qp5JrhXe40RiewQ.png" /></p>

<p>The two cups plus one magazine do not take up much space in the actual box.</p>

<p><img src="/assets/bea62c251f1b/1*x7NZZf3oWLDLAqZzktyYoA.webp" alt="" loading="lazy" decoding="async" width="1200" height="1052" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNTIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*x7NZZf3oWLDLAqZzktyYoA.png" /></p>

<p>Cover with Japanese-style warning stickers.</p>

<p><img src="/assets/bea62c251f1b/1*Rub-oAEfRnZjpn4eIAhPHg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1157" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExNTciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*Rub-oAEfRnZjpn4eIAhPHg.png" /></p>

<p>The reinforced box for international shipping includes this extra cardboard box plus shockproof paper packaging around and at the bottom.</p>

<p><img src="/assets/bea62c251f1b/1*sR3X_vfh6_7VH8x7jTA4GQ.webp" alt="" loading="lazy" decoding="async" width="913" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTMiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*sR3X_vfh6_7VH8x7jTA4GQ.png" /></p>

<p><img src="/assets/bea62c251f1b/1*a2ZQxQbXhyFm790bAqnZ4Q.webp" alt="" loading="lazy" decoding="async" width="931" height="1184" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzEiIGhlaWdodD0iMTE4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*a2ZQxQbXhyFm790bAqnZ4Q.png" /></p>

<p>The above is the magazine bought from Amazon (the paper packaging for shipping was damaged, but the contents are fine), and below is the Horie titanium cup.</p>

<p><img src="/assets/bea62c251f1b/1*jDdIWaMN0U_l0KrULJjk7g.webp" alt="" loading="lazy" decoding="async" width="1200" height="925" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkyNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*jDdIWaMN0U_l0KrULJjk7g.png" /></p>

<p>The Horie titanium cup’s original sealed box was placed intact inside the Rakuten forwarding box.</p>

<blockquote>
  <p><em>It seems you can choose to remove the original packaging during forwarding to reduce volume and weight, saving on shipping costs.</em></p>
</blockquote>

<p><img src="/assets/bea62c251f1b/1*ZVCaomYElKOQktBAGerrkg.webp" alt="" loading="lazy" decoding="async" width="1200" height="817" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgxNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*ZVCaomYElKOQktBAGerrkg.png" /></p>

<p>Contents: 2 titanium cups.</p>

<h3 id="horie-titanium-cup-1"><a href="https://a.r10.to/hkPx9m" target="_blank">Horie Titanium Cup</a></h3>

<p><img src="/assets/bea62c251f1b/1*bXj9-XVgjfsVta6cZg8Fpw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*bXj9-XVgjfsVta6cZg8Fpw.jpeg" /></p>

<p><img src="/assets/bea62c251f1b/1*z2CIeS7Y4Z65SV2WXSv-1Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*z2CIeS7Y4Z65SV2WXSv-1Q.jpeg" /></p>

<p><img src="/assets/bea62c251f1b/1*EZAkrzE5WkIEF0YMSoT3CA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/bea62c251f1b/1*EZAkrzE5WkIEF0YMSoT3CA.jpeg" /></p>

<p>The overall colors, patterns, and gradients are very vivid and magical, creating an unreal beauty.</p>

<p><img src="/assets/bea62c251f1b/1*F_L3JKPr1kd-Ljs9c2TA2Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/bea62c251f1b/1*F_L3JKPr1kd-Ljs9c2TA2Q.jpeg" /></p>

<p>Open a bottle of draft beer with ice cubes for testing. The ice retention is indeed good, the beer has a smooth foam, tastes great, <strong>and it really doesn’t sweat</strong>.</p>

<h3 id="final-cost-calculation">Final Cost Calculation</h3>

<ul>
  <li>
    <p><a href="https://a.r10.to/hkPx9m" target="_blank">Horie Titanium Cup</a> Kiln-made Booster / 400ml ¥19,800 JPY — NT $4,079</p>
  </li>
  <li>
    <p>Shipping cost per person: NT$665</p>
  </li>
  <li>
    <p>Customs Duty: Not charged</p>
  </li>
</ul>

<p>Total: NT $4,744 purchased.</p>

<blockquote>
  <p><strong><em>Because the titanium cup is very light, you can find a few more people to share the shipping cost, which should bring the price down to around NT4,200. I only bought two, so the shipping cost was a bit expensive.</em></strong></p>
</blockquote>

<blockquote>
  <p>Of course, the best deal is still to buy directly at Japanese department stores and get a tax refund! The estimated price is around NT$ 3,600.</p>
</blockquote>]]></content>
  </entry><entry>
    <title type="html">Tokyo Area Travel Guide｜Kawagoe Little Edo &amp;amp; Atami Fireworks Festival 5-Day Itinerary</title>
    <link href="https://en.zhgchg.li/posts/travel-journals/tokyo-area-travel-guide-kawagoe-little-edo-atami-fireworks-festival-5-day-itinerary-958599363857/" rel="alternate" type="text/html" title="Tokyo Area Travel Guide｜Kawagoe Little Edo &amp; Atami Fireworks Festival 5-Day Itinerary" />
    <published>2025-10-10T19:01:28+08:00</published>
    <updated>2025-10-16T23:55:33+08:00</updated>
    <id>https://en.zhgchg.li/posts/travel-journals/958599363857</id><summary type="html">Explore Tokyo and nearby attractions with a 5-day itinerary covering Kawagoe’s historic charm and Atami’s spectacular fireworks festival. Discover seamless transport tips, immersive experiences, and local highlights for a memorable trip.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="Travel Journals" /><category term="life" /><category term="travel" /><category term="japan" /><category term="tokyo" /><category term="travel-writing" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/958599363857/1*E-ZA-OYCi5vsTBGlsxyS0g.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/travel-journals/tokyo-area-travel-guide-kawagoe-little-edo-atami-fireworks-festival-5-day-itinerary-958599363857/"><![CDATA[<h3 id="travelogue-2025-tokyo-area--5-day-free-trip-to-kawagoe-little-edo-and-atami-sea-fireworks-festival">[Travelogue] 2025 Tokyo Area — 5-Day Free Trip to Kawagoe Little Edo and Atami Sea Fireworks Festival</h3>

<p>Tokyo City + Suburbs 5 Days 4 Nights Free Travel: Complete Guide to Transportation and Experiences for Atami Fireworks Festival and Kawagoe Day Trip</p>

<p><img src="/assets/958599363857/1*E-ZA-OYCi5vsTBGlsxyS0g.webp" alt="Summer Fireworks in Atami" loading="lazy" decoding="async" width="1400" height="933" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*E-ZA-OYCi5vsTBGlsxyS0g.jpeg" /></p>

<p>Summer Fireworks in Atami</p>

<p>Taking advantage of the start of my new job to recharge, <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">Revisiting Tokyo</a> — experiencing a once-in-a-lifetime Japanese fireworks festival and a day trip to Kawagoe near Tokyo.</p>

<h4 id="last-trip-itinerary">Last Trip Itinerary</h4>

<blockquote>
  <p><em>Last time, in the trip “<a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/"><strong>[Travelogue] 5 Days Free Trip to Tokyo 2023</strong></a>” in June 2023, it was not as hot as in September. The main spots visited were: Tokyo Tower, Shibuya Sky, Tokyo Skytree, Asakusa Kaminarimon, DisneySea, Yokohama Gundam, Yokohama Cable Car, Odaiba, Shinjuku, and the Imperial Palace.</em></p>
</blockquote>

<blockquote>
  <p><em>Interested friends can <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">check it out</a>.</em></p>
</blockquote>

<h3 id="fun">Fun</h3>

<ul>
  <li>
    <p>Day 1 (09/13 Sat) — Arrive in Tokyo, Shibuya, Shibuya Sky</p>
  </li>
  <li>
    <p>Day 2 (09/14) — Kawagoe Day Trip, Evening Shopping in Ikebukuro</p>
  </li>
  <li>
    <p>Day 3 (09/15 Mon) — Tokyo Station Underground Mall, Atami Fireworks Festival</p>
  </li>
  <li>
    <p>Day 4 (09/16 Tue) — Azabudai TeamLab, Harajuku Shopping, Harajuku HARRY Otter Café</p>
  </li>
  <li>
    <p>Day 5 (09/17 Wed) — Stroll around Ueno, Return Trip</p>
  </li>
</ul>

<h4 id="shibuya-sky">Shibuya Sky</h4>

<ul>
  <li>
    <p><strong>Shibuya Sky tickets are very popular, so be sure to purchase them in advance.</strong></p>
  </li>
  <li>
    <p><a href="https://www.shibuya-scramble-square.com.t.apy.hp.transer.com/sky/ticket/" target="_blank">Official Website</a> Ticket sales start at 11:00 PM Taiwan time. You can buy tickets for dates more than 2 weeks ahead.<br />
Please register as a member and get familiar with the purchase process before buying tickets on the official site. The ticket holder’s name can be a nickname; it does not have to match the passport name exactly.</p>
  </li>
  <li>
    <p>We bought tickets for the 18:40–18:59 night view slot. You can also choose an earlier time to watch the sunset or the last session at 21:00 after dinner.</p>
  </li>
  <li>
    <p>Other purchase options: <a href="https://www.kkday.com/zh-tw/product/133300-shibuya-sky-observatory-e-ticket-tokyo?cid=19365" target="_blank">KKday Shibuya SHIBUYA SKY Observatory Ticket｜Instant Use</a></p>
  </li>
</ul>

<blockquote>
  <p><em>Price: ¥3,400 JPY/person</em></p>
</blockquote>

<h4 id="atami-fireworks-festival">[Atami Fireworks Festival</h4>

<p>Atami Fireworks Festival](https://hoshinoresorts.com/zh_tw/hotels/risonareatami/activities/13288/){:target=”_blank”} official website.</p>

<ul>
  <li>
    <p>2025 Fireworks Dates: 3/23, 4/20, 4/28, 5/31, 7/25, 8/5, 8/8, 8/18, 8/25, <strong>9/15</strong>, 9/23, 10/13, 11/3, 11/24, 12/7, 12/19.</p>
  </li>
  <li>
    <p>July and August showtimes: 8:15 PM~8:40 PM, 25 minutes</p>
  </li>
  <li>
    <p><strong>Other months’ display time: 8:20 PM~8:40 PM, 20 minutes</strong></p>
  </li>
</ul>

<p>The beach is open to the public for free with no paid seating. It’s within walking distance from JR Atami Station. <strong>For safety, just make sure to book your round-trip JR tickets or accommodation in Atami in advance.</strong></p>

<p><strong>It is highly recommended to bring your own picnic mat! It is highly recommended to bring your own picnic mat! It is highly recommended to bring your own picnic mat!</strong></p>

<blockquote>
  <p><em>We attended the fireworks show on 9/15 at 8:20 PM.</em></p>
</blockquote>

<h4 id="azabudai-teamlab-teamlab-borderless-mori-building-digital-art-museum"><a href="https://borderless-azabudai.ticket.teamlab.art/#/" target="_blank">Azabudai TeamLab (teamLab Borderless: MORI Building DIGITAL ART MUSEUM)</a></h4>

<ul>
  <li>
    <p>There are quite a few tickets available, but it is still recommended to purchase them in advance before departure.</p>
  </li>
  <li>
    <p>Other purchase options: <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Factivity%2F20707-teamlab-borderless-admission-ticket-tokyo%2F" target="_blank">Tokyo teamLab Borderless Digital Art Museum Tickets</a></p>
  </li>
</ul>

<blockquote>
  <p><em>Price: ¥4,800 JPY/person</em></p>
</blockquote>

<h4 id="harry-harajuku-terrace-otter-cafe"><a href="https://harinezumi-cafe.com/store/harajuku-terrace/" target="_blank">HARRY Harajuku Terrace</a> Otter Cafe</h4>

<p>The <a href="https://harinezumi-cafe.com/store/harajuku-terrace/" target="_blank">cafe</a> where you can interact with otters, which I saw on Instagram before.</p>

<p><img src="/assets/958599363857/1*gV6F9gn_uu9pYeVZSlPQbQ.webp" alt="&lt;https://www.instagram.com/p/DPBKurVD99c/&gt;" loading="lazy" decoding="async" width="1148" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ4IiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*gV6F9gn_uu9pYeVZSlPQbQ.png" /></p>

<p><a href="https://www.instagram.com/p/DPBKurVD99c/" target="_blank">https://www.instagram.com/p/DPBKurVD99c/</a></p>

<ul>
  <li>
    <p><a href="https://book.squareup.com/appointments/3kuc83ozz99u5q/location/G3XVJ4FPKRGCP/services/5TOZDR3PNU7X52TOXELQN575" target="_blank">Official Booking</a></p>
  </li>
  <li>
    <p>There are branches in other areas of Tokyo as well. Check the <a href="https://harinezumi-cafe.com/store/" target="_blank">official website</a>.</p>
  </li>
  <li>
    <p><strong>Booking in advance is recommended; same-day visits may have no availability.</strong></p>
  </li>
  <li>
    <p>If the itinerary changes before the date, cancellation is free.</p>
  </li>
  <li>
    <p>Time: 16:30–17:30</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: ¥3,080 JPY/person for one hour basic fee + ¥880/person on-site for 5 minutes of close interaction with otters.</em></p>
</blockquote>

<p><a href="https://kantenlife.tw/2024/04/18/house-of-otters-japan/#%E6%97%A5%E6%9C%AC%E6%B0%B4%E7%8D%BA%E5%92%96%E5%95%A1%E5%BB%B3%EF%BD%9C%E3%82%AB%E3%83%AF%E3%82%A6%E3%82%BD_%E8%8B%A5%E6%9E%97%E3%81%AE%E5%AE%B6%E3%80%81HARRY_HARAJUKU_terrace%E6%AF%94%E8%BC%83%E5%88%86%E4%BA%AB" target="_blank"><strong>But later research suggests another place, カワウソ_若林の家, is better with longer interaction time.</strong></a></p>

<h3 id="transportation">Transportation</h3>

<h4 id="flights--japan-airlines">Flights — Japan Airlines</h4>

<ul>
  <li>
    <p>Outbound September 13: JL802 Japan Airlines — 10:00 TPE Taoyuan International Airport T2 -&gt; 14:25 NRT Narita Airport T2</p>
  </li>
  <li>
    <p>Return Flight September 17: JL809 Japan Airlines — 18:10 NRT Narita Airport T2 -&gt; 18:10 TPE Taoyuan International Airport T2</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT $12,324 per person</em></p>
</blockquote>

<p><a href="https://www.jal.co.jp/tw/zhtw/inter/baggage/checked/" target="_blank">Additionally, Japan Airlines offers a generous checked baggage allowance for economy class: <strong>2 pieces, 23 kg each</strong></a></p>

<h4 id="jr-shinkansen-tokyo---atami--45-minutes">JR Shinkansen Tokyo &lt;-&gt; Atami (~= 45 minutes)</h4>

<p>I bought the tickets in advance to avoid the risk of no trains available on the day.</p>

<ul>
  <li>
    <p>Sale Time: Tickets for dates +4 weeks ahead can be purchased starting at 11:00 PM Taiwan time</p>
  </li>
  <li>
    <p>You can purchase from the <a href="https://e5489.jr-odekake.net/e5489/ibpc/CBTrainEntryExternalPC?LANG=tc" target="_blank">official website</a> or through <a href="https://www.kkday.com/zh-tw/transportation/japan-rail?cid=19365" target="_blank">KKday</a> / <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fjapan-rail%2F" target="_blank">Klook</a> (compare prices yourself)</p>
  </li>
</ul>

<blockquote>
  <p><em>What we bought:</em></p>
</blockquote>

<blockquote>
  <p><em>- <strong>Tokyo 14:57 -&gt; Atami 15:42</strong> — Kodama 735</em></p>
</blockquote>

<blockquote>
  <p><em>- <strong>Atami 22:02 -&gt; Tokyo 22:48 —</strong> Kodama 752</em></p>
</blockquote>

<blockquote>
  <p><em>Price: NT $1,882 + NT $56 (seat preference fee on booking platform) / person</em></p>
</blockquote>

<h4 id="skyliner-keisei-electric-railway-ticket--41-minutes"><a href="https://www.kkday.com/zh-tw/product/7913-keisei-skyliner-narita-airport-express-ticket?cid=19365" target="_blank">Skyliner Keisei Electric Railway Ticket</a> (~= 41 minutes)</h4>

<p>This is probably the fastest way to get from Narita Airport to central Tokyo (Ueno).</p>

<ul>
  <li>
    <p>No unreserved seating</p>
  </li>
  <li>
    <p>KKday Purchase: <a href="https://www.kkday.com/zh-tw/product/7913-keisei-skyliner-narita-airport-express-ticket?cid=19365" target="_blank">Skyliner Keisei Electric Railway Ticket</a></p>
  </li>
  <li>
    <p>Buying online first is cheaper, then reserving seats at the station offers good flexibility.</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT $912 (round-trip for two tickets)/person</em></p>
</blockquote>

<blockquote>
  <p><em>Note that when you buy a round-trip ticket on KKday, you will receive two separate tickets. Don’t mistake this for an error.</em></p>
</blockquote>

<h4 id="kawagoe-pass-train--bus--30-minutes"><a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Factivity%2F100464-kawagoe-pass-digital-ticket%2F" target="_blank">Kawagoe Pass (Train + Bus)</a> (~= 30 minutes)</h4>

<ul>
  <li>
    <p>Tobu Tojo Line round trip: Ikebukuro Station -&gt; Kawagoe Station or Kawagoe-shi Station</p>
  </li>
  <li>
    <p>Unlimited rides on Tobu buses all day (including Tobu Koedo Loop Bus)</p>
  </li>
  <li>
    <p>Please note: There are Seibu and Tobu buses; this ticket is for Tobu.</p>
  </li>
  <li>
    <p>KLook Purchase: <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Factivity%2F100464-kawagoe-pass-digital-ticket%2F" target="_blank">Kawagoe Discount Pass</a></p>
  </li>
  <li>
    <p>No need to exchange; just redeem online before use and show the QR code to the train or bus driver.</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT $214/person</em></p>
</blockquote>

<h4 id="airport-transfer">Airport Transfer</h4>

<ul>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/airport-transfer?cid=19365" target="_blank">KKday Airport Transfer Booking</a> / <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fairport-transfers%2F" target="_blank">Klook Airport Transfer Booking</a></p>
  </li>
  <li>
    <p>Currently, both K services offer affordable, safe, and fast airport transfers. You can compare prices and choose freely.</p>
  </li>
</ul>

<blockquote>
  <p><em>Taipei and New Taipei -&gt; Taoyuan Airport</em></p>
</blockquote>

<blockquote>
  <p><em>Price: NT $752 per unit</em></p>
</blockquote>

<h4 id="esim">eSIM</h4>

<p>Also purchased the 5G unlimited data eSIM directly on KKday. This time, no special issues or speed throttling were encountered in Tokyo, Atami, or Kawagoe.</p>

<ul>
  <li>KKday Purchase: <a href="https://www.kkday.com/zh-tw/product/121004-unlimited-data-esim-card-japan?cid=19365" target="_blank">Japan eSIM Unlimited Data KDDI / Softbank</a></li>
</ul>

<blockquote>
  <p><em>Price: NT $309 / 5 days / unlimited data</em></p>
</blockquote>

<h4 id="visit-japan">Visit Japan</h4>

<p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E4%BA%AC%E9%98%AA%E7%A5%9E%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E4%BA%AC%E9%83%BD%E5%A4%A7%E9%98%AA%E7%A5%9E%E6%88%B68%E6%97%A5%E9%81%8A%E5%85%A8%E7%B4%80%E9%8C%84%E8%88%87%E5%AF%A6%E7%94%A8%E4%BA%A4%E9%80%9A%E4%BD%8F%E5%AE%BF%E6%8C%87%E5%8D%97-76d66c2e34af/#20241125-%E6%9B%B4%E6%96%B0%E5%85%A5%E5%A2%83%E5%AF%A9%E6%9F%A5%E8%88%87%E6%B5%B7%E9%97%9C%E7%94%B3%E5%A0%B1qr-code-%E5%B7%B2%E5%90%88%E4%BD%B5%E6%88%90%E5%90%8C%E4%B8%80%E5%80%8B%E5%85%A5%E5%A2%83%E5%AE%81%E6%9F%A5%E5%8F%8A%E6%B5%B7%E5%85%B3%E7%94%B3%E6%8A%A5%E7%9A%84qr%E7%A0%81%E6%B2%92%E6%9C%89%E8%97%8D%E7%A2%BC%E9%BB%83%E7%A2%BC%E5%8D%80%E5%88%A5%E4%BB%A5%E4%B8%8B%E5%85%A7%E5%AE%B9%E5%8F%AA%E7%95%B6%E7%B4%80%E9%8C%84%E5%8F%AF%E4%BB%A5%E5%BF%BD%E7%95%A5" target="_blank">Please refer to the previous article and complete the registration in advance. Currently, the QR codes for immigration inspection and customs inspection are combined into one.</a></p>

<h3 id="accommodation">Accommodation</h3>

<p>Because the booking was close to the departure date, after comparing prices and convenience, I chose to stay near Shimbashi Station. (Last time I stayed in Shiodome, but Shimbashi felt more convenient.)</p>

<h4 id="daiwa-roynet-hotel-shimbashi-tokyo-4-nights"><a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fhotels%2Fdetail%2F127656-daiwa-roynet-hotel-shimbashi%2F" target="_blank">Daiwa Roynet Hotel Shimbashi, Tokyo</a> (4 nights)</h4>

<p><img src="/assets/958599363857/1*FPa2l4mYfm_ApuJt_LqSag.webp" alt="Daiwa Roynet Hotel Shimbashi, Tokyo" loading="lazy" decoding="async" width="579" height="539" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzkiIGhlaWdodD0iNTM5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/958599363857/1*FPa2l4mYfm_ApuJt_LqSag.png" /></p>

<p><a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fhotels%2Fdetail%2F127656-daiwa-roynet-hotel-shimbashi%2F" target="_blank">Tokyo Shimbashi Daiwa ROYNET Hotel</a></p>

<ul>
  <li>
    <p><a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fhotels%2Fdetail%2F127656-daiwa-roynet-hotel-shimbashi%2F" target="_blank">Still book directly from Klook to earn points</a></p>
  </li>
  <li>
    <p>About a 2-minute walk from Shimbashi Station, very convenient.</p>
  </li>
  <li>
    <p>Stay for five days and four nights without changing hotels.</p>
  </li>
</ul>

<blockquote>
  <p><em>Price: NT$ 18,404/4 nights/2 persons/standard double room, about NT$ 2,300/person per night.</em></p>
</blockquote>

<blockquote>
  <p>Ready to go! Let’s start!</p>
</blockquote>

<p><a href="https://digital.jal.co.jp/ssci/identification?lang=zh-tw" target="_blank"><strong>You can complete online check-in and seat selection 24 hours before departure, then go straight to baggage drop-off at the airport to speed up the process.</strong></a></p>

<h3 id="day-1-0913-sat--arrive-in-tokyo-shibuya-sky">Day 1 (09/13 Sat) — Arrive in Tokyo, Shibuya Sky</h3>

<h4 id="0650-take-the-airport-shuttle">06:50 Take the airport shuttle</h4>

<h4 id="0720-arrive-at-taoyuan-airport-terminal-2">07:20 Arrive at Taoyuan Airport Terminal 2</h4>

<p><img src="/assets/958599363857/1*TkF8qP3e1-iladiHOZgERQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*TkF8qP3e1-iladiHOZgERQ.jpeg" /></p>

<h4 id="0730-pack-and-check-in-luggage">07:30 Pack and Check-in Luggage</h4>

<p><img src="/assets/958599363857/1*sNzgznqv8RxRYfwyCaIntg.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*sNzgznqv8RxRYfwyCaIntg.png" /></p>

<p>Assigned to counter number 1 at the corner, I had already completed <a href="https://digital.jal.co.jp/ssci/identification?lang=zh-tw" target="_blank">online check-in</a> and went straight to check in my luggage. However, the people ahead took a long time, so it took about 30 minutes to finish the luggage check-in.</p>

<h4 id="-0820-complete-security-check-and-immigration-clearance">~= 08:20 Complete security check and immigration clearance</h4>

<p><img src="/assets/958599363857/1*lKRxUWP4BGQA1q97X3jJJg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*lKRxUWP4BGQA1q97X3jJJg.jpeg" /></p>

<p>There were quite a few people early in the morning. I finished security and immigration around 8:20 and went to the second floor to have Mos Burger for breakfast. The assigned boarding gate was also the farthest one, D1.</p>

<h4 id="terminal-2-departure-duty-free-shop-le-labo">Terminal 2 Departure Duty-Free Shop Le Labo</h4>

<p><img src="/assets/958599363857/1*wyJNusrf7ElLdfZ33YaPWA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*wyJNusrf7ElLdfZ33YaPWA.jpeg" /></p>

<p>Unexpectedly found Le Labo at Terminal 2 departure duty-free shop. The sales clerk mentioned a promotion where new members get a coupon, making the 50ml price NT $5,500, cheaper than buying inside Japan (about NT $5,800).</p>

<p><strong>Latest Price List for 2025 — 50ml:</strong></p>

<ul>
  <li>
    <p>Taiwan Store Price: NT $7,600</p>
  </li>
  <li>
    <p>Taiwan airport store duty-free price: NT $5,500 (promotional price)</p>
  </li>
  <li>
    <p>Tax-refunded price at Japanese stores: approximately NT $5,700</p>
  </li>
  <li>
    <p><a href="https://www.fasola.jp/en/searchByBrand.aspx" target="_blank"><strong>Japan Airport Stores</strong></a> <strong>Duty-free price: about NT $5,200 🤩 — I bought mine at the very end after departing from Narita Airport</strong> Terminal 2.</p>
  </li>
</ul>

<p><img src="/assets/958599363857/1*4AU39FGOzbR7TnCVzCCjKg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*4AU39FGOzbR7TnCVzCCjKg.jpeg" /></p>

<p><img src="/assets/958599363857/1*sgPd1BrkZsr2F37f1m1Dlg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*sgPd1BrkZsr2F37f1m1Dlg.jpeg" /></p>

<p><img src="/assets/958599363857/1*I6HuL5NdPzQxp3lu43fiUw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*I6HuL5NdPzQxp3lu43fiUw.jpeg" /></p>

<p><img src="/assets/958599363857/1*gkCuhDtktYmN6ed170fRZg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*gkCuhDtktYmN6ed170fRZg.jpeg" /></p>

<p>While wandering around the airport waiting for boarding, there are two rows of free massage chairs next to The North Face on D1 (you can get free tokens from nearby stores to use them).</p>

<h4 id="0940-head-to-the-gate-to-prepare-for-boarding">09:40 Head to the gate to prepare for boarding</h4>

<p><img src="/assets/958599363857/1*U5QpF19SbJlVAllxZZiQMA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*U5QpF19SbJlVAllxZZiQMA.jpeg" /></p>

<p><img src="/assets/958599363857/1*izSSFMEAPqbhFtTgt13ndw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*izSSFMEAPqbhFtTgt13ndw.jpeg" /></p>

<p>The plane I took this time was a BOEING 737–800 model, which is a bit outdated.</p>

<blockquote>
  <p><em>But I only found out after returning that the plane offered free one-hour onboard WiFi.</em></p>
</blockquote>

<h4 id="1006-takeoff">10:06 Takeoff</h4>

<p><img src="/assets/958599363857/1*McNWkLo576Z3uJTm9QgYWA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*McNWkLo576Z3uJTm9QgYWA.jpeg" /></p>

<h4 id="japan-airlines-in-flight-meals">JAPAN AIRLINES In-flight Meals</h4>

<p><img src="/assets/958599363857/1*VoM9bEiz7mMma7fAwp48tw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*VoM9bEiz7mMma7fAwp48tw.jpeg" /></p>

<p><img src="/assets/958599363857/1*tu0L7uohZIqsd9V4JlKGFg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*tu0L7uohZIqsd9V4JlKGFg.jpeg" /></p>

<p><img src="/assets/958599363857/1*s69aVqVz7J7wXFSNi66SsQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*s69aVqVz7J7wXFSNi66SsQ.jpeg" /></p>

<p>Although the plane is a bit old, the in-flight meal was really good: hamburger meat sauce with penne pasta, a small bowl of cold soba noodles with tofu skin, and a lemon pudding dessert (delicious!).</p>

<h4 id="1439-landed-at-narita-international-airport-japan-14-minutes-delayed">14:39 Landed at Narita International Airport, Japan (14 minutes delayed)</h4>

<p><img src="/assets/958599363857/1*0Dvy2jcY6ED8uW6NgmxcXw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0Dvy2jcY6ED8uW6NgmxcXw.jpeg" /></p>

<p><img src="/assets/958599363857/1*W_bKd7fHb6kBH3uKPqepPg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*W_bKd7fHb6kBH3uKPqepPg.jpeg" /></p>

<p>The floor was a bit wet, and the weather was cloudy, indicating it had rained earlier; after arrival, we started the Narita Airport scavenger hunt (it takes about 5 minutes to walk from the plane to immigration).</p>

<h4 id="-1505-complete-immigration-procedures--pick-up-luggage-and-exit-the-airport">~= 15:05 Complete immigration procedures + pick up luggage and exit the airport</h4>

<p><img src="/assets/958599363857/1*Xru5o3DBU_GZyTlVW8Wfcw.webp" alt="" loading="lazy" decoding="async" width="937" height="1016" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzciIGhlaWdodD0iMTAxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Xru5o3DBU_GZyTlVW8Wfcw.png" /></p>

<p><img src="/assets/958599363857/1*oD9JhXDuh47h4aBYxepu7Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*oD9JhXDuh47h4aBYxepu7Q.jpeg" /></p>

<p>After exiting the airport, take the escalator down to the “Railway” direction. Find the “SKYLINER &amp; KEISEI INFORMATION CENTER” counter and show the pre-purchased <a href="https://www.kkday.com/zh-tw/product/7913-keisei-skyliner-narita-airport-express-ticket?cid=19365" target="_blank">Skyliner Keisei Railway ticket</a> QR code to the staff to exchange it for the next train ticket.</p>

<blockquote>
  <p><em>Note that there are other railway INFORMATION CENTERS at the station; please make sure to identify the INFORMATION CENTER shown in the picture.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*yvMJw_HeZ9Mk8qXaFj6gKw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*yvMJw_HeZ9Mk8qXaFj6gKw.jpeg" /></p>

<p><img src="/assets/958599363857/1*3rXtVnwanfORgy2DPDrKAA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*3rXtVnwanfORgy2DPDrKAA.jpeg" /></p>

<p>Couldn’t get a seat on the 15:16 train, so I have to take the 15:42 one.</p>

<p>At the station, you can buy the <a href="https://www.jreast.co.jp/zh-CHT/multi/welcomesuica/welcomesuica.html" target="_blank">Welcome Suica (must be used within 28 days)</a> and use the regular Suica card recharge machines. You can top up the amount needed for your trip while waiting for your train. (<a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E4%BA%AC%E9%98%AA%E7%A5%9E%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E4%BA%AC%E9%83%BD%E5%A4%A7%E9%98%AA%E7%A5%9E%E6%88%B68%E6%97%A5%E9%81%8A%E5%85%A8%E7%B4%80%E9%8C%84%E8%88%87%E5%AF%A6%E7%94%A8%E4%BA%A4%E9%80%9A%E4%BD%8F%E5%AE%BF%E6%8C%87%E5%8D%97-76d66c2e34af/#-2" target="_blank">But I personally find using Suica on my iPhone more convenient and easy!</a>)</p>

<p><img src="/assets/958599363857/1*Rx_dIfNqzUwhc21VzUvIkw.webp" alt="" loading="lazy" decoding="async" width="954" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Rx_dIfNqzUwhc21VzUvIkw.png" /></p>

<p>Follow the signs for the “Keisei Line” to enter the station, then find the corresponding platform to wait.</p>

<h4 id="-1542-boarding">~= 15:42 Boarding</h4>

<p><img src="/assets/958599363857/1*gM-xQihcfGhbEXFv8_uxIg.webp" alt="" loading="lazy" decoding="async" width="949" height="1081" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDkiIGhlaWdodD0iMTA4MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*gM-xQihcfGhbEXFv8_uxIg.png" /></p>

<p>Many types of trains stop at this platform, so please make sure to check before boarding; staff will also announce along the way. You can show your ticket for confirmation. <a href="https://www.jreast.co.jp/tc/downloads/pdf/ntrt10_tc.pdf" target="_blank"><strong>Narita Express is fully reserved seating with no non-reserved seats</strong></a> <strong>, do not board without a ticket</strong>.</p>

<p><img src="/assets/958599363857/1*IEQNy3qmUMrm_GaBAFqUTw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*IEQNy3qmUMrm_GaBAFqUTw.jpeg" /></p>

<p>The seats on the Narita Express are spacious enough to fit a 28-inch suitcase. If it’s tight, you can place your luggage in the areas in front of or behind the seats, or on the luggage rack (just be careful when taking it down).</p>

<p><img src="/assets/958599363857/1*Z0UWLv0qvBf77Ue0xfUQ4w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Z0UWLv0qvBf77Ue0xfUQ4w.jpeg" /></p>

<p>The weather is cloudy.</p>

<h4 id="1623-arrive-at-keisei-ueno-station">16:23 Arrive at Keisei Ueno Station</h4>

<p><img src="/assets/958599363857/1*Z8aKd12JgWIzCE7qtBsgiQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Z8aKd12JgWIzCE7qtBsgiQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*NEojh1UeOaVynhrnp_d7bQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*NEojh1UeOaVynhrnp_d7bQ.jpeg" /></p>

<p>After getting off, follow the signs to the underground passage to take the G Ginza Line toward Shibuya, from Ueno-hirokoji to Shimbashi Station.</p>

<h4 id="1650-arrive-at-tokyo-shimbashi-daiwa-roynet-hotel">16:50 Arrive at Tokyo Shimbashi Daiwa ROYNET Hotel</h4>

<p><img src="/assets/958599363857/1*b1FSV2C8ZsLRq_A_PHnI5Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*b1FSV2C8ZsLRq_A_PHnI5Q.jpeg" /></p>

<p><img src="/assets/958599363857/1*09O3hfLEqGKXuUjmv5ALMA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*09O3hfLEqGKXuUjmv5ALMA.jpeg" /></p>

<p>It’s just a short walk from Shimbashi Station, very convenient!</p>

<p>The hotel provides a wide range of free amenities, including face masks, bath additives, earplugs, portable mouthwash, and more.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/jKsZna0d38c" title="東京新橋大和 ROYNET 飯店" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/958599363857/1*JXSKiQeoOOtAlQt946SXiw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*JXSKiQeoOOtAlQt946SXiw.jpeg" /></p>

<p>It’s somewhat like the <a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/"><strong>trip to Hiroshima and Okayama two years ago</strong></a> where we stayed at the “<a href="https://www.youtube.com/watch?v=gmwHRihsyXI" target="_blank">Rifumax Okayama Kurashiki Ekimae Hotel</a>.” The room was very small, feeling like a single room but with a double bed.</p>

<blockquote>
  <p><em>The drawbacks, besides being small, include the room fridge lacking a freezer function.</em></p>
</blockquote>

<p>After putting our things down, we set off for Shibuya.</p>

<h4 id="1830-queue-to-enter-shibuya-sky">18:30 Queue to Enter Shibuya SKY</h4>

<p>On the first floor of Shibuya Scramble Square, find the elevator to go up to the “Shibuya SKY” observation deck. After lining up for the elevator, show your QR code to the ticket inspector to queue for entry. (Ticket will be scanned upon entry)</p>

<p><img src="/assets/958599363857/1*XTalH3d03U4tmBXD-iK5yw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*XTalH3d03U4tmBXD-iK5yw.jpeg" /></p>

<p>You can see the Shibuya Crossing from here.</p>

<blockquote>
  <p><em>After arrival, first use the free lockers to store your belongings. Selfie sticks and similar items are prohibited.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*y5jXwXKVIcG5Df9BxPTHew.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*y5jXwXKVIcG5Df9BxPTHew.jpeg" /></p>

<p><img src="/assets/958599363857/1*DcQwikjwYR6G4JQ9c22cIQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*DcQwikjwYR6G4JQ9c22cIQ.jpeg" /></p>

<blockquote>
  <p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E6%9D%B1%E4%BA%AC%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-5-%E5%A4%A9%E9%A3%9F%E4%BD%8F%E8%A1%8C%E5%85%A8%E8%A8%98%E9%8C%84%E8%88%87%E5%BF%85%E8%A8%AA%E6%99%AF%E9%BB%9E%E6%8E%A8%E8%96%A6-9da2c51fa4f2/#%E6%B8%8B%E8%B0%B7--shibuya-sky" target="*blank"><em>Revisiting Old Places</em></a> <em>, gained quite a bit of weight compared to two years ago.</em></p>
</blockquote>

<blockquote>
  <p><em>The escalator has two sides, one going up and the other going down, both are great for photos.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*cRbCKPoZF8FPxklnYTtZ0A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*cRbCKPoZF8FPxklnYTtZ0A.jpeg" /></p>

<p><img src="/assets/958599363857/1*Bzn4qBq4UfpqSXbcf8Gk-A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Bzn4qBq4UfpqSXbcf8Gk-A.jpeg" /></p>

<p>Rooftop night view, but compared to last time, the weather was worse this time—foggy with a light drizzle.</p>

<p><img src="/assets/958599363857/1*dinKHMOg-9bNoG3Chn5HWA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*dinKHMOg-9bNoG3Chn5HWA.jpeg" /></p>

<p><img src="/assets/958599363857/1*WNjqJ1VyhKnBzWEgV2lGfg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*WNjqJ1VyhKnBzWEgV2lGfg.jpeg" /></p>

<p>After walking all the way down, passing by the bar, the view here is also nice:</p>

<p><img src="/assets/958599363857/1*gsgDlAcVwHQ-pG49di-PIQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*gsgDlAcVwHQ-pG49di-PIQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*Hfioo92OP-JnADRbPH4oMA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Hfioo92OP-JnADRbPH4oMA.jpeg" /></p>

<h4 id="1945-return-to-shibuya-crossing">19:45 Return to Shibuya Crossing</h4>

<p><img src="/assets/958599363857/1*kLQ5R8LmFXiOduVRoXC-Jw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*kLQ5R8LmFXiOduVRoXC-Jw.jpeg" /></p>

<p><img src="/assets/958599363857/1*OOV8P-Wd_qcLWKvUcPdj_A.webp" alt="" loading="lazy" decoding="async" width="1200" height="854" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*OOV8P-Wd_qcLWKvUcPdj_A.png" /></p>

<p>The third season of “Alice in Borderland” is about to premiere, so let’s revisit the scenes; it feels like there are even more people than before.</p>

<p><img src="/assets/958599363857/1*nSlNoHtXjCcmZwLJdhK5Wg.webp" alt="" loading="lazy" decoding="async" width="955" height="1222" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTUiIGhlaWdodD0iMTIyMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*nSlNoHtXjCcmZwLJdhK5Wg.png" /></p>

<p>Cross the street and walk straight to Shibuya Parco to eat at <a href="https://maps.app.goo.gl/B11rcg3MLFRgJLqT8" target="_blank">極味屋</a>.</p>

<blockquote>
  <p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E6%9D%B1%E4%BA%AC%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-5-%E5%A4%A9%E9%A3%9F%E4%BD%8F%E8%A1%8C%E5%85%A8%E8%A8%98%E9%8C%84%E8%88%87%E5%BF%85%E8%A8%AA%E6%99%AF%E9%BB%9E%E6%8E%A8%E8%96%A6-9da2c51fa4f2/#%E6%B8%8B%E8%B0%B7-parco--%E6%A5%B5%E5%91%B3%E5%B1%8B" target="_blank"><em>Tried it once last time, still unforgettable.</em></a></p>
</blockquote>

<h4 id="2000-arrive-at-shibuya-parco">20:00 Arrive at Shibuya Parco</h4>

<p><img src="/assets/958599363857/1*5UmVsCAy5wcJHCK9h8xQvg.webp" alt="" loading="lazy" decoding="async" width="1400" height="984" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*5UmVsCAy5wcJHCK9h8xQvg.png" /></p>

<p>Unfortunately, <a href="https://maps.app.goo.gl/B11rcg3MLFRgJLqT8" target="_blank">Gokumiya</a> was sold out (will come back in a few days), so I tried another kushikatsu shop in the same food street.</p>

<h4 id="2015-kushikatsu-arata-shibuya-parco">20:15 <a href="https://maps.app.goo.gl/PTZVZEqdZayWUiap9" target="_blank">Kushikatsu Arata Shibuya Parco</a></h4>

<p><img src="/assets/958599363857/1*cbSR83bqt56GgvT8gTYRQw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*cbSR83bqt56GgvT8gTYRQw.jpeg" /></p>

<p>Ordered several signature fried skewers, double meat skewers, a bowl of beef brisket rice, and two drinks, totaling ¥6,340 JPY.</p>

<blockquote>
  <p><em>The Japanese-style crispy fried skewers are delicious and not greasy, and the beef brisket rice is flavorful and filling.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Note that fried skewer shops often charge an additional basic cabbage fee (お通し)</em></strong> <em>, I forgot the exact amount, but it should be around</em> ¥ <em>300 per person.</em></p>
</blockquote>

<h4 id="2130-c-pla-shibuya">21:30 <a href="https://maps.app.goo.gl/bTwvHKkbxHofxnig6" target="_blank">C-pla Shibuya</a></h4>

<p><img src="/assets/958599363857/1*G0pG6izsdcwUT_Pnfr7xYQ.webp" alt="" loading="lazy" decoding="async" width="950" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*G0pG6izsdcwUT_Pnfr7xYQ.png" /></p>

<p><img src="/assets/958599363857/1*a8oZSBgBI3ZIOftsOxSH5A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*a8oZSBgBI3ZIOftsOxSH5A.jpeg" /></p>

<p>On the way back to the station after eating, I passed by a large C-pla capsule toy store and went in to explore.</p>

<blockquote>
  <p><em>Didn’t find what I wanted to twist, retreat!</em></p>
</blockquote>

<h4 id="good-night-tokyo">Good night! Tokyo</h4>

<p><img src="/assets/958599363857/1*WwT578CuW9uDdF6FEoJogA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*WwT578CuW9uDdF6FEoJogA.jpeg" /></p>

<p>Bought a late-night snack and returned to the hotel to rest.</p>

<h3 id="day-2-0914--kawagoe-day-trip-shopping-in-ikebukuro">Day 2 (09/14) — Kawagoe Day Trip, Shopping in Ikebukuro</h3>

<h4 id="0930-head-out-and-have-mcdonalds-breakfast-first--mcdonalds-新橋日比谷口店">09:30 Head out and have McDonald’s breakfast first — <a href="https://maps.app.goo.gl/7kZzxwNhtNjBKdZC9" target="_blank">McDonald’s 新橋日比谷口店</a></h4>

<p><img src="/assets/958599363857/1*ylLKnhvJGSh1inGDz4QPXw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ylLKnhvJGSh1inGDz4QPXw.jpeg" /></p>

<p><img src="/assets/958599363857/1*PBCIcEtyaPLO7bcKEr_X9A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*PBCIcEtyaPLO7bcKEr_X9A.jpeg" /></p>

<p>The same pancake ham, cheese, and egg burger!</p>

<blockquote>
  <p><em>I visited this McDonald’s <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">last time as well</a>. The drawbacks are that it is quite old, not very clean, and lacks self-order kiosks.</em></p>
</blockquote>

<h4 id="1050-arrive-at-ikebukuro-station-take-yamanote-line">10:50 Arrive at Ikebukuro Station (Take Yamanote Line)</h4>

<p><img src="/assets/958599363857/1*1V90DtPc7kU51PnAhjgL8A.webp" alt="" loading="lazy" decoding="async" width="1200" height="663" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY2MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*1V90DtPc7kU51PnAhjgL8A.png" /></p>

<p>Transfer to Tobu Tojo Line to Kawagoe.</p>

<p><strong>We purchased the <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Factivity%2F100464-kawagoe-pass-digital-ticket%2F" target="_blank">Kawagoe Pass (Train + Bus)</a> online in advance and activated it online to start the timer:</strong></p>

<p><img src="/assets/958599363857/1*-RIG2ZudPZbbPykqbqCb0g.webp" alt="" loading="lazy" decoding="async" width="935" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*-RIG2ZudPZbbPykqbqCb0g.png" /></p>

<p><img src="/assets/958599363857/1*hD_LqigAsv0lTWLS4uI1uA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*hD_LqigAsv0lTWLS4uI1uA.jpeg" /></p>

<p>Go directly to the station office at the side and show the QR code to the staff to enter and exit the station. (No seat reservation needed)</p>

<h4 id="1100-take-tobu-tojo-line-kawagoe-limited-express--to-ogawamachi-saitama-prefecture--via-kawagoe">11:00 Take Tobu Tojo Line Kawagoe Limited Express — to Ogawamachi (Saitama Prefecture) — via Kawagoe</h4>

<p><img src="/assets/958599363857/1*3oFkNAprNiko_GHcF4r-3A.webp" alt="" loading="lazy" decoding="async" width="1400" height="976" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*3oFkNAprNiko_GHcF4r-3A.png" /></p>

<h4 id="1126-arrive-at-kawagoe-station">11:26 Arrive at Kawagoe Station</h4>

<p><img src="/assets/958599363857/1*TxaLPPDw4GDqEEWfRnOx7Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="850" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*TxaLPPDw4GDqEEWfRnOx7Q.png" /></p>

<p><img src="/assets/958599363857/1*fnZcZ9Ct4iNfcWq9Kog7jw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*fnZcZ9Ct4iNfcWq9Kog7jw.jpeg" /></p>

<p><img src="/assets/958599363857/1*pxQYLZnHM7-UA6B7RisQnw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*pxQYLZnHM7-UA6B7RisQnw.jpeg" /></p>

<p>After exiting from the station office at the far side, I first picked up a tourist brochure and decided to take the bus to the farthest spot, <a href="https://maps.app.goo.gl/3vVRnikJQL3zYuGi6" target="_blank">Hikawa Shrine</a>, then walk back while sightseeing.</p>

<p><img src="/assets/958599363857/1*h0c9y56GEfHWTrtP7q8kOQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="848" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*h0c9y56GEfHWTrtP7q8kOQ.png" /></p>

<p>Exit from the second-floor skybridge and go downstairs to the bus boarding area.</p>

<h4 id="1135-waiting-for-the-bus-at-kawagoe-bus-station">11:35 Waiting for the bus at Kawagoe Bus Station</h4>

<p><img src="/assets/958599363857/1*Z92lcpTR1OitKAL3LRVrtw.webp" alt="" loading="lazy" decoding="async" width="1200" height="853" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Z92lcpTR1OitKAL3LRVrtw.png" /></p>

<p>Both Platform 1 and Platform 3 have many trains going to Hikawa Shrine. In the end, I took the Me 01 from Platform 3.</p>

<blockquote>
  <p><strong><em>As long as you recognize “東武</em> バス <em>”, use the <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Factivity%2F100464-kawagoe-pass-digital-ticket%2F" target="_blank">Kawagoe Pass (Train + Bus)</a> without any special exchange; just show the QR code to the driver when getting off.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Do not tap your Suica card when boarding if you are using the tour pass.</em></strong></p>
</blockquote>

<h4 id="1155-arrive-at-hikawa-shrine">11:55 Arrive at Hikawa Shrine</h4>

<p><img src="/assets/958599363857/1*ebEe1SnjEeYVieePNBdmuw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ebEe1SnjEeYVieePNBdmuw.jpeg" /></p>

<h4 id="hikawa-shrine">Hikawa Shrine</h4>

<p><img src="/assets/958599363857/1*ouWflkODbhhc7t8gRUwhwA.webp" alt="" loading="lazy" decoding="async" width="1200" height="865" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ouWflkODbhhc7t8gRUwhwA.png" /></p>

<p><img src="/assets/958599363857/1*9W_Haty-iabNIwCd3m7Hiw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*9W_Haty-iabNIwCd3m7Hiw.jpeg" /></p>

<p>The shrine is small and has many pinwheels and wind chimes.</p>

<p><strong>Human Figure Style for Removing Negative Energy:</strong></p>

<p><img src="/assets/958599363857/1*ZzDJul6maBw4Km4g3ixHeQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ZzDJul6maBw4Km4g3ixHeQ.jpeg" /></p>

<p><strong>Wind Chime Blessing and Ema Area</strong></p>

<p><img src="/assets/958599363857/1*Xj_rvn15hT9p2t9Xte8Q7w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Xj_rvn15hT9p2t9Xte8Q7w.jpeg" /></p>

<p><img src="/assets/958599363857/1*ZnfYoNsMDNVNt1-pDg-jaQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*ZnfYoNsMDNVNt1-pDg-jaQ.jpeg" /></p>

<h4 id="sea-bream-omikuji">Sea Bream Omikuji</h4>

<p><img src="/assets/958599363857/1*QJZOlovCi13dIiAQuwobDw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*QJZOlovCi13dIiAQuwobDw.jpeg" /></p>

<p><img src="/assets/958599363857/1*0R_KFD3lJnrkfxQTxXt82w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0R_KFD3lJnrkfxQTxXt82w.jpeg" /></p>

<p><img src="/assets/958599363857/1*U6w5eGph85nVYP5rAAR76w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*U6w5eGph85nVYP5rAAR76w.jpeg" /></p>

<p>There are several fish ponds where you can catch sea bream for omikuji XD I caught a red one for a year of good luck.</p>

<p><img src="/assets/958599363857/1*FHiQfCIBVx-EY93PQKur-Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*FHiQfCIBVx-EY93PQKur-Q.jpeg" /></p>

<p><img src="/assets/958599363857/1*rKug8yofs-uBooqelaIEMQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*rKug8yofs-uBooqelaIEMQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*wA2-rSUSEQ-5ad0L2A2RCw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*wA2-rSUSEQ-5ad0L2A2RCw.jpeg" /></p>

<p>Great luck!</p>

<p><img src="/assets/958599363857/1*wPAUnu2NWojn3xDCq2Bb4Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="995" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*wPAUnu2NWojn3xDCq2Bb4Q.png" /></p>

<p><img src="/assets/958599363857/1*W0t4XZQ14zTYCb6k-84daA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*W0t4XZQ14zTYCb6k-84daA.jpeg" /></p>

<p>If you don’t have enough coins, you can exchange money with the shrine staff. Volunteers nearby can help translate the fortune slips into English for free. If you get a bad fortune, just tie it to the shrine to ward off bad luck.</p>

<h4 id="1245-leave-hikawa-shrine">12:45 Leave Hikawa Shrine</h4>

<p>After taking some photos and walking around, we left Hikawa Shrine at about 12:45, ready to take a bus to Koedo Street and Jiikawa MOGUMOGU Honpo Kawagoe store.</p>

<h4 id="seibu--koedo-loop-bus">Seibu — Koedo Loop Bus</h4>

<p><img src="/assets/958599363857/1*lbJXQmrjFElNnSoh_Rtc2g.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*lbJXQmrjFElNnSoh_Rtc2g.jpeg" /></p>

<p>While waiting for the bus, I saw a cool sightseeing bus with music — it was the <a href="https://eaglebus.group/co-edo/zh/" target="_blank">Seibu Transport Koedo Loop Bus</a>; <strong>Tobu passes cannot be used</strong>. You can pay with a transit card or buy the “ <a href="https://eaglebus.group/co-edo/2025/06/18/ryde0624/" target="_blank">Ryde Pass</a>” issued by Seibu Railway.</p>

<h4 id="1320-chiikawa-mogumogu-hompo-kawagoe-吉伊卡哇-mogumogu-本舗-川越店">13:20 <a href="https://maps.app.goo.gl/ccQP8s28jfD9iVne6" target="_blank">Chiikawa Mogumogu Hompo Kawagoe 吉伊卡哇 MOGUMOGU 本舗 川越店</a></h4>

<p><img src="/assets/958599363857/1*cRVbxgypDnyUhkdduGQrGQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="993" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*cRVbxgypDnyUhkdduGQrGQ.png" /></p>

<p><img src="/assets/958599363857/1*s1n-pLkMRKHB9LSTegvrsw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*s1n-pLkMRKHB9LSTegvrsw.jpeg" /></p>

<p>At the entrance, there are many cute babies and a sweet potato series exclusive to Kawagoe.</p>

<p><img src="/assets/958599363857/1*f1PB498M6RshUWre7oxG9w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1935" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE5MzUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*f1PB498M6RshUWre7oxG9w.jpeg" /></p>

<p>Although there is still full-day capacity control, the crowds are not as large as when it first opened. We arrived on-site and scanned the QR code to book the next available time slot at 13:40 (there were still spots). If you want to avoid missing out, you can book online in advance.</p>

<blockquote>
  <p><em>GIICAWA MOGUMOGU Honpo Kawagoe Store reservation and advance ticket lottery <a href="https://reurl.cc/ekrR5x" target="_blank">Ticket URL (Click Here)</a>.</em></p>
</blockquote>

<blockquote>
  <p><em>Please note: One phone and one account equals one person. You can only make one reservation per day and per time slot.</em></p>
</blockquote>

<p><strong>13:40 Arrive at the store entrance on time to line up for entry</strong></p>

<p><img src="/assets/958599363857/1*NtS_w3kiOP3Z_zdVg45duQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*NtS_w3kiOP3Z_zdVg45duQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*vEMmOWjXOAfNvjloEDclgA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*vEMmOWjXOAfNvjloEDclgA.jpeg" /></p>

<p>The stock of products is abundant, but some items have a purchase limit per person. You can pay extra to get a capsule toy.</p>

<p><strong>My Souvenirs:</strong></p>

<p><img src="/assets/958599363857/1*E4oKILRhstaUJTIRX-912Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*E4oKILRhstaUJTIRX-912Q.jpeg" /></p>

<p><img src="/assets/958599363857/1*Ctanxv2irMlgQ0HSQnwMNQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Ctanxv2irMlgQ0HSQnwMNQ.jpeg" /></p>

<blockquote>
  <p><em>Sweet Potato Bunny has floppy ears and is super cute!</em></p>
</blockquote>

<blockquote>
  <p><em>Product list and prices can be found on the <a href="https://chiikawamogumogu.shop/collections/nuigurumimasukoxtuto" target="_blank">official website</a>.</em></p>
</blockquote>

<blockquote>
  <p><em>Big Sweet Potato Bunny: ¥2,970 JPY / Small Sweet Potato Jiikawa: ¥1,870 JPY</em></p>
</blockquote>

<p><strong>There are also Miffy and Snoopy specialty stores</strong></p>

<p><img src="/assets/958599363857/1*8_7sCUHDuYn4_vMIl54Rcg.webp" alt="" loading="lazy" decoding="async" width="1200" height="846" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*8_7sCUHDuYn4_vMIl54Rcg.png" /></p>

<p><img src="/assets/958599363857/1*O9rii8mVXEZKpzy1wGqsuA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*O9rii8mVXEZKpzy1wGqsuA.jpeg" /></p>

<h4 id="bought-kawagoe-rice-crackers-and-brown-sugar-sweet-potato-sticks-at-the-roadside-souvenir-shop">Bought Kawagoe rice crackers and brown sugar sweet potato sticks at the roadside souvenir shop</h4>

<p><img src="/assets/958599363857/1*-l022L-y7yY7hAeZpzwOVg.webp" alt="" loading="lazy" decoding="async" width="1200" height="535" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUzNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*-l022L-y7yY7hAeZpzwOVg.png" /></p>

<blockquote>
  <p><em>The seafood is delicious; the original flavor is quite firm. The brown sugar sweet potato sticks are like Taiwan’s brown sugar sweet potato chips but in stick form.</em></p>
</blockquote>

<p>After shopping, it was almost 14:00, and I was really hungry and tired.</p>

<h4 id="former-85th-bank-main-building"><a href="https://maps.app.goo.gl/y9z4S4ApsRjsREY37" target="_blank">Former 85th Bank Main Building</a></h4>

<p><img src="/assets/958599363857/1*BpgkEdsDYYvqfpgVuZBKgQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*BpgkEdsDYYvqfpgVuZBKgQ.jpeg" /></p>

<p>Went to a second-floor café in a historic building to grab a quick bite before heading out.</p>

<p><img src="/assets/958599363857/1*QcyUQuLZg3kb-TuYqJzERg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1000" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*QcyUQuLZg3kb-TuYqJzERg.png" /></p>

<blockquote>
  <p><em>If I weren’t so hungry and it wasn’t so late, I would have wanted to queue for soba noodles.</em></p>
</blockquote>

<p>After eating, walk out to the terrace to take photos:</p>

<p><img src="/assets/958599363857/1*ndOuiIyVVmYi3e5gCyr2Fw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ndOuiIyVVmYi3e5gCyr2Fw.jpeg" /></p>

<p><strong>Here you can get a bird’s-eye view of Ichibangai shopping street, very beautiful:</strong></p>

<p><img src="/assets/958599363857/1*4Q0Q_xZ45C5_xCsYbZp-Wg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*4Q0Q_xZ45C5_xCsYbZp-Wg.jpeg" /></p>

<blockquote>
  <p><em>The terrace is a public area and can be accessed without any purchase.</em></p>
</blockquote>

<h4 id="1530-toki-no-kane-time-bell-tower">15:30 <a href="https://maps.app.goo.gl/5zsx2vLHKZvrKmp3A" target="_blank">Toki no Kane (Time Bell Tower)</a></h4>

<p>Continue strolling along the Koedo shopping street to Toki no Kane (Time Bell Tower).</p>

<p><img src="/assets/958599363857/1*gqfXKCkDR8r32kbh52lX3w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*gqfXKCkDR8r32kbh52lX3w.jpeg" /></p>

<p><img src="/assets/958599363857/1*uPy0lTzewXYdF-GwaOSgrw.webp" alt="" loading="lazy" decoding="async" width="959" height="1225" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTkiIGhlaWdodD0iMTIyNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*uPy0lTzewXYdF-GwaOSgrw.png" /></p>

<p>Bought takoyaki rice crackers from a nearby stall as a snack.</p>

<h4 id="taisho-romantic-dream-street-shopping-street"><a href="https://maps.app.goo.gl/nyidMKyz3L48WvNA7" target="_blank">Taisho Romantic Dream Street</a> (Shopping Street)</h4>

<p><img src="/assets/958599363857/1*SVz_ZtnmAp_L_yK-DFRJSQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*SVz_ZtnmAp_L_yK-DFRJSQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*gfbr5A94--q0WEdldobPsg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*gfbr5A94--q0WEdldobPsg.jpeg" /></p>

<p>Finally, we passed through Taisho Romantic Street and took a bus back to Kawagoe Station, preparing to return to Ikebukuro.</p>

<h4 id="1625-return-to-kawagoe-station-take-the-tobu-tojo-line-back-to-ikebukuro">16:25 Return to Kawagoe Station, take the Tobu Tojo Line back to Ikebukuro</h4>

<p><img src="/assets/958599363857/1*geGRZVP6HHBY9TKrjrUSqQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*geGRZVP6HHBY9TKrjrUSqQ.png" /></p>

<h4 id="1710-ikebukuro-station-parco">17:10 Ikebukuro Station Parco</h4>

<p><img src="/assets/958599363857/1*w50-WxlZgKpVvH5aIQR7BQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="844" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*w50-WxlZgKpVvH5aIQR7BQ.png" /></p>

<p>After returning to Ikebukuro, first browse the department stores at Ikebukuro Station. There are Parco and Tobu Department Store, with many things to explore.</p>

<h4 id="the-guest-cafe--diner-ikebukuro--chikawa-ramen-pork"><a href="https://maps.app.goo.gl/tGxXYhytPWoMXzY6A" target="_blank">THE GUEST cafe &amp; diner Ikebukuro</a> 、 <a href="https://maps.app.goo.gl/Xjj1pvnPpyyvUGH48" target="_blank">Chikawa Ramen Pork</a></h4>

<p><img src="/assets/958599363857/1*DrWhK8JSk5XfcqqefpV_jA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*DrWhK8JSk5XfcqqefpV_jA.jpeg" /></p>

<p><img src="/assets/958599363857/1*cbhgmbw3iv1dQ6rB6c0VPA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*cbhgmbw3iv1dQ6rB6c0VPA.jpeg" /></p>

<p>Upstairs, there are two Jiikawa restaurants. The café was fully booked when we passed by, but the ramen shop allowed entry with on-site queueing.</p>

<p><strong>Next to it is a resale shop where you can buy restaurant series Gii Kawa:</strong></p>

<p><img src="/assets/958599363857/1*AsMwLutWXcmRiXED4rVSuA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*AsMwLutWXcmRiXED4rVSuA.jpeg" /></p>

<p><img src="/assets/958599363857/1*cOPdUeltj43X_CKspzfxCg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*cOPdUeltj43X_CKspzfxCg.jpeg" /></p>

<h4 id="1745-gyutan-yaki-sendai-henmi-ikebukuro-parco">17:45 <a href="https://maps.app.goo.gl/PEMEHM7uMc8K5i6c8" target="_blank">Gyutan Yaki Sendai Henmi Ikebukuro PARCO</a></h4>

<p><img src="/assets/958599363857/1*AInBZ1mRnIZ3T2US8N7r2Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*AInBZ1mRnIZ3T2US8N7r2Q.jpeg" /></p>

<p>As dinner time approached, I decided to eat at the department store first and ended up choosing beef tongue at Parco.</p>

<blockquote>
  <p><a href="https://www.n-rs.co.jp/brand/menu/en/henmi_menu_1565.pdf" target="_blank"><em>Online Menu: Click Here</em></a></p>
</blockquote>

<h4 id="1800-draw-lots-for-the-next-days-chiikawa-tokyo-station-tickets-want-to-see-the-baby-series">18:00 Draw lots for the next day’s Chiikawa TOKYO Station tickets (want to see the baby series)</h4>

<blockquote>
  <p><a href="https://chiikawa-info.jp/chiikawaland/tokyo/" target="_blank"><em>Ticket reservation website: Click here</em></a></p>
</blockquote>

<p><img src="/assets/958599363857/1*tEZULMHIAdtguLKCJzaZDw.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*tEZULMHIAdtguLKCJzaZDw.jpeg" /></p>

<p>Loading converted… Didn’t get it QQ, very popular.</p>

<p><strong>Start eating after drawing:</strong></p>

<p><img src="/assets/958599363857/1*NKh1itXkvzs63EAiPtCZ6Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*NKh1itXkvzs63EAiPtCZ6Q.jpeg" /></p>

<blockquote>
  <p><em>I think I have <a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E5%B1%B1%E9%99%B0%E9%97%9C%E8%A5%BF%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E5%B3%B6%E6%A0%B9%E5%87%BA%E9%9B%B2%E6%9D%BE%E6%B1%9F%E9%B3%A5%E5%8F%96%E5%A7%AC%E8%B7%AF%E5%A4%A7%E9%98%AA%E7%A5%9E%E6%88%B67%E6%97%A5%E7%8D%A8%E6%97%85%E5%85%A8%E8%A8%98%E9%8C%84-aacd5f5cacd1/#%E8%A6%93%E9%A3%9F" target="_blank">taken out and eaten at this place before</a> before. The beef tongue has great texture and flavor, and the soup tastes like rich beef broth and is very delicious!</em></p>
</blockquote>

<h4 id="1900-shopping-in-ikebukuro">19:00 Shopping in Ikebukuro</h4>

<p>After eating, continue shopping.</p>

<p><img src="/assets/958599363857/1*VJ9rPJWTSgULeGzvtPEMFg.webp" alt="" loading="lazy" decoding="async" width="939" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzkiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*VJ9rPJWTSgULeGzvtPEMFg.png" /></p>

<p><img src="/assets/958599363857/1*BeovdjRzqdZdRxU1086meg.webp" alt="" loading="lazy" decoding="async" width="942" height="1273" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDIiIGhlaWdodD0iMTI3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*BeovdjRzqdZdRxU1086meg.png" /></p>

<p><img src="/assets/958599363857/1*XAtHfdnJdmXLQGFJxxyHaA.webp" alt="" loading="lazy" decoding="async" width="929" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjkiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*XAtHfdnJdmXLQGFJxxyHaA.png" /></p>

<p>Ikebukuro is also very lively, with many people and numerous shops.</p>

<h4 id="chiikawa-park"><a href="https://maps.app.goo.gl/H9nivHDc5i8ySG18A" target="_blank">Chiikawa Park</a></h4>

<p><img src="/assets/958599363857/1*15SBRn2tB_oNRXAztKzWdQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*15SBRn2tB_oNRXAztKzWdQ.jpeg" /></p>

<p>Chiikawa Park is also here, but it was closed since it was late.</p>

<h4 id="1915-sunshine-city--gashapon-ikebukuro-main-store--the-worlds-largest-gashapon-shop-3000-machines">19:15 <a href="https://maps.app.goo.gl/sMrThaudF2VwjBGP8" target="_blank">Sunshine City</a> — <a href="https://maps.app.goo.gl/6JgukBwhaJmmoAFQ7" target="_blank">Gashapon Ikebukuro Main Store — The World’s Largest Gashapon Shop (3000+ machines)</a></h4>

<p><img src="/assets/958599363857/1*HFUXnWbMb_i-gSmgTzNdZQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1006" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*HFUXnWbMb_i-gSmgTzNdZQ.png" /></p>

<p>Walked all the way to the world’s largest capsule toy store to hunt for treasures.</p>

<blockquote>
  <p><em>In the end, I didn’t play much. Although there were many machines here, nothing special caught my eye, and the categories were quite messy, making it overwhelming to browse.</em></p>
</blockquote>

<h4 id="2010-return-to-ikebukuro-station--return-to-hotel-to-rest">20:10 Return to Ikebukuro Station — Return to hotel to rest</h4>

<p><img src="/assets/958599363857/1*N6AmcHvBHP0LeQswh9iXSQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*N6AmcHvBHP0LeQswh9iXSQ.jpeg" /></p>

<p><strong>Tonight’s Late-Night Snack:</strong></p>

<p><img src="/assets/958599363857/1*pjTtrIIs2qmtQmlun7-P1A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*pjTtrIIs2qmtQmlun7-P1A.jpeg" /></p>

<p>Classic tofu skin instant noodles + convenience store fried chicken + alcohol.</p>

<h3 id="day-3-0915-mon--tokyo-station-ichibangai-atami-fireworks-festival">Day 3 (09/15 Mon) — Tokyo Station Ichibangai, Atami Fireworks Festival</h3>

<p>Planning to take the 14:57 Shinkansen to Atami.</p>

<p><strong>Highlight of this day — Atami Fireworks Festival</strong>; slept in and headed out late in the morning.</p>

<h4 id="1110-tokyo-station">11:10 Tokyo Station</h4>

<p><img src="/assets/958599363857/1*pohebZUwh5jI-z7iyBsUfQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1003" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*pohebZUwh5jI-z7iyBsUfQ.png" /></p>

<p>Approaching noon, we first looked for food inside the station.</p>

<p><img src="/assets/958599363857/1*lUVuLvW3e19wluc4X3LupA.webp" alt="" loading="lazy" decoding="async" width="1200" height="865" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*lUVuLvW3e19wluc4X3LupA.png" /></p>

<p><strong>There is also a <a href="https://maps.app.goo.gl/vkmPHf4j6SgRGprb8" target="_blank">Gokumiya</a> at Tokyo Station</strong>, but it gets very crowded. It opens at 11:00, and many people are already lining up. The wait seems to be about an hour.</p>

<p>Turn around and go eat Kobe beef across the street.</p>

<h4 id="kobebeef-daia-tokyo-station-store--神戸牛ダイア-東京駅店"><a href="https://maps.app.goo.gl/K1grYFDpH52WxMkdA" target="_blank">Kobebeef Daia Tokyo Station Store — 神戸牛ダイア 東京駅店</a></h4>

<p><img src="/assets/958599363857/1*67WiU_TZIuxie7OBx31Z6w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*67WiU_TZIuxie7OBx31Z6w.jpeg" /></p>

<p><img src="/assets/958599363857/1*ybIjFMLqTTSnvUne_N2qwA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ybIjFMLqTTSnvUne_N2qwA.jpeg" /></p>

<blockquote>
  <p><em>A nostalgic great taste, <a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E5%B1%B1%E9%99%B0%E9%97%9C%E8%A5%BF%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-%E5%B3%B6%E6%A0%B9%E5%87%BA%E9%9B%B2%E6%9D%BE%E6%B1%9F%E9%B3%A5%E5%8F%96%E5%A7%AC%E8%B7%AF%E5%A4%A7%E9%98%AA%E7%A5%9E%E6%88%B67%E6%97%A5%E7%8D%A8%E6%97%85%E5%85%A8%E8%A8%98%E9%8C%84-aacd5f5cacd1/#1700-%E7%A5%9E%E6%88%B8%E7%89%9B-%E5%90%89%E7%A5%A5%E5%90%89" target="_blank">last time in Kobe I specially went to eat Kobe beef</a> (Kissho Kichi), the price in Tokyo is about ¥8,000 more expensive than in Kobe (Prime Kobe beef sirloin 220g Tokyo ¥39,160 / Kobe ¥31,600).</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*d8dRk0mNMIBZ2WRlRvxdpQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*d8dRk0mNMIBZ2WRlRvxdpQ.jpeg" /></p>

<p>Just like the taste I remembered, the meat is tender with no off-flavors, only the natural aroma of beef; before I knew it, it was all gone!</p>

<h4 id="1220-start-exploring-tokyo-station-underground-first-avenue">12:20 Start exploring Tokyo Station Underground First Avenue</h4>

<p><img src="/assets/958599363857/1*L0hqu-EQj9j98YtOCGqfWw.webp" alt="" loading="lazy" decoding="async" width="931" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*L0hqu-EQj9j98YtOCGqfWw.png" /></p>

<p><img src="/assets/958599363857/1*8RfVcL7vNuaLnQ8RTqx77Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="995" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*8RfVcL7vNuaLnQ8RTqx77Q.png" /></p>

<p>The underground mall is crowded, featuring almost every IP you can think of: Sanrio, One Piece, Jump Store, Rilakkuma, Acorn Republic, Snoopy, Chiikawa, and more.</p>

<h4 id="chiikawa-baby-tokyo-station"><a href="https://maps.app.goo.gl/ujTHi4i1YM2Jhq7u6" target="_blank">Chiikawa baby Tokyo station</a></h4>

<p><img src="/assets/958599363857/1*5FO6T-tcqFfmD6qWrHfhTw.webp" alt="" loading="lazy" decoding="async" width="954" height="1201" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTQiIGhlaWdodD0iMTIwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*5FO6T-tcqFfmD6qWrHfhTw.png" /></p>

<p><img src="/assets/958599363857/1*c2ParIuNtMQ0WReypFW_AQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*c2ParIuNtMQ0WReypFW_AQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*14U3tJHvWtVDyF7A2XM3Gg.webp" alt="" loading="lazy" decoding="async" width="1200" height="853" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*14U3tJHvWtVDyF7A2XM3Gg.png" /></p>

<p>Didn’t get a <a href="https://chiikawa-info.jp/chiikawaland/tokyo/" target="_blank">ticket</a>, so I just passed by to take some photos. The stock looked ample and there were few people (feels like they didn’t control the crowd enough?).</p>

<p><img src="/assets/958599363857/1*RhUU4XWAV_mo818lqez6qw.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*RhUU4XWAV_mo818lqez6qw.png" /></p>

<p>If you haven’t had enough shopping, you can also visit the adjacent Daimaru Department Store or continue walking to other nearby underground shopping streets.</p>

<h4 id="1400-tax-refund-and-luggage-storage-at-tokyo-station">14:00 Tax refund and luggage storage at Tokyo Station</h4>

<p><img src="/assets/958599363857/1*V1V-6jaK-XgaIJtFUqaUTQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="857" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*V1V-6jaK-XgaIJtFUqaUTQ.png" /></p>

<p>For tax refunds on shopping at Tokyo Station, you can go directly back to the first floor and find the tax refund counter opposite the Tokaido Sanyo Shinkansen Nihonbashi Exit.</p>

<p><img src="/assets/958599363857/1*Y0o7PYsSkdhHB0F-pdG6jQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Y0o7PYsSkdhHB0F-pdG6jQ.jpeg" /></p>

<p>Bought some items. Since the locker counter closes at 8:00 PM, I looked for lockers to store my belongings. There are many lockers available here.</p>

<blockquote>
  <p><em>After storing our luggage, we went to the food court to buy some snacks to eat on the way.</em></p>
</blockquote>

<h4 id="1430-prepare-to-take-the-train-to-atami">14:30 Prepare to take the train to Atami</h4>

<p><img src="/assets/958599363857/1*PgBfGRbUfgWsyh1i24C8Mw.webp" alt="" loading="lazy" decoding="async" width="947" height="1057" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTA1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*PgBfGRbUfgWsyh1i24C8Mw.png" /></p>

<p>Also entering through the Nihonbashi Entrance of the Tokaido Sanyo Shinkansen.</p>

<p><img src="/assets/958599363857/1*0PMsHuVFQWeAGUgHwpO7pA.webp" alt="" loading="lazy" decoding="async" width="950" height="1160" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTAiIGhlaWdodD0iMTE2MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0PMsHuVFQWeAGUgHwpO7pA.png" /></p>

<p><img src="/assets/958599363857/1*OKIhEwPPdSa-tDFMyiHC_A.webp" alt="" loading="lazy" decoding="async" width="854" height="1262" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NTQiIGhlaWdodD0iMTI2MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*OKIhEwPPdSa-tDFMyiHC_A.png" /></p>

<p>For e-tickets purchased in advance on <a href="https://www.kkday.com/zh-tw/transportation/japan-rail?cid=19365" target="_blank">KKday</a> / <a href="https://affiliate.klook.com/redirect?aid=99683&amp;aff_adid=1134759&amp;k_site=https%3A%2F%2Fwww.klook.com%2Fzh-TW%2Fjapan-rail%2F" target="_blank">Klook</a>, there is no need to exchange tickets. Just use the blue gate and hold your phone’s QR code close to the sensor to enter.</p>

<p><img src="/assets/958599363857/1*8XhMmWSYamkoIpZcrcexiw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*8XhMmWSYamkoIpZcrcexiw.jpeg" /></p>

<p>Entering the station successfully will print out a ticket (for easy information viewing or ticket inspection on the train), but you still need to use the mobile QR code for entry and exit.</p>

<p><img src="/assets/958599363857/1*VjszDJiQ3FvhmPZ6lezlaQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*VjszDJiQ3FvhmPZ6lezlaQ.jpeg" /></p>

<h4 id="1457-depart-tokyo-for-atami">14:57 Depart Tokyo for Atami</h4>

<p><img src="/assets/958599363857/1*LtxEb2m13uqztgio8zrwrA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*LtxEb2m13uqztgio8zrwrA.jpeg" /></p>

<p><img src="/assets/958599363857/1*zEZdPMxvFTM1ZdGGClg_SQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*zEZdPMxvFTM1ZdGGClg_SQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*lMSwYOhx4zEClwuaRJqpnw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*lMSwYOhx4zEClwuaRJqpnw.jpeg" /></p>

<p>A quick snack break.</p>

<h4 id="1542-arrive-at-atami">15:42 Arrive at Atami</h4>

<p><img src="/assets/958599363857/1*6snvQ0gvBAze0Z4Poh7ywg.webp" alt="" loading="lazy" decoding="async" width="1200" height="868" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*6snvQ0gvBAze0Z4Poh7ywg.png" /></p>

<p><img src="/assets/958599363857/1*bMpYH8-Dvf2iqufVYC1w0w.webp" alt="" loading="lazy" decoding="async" width="954" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*bMpYH8-Dvf2iqufVYC1w0w.png" /></p>

<p><img src="/assets/958599363857/1*ECAXngp1TSBZehMFgTfruw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ECAXngp1TSBZehMFgTfruw.jpeg" /></p>

<p>Welcome to the 2025 Atami Sea Fireworks Festival!</p>

<blockquote>
  <p><em>Official reminder: The fireworks festival will end with large crowds, so please purchase your Shinkansen tickets in advance.</em></p>
</blockquote>

<p>After exiting the Shinkansen, you still need to pass through the conventional line ticket gate (just show the staff your ticket QR code to exit).</p>

<p><img src="/assets/958599363857/1*MzafXGx-5W__v6ER9L55qg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*MzafXGx-5W__v6ER9L55qg.jpeg" /></p>

<p>The weather was overcast, but the advantage was that it wasn’t too sunny!</p>

<p><img src="/assets/958599363857/1*sfEvxO-r_6o1zXerVBmmxw.webp" alt="The Glorious Path of Yakiniku" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*sfEvxO-r_6o1zXerVBmmxw.png" /></p>

<p>The Glorious Yakiniku Journey</p>

<p><img src="/assets/958599363857/1*7-Gke4s_SdS3Ak7FshhbYw.webp" alt="" loading="lazy" decoding="async" width="948" height="1208" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDgiIGhlaWdodD0iMTIwOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*7-Gke4s_SdS3Ak7FshhbYw.png" /></p>

<p><img src="/assets/958599363857/1*klSuH4wuVQev1vAE8uhrzg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*klSuH4wuVQev1vAE8uhrzg.jpeg" /></p>

<p><img src="/assets/958599363857/1*NoN8BV-7HvjIqgoMvrNo_Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*NoN8BV-7HvjIqgoMvrNo_Q.jpeg" /></p>

<p>Walk straight down after passing through the shopping street, and you will reach the beach.</p>

<p><img src="/assets/958599363857/1*xsPseN91hTY7i51fJPjcTA.webp" alt="" loading="lazy" decoding="async" width="963" height="1126" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjMiIGhlaWdodD0iMTEyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*xsPseN91hTY7i51fJPjcTA.png" /></p>

<p><a href="https://maps.app.goo.gl/xZ6QHf3UAWu4iXtD8" target="_blank">The route is shown in the above image</a>. First, head to Lawson to buy some food and drinks, then go to the beach for a picnic and wait for the fireworks.</p>

<p><img src="/assets/958599363857/1*x0YEr4v70zdBu1cMZyq7zw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*x0YEr4v70zdBu1cMZyq7zw.jpeg" /></p>

<p><img src="/assets/958599363857/1*OSrew6n7pXgiQMkmz6TFrw.webp" alt="" loading="lazy" decoding="async" width="1200" height="880" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*OSrew6n7pXgiQMkmz6TFrw.png" /></p>

<p>The fireworks are launched roughly at the location shown in the picture above. You can walk to <a href="https://maps.app.goo.gl/XZmFfi3wDBJFERyQ8" target="_blank">Higashikaigancho</a> and wait on the beach further back. Here, you can also go into the water, and there are shared restrooms and showers nearby for changing. (However, it’s recommended to check in advance, as some facilities may be old and poorly maintained.)</p>

<p><img src="/assets/958599363857/1*93jLDQa0cb1jewFvsazD9Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*93jLDQa0cb1jewFvsazD9Q.jpeg" /></p>

<p>You can also see Atami Castle from Atami Beach in the distance.</p>

<h4 id="1700-atami-sunshine-beach">17:00 Atami Sunshine Beach</h4>

<p><img src="/assets/958599363857/1*MaQ6luHYefGS_EdREO8Z8A.webp" alt="" loading="lazy" decoding="async" width="675" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NzUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*MaQ6luHYefGS_EdREO8Z8A.jpeg" /></p>

<p>It doesn’t feel necessary to arrive in Atami so early; the crowd is not as large as expected. Before around 18:30, there are still spots available on the beach, and even by the 20:20 fireworks show, there are still places to watch.</p>

<p><img src="/assets/958599363857/1*7TaD649tgt3KS0hfcDNuzg.webp" alt="" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*7TaD649tgt3KS0hfcDNuzg.jpeg" /></p>

<h4 id="1800-atami-fireworks-festival-beach-crowd">18:00 Atami Fireworks Festival Beach Crowd</h4>

<p><img src="/assets/958599363857/1*_FH0OfAii22pyLOikGi2Tg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*_FH0OfAii22pyLOikGi2Tg.jpeg" /></p>

<p><img src="/assets/958599363857/1*ET9HOKp_AUG6BXKz9YFRfQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*ET9HOKp_AUG6BXKz9YFRfQ.jpeg" /></p>

<p>You can see that at 18:00 there are still many seats available, or many people come early to lay out picnic mats to reserve spots and then walk around nearby, returning just before the fireworks start.</p>

<p><strong>A bit further ahead <a href="https://maps.app.goo.gl/fYK3FxCFxZ8oMzRj6" target="_blank">around the Sky Deck area</a>, there is a small night market selling food (though the cost-performance ratio is not great):</strong></p>

<p><img src="/assets/958599363857/1*ABMNXDPrn2rHZ_BT_q6piA.webp" alt="" loading="lazy" decoding="async" width="1200" height="841" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ABMNXDPrn2rHZ_BT_q6piA.png" /></p>

<p>There are restrooms around the beach, so you don’t have to worry about not having access.</p>

<h4 id="2015-crowd-at-the-beach-near-the-fireworks-release-time">20:15 Crowd at the beach near the fireworks release time</h4>

<p><img src="/assets/958599363857/1*MhEq7Oix6_1SHWcLHWXxLg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*MhEq7Oix6_1SHWcLHWXxLg.jpeg" /></p>

<p><img src="/assets/958599363857/1*GltFsKbSIEPBGCU0tujfHQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*GltFsKbSIEPBGCU0tujfHQ.jpeg" /></p>

<p>There are many people on the trail behind.</p>

<h4 id="2020-fireworks-festival-begins">20:20 Fireworks Festival Begins</h4>

<p><img src="/assets/958599363857/1*tQkiAdu0D5xhrsRaSwx2ow.webp" alt="" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*tQkiAdu0D5xhrsRaSwx2ow.jpeg" /></p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/mI8vaJUpAzY" title="2025/09/15 熱海煙花大會" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/958599363857/1*qsOMKdPSeRp3svZjdxg0bw.webp" alt="" loading="lazy" decoding="async" width="1342" height="929" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzQyIiBoZWlnaHQ9IjkyOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*qsOMKdPSeRp3svZjdxg0bw.png" /></p>

<p>The fireworks had several parts and lasted until 20:40 (20 minutes). My favorite part was the magnificent full-screen fireworks. Unfortunately, the humidity was high today, so the smoke didn’t disperse well towards the end.</p>

<h4 id="2045-start-leaving-the-venue">20:45 Start leaving the venue</h4>

<p><img src="/assets/958599363857/1*Is19CY4ydHSl6o_koAwUxw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Is19CY4ydHSl6o_koAwUxw.jpeg" /></p>

<p><img src="/assets/958599363857/1*-Op_qDpZVTPamDHpMjrLGA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*-Op_qDpZVTPamDHpMjrLGA.jpeg" /></p>

<p>There were many people leaving, but everyone moved toward the station in an orderly manner; it was downhill coming in and uphill going back (tiring).</p>

<h4 id="2105-return-to-atami-station">21:05 Return to Atami Station</h4>

<p><img src="/assets/958599363857/1*0YVBx1LVIM38oZnnFl-uHw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0YVBx1LVIM38oZnnFl-uHw.jpeg" /></p>

<p><img src="/assets/958599363857/1*AAk3JHAyjLZtD47VozQxhQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="999" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*AAk3JHAyjLZtD47VozQxhQ.png" /></p>

<p>It takes about 20–25 minutes to walk back to Atami Station due to the large crowd, and there are many people entering the station. If you take the local train line, you need to queue slowly to enter. <strong>If you have already purchased your Shinkansen ticket, you can go directly through the manned gate on the right and then access the Shinkansen platform without going through the local line.</strong></p>

<blockquote>
  <p><em>Originally worried about tight timing, I bought a 22:02 train ticket from Atami back to Tokyo; based on the situation on site, the earlier 21:26 train would have been more than enough.</em></p>
</blockquote>

<h4 id="2120-arrive-at-the-station-and-wait-for-the-train">21:20 Arrive at the station and wait for the train</h4>

<p><img src="/assets/958599363857/1*FlXrgL64tIESh9YANzlCgA.webp" alt="" loading="lazy" decoding="async" width="1400" height="864" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijg2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*FlXrgL64tIESh9YANzlCgA.png" /></p>

<p><img src="/assets/958599363857/1*P8UbBT810VfOu1d-FjmGfQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*P8UbBT810VfOu1d-FjmGfQ.jpeg" /></p>

<p>The Shinkansen here is very spacious and the air conditioning is cool.</p>

<p><img src="/assets/958599363857/1*1yTYNHadfcjXGCssVCOYuQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*1yTYNHadfcjXGCssVCOYuQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*Xh7kHOo5wLCsZq0HsfcxjA.webp" alt="" loading="lazy" decoding="async" width="1200" height="882" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Xh7kHOo5wLCsZq0HsfcxjA.png" /></p>

<p>Bought a can of apple soda to recharge, but heavy rain in other areas caused all trains to be delayed.</p>

<h4 id="2225-board-the-train-delayed-by-nearly-20-minutes">~=22:25 Board the train (delayed by nearly 20 minutes)</h4>

<p><img src="/assets/958599363857/1*ffvGyMO0-1naq-JFmy9kfw.webp" alt="" loading="lazy" decoding="async" width="941" height="1192" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDEiIGhlaWdodD0iMTE5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ffvGyMO0-1naq-JFmy9kfw.png" /></p>

<h4 id="-2310-arrive-in-tokyo">~= 23:10 Arrive in Tokyo</h4>

<p>Transfer to the Yamanote Line back to Shimbashi.</p>

<p><img src="/assets/958599363857/1*JUzg76hP_ZQMXHzkFKDmdA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*JUzg76hP_ZQMXHzkFKDmdA.jpeg" /></p>

<h4 id="-2340-return-to-the-hotel-to-rest">~= 23:40 Return to the hotel to rest</h4>

<p><img src="/assets/958599363857/1*xcMi52GR3TaBBuVYXjAVjg.webp" alt="" loading="lazy" decoding="async" width="1200" height="846" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*xcMi52GR3TaBBuVYXjAVjg.png" /></p>

<p><img src="/assets/958599363857/1*TZma2hnWNg9KTatodIad5w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*TZma2hnWNg9KTatodIad5w.jpeg" /></p>

<p>What a fulfilling day!</p>

<h3 id="day-4-0916-tue--azabudai-teamlab-harajuku-shopping-otter-café">Day 4 (09/16 Tue) — Azabudai TeamLab, Harajuku Shopping, Otter Café</h3>

<p>After a good sleep, take the bus out.</p>

<h4 id="1010-arrive-at-azabudai-hills">10:10 Arrive at <a href="https://maps.app.goo.gl/PbjLmES23cBRVRwK6" target="_blank">Azabudai Hills</a></h4>

<p><img src="/assets/958599363857/1*Vxi-ROH7326xUK1Q_RaSSA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Vxi-ROH7326xUK1Q_RaSSA.jpeg" /></p>

<p>I thought Azabudai no Mori only had <a href="https://maps.app.goo.gl/yyYwwHoZbHBmysqN6" target="_blank">JP Tower</a>, but it’s actually a whole cluster of fragmented buildings with many department store counters and dining options.</p>

<p><img src="/assets/958599363857/1*l04M339xYWoaX0St4PmTGA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*l04M339xYWoaX0St4PmTGA.jpeg" /></p>

<p>First, go to a café for breakfast and to wake up.</p>

<h4 id="1050-head-to-teamlab-borderless-mori-building-digital-art-museum-azabudai-hills-garden-plaza-b-b1">10:50 <a href="https://maps.app.goo.gl/a1VeYdbgBhvPCPXC7" target="_blank">Head to teamLab Borderless: MORI Building DIGITAL ART MUSEUM (Azabudai Hills Garden Plaza B, B1)</a></h4>

<p><img src="/assets/958599363857/1*G2WDXXLUhC1r2oueJDxDcQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*G2WDXXLUhC1r2oueJDxDcQ.jpeg" /></p>

<p>There is no map inside. If you want to explore everything, you can first get the official guidebook outside.</p>

<p><img src="/assets/958599363857/1*GKfgsOIo0qXpIshU_0N3UA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*GKfgsOIo0qXpIshU_0N3UA.jpeg" /></p>

<p><img src="/assets/958599363857/1*6BC5_ZTb1w6zcBWf5mNeng.webp" alt="" loading="lazy" decoding="async" width="1200" height="842" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*6BC5_ZTb1w6zcBWf5mNeng.png" /></p>

<p>Turn right for a free storage area. After storing your items, show your ticket to the staff to enter.</p>

<h4 id="1100-teamlab-borderless">11:00 <a href="https://maps.app.goo.gl/a1VeYdbgBhvPCPXC7" target="_blank">teamLab Borderless</a></h4>

<p><img src="/assets/958599363857/1*ZgsR6mKRK6TjNZkS22p_7A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ZgsR6mKRK6TjNZkS22p_7A.jpeg" /></p>

<p><img src="/assets/958599363857/1*UXRiHpWae9wvx-9kSnGZjQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*UXRiHpWae9wvx-9kSnGZjQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*w3vwhwr_94ICABHOe_z46Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*w3vwhwr_94ICABHOe_z46Q.jpeg" /></p>

<p><img src="/assets/958599363857/1*nMWtkgSZwXV7BDd0PHLNig.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*nMWtkgSZwXV7BDd0PHLNig.jpeg" /></p>

<p><img src="/assets/958599363857/1*kTlH8oGRnbrMmf3L_nWSrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*kTlH8oGRnbrMmf3L_nWSrg.jpeg" /></p>

<p>teamLab is truly amazing, featuring various magical and creative scenes that feel unreal. The interactions include light and shadow, mirrors, spheres, pillars, leaves, mist, and more. Almost all are 360-degree full scenes, providing a strong sense of immersion.</p>

<blockquote>
  <p><em>There is no map; you need to explore all the exhibition rooms on your own. Aprons are thoughtfully provided at the entrance for girls wearing skirts who are concerned about exposure.</em></p>
</blockquote>

<blockquote>
  <p><em>Before entering each room, staff usually provide instructions, mostly asking visitors not to touch anything and to walk carefully.</em></p>
</blockquote>

<blockquote>
  <p><em>There are restrooms inside, but you need to find the signs yourself or ask the staff.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*rOQik1WqrSYIlOlNE8GCNg.webp" alt="" loading="lazy" decoding="async" width="665" height="1182" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NjUiIGhlaWdodD0iMTE4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*rOQik1WqrSYIlOlNE8GCNg.jpeg" /></p>

<p>The photos also look very magical.</p>

<p><img src="/assets/958599363857/1*-QUcAm5LHPzXTaNqzEe-2g.webp" alt="To have seen something, is to not have seen something else" loading="lazy" decoding="async" width="1400" height="601" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjYwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*-QUcAm5LHPzXTaNqzEe-2g.png" /></p>

<p>To have seen something is to have missed seeing something else</p>

<blockquote>
  <p><em>We left around 12:15, but we can’t guarantee we saw everything; if we weren’t hungry, spending two to three hours inside would probably not be a problem.</em></p>
</blockquote>

<h4 id="harbs-azabudai-hill-store"><a href="https://maps.app.goo.gl/GYvhb3ucV6Xykrtr9" target="_blank">HARBS Azabudai Hill Store</a></h4>

<p><img src="/assets/958599363857/1*H3hzyw1S7R8QJqGClj7-Kg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*H3hzyw1S7R8QJqGClj7-Kg.jpeg" /></p>

<p>After visiting teamLab, there is a HARBS nearby. Let’s have a fruit mille crepe cake to settle our stomach first.</p>

<blockquote>
  <p><a href="https://www.harbs.co.jp/zh-TW/allmenu/flag/" target="_blank"><em>Online Menu: Click to View.</em></a></p>
</blockquote>

<p><img src="/assets/958599363857/1*KofxiaB6kF2_MBL4_xSMJA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*KofxiaB6kF2_MBL4_xSMJA.jpeg" /></p>

<p>Signature Fruit Mille Crepes (Mille Crepes/ミルクレープ) 1 slice ￥1,050 + minimum order of one drink.</p>

<blockquote>
  <p><em>Inside there is cantaloupe (very sweet and delicious) + banana + kiwi.</em></p>
</blockquote>

<h4 id="1320-tokyo-tower">13:20 Tokyo Tower</h4>

<p><img src="/assets/958599363857/1*bxUphy3A0fYyf5koQkTQNw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*bxUphy3A0fYyf5koQkTQNw.jpeg" /></p>

<p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E6%9D%B1%E4%BA%AC%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-5-%E5%A4%A9%E9%A3%9F%E4%BD%8F%E8%A1%8C%E5%85%A8%E8%A8%98%E9%8C%84%E8%88%87%E5%BF%85%E8%A8%AA%E6%99%AF%E9%BB%9E%E6%8E%A8%E8%96%A6-9da2c51fa4f2/#%E6%9D%B1%E4%BA%AC%E9%90%B5%E5%A1%94" target="_blank">Visited last time and went up <strong>(please refer to the previous travelogue)</strong></a>, this time just passed by to take some photos.</p>

<p>Take the train to Harajuku.</p>

<h4 id="1400-harajuku">14:00 Harajuku</h4>

<p><img src="/assets/958599363857/1*6SJEx_1Xe1cRtxNtxCACUw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*6SJEx_1Xe1cRtxNtxCACUw.jpeg" /></p>

<p>As soon as I came out, I saw Giikawa again and again. Upstairs at <a href="https://maps.app.goo.gl/Qo9uS7tVHoRda1MQ7" target="_blank">Tokyu Plaza Omotesando Harajuku</a> is the <a href="https://maps.app.goo.gl/7rPeXTvCVEtWonMd7" target="_blank">Giikawa Bakery</a>.</p>

<h4 id="chiikawa-bakery-伊卡哇麵包店"><a href="https://maps.app.goo.gl/7rPeXTvCVEtWonMd7" target="_blank">Chiikawa Bakery</a> 伊卡哇麵包店</h4>

<p><img src="/assets/958599363857/1*z3t6dt_iKNpI2quFtdIylg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*z3t6dt_iKNpI2quFtdIylg.jpeg" /></p>

<p><img src="/assets/958599363857/1*AZPe4DkecEm5YCnjvnokvA.webp" alt="" loading="lazy" decoding="async" width="879" height="1157" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzkiIGhlaWdodD0iMTE1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*AZPe4DkecEm5YCnjvnokvA.png" /></p>

<p>The bakery and jam shop on the left require a numbered ticket for entry; the doll shop on the right does not.</p>

<p>(Because I just happened to pass by, I didn’t go in.)</p>

<blockquote>
  <p><a href="https://airwait.jp/WCSP/storeDetail?storeNo=AKR3416883372&amp;langType=KeyTW" target="_blank"><em>Click here to join the online queue and get a number ticket</em></a></p>
</blockquote>

<p><img src="/assets/958599363857/1*W-7h4iIZMQRL6yk5WL7IQQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*W-7h4iIZMQRL6yk5WL7IQQ.jpeg" /></p>

<p>Food: Bakery Section.</p>

<p><img src="/assets/958599363857/1*zPGCxhXve2PB8gO21lFyLg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*zPGCxhXve2PB8gO21lFyLg.jpeg" /></p>

<p><img src="/assets/958599363857/1*AQGlxwc5OIDgwn8dJ62YRw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*AQGlxwc5OIDgwn8dJ62YRw.jpeg" /></p>

<p><img src="/assets/958599363857/1*BAveEbCyszfKzZc3L7CE9w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*BAveEbCyszfKzZc3L7CE9w.jpeg" /></p>

<p>The nearby shops had plenty of stock, but since I already had sweet potatoes, I didn’t buy any and just looked around.</p>

<h4 id="kiddy-land-キデイランド-harajuku-store"><a href="https://maps.app.goo.gl/JBGBKiEBNdcycYPz8" target="_blank">Kiddy Land キデイランド Harajuku Store</a></h4>

<p>After leaving, we went to another nearby Kiddy Land to browse. (<a href="https://www.kiddyland.co.jp/harajuku/" target="_blank">But this one doesn’t have Gii Kawa</a>)</p>

<p><img src="/assets/958599363857/1*WTTkpsTLZ9_35_KVXD01wQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*WTTkpsTLZ9_35_KVXD01wQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*KG4Y3IlpSLIcD-SOBxb4Ug.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*KG4Y3IlpSLIcD-SOBxb4Ug.jpeg" /></p>

<p>There are many IPs like Snoopy, Sanrio, Pokémon, Rilakkuma, and more.</p>

<p>By the time I finished walking around, it was nearly 3:00 PM. I had only eaten a piece of fruit mille-feuille for lunch and was a bit hungry, so I randomly picked a highly rated American burger joint I passed by to grab a bite.</p>

<h4 id="1450-the-great-burger"><a href="https://maps.app.goo.gl/xgoLanLfSSYFC5jP9" target="_blank">14:50 The Great Burger</a></h4>

<p><img src="/assets/958599363857/1*CxJhjOkE7NSkV2wwiZWlWA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*CxJhjOkE7NSkV2wwiZWlWA.jpeg" /></p>

<p><img src="/assets/958599363857/1*0wzaWAsar2xUmujgMfHVUg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0wzaWAsar2xUmujgMfHVUg.jpeg" /></p>

<p>The specialty is Wagyu mini burgers.</p>

<p><img src="/assets/958599363857/1*Anb4bgPGloM3oNkV6btS-g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Anb4bgPGloM3oNkV6btS-g.jpeg" /></p>

<p><img src="/assets/958599363857/1*2x_JAyGUZmJ58CSR30Rr7A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*2x_JAyGUZmJ58CSR30Rr7A.jpeg" /></p>

<p>The taste is quite good, but eating too much can be overwhelming.</p>

<h4 id="1540-shopping-in-harajuku">15:40 Shopping in Harajuku</h4>

<p>After eating, continue exploring Harajuku until evening (16:30) to visit the reserved Otter Café.</p>

<p><img src="/assets/958599363857/1*sWQbbQUfLq3EWzMhJrvTng.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*sWQbbQUfLq3EWzMhJrvTng.jpeg" /></p>

<p><img src="/assets/958599363857/1*XyVXantdzPWEZA0pYpTXKA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*XyVXantdzPWEZA0pYpTXKA.jpeg" /></p>

<p><img src="/assets/958599363857/1*EfEP8VdYHAbuyxi2C6EFSA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*EfEP8VdYHAbuyxi2C6EFSA.jpeg" /></p>

<p>Just wandering around, I ended up at the same Le Labo store where I bought perfume last time. In 2025, the price for 50ml perfume in Japan is ¥31,350, and after tax refund, it’s about ¥28,500.</p>

<h4 id="1630-harry-harajuku-terrace-otter-café">16:30 <a href="https://maps.app.goo.gl/WHyJ88K5hRVc4vnk9" target="_blank">HARRY HARAJUKU terrace Otter Café</a></h4>

<p><img src="/assets/958599363857/1*b20FSUf7uuIrFFNRLP6vwQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*b20FSUf7uuIrFFNRLP6vwQ.jpeg" /></p>

<p>Go up the stairs beside it to the rooftop of this building.</p>

<p><img src="/assets/958599363857/1*eoDe1nHReBDY4bv070eEBQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*eoDe1nHReBDY4bv070eEBQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*x3phhvrHJ3ocidaw3glJLw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*x3phhvrHJ3ocidaw3glJLw.jpeg" /></p>

<p><img src="/assets/958599363857/1*CpEDCXiZ8Js_st4tzrXtGA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*CpEDCXiZ8Js_st4tzrXtGA.jpeg" /></p>

<p>The venue is small, with three pools outside where three otters roam around. You can buy snacks to feed them (¥880). (They will reach out their paws)</p>

<p><img src="/assets/958599363857/1*U30ypE-233jdjvcR0aejGA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*U30ypE-233jdjvcR0aejGA.jpeg" /></p>

<p>The coffee is from the vending machine, self-served. (Buy 30 minutes and get 1 cup free)</p>

<p><img src="/assets/958599363857/1*fBpOYC967t_nalgHfuGCEg.webp" alt="" loading="lazy" decoding="async" width="1200" height="872" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*fBpOYC967t_nalgHfuGCEg.png" /></p>

<p><img src="/assets/958599363857/1*3dL7ITdswBmvR6KUU9MQSw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*3dL7ITdswBmvR6KUU9MQSw.jpeg" /></p>

<p>The venue itself is not very large. In the middle, there is a paid interactive otter area (additional fee required), next to it is a paid Totoro interactive area, and on the other side is a free hedgehog interactive area, along with a few otters inside a fenced enclosure.</p>

<p><img src="/assets/958599363857/1*L1tbx1lOqgPbpgBmEhGkVA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*L1tbx1lOqgPbpgBmEhGkVA.jpeg" /></p>

<p>We added ¥880 per person for a 5-minute close interaction with the otters; otherwise, you can’t actually touch them.</p>

<h4 id="1720-pet-the-otters">17:20 <a href="https://maps.app.goo.gl/WHyJ88K5hRVc4vnk9" target="_blank">Pet the Otters</a></h4>

<blockquote>
  <p><em>There were many visitors interacting with the otters, so our turn was not until 17:20. The staff monitored the time closely.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*BNyIXI7P-rccj0dy7yeXeg.webp" alt="" loading="lazy" decoding="async" width="1200" height="704" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcwNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*BNyIXI7P-rccj0dy7yeXeg.png" /></p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/-h9OupA3kCY" title="HARRY HARAJUKU terrace 水獺咖啡廳 ＨＡＲＲＹ原宿テラス店" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>You can pet him while he’s eating and rolling over; his fur is very soft.</p>

<blockquote>
  <p><strong><em>Overall: The rating might only be around 2.5–3 stars</em></strong></p>
</blockquote>

<blockquote>
  <p><em>The overall venue is quite small. The basic fee is ¥3,080 per person for one hour. Aside from the hedgehogs, there is no other interaction, and the coffee is only from vending machines. Although the staff are gentle with the animals, the other otters resting nearby and those in the outdoor pool have limited space to move, which feels a bit sad.</em></p>
</blockquote>

<blockquote>
  <p><em>It seems different from a typical coffee shop in size, plus there are otters running around.</em></p>
</blockquote>

<p><a href="https://kantenlife.tw/2024/04/18/house-of-otters-japan/#%E6%97%A5%E6%9C%AC%E6%B0%B4%E7%8D%BA%E5%92%96%E5%95%A1%E5%BB%B3%EF%BD%9C%E3%82%AB%E3%83%AF%E3%82%A6%E3%82%BD_%E8%8B%A5%E6%9E%97%E3%81%AE%E5%AE%B6%E3%80%81HARRY_HARAJUKU_terrace%E6%AF%94%E8%BC%83%E5%88%86%E4%BA%AB" target="_blank"><strong>After further research, it seems another place, カワウソ_若林の家, is better with longer interaction time.</strong></a></p>

<h4 id="1800-head-to-shibuya">18:00 Head to Shibuya</h4>

<p><img src="/assets/958599363857/1*Do_SdWdOCG8ZhXXDVcyEtg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Do_SdWdOCG8ZhXXDVcyEtg.jpeg" /></p>

<h4 id="1820-queue-at-gokumiya">18:20 Queue at <a href="https://maps.app.goo.gl/oZR5RprBErWbkAjq8" target="_blank">Gokumiya</a></h4>

<p>It’s still early, so I came back to Shibuya and started with a meal at Gokumiya.</p>

<p><img src="/assets/958599363857/1*bA7IPZZUq2LVdCbFs9H5lw.webp" alt="" loading="lazy" decoding="async" width="1200" height="861" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*bA7IPZZUq2LVdCbFs9H5lw.png" /></p>

<p>At this time, there weren’t many people, so after a short wait, we got to eat.</p>

<h4 id="1855-seating-at-gokumiya">18:55 Seating at <a href="https://maps.app.goo.gl/oZR5RprBErWbkAjq8" target="_blank">Gokumiya</a></h4>

<p><img src="/assets/958599363857/1*WRCSVqkiXQFWZcMWKs8GPw.webp" alt="" loading="lazy" decoding="async" width="711" height="1220" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MTEiIGhlaWdodD0iMTIyMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*WRCSVqkiXQFWZcMWKs8GPw.png" /></p>

<p><img src="/assets/958599363857/1*hb9il-B-7Hh_BJccl-OfyQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*hb9il-B-7Hh_BJccl-OfyQ.jpeg" /></p>

<p>I ate too many hamburger steaks at lunch and felt a bit tired of them. I ordered the pure beef steak set on the left; the juice was delicious.</p>

<blockquote>
  <p><a href="https://zhgchg.li/posts/z-%E5%BA%A6%E6%97%85%E8%A1%8C%E9%81%8A%E8%A8%98/%E6%9D%B1%E4%BA%AC%E8%87%AA%E7%94%B1%E8%A1%8C%E6%94%BB%E7%95%A5-5-%E5%A4%A9%E9%A3%9F%E4%BD%8F%E8%A1%8C%E5%85%A8%E8%A8%98%E9%8C%84%E8%88%87%E5%BF%85%E8%A8%AA%E6%99%AF%E9%BB%9E%E6%8E%A8%E8%96%A6-9da2c51fa4f2/#%E6%B8%8B%E8%B0%B7-parco--%E6%A5%B5%E5%91%B3%E5%B1%8B" target="*blank"><em>Last time I had the hamburger steak + beef steak set</em></a> <em>Just remember to bring two pairs of chopsticks: metal ones for grilling, wooden ones for eating.</em>***</p>
</blockquote>

<p><strong>Signature — Imari Steak + Gokumiya Hamburger Beef Prices:</strong></p>

<ul>
  <li>
    <p>Small (120g): ¥2,480 JPY</p>
  </li>
  <li>
    <p>Medium (160g): ¥2,850 JPY</p>
  </li>
  <li>
    <p>Large (200g): ¥3,180 JPY</p>
  </li>
</ul>

<p><strong>Price of Jun Imari Steak:</strong></p>

<ul>
  <li>
    <p>Medium (130g): ¥2,480 JPY</p>
  </li>
  <li>
    <p>Large (180g): ¥3,280 JPY</p>
  </li>
</ul>

<p><strong>Upgrade Package:</strong> ¥450 JPY</p>

<ul>
  <li>
    <p>Multiple soups, salads, and ice cream</p>
  </li>
  <li>
    <p>Personally, I think it’s just okay.</p>
  </li>
</ul>

<blockquote>
  <p><em>The price is very affordable.</em></p>
</blockquote>

<h4 id="shopping-and-buying-cosmetics">Shopping and Buying Cosmetics</h4>

<p><img src="/assets/958599363857/1*mOCaZv_M3wUL_shhpsklRw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*mOCaZv_M3wUL_shhpsklRw.jpeg" /></p>

<p>At the department store, I found some fragrances and bought two bottles along with a pillow spray to try at home.</p>

<p><img src="/assets/958599363857/1*hHziMmw8-TXZgTU96AaLAQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*hHziMmw8-TXZgTU96AaLAQ.jpeg" /></p>

<p>After eating, we strolled around Shibuya a bit more, then went back to the hotel to get some money and buy cosmetics at a drugstore. Returned to the hotel to rest, getting ready to say goodbye to Tokyo.</p>

<p><img src="/assets/958599363857/1*3F0Blw-rzDL6OCLtVJQJtA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*3F0Blw-rzDL6OCLtVJQJtA.jpeg" /></p>

<p><img src="/assets/958599363857/1*p8osyPhc0M_e21JVJLnmbQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*p8osyPhc0M_e21JVJLnmbQ.jpeg" /></p>

<p>Ate too much meat, so let’s just have dessert tonight!</p>

<blockquote>
  <p><em>Also, a colleague bought donuts for us at <a href="https://maps.app.goo.gl/ptoRy58awP8gVibR6" target="_blank">Harajuku I’m donut? Harajuku</a>. (No need to wait in line in Japan, unlike in Taiwan)</em></p>
</blockquote>

<h3 id="day-5-0917-wed--ueno-station-return-trip">Day 5 (09/17 Wed) — Ueno Station, Return Trip</h3>

<h4 id="1050-leave-before-the-final-check-out-time">10:50 Leave before the final check-out time</h4>

<p><img src="/assets/958599363857/1*_fnEVTQzT63qUc3tmEtY5A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*_fnEVTQzT63qUc3tmEtY5A.jpeg" /></p>

<p>Goodbye Shimbashi.</p>

<p><img src="/assets/958599363857/1*9iw9U6X2Rih37WcuoITOPw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*9iw9U6X2Rih37WcuoITOPw.jpeg" /></p>

<p>Take the Yamanote Line to Ueno.</p>

<h4 id="1110-arrive-at-ueno-station">11:10 Arrive at Ueno Station</h4>

<p><img src="/assets/958599363857/1*O_gvBqPHvCHYXyWu3h2sjw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*O_gvBqPHvCHYXyWu3h2sjw.jpeg" /></p>

<p>Didn’t explore much; worried about not finding a place to store luggage, so I used the locker counter inside the station.</p>

<blockquote>
  <p><em>Later found it not very cost-effective, as you need to pick up items and pay to enter the station, plus it’s expensive; the main issue is that it’s out of the way.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Later I found out that the Skyliner Keisei Ueno is at Ueno Station Yamashita Exit or Shinobazu Exit</em></strong> <em>You can reach it inside the station without going outside in the sun (but it’s easy to get lost inside the station); if you want to use the lockers here, you have to walk back (about 15 minutes), and there are many coin lockers there as well.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*stvzWK4vvJ2IxguPptM3Bg.webp" alt="" loading="lazy" decoding="async" width="1400" height="980" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*stvzWK4vvJ2IxguPptM3Bg.png" /></p>

<p>Exit here and you’ll be at Ueno Zoo. Turn left, <strong>ready to reserve a seat for the Skyliner at Keisei Ueno.</strong></p>

<p><img src="/assets/958599363857/1*8sL69lXMh1Mzhs05TclHqQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*8sL69lXMh1Mzhs05TclHqQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*R8RkmEqHQLr_405KQIulcg.webp" alt="" loading="lazy" decoding="async" width="946" height="1205" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDYiIGhlaWdodD0iMTIwNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*R8RkmEqHQLr_405KQIulcg.png" /></p>

<p>Finally saw the Skyliner Keisei Ueno Station.</p>

<p><img src="/assets/958599363857/1*9_ErGzabreeyy-qw0P8zVQ.webp" alt="" loading="lazy" decoding="async" width="945" height="1198" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTE5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*9_ErGzabreeyy-qw0P8zVQ.png" /></p>

<blockquote>
  <p><em>Since all Skyliner seats are reserved, it is recommended to book your seat early for safety.</em></p>
</blockquote>

<blockquote>
  <p><em>The station staff can speak Chinese (I just didn’t realize they were speaking Chinese at first).</em></p>
</blockquote>

<p>Spend the last moments strolling around Ueno.</p>

<h4 id="yodobashi-ueno"><a href="https://maps.app.goo.gl/KfAz3oU7DBQpXrUc8" target="_blank">Yodobashi Ueno</a></h4>

<p><img src="/assets/958599363857/1*dBKdSU8BqKOanuALBUEesQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*dBKdSU8BqKOanuALBUEesQ.jpeg" /></p>

<p><img src="/assets/958599363857/1*DDnDqOR4o8G6Jg60AospOg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*DDnDqOR4o8G6Jg60AospOg.jpeg" /></p>

<p>There is also a second store next door.</p>

<h4 id="walked-all-the-way-to-ueno-marui-department-store-for-shopping"><a href="https://maps.app.goo.gl/E3EfJWipQzSuC2Uv5" target="_blank">Walked all the way to Ueno Marui Department Store for shopping</a></h4>

<p><img src="/assets/958599363857/1*O5JJZtfdU1OyJlD1_b8UPw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*O5JJZtfdU1OyJlD1_b8UPw.jpeg" /></p>

<p><img src="/assets/958599363857/1*jmnShS8jUrzCbG0vej3Ksw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*jmnShS8jUrzCbG0vej3Ksw.jpeg" /></p>

<blockquote>
  <p><em>Opposite is another exit of Ueno Station. Ueno Station is really huge…</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*Ziu-Golfdiu6XpDVxun10g.webp" alt="" loading="lazy" decoding="async" width="1200" height="1017" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMTciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*Ziu-Golfdiu6XpDVxun10g.png" /></p>

<p>The general location is as shown in the image above.</p>

<h4 id="1200-visit-marui-department-store">12:00 Visit <a href="https://maps.app.goo.gl/DvPAhnaEz8HYGSQs7" target="_blank">Marui Department Store</a></h4>

<p><img src="/assets/958599363857/1*GsKiVACufVHtpDYxncJ40A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*GsKiVACufVHtpDYxncJ40A.jpeg" /></p>

<p><img src="/assets/958599363857/1*XoxAFbNqAcwJ8YDPjQlQxw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*XoxAFbNqAcwJ8YDPjQlQxw.jpeg" /></p>

<p>There is a gacha capsule toy area on the second floor.</p>

<p><img src="/assets/958599363857/1*37rlYGSdZtckKzwgv9gLWw.webp" alt="" loading="lazy" decoding="async" width="495" height="491" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OTUiIGhlaWdodD0iNDkxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/958599363857/1*37rlYGSdZtckKzwgv9gLWw.png" /></p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/jyFapci9NxY" title="東海道新幹線 チケット音声キーホルダー 新幹線 車票 廣播 扭蛋 Gotcha" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>Got a <a href="https://www.so-ta.com/products_detail/capsuletoy/tokaido_shinkansen/" target="_blank">Shinkansen ticket capsule toy</a>.</p>

<p><img src="/assets/958599363857/1*rysyBldMgjEOuR__7hQuOg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*rysyBldMgjEOuR__7hQuOg.jpeg" /></p>

<p>I also got tempted and bought a ReFa heart-shaped massage brush at Loft.</p>

<p><img src="/assets/958599363857/1*sGjazwQmnuqRKWbTubIZXA.webp" alt="" loading="lazy" decoding="async" width="1200" height="857" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*sGjazwQmnuqRKWbTubIZXA.png" /></p>

<p>Ate croissants here to fill up.</p>

<h4 id="1320-return-to-ueno-station">13:20 Return to Ueno Station</h4>

<p><img src="/assets/958599363857/1*Ht4JmIU70nKQWu560yEM2A.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Ht4JmIU70nKQWu560yEM2A.jpeg" /></p>

<p>Also, buy Sugar Butter Tree as a souvenir.</p>

<p>Finally, I returned to the park exit to pick up my luggage, walked around a large area, and then went back to Keisei Ueno.</p>

<p><img src="/assets/958599363857/1*oR_x1HqE9qwg094sF-uygA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*oR_x1HqE9qwg094sF-uygA.jpeg" /></p>

<p><img src="/assets/958599363857/1*maLcXgZQHSKZdJB2CQngZg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*maLcXgZQHSKZdJB2CQngZg.jpeg" /></p>

<p>The route is a bit unusual; you need to go down to the platform first, then exit from another exit to reach Shinobazu Guchi.</p>

<p><img src="/assets/958599363857/1*XLFR3n_fxsGWBK6sU5Lkhw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*XLFR3n_fxsGWBK6sU5Lkhw.jpeg" /></p>

<p>This underground passage has many coin-operated lockers, and they are mostly empty.</p>

<p><img src="/assets/958599363857/1*TyOvYS_0FCfSujRBojgRvg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*TyOvYS_0FCfSujRBojgRvg.jpeg" /></p>

<p>I couldn’t help but say that crossing the street leads directly to the Skyliner at Keisei Ueno. (Not sure if there’s a closer exit, but I didn’t wander around to avoid getting lost.)</p>

<h4 id="1410-return-to-skyliner-keisei-ueno-prepare-to-take-the-train-to-narita-airport">14:10 Return to Skyliner Keisei Ueno, prepare to take the train to Narita Airport</h4>

<p><img src="/assets/958599363857/1*j-oOGCBRflbJWVfkQLrA_Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="873" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*j-oOGCBRflbJWVfkQLrA_Q.png" /></p>

<h4 id="1420-depart-for-narita-airport">14:20 Depart for Narita Airport</h4>

<p><img src="/assets/958599363857/1*vV-Yxbd2JQ9acITed5qGpw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*vV-Yxbd2JQ9acITed5qGpw.jpeg" /></p>

<p><img src="/assets/958599363857/1*-bEzz-shmaBClAhFkmjf8A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*-bEzz-shmaBClAhFkmjf8A.jpeg" /></p>

<p>Just stopped by a convenience store on the way to grab some food and drinks to recharge.</p>

<h4 id="1503-arrive-at-narita-airport-terminal-2">15:03 Arrive at Narita Airport Terminal 2</h4>

<p><img src="/assets/958599363857/1*6thsMc0VyilD-DBmtf85Aw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*6thsMc0VyilD-DBmtf85Aw.jpeg" /></p>

<p><img src="/assets/958599363857/1*jaAOzbQTRmETdiie_Vi7Zg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*jaAOzbQTRmETdiie_Vi7Zg.jpeg" /></p>

<p><img src="/assets/958599363857/1*lpge7uQIA81oEOD9JjK3pQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*lpge7uQIA81oEOD9JjK3pQ.jpeg" /></p>

<p>Once you go straight up, you’ll reach the international departures.</p>

<blockquote>
  <p><em>I originally planned to eat at the newly opened Shake Shack burger at Haneda Airport <a href="https://matcha-jp.com/tw/25632" target="_blank">新開幕的 Shake Shack 漢堡</a>, but I was too tired to go.</em></p>
</blockquote>

<h4 id="1540-completed-check-in">15:40 Completed check-in</h4>

<p><img src="/assets/958599363857/1*qtNeXNg2HuWhOQBd0YqnyQ.webp" alt="" loading="lazy" decoding="async" width="946" height="1207" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDYiIGhlaWdodD0iMTIwNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*qtNeXNg2HuWhOQBd0YqnyQ.png" /></p>

<blockquote>
  <p><a href="https://digital.jal.co.jp/ssci/identification?lang=zh-tw" target="_blank"><em>You can complete online check-in and seat selection 24 hours before departure, then go straight to baggage drop-off at the airport to speed up the process.</em></a></p>
</blockquote>

<p>After checking in my luggage, I went straight to line up for departure (tired).</p>

<h4 id="1600-complete-security-check-and-immigration-clearance">16:00 Complete security check and immigration clearance</h4>

<p><img src="/assets/958599363857/1*41hw6bw7XnkIpESE-es7iw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*41hw6bw7XnkIpESE-es7iw.jpeg" /></p>

<p><img src="/assets/958599363857/1*XksioltS7RRgmtf4Gz-N3A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*XksioltS7RRgmtf4Gz-N3A.jpeg" /></p>

<p>Walk toward the smaller numbers to reach the central connecting hall, where you can find food and shopping.</p>

<h4 id="first-go-to-japan-food-hall-b1-to-grab-something-to-eat">First, go to JAPAN FOOD HALL B1 to grab something to eat</h4>

<p><img src="/assets/958599363857/1*xj-6KVdDgwx_GPgGF0bHqQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*xj-6KVdDgwx_GPgGF0bHqQ.jpeg" /></p>

<h4 id="1625-snack-time">16:25 Snack Time</h4>

<p><img src="/assets/958599363857/1*Khh3IUrvCCYk1Ccx_fiF7w.webp" alt="" loading="lazy" decoding="async" width="1200" height="842" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*Khh3IUrvCCYk1Ccx_fiF7w.png" /></p>

<p><img src="/assets/958599363857/1*bgttA6-L8uW93zM7TpwOmw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*bgttA6-L8uW93zM7TpwOmw.jpeg" /></p>

<blockquote>
  <p><em>Ordered a tempura shrimp soba with a draft beer.</em></p>
</blockquote>

<blockquote>
  <p><em>Actual spend: 955 TWD (Airport prices are just expensive…)</em></p>
</blockquote>

<h4 id="1650-shopping-at-duty-free-and-souvenir-shops">16:50 Shopping at Duty-Free and Souvenir Shops</h4>

<p><img src="/assets/958599363857/1*39UCCUtGuFBVV1FEBQG-9A.webp" alt="" loading="lazy" decoding="async" width="1400" height="921" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkyMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*39UCCUtGuFBVV1FEBQG-9A.png" /></p>

<p><img src="/assets/958599363857/1*UrCaiyBHekSvy0tzTbM5Gw.webp" alt="" loading="lazy" decoding="async" width="1200" height="848" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*UrCaiyBHekSvy0tzTbM5Gw.png" /></p>

<p><img src="/assets/958599363857/1*_ID9YTxBia4tz-CaQrGcEw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*_ID9YTxBia4tz-CaQrGcEw.jpeg" /></p>

<p><img src="/assets/958599363857/1*DubJNBeqI-tWI5amx_VbbQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*DubJNBeqI-tWI5amx_VbbQ.jpeg" /></p>

<p>Narita Airport duty-free shops really have everything. Besides luxury goods, skincare products, tobacco, and alcohol, there are also fun souvenir shops with a wide variety of items. Prices are similar to or even cheaper than outside. If you haven’t bought enough, you can take advantage of last-minute shopping before boarding.</p>

<h4 id="1705-le-labo">17:05 Le Labo</h4>

<p><img src="/assets/958599363857/1*J2hChay4G6BPHCItaueHFA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*J2hChay4G6BPHCItaueHFA.jpeg" /></p>

<p>Actually, without specifically looking, I unexpectedly found La Lebo at Narita Airport in <a href="https://www.fasola.jp/en/searchByBrand.aspx" target="_blank">Fa-So-La DUTY FREE Cosmetics &amp; Perfumery</a>. According to the official website, both Terminal 1 and 2 have it.</p>

<p><img src="/assets/958599363857/1*9XCr-56Q5AlUCDRedP1cBA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*9XCr-56Q5AlUCDRedP1cBA.jpeg" /></p>

<blockquote>
  <p><em>Even more surprising is the price, <strong>50ml — ¥25,700</strong>, which is cheaper than the tax-refunded price of ¥28,500 within Japan.</em></p>
</blockquote>

<p><img src="/assets/958599363857/1*ar416ZWoAliERsR_Fg1GJQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ar416ZWoAliERsR_Fg1GJQ.jpeg" /></p>

<p>Finally, I couldn’t resist and bought a 26–30ml (airport exclusive size) bottle, spending NT$3,720.</p>

<h4 id="1730-head-to-the-boarding-gate-and-wait-for-boarding">17:30 Head to the boarding gate and wait for boarding</h4>

<p><img src="/assets/958599363857/1*5PiE4fENJsi1dynUUL08OQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*5PiE4fENJsi1dynUUL08OQ.jpeg" /></p>

<p>Gate 67B is in a remote corner, quite far away; unfortunately, there were no vending machines selling peach water along the way.</p>

<p><img src="/assets/958599363857/1*K8V9RnmpGnx--VJ8HTZOaw.webp" alt="" loading="lazy" decoding="async" width="945" height="1202" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTIwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*K8V9RnmpGnx--VJ8HTZOaw.png" /></p>

<h4 id="1750-boarding-goodbye-tokyo">17:50 Boarding, goodbye Tokyo!</h4>

<p><img src="/assets/958599363857/1*OxCGLuV_XrFB_c-BHM10gA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*OxCGLuV_XrFB_c-BHM10gA.jpeg" /></p>

<p><img src="/assets/958599363857/1*0qZy2nb8aXRxB1fmR-uCUA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*0qZy2nb8aXRxB1fmR-uCUA.jpeg" /></p>

<p>The aircraft model is BOEING 737–800, same as on the way here.</p>

<h4 id="narita-airport-was-extremely-crowded-originally-scheduled-to-depart-at-1810-but-finally-took-off-close-to-1900-after-waiting-in-line">Narita Airport was extremely crowded; originally scheduled to depart at 18:10, but finally took off close to 19:00 after waiting in line.</h4>

<p><img src="/assets/958599363857/1*LbcG5trqHcp_4GejuI6iDg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*LbcG5trqHcp_4GejuI6iDg.jpeg" /></p>

<h4 id="japan-airlines-free-in-flight-wifi">Japan Airlines Free In-flight WiFi</h4>

<p><img src="/assets/958599363857/1*1sP5H5jRMsQxm02W2TG6QA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*1sP5H5jRMsQxm02W2TG6QA.jpeg" /></p>

<p><img src="/assets/958599363857/1*eZ9beWgHlSQufFt-2Mnegg.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*eZ9beWgHlSQufFt-2Mnegg.jpeg" /></p>

<p>Accidentally discovered that the plane offers one hour of free WiFi.</p>

<p><img src="/assets/958599363857/1*jnrR9EmlSy0PcQkKe1eOGA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*jnrR9EmlSy0PcQkKe1eOGA.jpeg" /></p>

<p><img src="/assets/958599363857/1*5CozWE6P0BhIjE6vksu9cQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*5CozWE6P0BhIjE6vksu9cQ.jpeg" /></p>

<p>The airplane meal was also excellent, featuring curry pork cutlet rice and Haagen-Dazs dessert.</p>

<h4 id="2107-return-to-taiwan">21:07 Return to Taiwan</h4>

<h4 id="souvenirs">Souvenirs</h4>

<p><img src="/assets/958599363857/1*ILt_i5ONuyBnDnJXMg0BTw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/958599363857/1*ILt_i5ONuyBnDnJXMg0BTw.jpeg" /></p>

<p><img src="/assets/958599363857/1*SUJ70ie42fbD9Y5_LL86Sg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1271" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEyNzEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/958599363857/1*SUJ70ie42fbD9Y5_LL86Sg.jpeg" /></p>

<h4 id="thank-you-for-reading">Thank you for reading</h4>

<p>Thank you for joining me on this five-day trip to Tokyo. I hope the information in this article helps with your next travel adventure.</p>

<h4 id="more-travelogues">More Travelogues</h4>

<ul>
  <li>
    <p><a href="/posts/travel-journals/busan-travel-guide-8-day-itinerary-with-visit-busan-pass-for-seamless-exploration-8ace34a1a3d8/">[Travelogue] 8 Days 7 Nights Free Trip to Busan, South Korea in 2025</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/">[Travelogue] Second Visit to Kyushu in 2024: 9-Day Free Trip via Busan → Hakata Cruise Entry</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/san-in-kansai-7-day-solo-trip-explore-izumo-matsue-tottori-himeji-osaka-kobe-aacd5f5cacd1/">[Travelogue] 2024 San’in Region Shimane Izumo Matsue Tottori Himeji Osaka Kobe 7-Day Solo Trip</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/kyushu-10-day-solo-travel-guide-explore-fukuoka-nagasaki-kumamoto-efficiently-d78e0b15a08a/">[Travelogue] 2023 Kyushu 10-Day Solo Trip</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">[Travelogue] 2023 Hiroshima Okayama 6-Day Free Trip</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/nagoya-one-day-quick-trip-peach-aviation-flight-experience-and-travel-tips-7b8a0563c157/">[Travelogue] 9/11 One-Day Quick Trip to Nagoya</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">2023 Tokyo 5-Day Free Trip</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">2023 Kansai 8-Day Free Trip</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">LINE Bank to Firstrade Transfer｜Fast Deposit &amp;amp; 150 NT Fee Guide with Account Upgrade &amp;amp; Fee Subsidy</title>
    <link href="https://en.zhgchg.li/posts/zrealm-life/line-bank-to-firstrade-transfer-fast-deposit-150-nt-fee-guide-with-account-upgrade-fee-subsidy-e4d139fe0685/" rel="alternate" type="text/html" title="LINE Bank to Firstrade Transfer｜Fast Deposit &amp; 150 NT Fee Guide with Account Upgrade &amp; Fee Subsidy" />
    <published>2025-09-02T15:09:54+08:00</published>
    <updated>2026-01-04T11:45:25+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-life/e4d139fe0685</id><summary type="html">Discover how LINE Bank users can deposit to Firstrade quickly with only 150 TWD fees, upgrade accounts, set up scheduled transfers, and apply for fee subsidies through step-by-step instructions ensuring smooth and cost-effective transactions.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Life." /><category term="line-bank" /><category term="firstrade" /><category term="nasdaq" /><category term="line" /><category term="deposit" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/e4d139fe0685/1*u5qqz4o0pCvUTF16sYHNCg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-life/line-bank-to-firstrade-transfer-fast-deposit-150-nt-fee-guide-with-account-upgrade-fee-subsidy-e4d139fe0685/"><![CDATA[<h3 id="step-by-step-guide--quick-deposit-from-line-bank-to-firstrade-only-150-twd-fee-and-account-upgradedesignated-transferfee-subsidy-application-tutorial">Step-by-Step Guide — Quick Deposit from LINE Bank to Firstrade, Only 150 TWD Fee, and Account Upgrade/Designated Transfer/Fee Subsidy Application Tutorial</h3>

<p>Line Bank Deposit to Firstrade, Account Upgrade Agreement, and Fee Subsidy Complete Step-by-Step Guide</p>

<h3 id="advantages-of-line-bank">Advantages of LINE Bank</h3>

<ul>
  <li>
    <p>Easy Account Opening: If you have credit records with other banks, you can open an account online by simply filling out the form.</p>
  </li>
  <li>
    <p>Interbank transfers free of charge: up to 50 times/month</p>
  </li>
  <li>
    <p><strong>Seamless LINE Transfer Integration:</strong> No need to use iPASS MONEY for transfers between friends</p>
  </li>
  <li>
    <p>Currency exchange available 24/7</p>
  </li>
  <li>
    <p>Multiple flexible high-interest deposit plans available for you to mix and match</p>
  </li>
  <li>
    <p>Personal Loan: Fast / Integrated / Easy 10-Year Payment</p>
  </li>
  <li>
    <p>Securities: Invest in Taiwan stocks starting from 100 NTD</p>
  </li>
</ul>

<h4 id="line-bank-us-remittance-offer--outgoing-fee-nt150-incoming-fee-nt50"><a href="https://www.linebank.com.tw/wealth-investment/promotions/10" target="_blank">Line Bank US Remittance Offer — Outgoing Fee NT$150, Incoming Fee NT$50</a></h4>

<p><strong>[2026/01/01 Extension Extended Again]:</strong></p>

<blockquote>
  <p><em>Promotion Period: The original promotion period is from 2026/1/1 to 2026/6/30</em></p>
</blockquote>

<blockquote>
  <p><em>Promotion: Foreign currency remittance fee is NT$150 per transaction (originally NT$600), and foreign currency deposit fee is NT$50 per transaction (originally NT$400)</em></p>
</blockquote>

<blockquote>
  <p><em>Promotional Products: Foreign Currency Outward Remittance, Foreign Currency Inward Remittance</em></p>
</blockquote>

<p>This is our biggest incentive to use LINE Bank: until 2026/06/30, depositing funds into Firstrade using a LINE Bank account only incurs a 150 TWD handling fee, with no telegraphic transfer or other fees.</p>

<blockquote>
  <p><em>Memo: This promotion started from 2025/01–06 and has been extended twice due to positive feedback (extended from 2025/06 to 2025/12, then again from 2025/12 to 2026/06). <strong>Please note the dates!</strong></em></p>
</blockquote>

<h4 id="click-here-to-apply"><a href="https://www.linebank.com.tw/R/mgm-portal?campaignId=2&amp;uid=bfQqph" target="_blank">Click here to apply</a></h4>

<p><img src="/assets/e4d139fe0685/1*u5qqz4o0pCvUTF16sYHNCg.webp" alt="Click here to apply" loading="lazy" decoding="async" width="1200" height="632" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*u5qqz4o0pCvUTF16sYHNCg.png" /></p>

<p><a href="https://www.linebank.com.tw/R/mgm-portal?campaignId=2&amp;uid=bfQqph" target="_blank">Click here to apply</a></p>

<h4 id="disadvantages">Disadvantages</h4>

<p>This time, I had to set up a designated account. Since LINE Bank has no physical branches, it requires authentication via the National Identification Card. So, I had to visit the household registration office to apply for the card before using the service, which is more troublesome than going directly to a bank.</p>

<h3 id="advantages-of-firstrade">Advantages of Firstrade</h3>

<ul>
  <li>
    <p>Account Opening, Interface, and Features Are Simple and Friendly for US Stock Beginners</p>
  </li>
  <li>
    <p>Supports Chinese Interface</p>
  </li>
  <li>
    <p>Trade with Zero Fees</p>
  </li>
  <li>
    <p>Established Brokerage</p>
  </li>
</ul>

<blockquote>
  <p><em>Please make sure to use the name on your passport for account opening.</em></p>
</blockquote>

<h4 id="deposit-10000-usd-or-more-to-apply-for-a-wire-fee-rebate-up-to-25-usd"><a href="https://www.firstrade.com/zh-TW/accounts/wire-fee-rebate" target="_blank">Deposit $10,000 USD or more to apply for a wire fee rebate up to $25 USD</a></h4>

<p><img src="/assets/e4d139fe0685/1*-4JN2hY_QP3oZaon2BES0A.webp" alt="Deposit over $10,000 USD to apply for a fee rebate up to $25 USD (up to three times per month)" loading="lazy" decoding="async" width="464" height="441" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NjQiIGhlaWdodD0iNDQxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*-4JN2hY_QP3oZaon2BES0A.png" /></p>

<p><a href="https://www.firstrade.com/zh-TW/accounts/wire-fee-rebate" target="_blank">Deposit over $10,000 USD to apply for a wire fee rebate up to $25 USD</a> (up to three times per month)</p>

<blockquote>
  <p>一旦您的帳戶完全設置好，您就可以開始入金。</p>
</blockquote>

<h3 id="reference-timeline-for-fund-deposit-fastest-2-days-to-account">Reference Timeline for Fund Deposit (Fastest: 2 Days to Account)</h3>

<p><img src="/assets/e4d139fe0685/1*mhmGifQYaJacIOXwPqIC_A.webp" alt="Here is the timeline from my deposit to Firstrade on 10/14 until receiving the remittance subsidy." loading="lazy" decoding="async" width="1200" height="657" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY1NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*mhmGifQYaJacIOXwPqIC_A.png" /></p>

<p>Here is the timeline from my deposit to Firstrade on 10/14 to finally receiving the remittance subsidy.</p>

<ul>
  <li>
    <p>Please note this applies only when the designated account is already linked or the amount is less than 50,000. The first time you link a designated account, it will take +2 days to take effect.</p>
  </li>
  <li>
    <p>Line Bank shows the beneficiary bank has received the funds, but there is a time lag before Firstrade actually receives the money. (It likely waits until Firstrade completes processing during business hours before showing as received.)</p>
  </li>
  <li>
    <p>There may be a delay when applying for the remittance subsidy (for single transfers over $10,000) after Firstrade receives the funds. If you don’t see the record, please check again later or the next day.</p>
  </li>
</ul>

<h3 id="line-bank-deposit-to-firstrade">LINE Bank Deposit to Firstrade</h3>

<p>First, let’s introduce how to deposit funds from Line Bank to Firstrade.</p>

<h4 id="deposit-your-assets-into-firstrade"><a href="https://invest.firstrade.com/cgi-bin/main#/content/customerservice/fundaccount/" target="_blank">Deposit Your Assets into Firstrade</a></h4>

<p><img src="/assets/e4d139fe0685/1*QEJlVLLykQrqTB2UACx95Q.webp" alt="" loading="lazy" decoding="async" width="1015" height="506" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDE1IiBoZWlnaHQ9IjUwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*QEJlVLLykQrqTB2UACx95Q.png" /></p>

<p>(Please use the desktop version) After logging into Firstrade, select:</p>

<ol>
  <li>
    <p>Customer Service</p>
  </li>
  <li>
    <p>Account Funding</p>
  </li>
</ol>

<blockquote>
  <p><em>If the page is in English, you can select “Traditional Chinese” at the top to change the language.</em></p>
</blockquote>

<h4 id="get-wire-transfer-information"><a href="https://invest.firstrade.com/scripts/fundmgt/wire_instruction/" target="_blank">Get Wire Transfer Information</a></h4>

<p><img src="/assets/e4d139fe0685/1*66aA2JSjEWOLHdj4P6JwVA.webp" alt="" loading="lazy" decoding="async" width="1000" height="426" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAwIiBoZWlnaHQ9IjQyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*66aA2JSjEWOLHdj4P6JwVA.png" /></p>

<p>Select “Remittance” → “Remittance Information” to open a PDF file. You can save this file for easy reference when making deposits later:</p>

<p><img src="/assets/e4d139fe0685/1*11qj5ItxI9rxNrOqCVBw8g.webp" alt="" loading="lazy" decoding="async" width="781" height="1109" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODEiIGhlaWdodD0iMTEwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*11qj5ItxI9rxNrOqCVBw8g.png" /></p>

<ol>
  <li>
    <p>Beneficiary Bank Information — SWIFT Code</p>
  </li>
  <li>
    <p>Beneficiary Account Name</p>
  </li>
  <li>
    <p>Recipient’s English Address</p>
  </li>
  <li>
    <p>Recipient Account Number</p>
  </li>
  <li>
    <p><strong>Postscript</strong></p>
  </li>
</ol>

<h4 id="line-bank-app--remittance-to-overseas">LINE Bank App — Remittance to Overseas</h4>

<p><img src="/assets/e4d139fe0685/1*8zCXdiJWyPNCFW_NuIKNhQ.webp" alt="" loading="lazy" decoding="async" width="683" height="749" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODMiIGhlaWdodD0iNzQ5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*8zCXdiJWyPNCFW_NuIKNhQ.png" /></p>

<ol>
  <li>
    <p>Click “More”</p>
  </li>
  <li>
    <p>Scroll down to the “Foreign Exchange” section → Click “Remit Overseas”</p>
  </li>
  <li>
    <p>Choose the debit currency: “TWD or USD,” and enter the remittance amount.<br />
<strong>Please refer to the Line Bank account upgrade and designated account binding tutorial below first.</strong><br />
<strong>Please refer to the Line Bank account upgrade and designated account binding tutorial below first.</strong><br />
<strong>Please refer to the Line Bank account upgrade and designated account binding tutorial below first.</strong><br />
<strong>Otherwise, you can only remit up to 10,000 TWD.</strong></p>
  </li>
  <li>
    <p>Click “Go to Remittance”</p>
  </li>
</ol>

<h4 id="fee-rate-flat-150-until-20260630">Fee Rate (Flat $150 until 2026/06/30)</h4>

<ul>
  <li>
    <p>Remittance NTD $10,000 / Fee NTD $150 = 1.5%</p>
  </li>
  <li>
    <p>Remittance NTD $50,000 / Fee NTD $150 = 0.3%</p>
  </li>
  <li>
    <p>Remittance NTD $300,000 / Fee NTD $150 = 0.05%</p>
  </li>
  <li>
    <p>Remittance NTD $500,000 / Fee NTD $150 = 0.03%</p>
  </li>
</ul>

<blockquote>
  <p><em>The larger the amount per transfer, the lower the fee rate. <strong>For Firstrade single deposits over $10,000 USD (about $310,000 TWD), you can apply for a fee rebate of up to $25 USD, up to three times per month, which must be requested within 30 days after the transfer (see the tutorial at the end).</strong></em></p>
</blockquote>

<h4 id="enter-remittance-information">Enter Remittance Information</h4>

<p><img src="/assets/e4d139fe0685/1*5Bc1Gogt-HJRVdh_ygOPbg.webp" alt="" loading="lazy" decoding="async" width="791" height="789" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTEiIGhlaWdodD0iNzg5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*5Bc1Gogt-HJRVdh_ygOPbg.png" /></p>

<p><strong>Enter the information from the previous step <a href="https://invest.firstrade.com/scripts/fundmgt/wire_instruction/" target="_blank">Remittance Information</a>:</strong></p>

<ol>
  <li>
    <p>Beneficiary’s English Account Name: <code class="language-plaintext highlighter-rouge">Apex Clearing Corporation</code></p>
  </li>
  <li>
    <p>Beneficiary Bank Information — Automatically filled by entering the SWIFT code directly</p>
  </li>
  <li>
    <p>Enter search: <code class="language-plaintext highlighter-rouge">HATRUS44</code></p>
  </li>
  <li>
    <p>Choose the first one: <code class="language-plaintext highlighter-rouge">HATRUS44 — BMO Bank N.A CHICAGO</code></p>
  </li>
  <li>
    <p>Recipient account number: <code class="language-plaintext highlighter-rouge">1617711</code> <strong>(may change, please refer to actual information)</strong></p>
  </li>
</ol>

<p><strong>Scroll down to continue input:</strong></p>

<p><img src="/assets/e4d139fe0685/1*i2kQnOFRD-azviSj3fCfyA.webp" alt="" loading="lazy" decoding="async" width="358" height="762" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNTgiIGhlaWdodD0iNzYyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*i2kQnOFRD-azviSj3fCfyA.png" /></p>

<ol>
  <li>
    <p>Recipient Type: <code class="language-plaintext highlighter-rouge">Private (Corporate, Individual)</code></p>
  </li>
  <li>
    <p>Recipient’s English Address — Country: select <code class="language-plaintext highlighter-rouge">United States</code></p>
  </li>
  <li>
    <p>Recipient’s English Address — City/District: Enter <code class="language-plaintext highlighter-rouge">Texas</code></p>
  </li>
  <li>
    <p>Recipient’s English Address — Detailed address: <code class="language-plaintext highlighter-rouge">One Dallas Center 350 N. St Paul, Suite 1300, Dallas, TX 75201</code> (Refer to the previous step’s remittance information)</p>
  </li>
  <li>
    <p><strong>⭐️️️️️️ The most important remark, please be sure to fill it in</strong><br />
<strong>⭐️️️️️️ The most important remark, please be sure to fill it in</strong><br />
<strong>⭐️️️️️️ The most important remark, please be sure to fill it in</strong><br />
<strong>Enter according to the previous remittance information:</strong> <code class="language-plaintext highlighter-rouge">8-digit account number + account holder name</code><br />
As shown in the picture above: <code class="language-plaintext highlighter-rouge">12345678 ZXXXG CXXXG LI</code></p>
  </li>
  <li>
    <p>Click “Next”</p>
  </li>
</ol>

<h4 id="nature-of-remittance">Nature of Remittance</h4>

<p><img src="/assets/e4d139fe0685/1*jrDAKxcP5jxjHQ41Wf5LWQ.webp" alt="" loading="lazy" decoding="async" width="841" height="794" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDEiIGhlaWdodD0iNzk0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*jrDAKxcP5jxjHQ41Wf5LWQ.png" /></p>

<ul>
  <li>
    <p>Select “262 Invest in Foreign Equity Securities”</p>
  </li>
  <li>
    <p>Click “Agree” to acknowledge the important notes</p>
  </li>
</ul>

<h4 id="remittance-confirmation">Remittance Confirmation</h4>

<p><img src="/assets/e4d139fe0685/1*yiYOU7hagogCvHF1AUxEHQ.webp" alt="" loading="lazy" decoding="async" width="743" height="686" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDMiIGhlaWdodD0iNjg2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*yiYOU7hagogCvHF1AUxEHQ.png" /></p>

<p>The final step is to verify that the information entered is correct, especially the recipient’s account details and the <strong>memo section</strong>.</p>

<ul>
  <li>
    <p>If the transaction details are not confirmed within 60 seconds, the exchange rate quote may be updated.</p>
  </li>
  <li>
    <p>No problem, just click “Next” to <strong>complete the remittance application</strong>.</p>
  </li>
</ul>

<h4 id="completed">Completed</h4>

<p><img src="/assets/e4d139fe0685/1*jaADV3k1wdNZuz2zpogLQA.webp" alt="" loading="lazy" decoding="async" width="869" height="1884" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjkiIGhlaWdodD0iMTg4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*jaADV3k1wdNZuz2zpogLQA.jpeg" /></p>

<p>Seeing this screen means your remittance application is complete. Please wait for the deposit notification.</p>

<blockquote>
  <p><em>The remittance amounts shown in the images are for reference only. (Some images show 50,000, others 100,000)</em></p>
</blockquote>

<h3 id="line-bank-remittance-deposit-firstrade-progress-check">LINE Bank Remittance Deposit Firstrade Progress Check</h3>

<p><img src="/assets/e4d139fe0685/1*nEUD0C8nOO1E2uS9p6u8rw.webp" alt="" loading="lazy" decoding="async" width="1114" height="774" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE0IiBoZWlnaHQ9Ijc3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*nEUD0C8nOO1E2uS9p6u8rw.png" /></p>

<p>You can find the foreign currency transfer notification in the top right notification area of the LINE Bank App. Click it to check the transfer progress.</p>

<ul>
  <li>
    <p>On non-holidays, the transfer can be completed on the same day at the earliest. On holidays, it will be processed on the second business day after.</p>
  </li>
  <li>
    <p><strong>If the beneficiary information is incorrect, the payment will be rejected, and you will need to pay a return fee and resend the transfer</strong></p>
  </li>
</ul>

<h4 id="apply-for-remittance-slip-transaction-recordreceipt">Apply for Remittance Slip (Transaction Record/Receipt)</h4>

<p><img src="/assets/e4d139fe0685/1*EH8g25Pxoewchc5gt7qK_w.webp" alt="" loading="lazy" decoding="async" width="1179" height="2556" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc5IiBoZWlnaHQ9IjI1NTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/e4d139fe0685/1*EH8g25Pxoewchc5gt7qK_w.jpeg" /></p>

<p><img src="/assets/e4d139fe0685/1*DMrWpw9JdcFVOYuEWgNmkw.webp" alt="" loading="lazy" decoding="async" width="790" height="1031" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTAiIGhlaWdodD0iMTAzMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*DMrWpw9JdcFVOYuEWgNmkw.png" /></p>

<p>In the Line Bank App progress notification (as mentioned earlier), scroll down to find the “Request Receipt/Transaction Certificate” button. Clicking it will send the remittance slip to your email, which you can open using your ID number.</p>

<h4 id="the-receiving-bank-has-received-the-funds-but-firstrade-hasnt-seen-the-money">The receiving bank has received the funds, but Firstrade hasn’t seen the money?</h4>

<p>This is normal. Although the remittance status on LINE Bank shows “Beneficiary bank has received the funds” and the transfer is complete, Firstrade still needs to wait for US business hours to confirm the funds.</p>

<p>As shown in the image above, although the funds were received on 8/27 at 9:00 AM, Firstrade only confirmed the funds and sent the deposit notification email at 8/28 2:10 AM:</p>

<p><img src="/assets/e4d139fe0685/1*7g0qrnxcoE5aYYz0erJrpw.webp" alt="" loading="lazy" decoding="async" width="625" height="715" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjUiIGhlaWdodD0iNzE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*7g0qrnxcoE5aYYz0erJrpw.png" /></p>

<p>At this point, log in to Firstrade to see the funds credited, and you can start trading US stocks:</p>

<p><img src="/assets/e4d139fe0685/1*83_EXDsxQpwqgfmkHogYbw.webp" alt="" loading="lazy" decoding="async" width="980" height="704" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODAiIGhlaWdodD0iNzA0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*83_EXDsxQpwqgfmkHogYbw.png" /></p>

<p>“My Account” → “Account Records” will also have deposit records available for inquiry.</p>

<h3 id="line-bank-account-upgrade-and-binding-designated-account-tutorial">Line Bank Account Upgrade and Binding Designated Account Tutorial</h3>

<p><img src="/assets/e4d139fe0685/1*ZbYDlcVZlCEE4fdKgeaWWQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="410" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQxMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*ZbYDlcVZlCEE4fdKgeaWWQ.png" /></p>

<p><strong>Benefits Comparison:</strong></p>

<ul>
  <li>
    <p>(After account opening) Basic account: Maximum NT$10,000 per transaction, NT$30,000 per day, NT$50,000 per month.</p>
  </li>
  <li>
    <p>(After customer service video call) Account upgrade: Maximum NT$50,000 per transaction, NT$100,000 per day, NT$200,000 per month.</p>
  </li>
  <li>
    <p>(After Natural Person Certificate + Customer Service Video Verification) Designated Account: Maximum NT$500,000 per transaction.</p>
  </li>
</ul>

<h4 id="line-bank-account-upgrade">Line Bank Account Upgrade</h4>

<p>This step actually just raises the basic limit to other banks. <strong>(No need for the National ID card here.)</strong></p>

<p><img src="/assets/e4d139fe0685/1*aFqGx67be74ZZr75wUvT-w.webp" alt="" loading="lazy" decoding="async" width="1117" height="779" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE3IiBoZWlnaHQ9Ijc3OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*aFqGx67be74ZZr75wUvT-w.png" /></p>

<p>Open the LINE Bank App, scroll down under “More” to find “Others” → “Account Upgrade,” select the type of account to upgrade → choose <strong>2. Video Verification “Start Verification &gt;”.</strong></p>

<ul>
  <li>
    <p>Please ensure your internet connection is stable.</p>
  </li>
  <li>
    <p>Ensure video call is available</p>
  </li>
</ul>

<p>Clicking will start a video call with customer service. You will need to have a video conversation with the representative, who may verify your account information (e.g., ask about your current balance). Once confirmed, your account upgrade will be complete.</p>

<h3 id="set-up-firstrade-remittance-account">Set Up Firstrade Remittance Account</h3>

<p>It is recommended to directly set the Firstrade remittance account as the designated account for convenience, speed, and security.</p>

<ul>
  <li>
    <p>The designated account will take 2 days to become effective.</p>
  </li>
  <li>
    <p>The designated account must first pass verification with a National Identification Certificate.</p>
  </li>
  <li>
    <p><strong>The citizen digital certificate verification only supports cards starting with TP07 that contain an NFC chip</strong></p>
  </li>
</ul>

<h4 id="applying-for-a-citizen-digital-certificate">Applying for a Citizen Digital Certificate</h4>

<p>Please first complete the online <a href="https://moica.nat.gov.tw/rac_form.html" target="_blank">Natural Person Application Form</a>:</p>

<p><img src="/assets/e4d139fe0685/1*4xi8pHeUNAPZKijWiXfj1Q.webp" alt="Natural Person Application Form" loading="lazy" decoding="async" width="727" height="1237" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjciIGhlaWdodD0iMTIzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*4xi8pHeUNAPZKijWiXfj1Q.png" /></p>

<p><a href="https://moica.nat.gov.tw/rac_form.html" target="_blank">Individual Application Form</a></p>

<p>After submission, you can complete the process at any Household Registration Office counter within 7 days (it does not have to be your registered residence).</p>

<ul>
  <li>
    <p><strong>Processing Fee: $250 NTD</strong></p>
  </li>
  <li>
    <p>On-site processing and issuance</p>
  </li>
  <li>
    <p>Few people apply for the Citizen Digital Certificate at the Household Registration Office, which usually remains open during lunch.</p>
  </li>
</ul>

<p><img src="/assets/e4d139fe0685/1*zrmIss8K7vrn18f_CGffDg.webp" alt="" loading="lazy" decoding="async" width="1200" height="873" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*zrmIss8K7vrn18f_CGffDg.png" /></p>

<h4 id="line-bank-adds-designated-firstrade-overseas-remittance-account">Line Bank Adds Designated Firstrade Overseas Remittance Account</h4>

<p><img src="/assets/e4d139fe0685/1*Mi9tRTDMpubsVfWF6AXidg.webp" alt="" loading="lazy" decoding="async" width="1127" height="767" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTI3IiBoZWlnaHQ9Ijc2NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*Mi9tRTDMpubsVfWF6AXidg.png" /></p>

<p>Open the LINE Bank App → “More” → scroll down to Foreign Exchange, tap “My Remittance Records” → “Remittance Designated Account” → “Add Designated Account”.</p>

<h4 id="first-time-use--complete-account-verification">First Time Use — Complete Account Verification</h4>

<p><img src="/assets/e4d139fe0685/1*nLPxsTJ1ALuv4hLZPcdqmw.webp" alt="" loading="lazy" decoding="async" width="1179" height="1199" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc5IiBoZWlnaHQ9IjExOTkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/e4d139fe0685/1*nLPxsTJ1ALuv4hLZPcdqmw.jpeg" /></p>

<p>The first binding requires completing both the “Citizen Digital Certificate Verification” and “Video Verification.” The video verification for account upgrade must be done via a video call with customer service to verify account information.</p>

<p><strong>For the Citizen Digital Certificate, you need to use the newly issued certificate card and complete the card-to-card verification:</strong></p>

<p><img src="/assets/e4d139fe0685/1*-N0scmyLMlqgPHbuDpmD-Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="579" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*-N0scmyLMlqgPHbuDpmD-Q.png" /></p>

<blockquote>
  <p><em>The NFC range of the Citizen Digital Certificate is quite short. I tried several times before it worked; on iPhone, hold the camera near the center of the card and slightly move the card left and right. After a few tries, the verification will succeed.</em></p>
</blockquote>

<h4 id="add-a-designated-account">Add a Designated Account</h4>

<p><img src="/assets/e4d139fe0685/1*RvHTvjTzjap4M09RG5Q8xQ.webp" alt="" loading="lazy" decoding="async" width="749" height="765" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDkiIGhlaWdodD0iNzY1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*RvHTvjTzjap4M09RG5Q8xQ.png" /></p>

<p>Enter the account information as introduced earlier in the remittance details. After confirming everything is correct, click “Next.”</p>

<p><img src="/assets/e4d139fe0685/1*2VbwWyl9zQCY9p2fVVoL2w.webp" alt="" loading="lazy" decoding="async" width="1143" height="786" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQzIiBoZWlnaHQ9Ijc4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*2VbwWyl9zQCY9p2fVVoL2w.png" /></p>

<p>You need to complete two-factor SMS verification by switching to the newly received message and entering the four-digit code; wait about 1–2 minutes, and receiving a confirmation SMS means the verification is complete.</p>

<p><img src="/assets/e4d139fe0685/1*WmXzGCmOWMkOxwMhYJ-mfQ.webp" alt="" loading="lazy" decoding="async" width="715" height="791" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MTUiIGhlaWdodD0iNzkxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*WmXzGCmOWMkOxwMhYJ-mfQ.png" /></p>

<p>After successful verification, the designated account will be added successfully!</p>

<ul>
  <li>
    <p>After adding, an exclamation mark will appear at the top right corner, indicating it is not yet effective.</p>
  </li>
  <li>
    <p><strong>Takes effect after 2 days.</strong></p>
  </li>
</ul>

<h4 id="line-bank-firstrade-designated-account-remittance">Line Bank Firstrade Designated Account Remittance</h4>

<p><img src="/assets/e4d139fe0685/1*TfcH3RFr1wR7XKjKjqnvJQ.webp" alt="" loading="lazy" decoding="async" width="740" height="766" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDAiIGhlaWdodD0iNzY2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*TfcH3RFr1wR7XKjKjqnvJQ.png" /></p>

<p>“My Remittance Records” → “Remittance Designated Account” → Select Account → Enter Amount → Choose “Remit via Designated Account” → All information will be filled automatically.</p>

<h3 id="firstrade-fee-reimbursement-application-guide">Firstrade Fee Reimbursement Application Guide</h3>

<ul>
  <li>
    <p>Remittance Amount Requirement: <strong>Single transaction</strong> over $10,000 USD</p>
  </li>
  <li>
    <p>Maximum subsidy amount: $25 USD</p>
  </li>
  <li>
    <p>Limit: Up to 3 times per month</p>
  </li>
  <li>
    <p>Application period: Apply within 30 days after remittance</p>
  </li>
</ul>

<p><img src="/assets/e4d139fe0685/1*TFU213ZsLmlrAt7Wj7Vmtw.webp" alt="" loading="lazy" decoding="async" width="1032" height="450" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDMyIiBoZWlnaHQ9IjQ1MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*TFU213ZsLmlrAt7Wj7Vmtw.png" /></p>

<p><img src="/assets/e4d139fe0685/1*JbqxnPqZkNsZuqvSGpkRqw.webp" alt="" loading="lazy" decoding="async" width="1025" height="695" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI1IiBoZWlnaHQ9IjY5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*JbqxnPqZkNsZuqvSGpkRqw.png" /></p>

<p><a href="https://invest.firstrade.com/cgi-bin/main#/content/customerservice/promos/wirerebate/" target="_blank">After logging into Firstrade, select “Customer Service” → “Special Offers” → “Wire Fee Rebate.”</a></p>

<p><img src="/assets/e4d139fe0685/1*3UDeTN3dnh3fnM9HMdIurw.webp" alt="" loading="lazy" decoding="async" width="866" height="674" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjYiIGhlaWdodD0iNjc0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*3UDeTN3dnh3fnM9HMdIurw.png" /></p>

<p>Select remittance information.</p>

<blockquote>
  <p><strong><em>If no remittance information appears, please wait a few more days for the data to arrive. (It may not show up immediately after the funds arrive)</em></strong></p>
</blockquote>

<p><img src="/assets/e4d139fe0685/1*U598Ew1kBCBC4fxCjLVv_w.webp" alt="" loading="lazy" decoding="async" width="872" height="918" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iOTE4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*U598Ew1kBCBC4fxCjLVv_w.png" /></p>

<p><img src="/assets/e4d139fe0685/1*-KOs-E0FQ_Wy80GLl581OA.webp" alt="" loading="lazy" decoding="async" width="866" height="766" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjYiIGhlaWdodD0iNzY2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*-KOs-E0FQ_Wy80GLl581OA.png" /></p>

<p><strong>Select Remittance Record Data:</strong></p>

<ul>
  <li>
    <p>Remitting Financial Institution Name: <code class="language-plaintext highlighter-rouge">LINE Bank Taiwan Limited</code></p>
  </li>
  <li>
    <p>Remittance fee charged by the financial institution: <code class="language-plaintext highlighter-rouge">25</code></p>
  </li>
</ul>

<p>Click “Submit” → “Confirm” to complete the application.</p>

<p><img src="/assets/e4d139fe0685/1*RYYu26VBBJhxT76Ht1toMQ.webp" alt="" loading="lazy" decoding="async" width="691" height="388" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTEiIGhlaWdodD0iMzg4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*RYYu26VBBJhxT76Ht1toMQ.png" /></p>

<p>It usually takes about 2–3 business days to receive the remittance subsidy.</p>

<p><img src="/assets/e4d139fe0685/1*L-g13ZQ2uS2PqHRyomDD4g.webp" alt="" loading="lazy" decoding="async" width="976" height="124" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzYiIGhlaWdodD0iMTI0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*L-g13ZQ2uS2PqHRyomDD4g.png" /></p>

<p>You can check the fee subsidy records in “My Account” → “Account Records”.</p>

<blockquote>
  <p><em>Firstrade has some issues, causing about a one-day delay: the application status disappears, and the account record does not show the subsidy; after about another day, the deposit record appears!</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Although LINE Bank only charges us TWD $150, the actual remittance fee exceeds USD $25, so we still subsidize USD $25.</em></strong></p>
</blockquote>

<h3 id="disclaimer">Disclaimer</h3>

<p>This article is for personal experience sharing only and does not guarantee complete accuracy. Please rely on actual conditions when using. The author is not responsible for any losses incurred.</p>

<h3 id="anti-fraud-section">Anti-Fraud Section</h3>

<h4 id="two-factor-authentication">Two-Factor Authentication</h4>

<p><img src="/assets/e4d139fe0685/1*jPfNxJGPBCY20do8y8Pz_g.webp" alt="" loading="lazy" decoding="async" width="1016" height="736" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDE2IiBoZWlnaHQ9IjczNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/e4d139fe0685/1*jPfNxJGPBCY20do8y8Pz_g.png" /></p>

<p>Be sure to go to “My Account” → “Settings” → Enable “Authenticator App”.</p>

<p>This way, logging in on an unfamiliar device requires completing two-factor authentication first.</p>

<h4 id="email-and-link-domains">Email and Link Domains</h4>

<p><img src="/assets/e4d139fe0685/1*Rqr5E1ptXgjXh686EAr_UQ.webp" alt="" loading="lazy" decoding="async" width="922" height="646" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjIiIGhlaWdodD0iNjQ2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/e4d139fe0685/1*Rqr5E1ptXgjXh686EAr_UQ.png" /></p>

<p><img src="/assets/e4d139fe0685/1*R_zbtsoMNVK6ryeSjkMzIQ.webp" alt="" loading="lazy" decoding="async" width="564" height="56" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjQiIGhlaWdodD0iNTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/e4d139fe0685/1*R_zbtsoMNVK6ryeSjkMzIQ.png" /></p>

<p>No matter how authentic the email looks, always check if the sender is <strong>firstrade.com</strong> and do not click any links in the email. <strong>If you must click, double-check that the URL is from firstrade.com.</strong></p>

<blockquote>
  <p><em>Technically, <strong>scam emails and fake websites can look exactly the same</strong>, making it hard to tell real from fake; regarding URLs:</em></p>
</blockquote>

<blockquote>
  <p><strong><em>If it’s not firstrade.com, it’s a scam! It’s a scam! It’s a scam!</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>If it’s not firstrade.com, it’s a scam! It’s a scam! It’s a scam!</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>If it’s not firstrade.com, it’s a scam! It’s a scam! It’s a scam!</em></strong></p>
</blockquote>]]></content>
  </entry><entry>
    <title type="html">Busan Travel Guide｜8-Day Itinerary with VISIT BUSAN PASS for Seamless Exploration</title>
    <link href="https://en.zhgchg.li/posts/travel-journals/busan-travel-guide-8-day-itinerary-with-visit-busan-pass-for-seamless-exploration-8ace34a1a3d8/" rel="alternate" type="text/html" title="Busan Travel Guide｜8-Day Itinerary with VISIT BUSAN PASS for Seamless Exploration" />
    <published>2025-08-13T12:38:54+08:00</published>
    <updated>2025-08-17T10:55:29+08:00</updated>
    <id>https://en.zhgchg.li/posts/travel-journals/8ace34a1a3d8</id><summary type="html">Explore Busan from south to north using the VISIT BUSAN PASS to access top attractions and local cuisine effortlessly, creating your personalized 8-day travel experience in South Korea’s vibrant coastal city.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="Travel Journals" /><category term="lifestyle" /><category term="travel" /><category term="busan" /><category term="korea" /><category term="travel-writing" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/8ace34a1a3d8/1*06AC2xvA-jb4pjaIQg6fbQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/travel-journals/busan-travel-guide-8-day-itinerary-with-visit-busan-pass-for-seamless-exploration-8ace34a1a3d8/"><![CDATA[<h3 id="travelogue-8-days-7-nights-free-trip-to-busan-south-korea-2025">[Travelogue] 8 Days 7 Nights Free Trip to Busan, South Korea 2025</h3>

<p>From south to north, use the VISIT BUSAN PASS to explore beautiful sights and enjoy delicious food, starting your personalized Busan journey.</p>

<p><img src="/assets/8ace34a1a3d8/1*06AC2xvA-jb4pjaIQg6fbQ.webp" alt="Gwangandaegyo Bridge" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*06AC2xvA-jb4pjaIQg6fbQ.jpeg" /></p>

<p>Gwangandaegyo Bridge</p>

<h4 id="background">Background</h4>

<p>Last year, during the <a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/"><strong>2024 “Second Visit to Kyushu 9-Day Free Travel, Entering via Busan→Hakata Cruise”</strong></a>, I entered Japan Fukuoka by ship from Busan, South Korea. That time, I only spent a short morning in Busan, briefly visiting Haedong Yonggungsa Temple and Haeundae. This July–August, with some work breaks, I set off again to explore Busan more deeply, this time with my equally free-spirited Pinkoi colleague Sean.</p>

<p>Date: 2025/07/29–08/05</p>

<h3 id="tldr">TL;DR</h3>

<p>Summary: Although Busan, South Korea, lacks the quietness and order of Japan, it offers a relaxed atmosphere and warmth of human connection.</p>

<p><strong>Must-Visit Attractions:</strong></p>

<ul>
  <li>
    <p>[North] <a href="https://www.kkday.com/zh-tw/product/152412-gijang-skyline-luge-ticket-busan-south-korea?cid=19365" target="_blank">Skyline Luge</a></p>
  </li>
  <li>
    <p>[North] <a href="https://www.kkday.com/zh-tw/product/134684-yacht-holic-busan-yacht-public-tour-gwangan-ri-haeundae-south-korea" target="_blank">Busan Yacht Tour Y Holic</a></p>
  </li>
  <li>
    <p>[North] <a href="https://www.kkday.com/zh-tw/product/123012-haeundae-blueline-park-sky-capsule-beach-train-ticket?cid=19365" target="_blank">Capsule Train</a></p>
  </li>
  <li>
    <p>[North] <a href="https://www.kkday.com/zh-tw/product/12213-busan-spa-land-centum-city-ticket?cid=19365" target="_blank">Jjimjilbang</a></p>
  </li>
  <li>
    <p>[North] Haedong Yonggungsa Temple</p>
  </li>
  <li>
    <p>[North] Shinsegae Department Store</p>
  </li>
  <li>
    <p>[South] Yeongdo Bridge</p>
  </li>
</ul>

<p><strong>Must Eat:</strong></p>

<ul>
  <li>
    <p>[North] <a href="https://naver.me/GEiuqenG" target="_blank">Grilled Eel — PUNGCHEONMAN</a></p>
  </li>
  <li>
    <p>[North] <a href="https://naver.me/x9zcuKNm" target="_blank">Salt Bread — Jayeondo saltbread</a></p>
  </li>
  <li>
    <p>[South] <a href="https://naver.me/xHmnku7F" target="_blank">Throat Pot Lid BBQ — Moggumung Nampo Branch</a></p>
  </li>
  <li>
    <p>[South] <a href="https://naver.me/GEiuXPsf" target="_blank">Milgot</a> Red Bean Cream Mugwort Glutinous Rice Cake</p>
  </li>
</ul>

<p><strong>Must-Have:</strong></p>

<ul>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank"><strong>Korea Busan Pass VISIT BUSAN PASS</strong></a> <strong>(A must-have, saves a lot of money)</strong></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank"><strong>Korea Busan Pass VISIT BUSAN PASS</strong></a> <strong>(A must-have, saves a lot of money)</strong></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank"><strong>Korea Busan Pass VISIT BUSAN PASS</strong></a> <strong>(A must-have, saves a lot of money)</strong></p>
  </li>
</ul>

<blockquote>
  <p><em>Things I didn’t get to eat this time: seafood pancake (missed), pork soup rice (<a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/">had it last time</a>), live octopus (don’t eat raw, many on IG got food poisoning), marinated crab (don’t eat raw)</em></p>
</blockquote>

<h3 id="pre-trip-preparation">Pre-Trip Preparation</h3>

<h4 id="transportation">Transportation</h4>

<h4 id="flight-tickets">Flight Tickets</h4>

<p><img src="/assets/8ace34a1a3d8/1*UkWjvZsjTVT4z6IbvwBBFQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="634" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjYzNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*UkWjvZsjTVT4z6IbvwBBFQ.png" /></p>

<p>Busan Air (<strong>includes 15KG checked baggage</strong>).</p>

<p>Price: <strong>NT$6,728 / person</strong>.</p>

<ul>
  <li>
    <p>Departure: 07/29 (Tue) Taipei Taoyuan (TPE) 16:25 -&gt; Busan Gimhae (PUS) 19:55</p>
  </li>
  <li>
    <p>Return: 08/05 (Tue) Busan Gimhae (PUS) 10:50 -&gt; Taipei Taoyuan (TPE) 12:35</p>
  </li>
</ul>

<p>Total days: 8, Actual travel days: 6 (The first and last days are basically just for flights).</p>

<h4 id="transportation-card">Transportation Card</h4>

<p><img src="/assets/8ace34a1a3d8/1*AnOi_kWaK4ET_udnUwB6nQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="702" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjcwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*AnOi_kWaK4ET_udnUwB6nQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*TwuSm6wAS-J7_s6nLrnKLg.webp" alt="KKday Physical Transit Card" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*TwuSm6wAS-J7_s6nLrnKLg.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/18161-t-money-public-transit-card-pick-up-at-taiwan-taoyuan-international-airport-south-korea?cid=19365" target="_blank">KKday Physical Transportation Card</a></p>

<p>This year, iPhone can directly use the Tmoney in the Apple Wallet transit card, allowing easy top-up and use; however, I had already <a href="https://www.kkday.com/zh-tw/product/18161-t-money-public-transit-card-pick-up-at-taiwan-taoyuan-international-airport-south-korea?cid=19365" target="_blank">topped up a physical card purchased last year</a>, so I didn’t use it.</p>

<p>Starting this year, you can also top up your transportation card directly through the <a href="https://wowpass.io/zh-TW" target="_blank">Wowpass App</a>. The process involves first loading money onto Wowpass using a credit card, then transferring funds from Wowpass to Tmoney. This method has more steps and requires a handling fee.</p>

<p><img src="/assets/8ace34a1a3d8/1*Sz8gh4I3s8sx5_0fsyT4oA.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Sz8gh4I3s8sx5_0fsyT4oA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*PzzH6uT3djtj-egP-1A3YA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PzzH6uT3djtj-egP-1A3YA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*C5s-bq9vOTiwDkIIbGHQig.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*C5s-bq9vOTiwDkIIbGHQig.jpeg" /></p>

<blockquote>
  <p>For any type, it is recommended to top up in Taiwan using a credit card (since some machines may only support certain card issuers) or bring some cash to Korea to top up at the machines. <a href="https://map.naver.com/p/" target="_blank">Naver Map</a></p>
</blockquote>

<blockquote>
  <p><a href="https://map.naver.com/p/" target="_blank"><strong><em>Naver Map is a must-use in Korea.</em></strong></a></p>
</blockquote>

<blockquote>
  <p><strong><em><a href="https://map.naver.com/p/" target="_blank">Always use Naver Map in Korea.</a></em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em><a href="https://map.naver.com/p/" target="_blank">Always use Naver Map in Korea.</a></em></strong></p>
</blockquote>

<h4 id="bus-rules">Bus Rules</h4>

<p><img src="/assets/8ace34a1a3d8/1*SdjC7IxAtx08bLLFngwnzA.webp" alt="" loading="lazy" decoding="async" width="928" height="720" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iNzIwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*SdjC7IxAtx08bLLFngwnzA.png" /></p>

<ul>
  <li>
    <p><strong>Bus rules in Busan: Carry-on luggage larger than 20 inches and handheld beverage cups are prohibited (holding but not drinking is also not allowed; you will be stopped).</strong></p>
  </li>
  <li>
    <p>For buses, always <strong>board from the front door and exit from the rear door</strong>; the card reader at the front door is usually located behind the driver’s seat.</p>
  </li>
  <li>
    <p><strong>Also, stores do not accept outside trash, so if you bring your own iced drink, it’s best to finish it and throw it away at the attraction or department store before leaving, otherwise it can be troublesome.</strong></p>
  </li>
  <li>
    <p>For two or more people, you can just take a taxi comfortably. Busan has many taxis that are cheap, new, and very convenient.</p>
  </li>
  <li>
    <p>Please confirm the direction before entering the subway station</p>
  </li>
</ul>

<h4 id="korea-busan-pass-visit-busan-pass"><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank">Korea Busan Pass VISIT BUSAN PASS</a></h4>

<p>Busan Pass comes in two types: unlimited use for a limited time (24/48 hours) and limited use within 180 days.</p>

<p><img src="/assets/8ace34a1a3d8/1*87TkvJQSQbrD1GcZsN3i7w.webp" alt="" loading="lazy" decoding="async" width="1252" height="1124" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjUyIiBoZWlnaHQ9IjExMjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*87TkvJQSQbrD1GcZsN3i7w.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ujmSSMVX5IINmCmCknawlQ.webp" alt="KKday 2025/08 Busan Pass Information" loading="lazy" decoding="async" width="1058" height="1240" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDU4IiBoZWlnaHQ9IjEyNDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*ujmSSMVX5IINmCmCknawlQ.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank">KKday 2025/08 Busan Pass Information</a></p>

<ul>
  <li>
    <p>Because the itinerary is spread over these 8 days and the attractions we want to visit are limited, we <strong>chose to buy two BIG5 passes.</strong></p>
  </li>
  <li>
    <p>Price: <strong>NT$1,380 * 2 tickets = NT$2,760</strong></p>
  </li>
  <li>
    <p>Show the order QR code directly on the electronic version for use</p>
  </li>
</ul>

<p><strong>Actual usage is as follows:</strong></p>

<p><img src="/assets/8ace34a1a3d8/1*ItIzgsZFoDEzwSLJK870Dw.webp" alt="" loading="lazy" decoding="async" width="1020" height="826" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDIwIiBoZWlnaHQ9IjgyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ItIzgsZFoDEzwSLJK870Dw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Q1L0AgRhDM9Eh2WiByj06w.webp" alt="KKday 2025/08 Busan Pass Information" loading="lazy" decoding="async" width="1000" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAwIiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Q1L0AgRhDM9Eh2WiByj06w.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank">KKday 2025/08 Busan Pass Information</a></p>

<ul>
  <li>
    <p>One less blue spot (Did not visit Taejongdae)</p>
  </li>
  <li>
    <p>I rode the Haeundae Coastal Train once per day for two days.</p>
  </li>
</ul>

<h4 id="busan-yacht-tour-y-holic-reservation-️">Busan Yacht Tour Y Holic Reservation ⚠️</h4>

<p>To use the Busan Pass for the Busan Yacht Tour, you need to make a reservation with the boat operator first. We chose Y Holic as an example.</p>

<p><strong>After purchasing the Pass, contact the boat operator through the following channels:</strong></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IG: @yachtholic_info (We contacted through Line)
LINE: @yachtholic
Email: yh@yachtholic.com
WeChat: yachtholic
</code></pre></div></div>

<p>Provide the number of people, date, time slot, email or contact information, and the QR Code for the Busan Pass.</p>

<p>Waiting for confirmation. Once approved, the reservation is successful!</p>

<ul>
  <li>
    <p>For the night session (18:00), <strong>an additional fee of 5,000 KRW per person</strong> is required. You can pay on-site or transfer in advance.</p>
  </li>
  <li>
    <p><strong>Arrive 30 minutes early to queue for identity verification</strong></p>
  </li>
  <li>
    <p>We planned to watch the sunset around 18:30.</p>
  </li>
</ul>

<blockquote>
  <p><a href="https://www.kkday.com/zh-tw/product/134684-yacht-holic-busan-yacht-public-tour-gwangan-ri-haeundae-south-korea?cid=19365" target="_blank"><em>If you don’t want to buy the Pass, you can directly book the yacht tour.</em></a></p>
</blockquote>

<h4 id="korea-busanhaeundae-blue-line-park-coastal-train--sky-capsule-train-ticket"><a href="https://www.kkday.com/zh-tw/product/123012-haeundae-blueline-park-sky-capsule-beach-train-ticket?cid=19365" target="_blank">Korea Busan｜Haeundae Blue Line Park Coastal Train &amp; Sky Capsule Train Ticket</a></h4>

<p>Please note: <strong>The Coastal Train and the Capsule Train are different.</strong> Do not buy the wrong ticket. The beautiful Capsule Train is not included in the Busan Pass and requires a separate ticket purchase; it is <strong>very popular, so be sure to book in advance before your trip.</strong></p>

<ul>
  <li>
    <p>Sky Capsule Departure from Uipo</p>
  </li>
  <li>
    <p>2-person seat (1 ticket for 2 people)</p>
  </li>
  <li>
    <p><strong>Price: NT$861</strong></p>
  </li>
</ul>

<h4 id="korea-sim-cardkorea-high-speed-unlimited-data-esim"><a href="https://www.kkday.com/zh-tw/product/138273-korea-network-card-korea-express-esim-500mb-5gb-50gb-unlimited-plan-south-korea?cid=19365" target="_blank">Korea SIM Card｜Korea High-Speed Unlimited Data eSIM</a></h4>

<ul>
  <li>7-day unlimited data plan (Did not purchase because I went to the airport on the last morning)</li>
</ul>

<h4 id="naver-map"><a href="https://map.naver.com/p/" target="_blank">Naver Map</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*PrUwaqdQWWW0uvNzc6NnLQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="959" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk1OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PrUwaqdQWWW0uvNzc6NnLQ.png" /></p>

<blockquote>
  <p><a href="https://map.naver.com/p/" target="_blank"><strong><em>Naver Map is a must-use in Korea.</em></strong></a></p>
</blockquote>

<blockquote>
  <p><strong><em><a href="https://map.naver.com/p/" target="_blank">Always use Naver Map in Korea.</a></em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em><a href="https://map.naver.com/p/" target="_blank">Always use Naver Map in Korea.</a></em></strong></p>
</blockquote>

<p><strong>Interlude — The Day Before Departure, I Found My Naver Map Account Locked</strong></p>

<p><img src="/assets/8ace34a1a3d8/1*0SzoLbnn_g-Nu1Gx0aYF9Q.webp" alt="" loading="lazy" decoding="async" width="456" height="616" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NTYiIGhlaWdodD0iNjE2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*0SzoLbnn_g-Nu1Gx0aYF9Q.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*vjnO23ajw0h8XUZ5LBhryw.webp" alt="&lt;https://help.naver.com/service/5640/contents/20783?lang=en&amp;osType=COMMONOS&gt;" loading="lazy" decoding="async" width="440" height="616" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NDAiIGhlaWdodD0iNjE2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*vjnO23ajw0h8XUZ5LBhryw.png" /></p>

<p><a href="https://help.naver.com/service/5640/contents/20783?lang=en&amp;osType=COMMONOS" target="_blank">https://help.naver.com/service/5640/contents/20783?lang=en&amp;osType=COMMONOS</a></p>

<p><strong>The reason is suspected to be that logging in from Taiwan triggered Naver’s account protection mechanism due to suspicious activity.</strong></p>

<p>You can reactivate your account through identity verification, but I’m not sure if it’s because I forgot what name to enter or if foreigners can’t pass the real-name verification. No matter what I entered, it always showed incorrect information, and eventually, the account got permanently locked.</p>

<p>According to <a href="https://www.dcard.tw/f/korea_study/p/257877877" target="_blank">online information</a>, you can fill out a form to request lifting the protection mechanism “ <a href="https://help.naver.com/inquiry/input.help?categoryNo=18202&amp;serviceNo=5640&amp;lang=en" target="_blank">Members residing outside of Korea_Request to lift account protection measures</a>”, but I was unsure about the name issue, so my first request was rejected:</p>

<p><img src="/assets/8ace34a1a3d8/1*lc5k61s4A4CBsVQ7ZYBOKg.webp" alt="Appeal Failed" loading="lazy" decoding="async" width="1101" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTAxIiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*lc5k61s4A4CBsVQ7ZYBOKg.png" /></p>

<p>Appeal Failed</p>

<p>For the second time, I <a href="https://help.naver.com/inquiry/input.help?categoryNo=14955&amp;serviceNo=5640&amp;lang=ko" target="_blank">directly went to the Help Center</a> to fill out the application form:</p>

<p><img src="/assets/8ace34a1a3d8/1*Mn_YZfHr9U8tXKYCjySHVg.webp" alt="" loading="lazy" decoding="async" width="1200" height="1071" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNzEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Mn_YZfHr9U8tXKYCjySHVg.png" /></p>

<p>The content of my appeal:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Your NAVER ID.
Your Naver account. If you forgot your ID, you can recover it using email or phone number.

2. The registered name, date of birth, and contact information (mobile phone number or email address) of your NAVER ID.
Date of birth: YYYY/MM/DD The birthday you registered
Mobile Number: +886XXXXXXXXX The phone number you registered
Email: The email you registered
Registered Name: Sorry I really forget which name I've input, it may be one of the following:
Possible name combinations you want to try
XXXXX LI
LI XXXXX
XXX
李XX
but my real first name is XXXX, last name is Li

3. Attach your overseas identification card.
As attached file.

4. The details of your request to the NAVER Help Center.
I forgot the name I input and want to lift the account protection measures.
</code></pre></div></div>

<p>The more information you can provide, the better. Finally, attach a file with a photo of the passport personal information page.</p>

<p><strong>Then it was successfully unlocked:</strong></p>

<p><img src="/assets/8ace34a1a3d8/1*uvUo0CgFkqTfuOp87UtahQ.webp" alt="" loading="lazy" decoding="async" width="1038" height="1122" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDM4IiBoZWlnaHQ9IjExMjIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*uvUo0CgFkqTfuOp87UtahQ.png" /></p>

<p>You will receive a temporary password email. Use the temporary password to log in to Naver Map. <strong>Remember to change your password and enable two-factor authentication login (because we have disabled Naver’s login protection mechanism).</strong></p>

<blockquote>
  <p><em>Naver’s response is quite fast, usually resolving issues within half a day; if you worry about being locked out unexpectedly, you can first apply for “ <a href="https://help.naver.com/inquiry/input.help?categoryNo=18202&amp;serviceNo=5640&amp;lang=en" target="_blank">Members residing outside of Korea_Request to lift account protection measures</a> “ (</em> <a href="https://www.dcard.tw/f/korea_study/p/257877877" target="*blank">Refer to this guide</a> _).*</p>
</blockquote>

<h4 id="food">Food</h4>

<p>The only advance reservation made was for the Korean beef BBQ in the west, <a href="https://www.catchtable.net/zh-TW/shop/busan_kosaljip?operationType=REMOTE_WAITING_GLOBAL" target="_blank">Hwanokjeom Jeonpo Branch</a>.</p>

<p><img src="/assets/8ace34a1a3d8/1*oXyIDJJvynQfqfD_-Yb8cw.webp" alt="CatchTable" loading="lazy" decoding="async" width="998" height="1198" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5OTgiIGhlaWdodD0iMTE5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*oXyIDJJvynQfqfD_-Yb8cw.png" /></p>

<p>CatchTable</p>

<p>Speaking of <a href="https://www.catchtable.net/zh-TW/" target="_blank">Catchtable</a>, this platform is quite convenient for booking Korean restaurants in advance.</p>

<h4 id="fun">Fun</h4>

<ul>
  <li>
    <p>Day 1 07/29 (Tue): Arrive in Busan, Head Straight to Haeundae Hotel</p>
  </li>
  <li>
    <p>Day 2 07/30 (Wed): Morning Skyline Luge, Haedong Yonggungsa Temple; afternoon Coastal Train + Capsule Train, return to Haeundae, Busan BUSAN X the SKY; evening Mijeon Wang BBQ</p>
  </li>
  <li>
    <p>Day 3 07/31 (Thu): Morning Cheongsapo, honey butter toast, coastal train back to Haeundae, afternoon Spa Land, evening Busan yacht tour, grilled eel for dinner</p>
  </li>
  <li>
    <p>Day 4 08/01 (Fri): Move to Nampo-dong, Songdo Marine Cable Car, Yonggung Cloud Bridge, Gamcheon Culture Village, Throat Pot Lid BBQ for dinner, Busan Tower night view</p>
  </li>
  <li>
    <p>Day 5 08/02 (Sat): Lotte Department Store, Yeongdo Bridge Opening (only on Saturdays)</p>
  </li>
  <li>
    <p>Day 6 08/03 (Sun): Lotte Department Store, Bupyeong Can Market, Baeksan Bay Cultural Village, Bokcheon Temple Night View</p>
  </li>
  <li>
    <p>Day 7 08/04 (Mon): Move to Seomyeon, have Korean beef for dinner</p>
  </li>
  <li>
    <p>Day 8 08/05 (Tue): Gimhae International Airport, Busan, Return Trip</p>
  </li>
</ul>

<h4 id="accommodation">Accommodation</h4>

<h4 id="benikea-haeundae-kolon-hotel-day-1--day-4-three-nights-"><a href="https://www.booking.com/hotel/kr/benikea-haeundae.zh-tw.html?label=gen173nr-10CAEoggI46AdIM1gEaOcBiAEBmAEzuAEHyAEM2AED6AEB-AEBiAIBqAIBuAKWo-bEBsACAdICJGZjZWEwODk4LTZkNzAtNGY0MS04OGViLTE1ZmUxZmJkOGQwZtgCAeACAQ&amp;sid=594a3964e5a18310cf9a473a9d09eb36&amp;aid=304142" target="_blank">Benikea Haeundae Kolon Hotel (Day 1 — Day 4, Three Nights)</a> )</h4>

<p><img src="/assets/8ace34a1a3d8/1*f5NuKjYVcPd6G78lEtIncQ.webp" alt="" loading="lazy" decoding="async" width="1090" height="872" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDkwIiBoZWlnaHQ9Ijg3MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*f5NuKjYVcPd6G78lEtIncQ.png" /></p>

<p>The location is quite convenient, about a 10-minute walk to Haeundae Main Street. The room has windows but no sea view, with twin beds for two people.</p>

<ul>
  <li><strong>Price: Actual cost NT$12,032, about NT$2,000 per person per night.</strong></li>
</ul>

<h4 id="hotel-noah-day-4--day-7-three-nights"><a href="https://www.agoda.com/zh-cn/hotel-noah_3/hotel/busan-kr.html" target="_blank">Hotel Noah (Day 4 — Day 7, Three Nights)</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*UgPFi5UgawBzJbgluC5Vdw.webp" alt="" loading="lazy" decoding="async" width="1100" height="652" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTAwIiBoZWlnaHQ9IjY1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*UgPFi5UgawBzJbgluC5Vdw.png" /></p>

<p>About a 5-minute walk from Jagalchi Subway Station, directly across the street is BIFF Square. The room has windows and twin beds.</p>

<ul>
  <li><strong>Price: Actual payment NT$7,783, about NT$1,298 per person per night.</strong></li>
</ul>

<h4 id="air-sky-hotel-day-7--day-8-one-night"><a href="https://www.booking.com/hotel/kr/eeoseukaigwangwanghotel.zh-tw.html" target="_blank">Air Sky Hotel (Day 7 — Day 8, One Night)</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*mosy-Q7nQzyOIfPmAi3twQ.webp" alt="" loading="lazy" decoding="async" width="1124" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mosy-Q7nQzyOIfPmAi3twQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*26rsk_MCYmlbNhmKiTKqKw.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*26rsk_MCYmlbNhmKiTKqKw.jpeg" /></p>

<p>Only one stop from the airport, the total time for waiting, riding, and walking is about 10 minutes. The room has windows and two single beds.</p>

<ul>
  <li><strong>Price: Actual charge NT$2,835, about NT$1,418 per person per night.</strong></li>
</ul>

<blockquote>
  <p><em>Side note: After completing the booking, this hotel will send an automated message requesting a deposit. If you have already paid the full amount at the time of booking, you can ignore it.</em></p>
</blockquote>

<h4 id="visa">Visa</h4>

<p>South Korea offers visa-free entry. You only need to fill out the <a href="https://www.e-arrivalcard.go.kr/portal/apply/agreementPolicy.do?applyType=P" target="_blank">online entry application form</a> three days before departure, and you can complete immigration upon arrival with just your passport.</p>

<blockquote>
  <p>Ready to go, let’s start!</p>
</blockquote>

<h3 id="day-1-0729-tue-taiwan-to-haeundae">Day 1 07/29 (Tue) Taiwan to Haeundae</h3>

<h4 id="1330-arrive-at-taoyuan-international-airport-terminal-2">~=13:30 Arrive at Taoyuan International Airport Terminal 2</h4>

<p><img src="/assets/8ace34a1a3d8/1*V9fB_UU_9T-t0Z4QN1WE4g.webp" alt="" loading="lazy" decoding="async" width="1200" height="807" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*V9fB_UU_9T-t0Z4QN1WE4g.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZQfPfUI56V4EI_U2vgPj5A.webp" alt="" loading="lazy" decoding="async" width="964" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZQfPfUI56V4EI_U2vgPj5A.png" /></p>

<p>Check-in counters open around 13:50, with check-in and baggage drop completed by about 14:10.</p>

<blockquote>
  <p><em>You can first complete online check-in and seat selection (free) on the Busan Air official website <a href="https://mtw.airbusan.com/mw/checkin/checkinList" target="_blank">here</a>.</em></p>
</blockquote>

<h4 id="1430-complete-immigration--eat-something">14:30 Complete Immigration + Eat Something</h4>

<p><img src="/assets/8ace34a1a3d8/1*QzSioHsBFjr_LBpX2NlgWw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QzSioHsBFjr_LBpX2NlgWw.jpeg" /></p>

<p>Since we had to head straight to the hotel after landing in Busan, we ate well in Taiwan beforehand.</p>

<h4 id="1500-start-waiting-for-the-flight-activate-esim">15:00 Start waiting for the flight, activate eSIM</h4>

<p><img src="/assets/8ace34a1a3d8/1*AXE6WNQdcgDXCFuGn7k5PA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*AXE6WNQdcgDXCFuGn7k5PA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZdqpL-V_F7GA8qsTKEa93g.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZdqpL-V_F7GA8qsTKEa93g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QbB73S1hJj3Buwzqf8wdSw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QbB73S1hJj3Buwzqf8wdSw.jpeg" /></p>

<p>Because I was worried about poor airport Wi-Fi, I activated the eSIM in Taiwan first via <a href="https://zhgchg.li/posts/aacd5f5cacd1/#%E4%BA%8B%E5%85%88%E5%95%9F%E7%94%A8-esim-iphone-%E7%82%BA%E4%BE%8B" target="_blank">Taiwan eSIM Activation</a>.</p>

<h4 id="1640-flight-departure">~=16:40 Flight Departure</h4>

<p><img src="/assets/8ace34a1a3d8/1*TnEN0BSfol902HADb8_rNw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*TnEN0BSfol902HADb8_rNw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*_2jmZ8VV-D_8uI43mnnLHQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*_2jmZ8VV-D_8uI43mnnLHQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*mKI4jAmJiEQdoyY8E1VU8g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mKI4jAmJiEQdoyY8E1VU8g.jpeg" /></p>

<p>Slight delay of 15 minutes for takeoff, the seats are quite small.</p>

<h4 id="2005-arrive-in-busan-south-korea">~=20:05 Arrive in Busan, South Korea</h4>

<p><img src="/assets/8ace34a1a3d8/1*GmQ4yzkxgxt9MJgcXwwauA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*GmQ4yzkxgxt9MJgcXwwauA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZmBqlt5oYtJRpCHdqD1W1g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZmBqlt5oYtJRpCHdqD1W1g.jpeg" /></p>

<blockquote>
  <p><em>Busan Airport is a joint military-civilian airport, and photography is prohibited.</em></p>
</blockquote>

<h4 id="2030-pick-up-luggage--complete-immigration-clearance">~=20:30 Pick up luggage + complete immigration clearance</h4>

<p><img src="/assets/8ace34a1a3d8/1*6TN-R2eaqfuh_m2cddvJRA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*6TN-R2eaqfuh_m2cddvJRA.jpeg" /></p>

<p>First, went to the convenience store at the airport to buy some snacks for energy, exchanged money, and took a short rest.</p>

<h4 id="2100-arrive-at-gimhae-light-rail--airport-station">~=21:00 Arrive at Gimhae Light Rail — Airport Station</h4>

<p>It takes about a 5-minute walk from the airport.</p>

<p><img src="/assets/8ace34a1a3d8/1*9qFJw2RhaG6aaQae1WHrPA.webp" alt="" loading="lazy" decoding="async" width="1204" height="1470" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjA0IiBoZWlnaHQ9IjE0NzAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*9qFJw2RhaG6aaQae1WHrPA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*qovf8TmzIHYe6jOPqAyqAw.webp" alt="" loading="lazy" decoding="async" width="990" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5OTAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*qovf8TmzIHYe6jOPqAyqAw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*S4Gkbfsb_0uApwcUSEiTnA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*S4Gkbfsb_0uApwcUSEiTnA.jpeg" /></p>

<p>First, accompany Sean to top up the transportation card. There are two machines here for topping up transportation cards (Self Service Charger), <strong>and you can only add value in multiples of 1000 KRW</strong>.</p>

<p><img src="/assets/8ace34a1a3d8/1*o-mrmvVu8YonD9G7kFgn0A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*o-mrmvVu8YonD9G7kFgn0A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*5PANgTxqra2B7_S1knjfkA.webp" alt="" loading="lazy" decoding="async" width="1200" height="1023" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMjMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*5PANgTxqra2B7_S1knjfkA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*jILCIPwHH4B2sUCZb5glEg.webp" alt="" loading="lazy" decoding="async" width="742" height="1382" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NDIiIGhlaWdodD0iMTM4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*jILCIPwHH4B2sUCZb5glEg.png" /></p>

<p>After swiping your card to enter, please find the platform heading to Sasang Station. The next stop is “West Busan Distribution Area.”</p>

<h4 id="2115-arrive-at-sasang-to-transfer-to-the-green-line">~=21:15 Arrive at Sasang to transfer to the Green Line</h4>

<p><img src="/assets/8ace34a1a3d8/1*54uF-nAbXp068jDEvOSRxQ.webp" alt="" loading="lazy" decoding="async" width="732" height="1592" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MzIiIGhlaWdodD0iMTU5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*54uF-nAbXp068jDEvOSRxQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SU68g_MSrPTZeJQ7BFQLhg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SU68g_MSrPTZeJQ7BFQLhg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*WR7xmcLqxByn81lIAdRl6g.webp" alt="" loading="lazy" decoding="async" width="676" height="1572" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NzYiIGhlaWdodD0iMTU3MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*WR7xmcLqxByn81lIAdRl6g.png" /></p>

<p>Just follow the green signs on the ground to transfer from the light rail to the subway Green Line, heading towards “<strong>Changsan</strong>” (the other direction is “<strong>Yangsan</strong>,” don’t confuse the characters). The next stop is “Gamcheon.”</p>

<h4 id="2120-arrive-at-green-line-sasang-station-to-wait-for-the-bus">~=21:20 Arrive at Green Line Sasang Station to wait for the bus</h4>

<p><img src="/assets/8ace34a1a3d8/1*mhSqB1__ButsItcOkBuTBw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mhSqB1__ButsItcOkBuTBw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*rpFYA13Lb9nyNy1dAp50PA.webp" alt="" loading="lazy" decoding="async" width="1200" height="501" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjUwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*rpFYA13Lb9nyNy1dAp50PA.png" /></p>

<h4 id="2220-arrive-at-haeundae">~=22:20 Arrive at Haeundae</h4>

<p>When approaching Haeundae Station, the subway announcement includes the sound of seagulls.</p>

<p><img src="/assets/8ace34a1a3d8/1*FCNMmVBqS2AKfNQjh5cVmg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*FCNMmVBqS2AKfNQjh5cVmg.jpeg" /></p>

<h4 id="2230-arrive-at-the-hotel">~=22:30 Arrive at the hotel</h4>

<p><img src="/assets/8ace34a1a3d8/1*LOXnjLsRW91_eVYck5kGeQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*LOXnjLsRW91_eVYck5kGeQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*30mP_dahFonL0ve1n_vEig.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*30mP_dahFonL0ve1n_vEig.jpeg" /></p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/YbU4uG3pLjI" title="海雲台高麗良宵酒店" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="2250-出門海雲台大街覓食">~=22:50 出門海雲台大街覓食</h4>

<p><img src="/assets/8ace34a1a3d8/1*yKUlVFqVaS2D6M7j16mfoQ.webp" alt="" loading="lazy" decoding="async" width="971" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*yKUlVFqVaS2D6M7j16mfoQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*64lyMg61i9rRbmJcwxjjgQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*64lyMg61i9rRbmJcwxjjgQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*HjfYqKBHDe5_c0iF7QaHuw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*HjfYqKBHDe5_c0iF7QaHuw.jpeg" /></p>

<p>The accommodation is located near Haeundae Traditional Market, which offers a wide variety of food.</p>

<h4 id="2330-back-to-the-hotel-to-start-eating">~=23:30 Back to the hotel to start eating</h4>

<p><img src="/assets/8ace34a1a3d8/1*vQBulLYRFkoUQC_Os4hPFw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*vQBulLYRFkoUQC_Os4hPFw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SyBfYDcG8uh5ArLioXV9vg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SyBfYDcG8uh5ArLioXV9vg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*My3vIyTLBNbTSDtfh4FtkA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*My3vIyTLBNbTSDtfh4FtkA.jpeg" /></p>

<p>Finally bought BBQ fried chicken in two flavors, traditional market spicy rice cakes, and convenience store alcohol.</p>

<blockquote>
  <p><em>One order of fried chicken is enough for two people; two orders are too much (like four orders in Taiwan). The actual cost was NT $1,210.</em></p>
</blockquote>

<blockquote>
  <p><em>This mango wine (</em>Iced tea highball with mango cubes<em>) is delicious (5%). It contains canned mango chunks, and I later found out that Taiwan’s FamilyMart also carries it.</em></p>
</blockquote>

<h3 id="day-2-0730-wed-skyline-luge-haedong-yonggungsa-temple-coastal-train-capsule-train-busan-x-the-sky-mijangwang-bbq">Day 2 07/30 (Wed) Skyline Luge, Haedong Yonggungsa Temple, Coastal Train, Capsule Train, BUSAN X the SKY, Mijangwang BBQ</h3>

<h4 id="0930-leaving--super-nice-weather">09:30 Leaving — Super nice weather</h4>

<p><img src="/assets/8ace34a1a3d8/1*zShkz0mzeWJhZKml3P1D2A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*zShkz0mzeWJhZKml3P1D2A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Yf9ZxEjhX1K-YMwhTaDgFw.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Yf9ZxEjhX1K-YMwhTaDgFw.jpeg" /></p>

<p>There is a direct bus to Skyline Luge right at the hotel entrance.</p>

<h4 id="1025-skyline-luge-">10:25 Skyline Luge 👍👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*Yv8wNXoEqXLZZJII4DQsxg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Yv8wNXoEqXLZZJII4DQsxg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QXuBBjbiGpENtziQS-sagg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QXuBBjbiGpENtziQS-sagg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*s71ofWNtvFfoIBqnf-0VJw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*s71ofWNtvFfoIBqnf-0VJw.jpeg" /></p>

<p>Around 10:30, we started lining up to exchange tickets (the Busan Pass must be exchanged before use). There were quite a few people, and we waited about 20 minutes to get the tickets. The Busan Pass allows you to choose between riding the luge twice or once on the luge and once on the zipline. Most people chose to ride the luge twice.</p>

<blockquote>
  <p><a href="https://www.kkday.com/zh-tw/product/152412-gijang-skyline-luge-ticket-busan-south-korea?cid=19365" target="*blank"><em>If you don’t have a Pass, you can also buy tickets on KKday first.</em></a> _<strong>(Cheaper than buying on-site, but you still need to exchange the ticket to enter)</strong>*</p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*cLgWZh4YrAkhLDfIkUICpw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*cLgWZh4YrAkhLDfIkUICpw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*_V9oQVANR9ncErO9tWEsCw.webp" alt="" loading="lazy" decoding="async" width="978" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*_V9oQVANR9ncErO9tWEsCw.png" /></p>

<p>After exchanging the ticket, go next door to choose a helmet that fits your size. Once you put on the helmet, you can take the cable car up.</p>

<h4 id="1100-take-the-cable-car">~=11:00 Take the cable car</h4>

<p><img src="/assets/8ace34a1a3d8/1*PT-mGar4So6od4dD3A6F5g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PT-mGar4So6od4dD3A6F5g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*YzED-cEVaw7wrylcSUjuKw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YzED-cEVaw7wrylcSUjuKw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*YHu8QYJ2Cx1grupJ55ZmsQ.webp" alt="" loading="lazy" decoding="async" width="703" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YHu8QYJ2Cx1grupJ55ZmsQ.png" /></p>

<p>People all gather at the ticket exchange and purchase counters. There is hardly any queue for the cable car and luge ride afterward. During the cable car ride, you can see the track below. It takes about 5 minutes to reach the starting point.</p>

<p><img src="/assets/8ace34a1a3d8/1*doGVKHy5yunEpR7OFaa6rw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*doGVKHy5yunEpR7OFaa6rw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*f0KgbnLu921Hiq7QAZg5HQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*f0KgbnLu921Hiq7QAZg5HQ.jpeg" /></p>

<p>If you are afraid of open-air cable cars, I noticed another cabin-style cable car nearby that seems to be opening soon(?).</p>

<h4 id="1105-ready-to-depart">11:05 Ready to Depart</h4>

<p><img src="/assets/8ace34a1a3d8/1*QmbycT8d2Gh5wimLP18CPQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*QmbycT8d2Gh5wimLP18CPQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SA-bWwl8EuFsllB6_O5IrA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SA-bWwl8EuFsllB6_O5IrA.jpeg" /></p>

<p>You can overlook the adjacent Lotte World from above.</p>

<p>For first-time riders, a hand stamp will be given upon entry, and staff will provide a brief tutorial.</p>

<p><img src="/assets/8ace34a1a3d8/1*UCPhQh3lnSwMiO9UBqoHDQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*UCPhQh3lnSwMiO9UBqoHDQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*loe9DLrp_dtUFnpYrwxAOw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*loe9DLrp_dtUFnpYrwxAOw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*i9BnnhpabwgnxuBDw_7dbA.webp" alt="" loading="lazy" decoding="async" width="917" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTciIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*i9BnnhpabwgnxuBDw_7dbA.png" /></p>

<p>The operation method is basically:</p>

<ul>
  <li>
    <p><strong>Keep your legs straight</strong></p>
  </li>
  <li>
    <p><strong>Move backward, apply the front brake</strong></p>
  </li>
  <li>
    <p>No stopping midway, no stopping midway, no stopping midway; if you stop completely, it’s very hard to keep sliding.</p>
  </li>
  <li>
    <p>Slow down before turning to avoid flipping over.</p>
  </li>
  <li>
    <p>Beware of overtaking</p>
  </li>
  <li>
    <p><strong>Both hands must control; holding the phone is not allowed. There will be cameras taking photos along the way, which you can purchase at the souvenir shop.</strong></p>
  </li>
  <li>
    <p>Be careful not to lose your personal belongings.</p>
  </li>
</ul>

<p><img src="/assets/8ace34a1a3d8/1*GGsBBz5201M0fOrH4T1sqQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="862" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*GGsBBz5201M0fOrH4T1sqQ.png" /></p>

<p>It takes about 6 minutes to scroll from top to bottom.</p>

<p>Next are the souvenir centers and places where you can buy photos taken along the way.</p>

<p><img src="/assets/8ace34a1a3d8/1*17zdVTPo_LP48TizhwValw.webp" alt="" loading="lazy" decoding="async" width="941" height="1185" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDEiIGhlaWdodD0iMTE4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*17zdVTPo_LP48TizhwValw.png" /></p>

<p>Pass through the souvenir center back to the entrance, then line up again for the cable car.</p>

<p><img src="/assets/8ace34a1a3d8/1*I0y7aZ0bpynBInkP18lcUQ.webp" alt="" loading="lazy" decoding="async" width="694" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*I0y7aZ0bpynBInkP18lcUQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*2Y0iQTWf9pE0fAZJEK7JDg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2Y0iQTWf9pE0fAZJEK7JDg.jpeg" /></p>

<p>This time, we lined up at the entrance gate (you can play directly without staff instruction).</p>

<p><img src="/assets/8ace34a1a3d8/1*1aOU7IAPVNd6bSLNTaXRsw.webp" alt="" loading="lazy" decoding="async" width="1200" height="865" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*1aOU7IAPVNd6bSLNTaXRsw.png" /></p>

<p>H-sheng.</p>

<h4 id="1145-leave-and-head-to-haedong-yonggungsa-temple">11:45 Leave and head to Haedong Yonggungsa Temple</h4>

<p>After exiting Skyline Luge, walk straight across the street to reach Haedong Yonggungsa Temple. It takes about a 10-minute uphill walk.</p>

<p><img src="/assets/8ace34a1a3d8/1*W8pHXqKZ-gEPUqqLQV7cSA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*W8pHXqKZ-gEPUqqLQV7cSA.jpeg" /></p>

<h4 id="1155-haedong-yonggungsa-temple">11:55 Haedong Yonggungsa Temple</h4>

<p>Since I <a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/"><strong>visited last year</strong></a>, this time I just took a quick look around.</p>

<p><img src="/assets/8ace34a1a3d8/1*QO6ie1PN4o8liIjIs2Y0qQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="861" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QO6ie1PN4o8liIjIs2Y0qQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*5RNxsL0nD0TQ3I_UvwZQPg.webp" alt="" loading="lazy" decoding="async" width="947" height="1216" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTIxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*5RNxsL0nD0TQ3I_UvwZQPg.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*NllO9Hy73kJQzWNGoW0ABA.webp" alt="" loading="lazy" decoding="async" width="917" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTciIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*NllO9Hy73kJQzWNGoW0ABA.png" /></p>

<p>Arrived at the Haedong Yonggungsa Temple vendor entrance around 11:55.</p>

<p><img src="/assets/8ace34a1a3d8/1*m-tact5LRxGmwTBeDj7D2w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*m-tact5LRxGmwTBeDj7D2w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*K2ByL3USjy1fUlipsZLv1g.webp" alt="" loading="lazy" decoding="async" width="1400" height="984" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*K2ByL3USjy1fUlipsZLv1g.png" /></p>

<p>This time I discovered that you can also enter Haedong Yonggungsa Temple from the right side of the entrance sign, which is a gentle downhill path leading directly to the main hall; the entrance side has steep stairs that are hard to climb.</p>

<p><img src="/assets/8ace34a1a3d8/1*dyci1YPm-ApaeV2MDLsW2Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*dyci1YPm-ApaeV2MDLsW2Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*wpe6ymEfLMASAEivSVyyJQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*wpe6ymEfLMASAEivSVyyJQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*AvKx5_BLU1cAJSkrvY8u7A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*AvKx5_BLU1cAJSkrvY8u7A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*8RX8RXzZJ_4mW8hxss7hTw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*8RX8RXzZJ_4mW8hxss7hTw.jpeg" /></p>

<h4 id="1245-waiting-for-the-bus-to-songjeong-station">12:45 Waiting for the bus to Songjeong Station</h4>

<p><img src="/assets/8ace34a1a3d8/1*hnGZPnjF3eIAeAp7nu-Seg.webp" alt="" loading="lazy" decoding="async" width="942" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDIiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hnGZPnjF3eIAeAp7nu-Seg.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*05IR75W3B-T4g2QkInLjHg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*05IR75W3B-T4g2QkInLjHg.jpeg" /></p>

<p>Busan has good heat relief measures. There are umbrellas at traffic lights, and most bus stops have fans that you can turn on with a button.</p>

<h4 id="the-route-plan-is-shown-in-the-image-below">The route plan is shown in the image below:</h4>

<p><img src="/assets/8ace34a1a3d8/1*wbyHE15DgoW3_pkit_OR_A.webp" alt="" loading="lazy" decoding="async" width="1400" height="947" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk0NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*wbyHE15DgoW3_pkit_OR_A.png" /></p>

<p>Take a bus and walk from Skyline Luge to Songjeong Station of the Coastal Train, then take the Coastal Train to Cheongsapo Station, and transfer to the Capsule Train back to Haeundae.</p>

<h4 id="1315-arrive-at-songjeong-station">~=13:15 Arrive at Songjeong Station</h4>

<p><img src="/assets/8ace34a1a3d8/1*1W63YJR27-niR0rPFiNkdw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*1W63YJR27-niR0rPFiNkdw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*qKTgaE6w7QYNnHU41kgdRg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*qKTgaE6w7QYNnHU41kgdRg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*E1rZoLfWgHKUxb3qRAXhkg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*E1rZoLfWgHKUxb3qRAXhkg.jpeg" /></p>

<p>It takes about 10 minutes to walk here from the bus stop. It’s very hot and sunny.</p>

<p><img src="/assets/8ace34a1a3d8/1*uQegRUm5OVql_OGz5L4TQg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*uQegRUm5OVql_OGz5L4TQg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*PvM5nguf_-Fv6NfoWztP-w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PvM5nguf_-Fv6NfoWztP-w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*J76MOVL-li-Tg6c__KNMKg.webp" alt="ref" loading="lazy" decoding="async" width="927" height="322" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjciIGhlaWdodD0iMzIyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*J76MOVL-li-Tg6c__KNMKg.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/123012-haeundae-blueline-park-sky-capsule-beach-train-ticket?cid=19365" target="_blank">ref</a></p>

<p>The 13:30 slot was full, so I booked the 14:00 slot. ( <a href="http://m.site.naver.com/0URl8" target="_blank">Timetable</a> )</p>

<blockquote>
  <p><em>Side note: The station staff did not allow the use of the Busan Pass PDF file and insisted that we must open the voucher from the KKday App.</em></p>
</blockquote>

<blockquote>
  <p><em>The Busan Pass tickets can be used once per day and per station, so you can actually stop at various attractions along the route.</em></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*mCtniHyCFiDVmGqsr8fFfw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mCtniHyCFiDVmGqsr8fFfw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*LBR8tx9HfigYr51-ih8dag.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*LBR8tx9HfigYr51-ih8dag.jpeg" /></p>

<p>It’s still early, so let’s grab a bite at the convenience store; right outside Songjeong Station is Songjeong Beach (which seems less crowded), and there’s a convenience store nearby.</p>

<h4 id="1340-start-waiting-for-the-bus">~=13:40 Start waiting for the bus</h4>

<p>Remember to line up early for the bus, or you’ll end up standing at the very back.</p>

<p><img src="/assets/8ace34a1a3d8/1*wMw1Bmz47wE2_ivQ4LtQAw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*wMw1Bmz47wE2_ivQ4LtQAw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*_jexN3EvstbOgkts27zQhg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*_jexN3EvstbOgkts27zQhg.jpeg" /></p>

<p>After exiting the station, walk all the way to the platform. You can start waiting for the train after ticket inspection at the platform.</p>

<p>Around 13:50, the bus will arrive, and the crowd starts to stir and line up.</p>

<h4 id="1400-departure">~=14:00 Departure</h4>

<p><img src="/assets/8ace34a1a3d8/1*HT17e69lak7tbTqoXCpVUg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*HT17e69lak7tbTqoXCpVUg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QBfTvy19T_Jl6zvxVNDqfQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*QBfTvy19T_Jl6zvxVNDqfQ.jpeg" /></p>

<p>Sitting in the second row, the view is quite nice.</p>

<h4 id="1415-arrive-at-cheongsap-port">~=14:15 Arrive at Cheongsap Port</h4>

<p><img src="/assets/8ace34a1a3d8/1*xeOjrCl9pQx_u28XnJXELA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*xeOjrCl9pQx_u28XnJXELA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6JA5jGLBfsMl2GJMmehhtw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*6JA5jGLBfsMl2GJMmehhtw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*H0sBRecqIdECSploIAYVpw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*H0sBRecqIdECSploIAYVpw.jpeg" /></p>

<p>After getting off, you can see the stairs to the 2nd floor on the platform, which lead to the Capsule Train boarding area.</p>

<p><img src="/assets/8ace34a1a3d8/1*Y4jeQwk6jnpsbAklaKZI6g.webp" alt="" loading="lazy" decoding="async" width="945" height="1193" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTE5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Y4jeQwk6jnpsbAklaKZI6g.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*XDAIgiQb3qdEDSr546kNzg.webp" alt="" loading="lazy" decoding="async" width="572" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*XDAIgiQb3qdEDSr546kNzg.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*hrnYxziizbB6PrFv97Qiow.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hrnYxziizbB6PrFv97Qiow.jpeg" /></p>

<p>Open the <a href="https://www.kkday.com/zh-tw/product/123012-haeundae-blueline-park-sky-capsule-beach-train-ticket?cid=19365" target="_blank">link in the KKday order voucher</a> to find the ticket voucher. After going upstairs, show the voucher to the staff at the pavilion (we booked the 14:30–15:00 slot). They will give you a number tag. When your number is called, you can board. If the calling has already started, you have to wait until they finish calling.</p>

<p><img src="/assets/8ace34a1a3d8/1*KCbwTzka0L2cWDvX2O37iQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*KCbwTzka0L2cWDvX2O37iQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*FjPJSkqC67-UAhlwm9RRzA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*FjPJSkqC67-UAhlwm9RRzA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*5Tx02qf1e3U_IKWJaWjp8g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*5Tx02qf1e3U_IKWJaWjp8g.jpeg" /></p>

<p>During the wait, I went to the souvenir shop next door and bought a fan.</p>

<blockquote>
  <p><em>The capsule train also provides free fans (see the far right photo).</em></p>
</blockquote>

<h4 id="1430-capsule-train-number-call-and-queue">~=14:30 Capsule Train Number Call and Queue</h4>

<p>When the time comes, the staff will start calling numbers in order for the queue.</p>

<p><img src="/assets/8ace34a1a3d8/1*ZPQgbN2k7kRG2KI-3YEfEA.webp" alt="" loading="lazy" decoding="async" width="934" height="1229" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzQiIGhlaWdodD0iMTIyOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZPQgbN2k7kRG2KI-3YEfEA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*7S7dQYAf2Zqj_dxMjl_pDw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*7S7dQYAf2Zqj_dxMjl_pDw.jpeg" /></p>

<p>There is air conditioning above the queue area. Your voucher will be verified right before boarding (remember to have it ready). After boarding, the staff will take a group photo, and you can decide whether to buy it from them after getting off.</p>

<h4 id="1440-capsule-train-from-cheongsapo---upo-haeundae-departure-">~=14:40 Capsule Train from Cheongsapo -&gt; Upo (Haeundae) Departure! 👍👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*fLpUWvXcdl3Ryc1ary_arQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fLpUWvXcdl3Ryc1ary_arQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*nMQDysR6bNzOxVTLLU7rPQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*nMQDysR6bNzOxVTLLU7rPQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*l1JWUq3qAKEyDoyLJyzSPQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*l1JWUq3qAKEyDoyLJyzSPQ.jpeg" /></p>

<p>It looks thrilling but actually goes quite slow; there’s no air conditioning inside the cabin, but there are fans and speakers where you can plug in your phone to play music.</p>

<p><img src="/assets/8ace34a1a3d8/1*w4zPWlwQcqHhF4WCokBVyQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*w4zPWlwQcqHhF4WCokBVyQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*L4f0Ebz-fi6SeW7ZZJeTRQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*L4f0Ebz-fi6SeW7ZZJeTRQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*67tHAjdjNoZLmHdg0bLZFw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*67tHAjdjNoZLmHdg0bLZFw.jpeg" /></p>

<p>Enjoy the scenery and take some trendy photos along the way (?)</p>

<h4 id="1510-arrive-at-oe-po-haeundae">~=15:10 Arrive at Oe-po (Haeundae)</h4>

<p><img src="/assets/8ace34a1a3d8/1*hTEuxliIs2s_098pdVaOsg.webp" alt="" loading="lazy" decoding="async" width="944" height="1203" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDQiIGhlaWdodD0iMTIwMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hTEuxliIs2s_098pdVaOsg.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*mrsat28nAJ7e1PWJcb-KMQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="986" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mrsat28nAJ7e1PWJcb-KMQ.png" /></p>

<p>Head towards the Haeundae area (tall buildings) and come out to the intersection. On the opposite right side is the famous and delicious <a href="https://naver.me/x9zcuKNm" target="_blank">Salt Bread — Jayeondo saltbread</a> (will come back to buy later). On the left, walk down all the way to the entrance facing the sea, which is the Busan Sky entrance.</p>

<p><img src="/assets/8ace34a1a3d8/1*mxRI19obHDJEYWgaWpLC4g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mxRI19obHDJEYWgaWpLC4g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*kG7OMT38Kq5n4inz0Zi1VQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*kG7OMT38Kq5n4inz0Zi1VQ.jpeg" /></p>

<h4 id="1535-busan-busan-x-the-sky">~=15:35 Busan BUSAN X the SKY</h4>

<p><img src="/assets/8ace34a1a3d8/1*wLhiiMFHth1q0M0I52w0BQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*wLhiiMFHth1q0M0I52w0BQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*4GZH5tai41pbv0WbydQBzQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*4GZH5tai41pbv0WbydQBzQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*MZtM_pbelaE_9DSkReik6Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*MZtM_pbelaE_9DSkReik6Q.jpeg" /></p>

<p>The glass facing Haeundae Beach is fenced off, so you can’t take photos up close.</p>

<p><img src="/assets/8ace34a1a3d8/1*LphemYMPN0KncWvpEvv-5w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*LphemYMPN0KncWvpEvv-5w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*59D8d2G9l56zMvbn3yw55Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*59D8d2G9l56zMvbn3yw55Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Naj3fWNQzQthcPQJvr3onA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Naj3fWNQzQthcPQJvr3onA.jpeg" /></p>

<p>There is a transparent glass floor bridge, but the glass is heavily worn, so the effect is so-so; there is also a transparent glass restroom.</p>

<p><img src="/assets/8ace34a1a3d8/1*-H0j9zTeDr4mJ6Cqk3wWfA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*-H0j9zTeDr4mJ6Cqk3wWfA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*8kH5BBvg3v0xYPxl-NknSw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*8kH5BBvg3v0xYPxl-NknSw.jpeg" /></p>

<p>Walking down to the café and souvenir shop, you can look back at the route the capsule train just took.</p>

<p><img src="/assets/8ace34a1a3d8/1*89JfpBVVoL3GcEbkqqMNyQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*89JfpBVVoL3GcEbkqqMNyQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*3MGWqKezW4zdue_uNECOWw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*3MGWqKezW4zdue_uNECOWw.jpeg" /></p>

<p>Ate a fish cake sausage stick to boost energy (taste was average).</p>

<p><img src="/assets/8ace34a1a3d8/1*--lNPiay_O4al6tfX5spwg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*--lNPiay_O4al6tfX5spwg.jpeg" /></p>

<p>Besides the cafes and souvenir shops, this floor also has free exhibitions.</p>

<h4 id="1600-leave-and-walk-back-to-buy-salt-bread--jayeondo-saltbread-">~=16:00 Leave and walk back to buy <a href="https://naver.me/x9zcuKNm" target="_blank">Salt Bread — Jayeondo saltbread</a> 👍👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*w9_aMzKXaNfaz_haZN-XIw.webp" alt="" loading="lazy" decoding="async" width="1200" height="853" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*w9_aMzKXaNfaz_haZN-XIw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*XF58GQnLL6PnuqbvlxDNEQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*XF58GQnLL6PnuqbvlxDNEQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*O8DuQ0ywuuN5fxZXc_0_JQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*O8DuQ0ywuuN5fxZXc_0_JQ.jpeg" /></p>

<p>One order contains 6 pieces. I bought one to share at the hotel. The combination of fragrant, crispy texture with a slight touch of salt makes it very tasty.</p>

<p><img src="/assets/8ace34a1a3d8/1*2oV0hKVku4_AgQI7cf_cag.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2oV0hKVku4_AgQI7cf_cag.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*8-6mY9FWINdMJiYxpfPZEQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*8-6mY9FWINdMJiYxpfPZEQ.jpeg" /></p>

<p>There are many large-scale construction projects in Haeundae, seemingly preparing for the 2030 World Expo bid (which has already failed).</p>

<blockquote>
  <p><em>Rest at the hotel for a while, then go eat Mijeon Wang in the evening.</em></p>
</blockquote>

<h4 id="1750-arrive-at-맛찬들왕소금구이-해운대점--matchandeul-salted-grill-haeundae-branch">17:50 Arrive at <a href="https://naver.me/5sG3vEe5" target="_blank">맛찬들왕소금구이 해운대점 — Matchandeul Salted Grill Haeundae Branch</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*AsOMjouHVz3BRue3olauuw.webp" alt="" loading="lazy" decoding="async" width="943" height="1198" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDMiIGhlaWdodD0iMTE5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*AsOMjouHVz3BRue3olauuw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*O39pGmtLGJIa-bXxK-Nnig.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*O39pGmtLGJIa-bXxK-Nnig.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*52iHV2Dv0vDDxNuqJxYmhg.webp" alt="" loading="lazy" decoding="async" width="484" height="811" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0ODQiIGhlaWdodD0iODExIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*52iHV2Dv0vDDxNuqJxYmhg.png" /></p>

<p>There is a machine at the entrance for waiting. Enter your email to receive a notification. At 17:50, a small queue started to form.</p>

<p><img src="/assets/8ace34a1a3d8/1*PQL9i8rdeGLZD0rNLEaLxw.webp" alt="" loading="lazy" decoding="async" width="515" height="682" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTUiIGhlaWdodD0iNjgyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*PQL9i8rdeGLZD0rNLEaLxw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*NNmf9vfpx9sX-EPv-6-MCQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*NNmf9vfpx9sX-EPv-6-MCQ.jpeg" /></p>

<p>Click the button to check the current number. You will also receive an entry notification email when it’s your turn, which is very convenient; <strong>also, be careful as the building opposite and next door are under construction.</strong></p>

<h4 id="1825-enter-for-dinner">18:25 Enter for Dinner</h4>

<p>Waited for about 30 minutes before entering.</p>

<p><img src="/assets/8ace34a1a3d8/1*7MgY9p6aZMbGejj56iU11A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*7MgY9p6aZMbGejj56iU11A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*1dn6e5L69VTVnrcEfhMvDw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*1dn6e5L69VTVnrcEfhMvDw.jpeg" /></p>

<p>We directly ordered the double set meal (includes one meat each + soybean paste soup + pot rice) and a bottle of Cass beer, totaling about NT$1,500.</p>

<blockquote>
  <p><em>They will grill it well for you. Reviews say the meat tastes good but the portion is small and you have to wait in line.</em></p>
</blockquote>

<h4 id="2030-after-eating-return-to-haeundae-main-street">~=20:30 After eating, return to Haeundae Main Street</h4>

<p><img src="/assets/8ace34a1a3d8/1*O2UXV7D8gKQ0hImc8iDnMA.webp" alt="" loading="lazy" decoding="async" width="948" height="1199" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDgiIGhlaWdodD0iMTE5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*O2UXV7D8gKQ0hImc8iDnMA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*YINokSS0bT-ebB5VXJhW_w.webp" alt="" loading="lazy" decoding="async" width="918" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YINokSS0bT-ebB5VXJhW_w.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*PjSGfzXsbc9bYFb1sK18Tw.webp" alt="" loading="lazy" decoding="async" width="943" height="1233" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDMiIGhlaWdodD0iMTIzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PjSGfzXsbc9bYFb1sK18Tw.png" /></p>

<p>Strolled along Haeundae Main Street to digest a bit and bought ice cream from a matcha specialty shop as a dessert to refresh.</p>

<p><img src="/assets/8ace34a1a3d8/1*eEqklwquew6rGg4-Il6FUw.webp" alt="" loading="lazy" decoding="async" width="937" height="1186" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzciIGhlaWdodD0iMTE4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*eEqklwquew6rGg4-Il6FUw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*XIW1F6tF8P0ExjjlixWKxQ.webp" alt="" loading="lazy" decoding="async" width="1146" height="961" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ2IiBoZWlnaHQ9Ijk2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*XIW1F6tF8P0ExjjlixWKxQ.png" /></p>

<p>Next door is the newly opened Olive Young Delight Project store, where I did some shopping; many items are buy two get one free, and you can get an instant tax refund on purchases over 15,000 KRW.</p>

<p><img src="/assets/8ace34a1a3d8/1*9eNytaAkCGcIgbOzagwTiQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="833" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9eNytaAkCGcIgbOzagwTiQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*hW_N_93CpyyzWCGZ-OtJ4Q.webp" alt="" loading="lazy" decoding="async" width="943" height="1214" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDMiIGhlaWdodD0iMTIxNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hW_N_93CpyyzWCGZ-OtJ4Q.png" /></p>

<p>Went for a stroll at Haeundae Beach again before heading back to the hotel to rest.</p>

<h3 id="day-3-0731-thu-cheongsapo-spa-land-coastal-train-busan-yacht-tour">Day 3 07/31 (Thu) Cheongsapo, Spa Land, Coastal Train, Busan Yacht Tour</h3>

<p>Because I only transferred at Cheongsapo yesterday without exploring, I came again today.</p>

<h4 id="1100-took-the-bus-to-cheongsapo">11:00 Took the bus to Cheongsapo</h4>

<p><img src="/assets/8ace34a1a3d8/1*rbpv52Shw77IaUdT2d7-8w.webp" alt="" loading="lazy" decoding="async" width="954" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*rbpv52Shw77IaUdT2d7-8w.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*DJj6wZCG0KRomyT94xO1lA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*DJj6wZCG0KRomyT94xO1lA.jpeg" /></p>

<p>The bus from Haeundae directly to Cheongsapo is a minibus. It passes through communities like Busan Sanso, where the roads are narrow and quite steep.</p>

<h4 id="1130-arrive-at-cheongsapo">11:30 Arrive at Cheongsapo</h4>

<p><img src="/assets/8ace34a1a3d8/1*fRTe_hokNNcPNgX97qZcQw.webp" alt="" loading="lazy" decoding="async" width="1200" height="846" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fRTe_hokNNcPNgX97qZcQw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*77MdQpIY5lupV8oQ3chyDw.webp" alt="" loading="lazy" decoding="async" width="1200" height="990" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*77MdQpIY5lupV8oQ3chyDw.png" /></p>

<p>Nearby, I randomly picked a place to have lunch (<a href="https://naver.me/GlGmYxyY" target="_blank">Gohyang katsu</a>). I ordered a pork cutlet sandwich; the meat was thick, tender, and reasonably priced.</p>

<h4 id="1230-diart-coffee-honey-butter-toast">~=12:30 <a href="https://naver.me/GkUJQI6r" target="_blank">DIART COFFEE Honey Butter Toast</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*Su_kwIRgZb18zoMS0Tv0sw.webp" alt="" loading="lazy" decoding="async" width="948" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDgiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Su_kwIRgZb18zoMS0Tv0sw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*smtxM51j_tPmnW-RQql09g.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*smtxM51j_tPmnW-RQql09g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*8XzQQm3AZ0JR7Gr_TfaYxA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*8XzQQm3AZ0JR7Gr_TfaYxA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6kxh55_key4KL-Tj5V5WNQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*6kxh55_key4KL-Tj5V5WNQ.jpeg" /></p>

<p>After lunch, we stepped out to the highly recommended Busan Honey Butter Toast at <a href="https://naver.me/GkUJQI6r" target="_blank">DIART COFFEE</a>. We ordered one toast (₩10,000) and a coffee (₩5,500) to take a break.</p>

<p>You can refer to the <a href="https://m.site.naver.com/1Abyy" target="_blank">card</a> for how to eat it. In short, cut the bread into pieces first, then evenly spread butter and honey before eating.</p>

<blockquote>
  <p><em>The honey isn’t too sweet, combined with the buttery aroma and crispy bread, it’s delicious and not greasy.</em></p>
</blockquote>

<h4 id="1330-return-to-cheongsapo-to-catch-the-coastal-train-back-to-mi-po-haeundae">~=13:30 Return to Cheongsapo to catch the coastal train back to Mi-po (Haeundae)</h4>

<p><img src="/assets/8ace34a1a3d8/1*W-yaVbvzi87W8xZk_cza4g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*W-yaVbvzi87W8xZk_cza4g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*VKTdBhWwiPmOoE4eX7pwVw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*VKTdBhWwiPmOoE4eX7pwVw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*dICe7b0tdWsGppYFDADrhQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*dICe7b0tdWsGppYFDADrhQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*fWJrPrMNalY2YbTFk9ct0Q.webp" alt="" loading="lazy" decoding="async" width="950" height="1214" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTAiIGhlaWdodD0iMTIxNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fWJrPrMNalY2YbTFk9ct0Q.png" /></p>

<p>The ticket from yesterday was no longer valid, so I used the Busan Pass to exchange for a coastal train ticket again; here, since I boarded at a mid-station, I could only stand in the last row.</p>

<p><img src="/assets/8ace34a1a3d8/1*_a9Gqyx5xSatuoqZVvDwPQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*_a9Gqyx5xSatuoqZVvDwPQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6ypVO-ZjcG0zkZAuJKC7Mg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*6ypVO-ZjcG0zkZAuJKC7Mg.jpeg" /></p>

<p>We passed by several observation decks along the way but didn’t get off because it was too hot.</p>

<h4 id="1355-arrive-at-oe-po-haeundae">~=13:55 Arrive at Oe-po (Haeundae)</h4>

<p><img src="/assets/8ace34a1a3d8/1*viDnwtL1r7EWe3K1FtDKiA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*viDnwtL1r7EWe3K1FtDKiA.jpeg" /></p>

<p>After returning to Haeundae, I walked and took a bus to Shinsegae Spa Land.</p>

<h4 id="1430-arrive-at-shinsegae-shinsegae-department-store">~=14:30 Arrive at <a href="https://naver.me/xfYRYAfu" target="_blank">Shinsegae (SHINSEGAE) Department Store</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*ZqGLAD3UKpURs3HKbviTwQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="989" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZqGLAD3UKpURs3HKbviTwQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*WneX4dSsqbfvEps927Zqjw.webp" alt="" loading="lazy" decoding="async" width="925" height="1176" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjUiIGhlaWdodD0iMTE3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*WneX4dSsqbfvEps927Zqjw.png" /></p>

<p>No shopping, headed straight to Spa Land on the first floor.</p>

<h4 id="1440-spa-land">14:40 Spa Land</h4>

<p>Use the Busan Pass to redeem Spa Land access.</p>

<blockquote>
  <p><a href="https://www.kkday.com/zh-tw/product/12213-busan-spa-land-centum-city-ticket?cid=19365" target="_blank"><em>You can also purchase it in advance on Kkday even if you haven’t bought the Pass.</em></a></p>
</blockquote>

<p>You will receive a sensor wristband upon entry. Use the wristband to store your shoes and clothes. Change into the special sauna clothes inside. You can also visit the sauna first (full nudity required) before going to the sauna rooms. The sauna entrance is in the direction of the women’s changing room.</p>

<p><img src="/assets/8ace34a1a3d8/1*2UvncJZg2YqeC9pmzSstww.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2UvncJZg2YqeC9pmzSstww.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*rgOo09p9c0DJFD9h_UH7fQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*rgOo09p9c0DJFD9h_UH7fQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*9oMyMc4jYnkhHcJKydzXJw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*9oMyMc4jYnkhHcJKydzXJw.jpeg" /></p>

<p>The first floor has a sunbathing area, nail salon, snack stalls, and various types of jjimjilbang (sauna) rooms (during summer vacation, almost all are packed with people). The third floor has a restaurant (where you can eat grilled eggs and instant noodles), massage services, massage chairs, and a yoga room.</p>

<p>After the sauna, I went to the sunbathing area to lie down and soak up the sun, and ordered a drink (super sweet).</p>

<blockquote>
  <p><em>All purchases inside the venue are made by scanning the wristband, with payment settled upon exit.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Honestly, I didn’t get to experience the sauna this time; it was extremely crowded. I’ll try it next time.</em></strong></p>
</blockquote>

<h4 id="1700-prepare-to-head-to-the-yacht-pier-for-the-gathering">17:00 Prepare to head to the yacht pier for the gathering</h4>

<p>Reservation for the 18:30 session requires arriving early at the meeting point for roll call.</p>

<p><img src="/assets/8ace34a1a3d8/1*I05lgEC-kLYtbw1s_OWJ6Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*I05lgEC-kLYtbw1s_OWJ6Q.jpeg" /></p>

<p>Before departure, I bought and ate some food at Shinsegae Department Store B1 to fill my stomach.</p>

<h4 id="take-the-subway-to-dongbaek-station">Take the subway to Dongbaek Station</h4>

<p><img src="/assets/8ace34a1a3d8/1*nLZyTcQcoS4NBTV_EozYFA.webp" alt="" loading="lazy" decoding="async" width="721" height="514" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjEiIGhlaWdodD0iNTE0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*nLZyTcQcoS4NBTV_EozYFA.png" /></p>

<p>After exiting the station, it takes about 15 minutes on foot to reach the pier.</p>

<blockquote>
  <p><em>The pier area only has vending machines and restrooms, which are dirtier and crowded with queues; <strong>it is recommended to buy what you need and use the restroom in the city before coming.</strong></em></p>
</blockquote>

<h4 id="1800-arrive-at-the-yacht-pier">~=18:00 Arrive at the yacht pier</h4>

<p><img src="/assets/8ace34a1a3d8/1*2IAvf0t9-X64N-VhcqU3Hw.webp" alt="" loading="lazy" decoding="async" width="944" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2IAvf0t9-X64N-VhcqU3Hw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*gzCg-qoszdj6FPTDnl6OxA.webp" alt="" loading="lazy" decoding="async" width="1200" height="854" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*gzCg-qoszdj6FPTDnl6OxA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SEdvFIL6AktezC9W238zDA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SEdvFIL6AktezC9W238zDA.jpeg" /></p>

<p>After passing through the tourist center, you will arrive at the pier meeting point. Each boat operator has a different pier meeting spot.</p>

<p><img src="/assets/8ace34a1a3d8/1*XWHxS8z33CPpiugdE2ddVw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*XWHxS8z33CPpiugdE2ddVw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*uGw5fIqLJIJoxtzTpzNm3g.webp" alt="" loading="lazy" decoding="async" width="947" height="1182" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTE4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*uGw5fIqLJIJoxtzTpzNm3g.png" /></p>

<p>Around 18:15, the boat operator appeared to start lining up and taking attendance, collecting an additional night fee on site (5,000 KRW per person).</p>

<h4 id="1830-yacht-departure-">18:30 Yacht Departure 👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*UdLDGZfqHM_HneNjqh6qlA.webp" alt="" loading="lazy" decoding="async" width="951" height="1209" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTEiIGhlaWdodD0iMTIwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*UdLDGZfqHM_HneNjqh6qlA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Z52u6yd6i_bwsvmdKECD1A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Z52u6yd6i_bwsvmdKECD1A.jpeg" /></p>

<p>Wear a life jacket when boarding the boat and sit in the front seat on the second level.</p>

<p><img src="/assets/8ace34a1a3d8/1*SEVJsECS6H4ngAof9V0nQQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SEVJsECS6H4ngAof9V0nQQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*fLsO9aiXUtVx3vm_etmEaA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fLsO9aiXUtVx3vm_etmEaA.jpeg" /></p>

<p>It runs slowly and smoothly, heading all the way across Gwangandaegyo Bridge.</p>

<blockquote>
  <p><strong><em>However, if you are prone to seasickness, it is still recommended to take motion sickness medication.</em></strong></p>
</blockquote>

<h4 id="general-route-map">General Route Map:</h4>

<p><img src="/assets/8ace34a1a3d8/1*J0YqfliMZuX7rnrgzKItkA.webp" alt="" loading="lazy" decoding="async" width="1200" height="798" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*J0YqfliMZuX7rnrgzKItkA.png" /></p>

<h4 id="1850-arriving-before-gwangalli-beach">~=18:50 Arriving before Gwangalli Beach</h4>

<p><img src="/assets/8ace34a1a3d8/1*pDMeZQ252T2zQHU4fnsdmg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*pDMeZQ252T2zQHU4fnsdmg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*0xgRuRCfH8hUew63a7fqqg.webp" alt="" loading="lazy" decoding="async" width="1182" height="665" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTgyIiBoZWlnaHQ9IjY2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*0xgRuRCfH8hUew63a7fqqg.jpeg" /></p>

<p>About 20 minutes later, we will arrive at the waters in front of Gwangalli Beach, where many visitors are playing SUP. We will stop here and set off fireworks. The fireworks are lit together by the crew members of the yachts sailing during this time, but it’s still quite bright, so it’s hard to see clearly.</p>

<p><img src="/assets/8ace34a1a3d8/1*aPIQ_aNeOjF3F0vc_ghnEA.webp" alt="" loading="lazy" decoding="async" width="1200" height="858" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*aPIQ_aNeOjF3F0vc_ghnEA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*xlY1CbhaJm_y2MH2H2x6FA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*xlY1CbhaJm_y2MH2H2x6FA.jpeg" /></p>

<p>Staff along the way are also very enthusiastic in helping to take photos.</p>

<h4 id="1900-return-trip">~=19:00 Return trip</h4>

<p><img src="/assets/8ace34a1a3d8/1*YFAwnj0StqttQX1mpGD93Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*YFAwnj0StqttQX1mpGD93Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*XfSCLtKiHYCFpboyNPiWcg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*XfSCLtKiHYCFpboyNPiWcg.jpeg" /></p>

<p>Caught the sunset glow.</p>

<h4 id="1920-back-to-land">~=19:20 Back to Land</h4>

<p>After returning to the mainland, I took the bus back to Haeundae.</p>

<h4 id="1935-return-to-haeundae">~=19:35 Return to Haeundae</h4>

<p><img src="/assets/8ace34a1a3d8/1*5fS1FH22njGy1ppRJNvRhQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*5fS1FH22njGy1ppRJNvRhQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*hS21eQGZ-VvsLKqQINEiEw.webp" alt="" loading="lazy" decoding="async" width="932" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hS21eQGZ-VvsLKqQINEiEw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*nEMJPKds-CrjF2BDsR3ZcA.webp" alt="" loading="lazy" decoding="async" width="1200" height="734" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjczNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*nEMJPKds-CrjF2BDsR3ZcA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*K8XU7m63X0bGVYcaMOwgGQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*K8XU7m63X0bGVYcaMOwgGQ.jpeg" /></p>

<p>Haeundae Beach has markets and food trucks.</p>

<h4 id="1955-head-to-haeundae-for-grilled-eel-at-pungcheonman-">~=19:55 Head to Haeundae for Grilled Eel at <a href="https://naver.me/GEiuqenG" target="_blank">PUNGCHEONMAN</a> 👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*--tFcHDWgpPDD__zrK8Jlg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*--tFcHDWgpPDD__zrK8Jlg.jpeg" /></p>

<p>There were many seats available inside, so we came in without waiting.</p>

<p><img src="/assets/8ace34a1a3d8/1*R-kS0zI7e106d0e2SAaMKg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*R-kS0zI7e106d0e2SAaMKg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*CHuWZKV3-zp9Z5vDy3h2kQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*CHuWZKV3-zp9Z5vDy3h2kQ.jpeg" /></p>

<p>We ordered grilled eel, grilled beef slices, two cold noodles, and draft beer, paying NT$2,387 in total.</p>

<p><img src="/assets/8ace34a1a3d8/1*eebei6lY1kzg2ZkEunW1Gw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*eebei6lY1kzg2ZkEunW1Gw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*3v8CcPgaFWmH4190p4q1og.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*3v8CcPgaFWmH4190p4q1og.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*qku3gySyCW3u98SQ7qXA7g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*qku3gySyCW3u98SQ7qXA7g.jpeg" /></p>

<p>Grilled eel is crispy, tender, and juicy, very delicious with no fishy smell at all; the grilled beef slices are good but ordinary; the buckwheat cold noodles resemble brown sugar shaved ice but actually have a soy sauce flavor with crushed ice, making them refreshing and tasty.</p>

<p><img src="/assets/8ace34a1a3d8/1*IakblW3IGN6wxf1NkX29Ww.webp" alt="" loading="lazy" decoding="async" width="957" height="1213" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTciIGhlaWdodD0iMTIxMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*IakblW3IGN6wxf1NkX29Ww.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*NUtsqQvPLceti09GjSi1Ew.webp" alt="" loading="lazy" decoding="async" width="947" height="1182" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTE4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*NUtsqQvPLceti09GjSi1Ew.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*NqVaJBSjpavz5Pqaee01Zg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*NqVaJBSjpavz5Pqaee01Zg.jpeg" /></p>

<p>After eating, we went for a walk along Haeundae Street, visited Haeundae Traditional Market (forgot to try the seafood pancake, so frustrating!), and bought some souvenirs. Tomorrow we will leave Haeundae.</p>

<h3 id="day-4-0801-fri-nampo-dong-songdo-marine-cable-car-yonggung-cloud-bridge-gamcheon-culture-village-busan-tower">Day 4 08/01 (Fri) Nampo-dong, Songdo Marine Cable Car, Yonggung Cloud Bridge, Gamcheon Culture Village, Busan Tower</h3>

<h4 id="0915-head-out-to-nampo-dong">09:15 Head out to Nampo-dong</h4>

<p><img src="/assets/8ace34a1a3d8/1*amKvoodXorVlvXyKiBALLg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*amKvoodXorVlvXyKiBALLg.jpeg" /></p>

<p>Early in the morning, prepare to leave Haeundae and head to Nampo-dong.</p>

<h4 id="1030-arrive-at-jagalchi-station-hotel-noah-hotel">~=10:30 Arrive at Jagalchi Station, <a href="https://www.agoda.com/zh-cn/hotel-noah_3/hotel/busan-kr.html" target="_blank">Hotel Noah</a> hotel</h4>

<p><img src="/assets/8ace34a1a3d8/1*Kq_QYpAk4Kr-F09JJ_FRWQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Kq_QYpAk4Kr-F09JJ_FRWQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*sMUSi1v4EluBNNT5emHqOg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*sMUSi1v4EluBNNT5emHqOg.jpeg" /></p>

<p>Luggage Storage.</p>

<h4 id="biff-square">BIFF Square</h4>

<p><img src="/assets/8ace34a1a3d8/1*cQXerZP_VIvP5ToSBrLgEw.webp" alt="" loading="lazy" decoding="async" width="945" height="1194" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTE5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*cQXerZP_VIvP5ToSBrLgEw.png" /></p>

<p>Right outside the hotel is BIFF Square, which is lively and convenient.</p>

<h4 id="1100-lunch--hongkong-0410-nampo-1st-branch">~=11:00 Lunch — <a href="https://naver.me/G28VhTtY" target="_blank">Hongkong 0410 Nampo 1st Branch</a></h4>

<p>Near noon, we casually picked a restaurant inside BIFF for lunch. This one serves Korean-Chinese cuisine.</p>

<p><img src="/assets/8ace34a1a3d8/1*PFYie7L_VFf4E70JCoCjAw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PFYie7L_VFf4E70JCoCjAw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*YNNdD826iQ8fqvZnDgJQFA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YNNdD826iQ8fqvZnDgJQFA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ERESHYR_kpkJluI7uTd3CQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*ERESHYR_kpkJluI7uTd3CQ.jpeg" /></p>

<p>Ordered fried dumplings, sweet and sour pork (medium, felt like a small portion would be enough), and Korean black bean noodles.</p>

<p>Fried dumplings are a bit greasy, but the sweet and sour pork is good.</p>

<h4 id="1215-take-the-bus--walk-to-songdo-cable-car-station">12:15 Take the bus + walk to Songdo Cable Car Station</h4>

<p><img src="/assets/8ace34a1a3d8/1*O-7uqMkqeEY_hnPImCZ4nA.webp" alt="" loading="lazy" decoding="async" width="604" height="729" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDQiIGhlaWdodD0iNzI5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*O-7uqMkqeEY_hnPImCZ4nA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*e07O9sxOWghCiDG4t57fTQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*e07O9sxOWghCiDG4t57fTQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*G55PQci1BikM7kS7jRyBUg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*G55PQci1BikM7kS7jRyBUg.jpeg" /></p>

<p>After getting off the bus, there’s about a 10-minute walk. It’s too hot, so I first went to a convenience store to get a self-made iced latte to cool down.</p>

<p><img src="/assets/8ace34a1a3d8/1*ntKH7dp9EHgiSQfDiETb1w.webp" alt="" loading="lazy" decoding="async" width="929" height="1189" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjkiIGhlaWdodD0iMTE4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ntKH7dp9EHgiSQfDiETb1w.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*gMrRyaT7YG7QVkW6vs4aYw.webp" alt="" loading="lazy" decoding="async" width="928" height="1186" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iMTE4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*gMrRyaT7YG7QVkW6vs4aYw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*9hyji_BcmpmX8mnbQ_8sYg.webp" alt="" loading="lazy" decoding="async" width="938" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9hyji_BcmpmX8mnbQ_8sYg.png" /></p>

<p>After entering, line up at the counter to exchange your Busan Pass for the cable car ticket (Pass is for the crystal cabin, <strong>round trip, so keep the ticket safe</strong>).</p>

<p>After getting the ticket, take the escalator upstairs to line up for the cable car. (They gave out lollipops, but I didn’t take one)</p>

<h4 id="1225-board-the-bus">12:25 Board the bus</h4>

<p><img src="/assets/8ace34a1a3d8/1*2rQjr9yJHeMqKNL2WsdftQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*2rQjr9yJHeMqKNL2WsdftQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*zzQbyUo9b66wPcze_M5CTw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*zzQbyUo9b66wPcze_M5CTw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*OL7mGet2prUpedl96EX1PA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*OL7mGet2prUpedl96EX1PA.jpeg" /></p>

<p>5–6 people per unit.</p>

<h4 id="1235-arrive-at-songdo">~=12:35 Arrive at Songdo</h4>

<p><img src="/assets/8ace34a1a3d8/1*qPfpLan9npUSJvb3WCumNA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*qPfpLan9npUSJvb3WCumNA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*iMGGxqNqL_cW6nGUL700ag.webp" alt="" loading="lazy" decoding="async" width="940" height="1176" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDAiIGhlaWdodD0iMTE3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*iMGGxqNqL_cW6nGUL700ag.png" /></p>

<p>Turn right immediately and walk down.</p>

<p><img src="/assets/8ace34a1a3d8/1*OTbo_MgGmzY5e3BUofU85A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*OTbo_MgGmzY5e3BUofU85A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*jeVCIVeLt_fhFSTgRBKjow.webp" alt="" loading="lazy" decoding="async" width="955" height="1194" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTUiIGhlaWdodD0iMTE5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*jeVCIVeLt_fhFSTgRBKjow.png" /></p>

<p>Go down the stairs, turn right, and walk straight to reach the “Songdo Yonggung Suspension Bridge.”</p>

<h4 id="1240-arrive-at-songdo-yonggung-suspension-bridge">~=12:40 Arrive at Songdo Yonggung Suspension Bridge</h4>

<p><img src="/assets/8ace34a1a3d8/1*witcQsiZpHm_OvIQKoGY3g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*witcQsiZpHm_OvIQKoGY3g.jpeg" /></p>

<p>Use the Busan Pass at the nearby ticket counter to exchange for entry tickets.</p>

<p><img src="/assets/8ace34a1a3d8/1*0vBkG9nJIvyD7hrhYj-xnw.webp" alt="" loading="lazy" decoding="async" width="945" height="1203" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTIwMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*0vBkG9nJIvyD7hrhYj-xnw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*PhNqmy6wBPgUbjY3zRT82w.webp" alt="" loading="lazy" decoding="async" width="360" height="480" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNjAiIGhlaWdodD0iNDgwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*PhNqmy6wBPgUbjY3zRT82w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*hjmWsaA7pNfx77EXr5M7aQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1001" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*hjmWsaA7pNfx77EXr5M7aQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*-KX5b9yL0bniAwPYFgXcgw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*-KX5b9yL0bniAwPYFgXcgw.jpeg" /></p>

<p>There is no shade here at all, very sunny and hot, but you can get a bird’s-eye view of the entire Songdo Cable Car.</p>

<h4 id="1300-return-to-songdo-cable-car-queue-for-the-cable-car-back">~=13:00 Return to Songdo Cable Car, queue for the cable car back</h4>

<p><img src="/assets/8ace34a1a3d8/1*Og_17fLIQsrceTBxCRxZow.webp" alt="" loading="lazy" decoding="async" width="960" height="1192" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTE5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Og_17fLIQsrceTBxCRxZow.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*OHD1o7Nau7E8xTUOb1yjeQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*OHD1o7Nau7E8xTUOb1yjeQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*zkbCoM-RhqaU33418vE2aQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*zkbCoM-RhqaU33418vE2aQ.jpeg" /></p>

<h4 id="1325-leave-songdo-and-walk-back-to-take-the-bus">~=13:25 Leave Songdo and walk back to take the bus</h4>

<p><img src="/assets/8ace34a1a3d8/1*RCy2E4mmH3saWnKRoc7xWg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*RCy2E4mmH3saWnKRoc7xWg.jpeg" /></p>

<p>There is also an observation deck near the cable car, but we skipped it because it was too hot.</p>

<p>Walk back to the bus stop, return to the city, then transfer to the shuttle bus to Gamcheon Culture Village.</p>

<p><img src="/assets/8ace34a1a3d8/1*zTVKv3tTtOdHSoOj81jtTQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*zTVKv3tTtOdHSoOj81jtTQ.jpeg" /></p>

<h4 id="1420-gamcheon-culture-village">~=14:20 Gamcheon Culture Village</h4>

<p><img src="/assets/8ace34a1a3d8/1*rn4LG3Z1ckz_GMiNQe-8FA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*rn4LG3Z1ckz_GMiNQe-8FA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*iJV9jBpSfiIbdpOf6t02Kw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*iJV9jBpSfiIbdpOf6t02Kw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*EDPA83YXOSXbRqmWTUzirg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*EDPA83YXOSXbRqmWTUzirg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Dhi69ZSRcSJyvFgv_Oz5Cw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Dhi69ZSRcSJyvFgv_Oz5Cw.jpeg" /></p>

<p>We didn’t plan to take photos, just visiting spots; it felt like a rainbow village with some cafes. We strolled through quickly and then took the bus back to the hotel from another bus stop.</p>

<blockquote>
  <p><em>Here, the bus is a minibus, similar to the small buses on Yangmingshan. The driver drives aggressively, and it is very crowded.</em></p>
</blockquote>

<h4 id="1500-return-to-noah-hotel-for-rest">~=15:00 Return to Noah Hotel for rest</h4>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/ESP5SgZ4MMA" title="Hotel Noah" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/8ace34a1a3d8/1*c7JIBKmacB_0FpkD1VSV3A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*c7JIBKmacB_0FpkD1VSV3A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*fAl0cr7HLrPVGIc76JM94Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fAl0cr7HLrPVGIc76JM94Q.jpeg" /></p>

<ul>
  <li>
    <p>View from the room window</p>
  </li>
  <li>
    <p>I originally thought there were no coin-operated washers, but there is a free washing machine available in the third-floor restroom.</p>
  </li>
</ul>

<h4 id="1740-depart-to-have-dinner">~=17:40 Depart to have dinner</h4>

<p><img src="/assets/8ace34a1a3d8/1*IX5LmJYb0y7bIOE2FWW1DQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*IX5LmJYb0y7bIOE2FWW1DQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*4qK7MSMMHkG60tIe-3yoDQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*4qK7MSMMHkG60tIe-3yoDQ.jpeg" /></p>

<p>About a 10-minute walk from the hotel.</p>

<h4 id="1750-throat-pot-lid-bbq--moggumung-nampo-branch-">~=17:50 <a href="https://naver.me/xHmnku7F" target="_blank">Throat Pot Lid BBQ — Moggumung Nampo Branch</a> 👍👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*4nvoaUKeXZOVIb1IOCYYXQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*4nvoaUKeXZOVIb1IOCYYXQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*2KHw99GTRvBgKhL2jVZxBw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2KHw99GTRvBgKhL2jVZxBw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*DZdWxcnw-93O_23CzoNHGg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*DZdWxcnw-93O_23CzoNHGg.jpeg" /></p>

<ul>
  <li>
    <p>The unique feature is that the grill is a pot lid.</p>
  </li>
  <li>
    <p>The side dishes include a piece of vitamin C (I checked, and taking vitamin C before or after barbecue can reduce free radicals and lower the risk of cancer).</p>
  </li>
  <li>
    <p>At minimum, order three servings of meat, all grilled by the staff for you.</p>
  </li>
  <li>
    <p>Ordered two pork, one beef, two bowls of instant noodles, and Cass beer. Each meat portion was 18,000 KRW. <strong>Actually paid NT$1,060, which is very affordable, and the meat was delicious!! Highly recommend.</strong></p>
  </li>
</ul>

<h4 id="-1830-walk-to-busan-tower">~= 18:30 Walk to Busan Tower</h4>

<p>After eating, walk all the way to Busan Tower to digest a bit, about 20 minutes.</p>

<p><img src="/assets/8ace34a1a3d8/1*sLs757OOzNAkMKeiRv3lPQ.webp" alt="" loading="lazy" decoding="async" width="698" height="539" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTgiIGhlaWdodD0iNTM5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*sLs757OOzNAkMKeiRv3lPQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*wrz7_9izA0-r02hyNB38sg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*wrz7_9izA0-r02hyNB38sg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*k3Z6gYsAotOt5OVIdFIKgw.webp" alt="" loading="lazy" decoding="async" width="933" height="1170" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzMiIGhlaWdodD0iMTE3MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*k3Z6gYsAotOt5OVIdFIKgw.png" /></p>

<p>The whole way was lively, with many restaurants to eat at.</p>

<p><img src="/assets/8ace34a1a3d8/1*U8r_Qon2rFimBcnEdWpmyw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*U8r_Qon2rFimBcnEdWpmyw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Ovz-36q_zBh3L-jFrMdUZQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="833" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Ovz-36q_zBh3L-jFrMdUZQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*DL3TyNS4bk-sKRx3rNkFqw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*DL3TyNS4bk-sKRx3rNkFqw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*mrV2TAQELoqtWkPK1uj22g.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*mrV2TAQELoqtWkPK1uj22g.jpeg" /></p>

<blockquote>
  <p><strong><em>The navigation should actually be set to — <a href="https://naver.me/GDL5X5TV" target="_blank">龍頭山紀念公園入口 Yongdusan Memorial Park Entrance</a></em></strong> <em>, where there is an escalator going up; from there, you need to climb a bit of a hill (about 5 minutes).</em></p>
</blockquote>

<h4 id="1900-arrive-at-the-foot-of-busan-tower-wait-for-sunset-before-going-up-to-see-the-night-view">19:00 Arrive at the foot of Busan Tower, wait for sunset before going up to see the night view</h4>

<p><img src="/assets/8ace34a1a3d8/1*q4kM-CGN-dIC7w5NU32a0A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*q4kM-CGN-dIC7w5NU32a0A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*JcfGi8vcQtevAvPWGNnuMg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*JcfGi8vcQtevAvPWGNnuMg.jpeg" /></p>

<p>During this time, I visited the souvenir shop.</p>

<h4 id="1930-go-up-busan-tower">19:30 Go up Busan Tower</h4>

<p>Show the Busan Pass at the first-floor counter to exchange for an entrance ticket.</p>

<p><img src="/assets/8ace34a1a3d8/1*xA5Zg72P8NEN_tUVyPZMkA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*xA5Zg72P8NEN_tUVyPZMkA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SOJbuAh7kPr2jx-dl1x1zw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SOJbuAh7kPr2jx-dl1x1zw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*YCkHak-FlJ-mnq0yQWx-yQ.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YCkHak-FlJ-mnq0yQWx-yQ.jpeg" /></p>

<p>You can scan the QR code on your Busan Tower ticket to play a puzzle game. Solving all the codes will earn you a small prize. (I have written the codes later on)</p>

<p><img src="/assets/8ace34a1a3d8/1*JnjlJkLecp-f0MseLSVbwQ.webp" alt="" loading="lazy" decoding="async" width="949" height="1148" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDkiIGhlaWdodD0iMTE0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*JnjlJkLecp-f0MseLSVbwQ.png" /></p>

<p>There were already many people waiting when I arrived.</p>

<p><img src="/assets/8ace34a1a3d8/1*Kmf1d-_Vm4iMYNKmF1sb_g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Kmf1d-_Vm4iMYNKmF1sb_g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*lIwknEUuQ6jAeggYxZZegA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*lIwknEUuQ6jAeggYxZZegA.jpeg" /></p>

<p>19:45 Busan sunset glow.</p>

<p><img src="/assets/8ace34a1a3d8/1*iYaorZ3UxxKx0uFa5qpe6A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*iYaorZ3UxxKx0uFa5qpe6A.jpeg" /></p>

<p>20:00/20:10…There was a light projection and fireworks show, but the effect was mediocre.</p>

<h4 id="2000-busan-night-view">20:00 Busan Night View</h4>

<p><img src="/assets/8ace34a1a3d8/1*DH6uu6N5y8MvhoUoJYt-GQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*DH6uu6N5y8MvhoUoJYt-GQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*9WInxaDdY5aNuL-5CcyuKQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9WInxaDdY5aNuL-5CcyuKQ.jpeg" /></p>

<h4 id="-2010-leave-the-tower">~= 20:10 Leave the tower</h4>

<p><img src="/assets/8ace34a1a3d8/1*SYJBo1VeGTPz0_-ba7Okbw.webp" alt="" loading="lazy" decoding="async" width="956" height="1223" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTYiIGhlaWdodD0iMTIyMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*SYJBo1VeGTPz0_-ba7Okbw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*bpHpZ2j7hlGjfUqN24QkXQ.webp" alt="" loading="lazy" decoding="async" width="918" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*bpHpZ2j7hlGjfUqN24QkXQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*trPGYy37LVKvPbDFepZS6g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*trPGYy37LVKvPbDFepZS6g.jpeg" /></p>

<p>There are still many photo spots before leaving.</p>

<h4 id="-2015-redeem-small-souvenirs">~= 20:15 Redeem Small Souvenirs</h4>

<p><img src="/assets/8ace34a1a3d8/1*GIz7xVJewRWy35RBleksMw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*GIz7xVJewRWy35RBleksMw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*kUe5ECb7bsWp_Qwh2hlEug.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*kUe5ECb7bsWp_Qwh2hlEug.jpeg" /></p>

<p>Before heading down the mountain, remember to go to the outdoor unmanned ticket machine (Ticket Box):</p>

<ul>
  <li>
    <p>Enter password: <code class="language-plaintext highlighter-rouge">19731121</code> (Busan Tower construction date)</p>
  </li>
  <li>
    <p>You can use the voucher to redeem small prizes at the souvenir center in the blue building outside (not the one on the first floor of Busan Tower).</p>
  </li>
</ul>

<h4 id="a-small-souvenir-is-a-card-available-in-various-styles-i-chose-a-transparent-busan-tower-card">A small souvenir is a card, available in various styles. I chose a transparent Busan Tower card:</h4>

<p><img src="/assets/8ace34a1a3d8/1*jPcatm0ieKrZEYkk75hpmA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*jPcatm0ieKrZEYkk75hpmA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*gpu_v-fM13P4wilbaGzEwA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*gpu_v-fM13P4wilbaGzEwA.jpeg" /></p>

<p>Descended all the way down from Yongdusan Park.</p>

<p><img src="/assets/8ace34a1a3d8/1*oDNlLOobzNrq0E5Hue-GHA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*oDNlLOobzNrq0E5Hue-GHA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*y3olhVcaCU5pYyDz3U8lcQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*y3olhVcaCU5pYyDz3U8lcQ.jpeg" /></p>

<blockquote>
  <p><strong><em>There is an escalator here!!! (Going up only, no down escalator; part of it is under renovation)</em></strong></p>
</blockquote>

<h4 id="2030-return-to--龍頭山紀念公園入口-yongdusan-media-park-entrance">20:30 Return to — <a href="https://naver.me/GDL5X5TV" target="_blank">龍頭山紀念公園入口 Yongdusan Media Park Entrance</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*G_jAYR7TS8ESc3ddWf-CpA.webp" alt="" loading="lazy" decoding="async" width="961" height="1195" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjEiIGhlaWdodD0iMTE5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*G_jAYR7TS8ESc3ddWf-CpA.png" /></p>

<p>Remember, it’s closer to go to Busan Tower from here.</p>

<p><img src="/assets/8ace34a1a3d8/1*gavdcweW2vtAfov9NgR9_w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*gavdcweW2vtAfov9NgR9_w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QXT1p_8zTY09QXle3f8JTA.webp" alt="" loading="lazy" decoding="async" width="947" height="1197" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTE5NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QXT1p_8zTY09QXle3f8JTA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QdwuA16DAl-Abv2AqrjJEw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QdwuA16DAl-Abv2AqrjJEw.jpeg" /></p>

<p>Strolled around the shopping street and found an HBAF almond specialty store, which seems to have the most complete range of flavors. Bought the Yellow combo pack (with small bags inside) along with some unique flavors (popping candy that really pops, spicy stir-fried rice cakes) and popular flavors (honey, Oreo, corn) as souvenirs.</p>

<h4 id="2045-return-to-biff-square-to-buy-late-night-snacks">20:45 Return to —BIFF Square to buy late-night snacks</h4>

<p><img src="/assets/8ace34a1a3d8/1*hGy9kfVNX80z2gYTpXsO1A.webp" alt="" loading="lazy" decoding="async" width="958" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTgiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hGy9kfVNX80z2gYTpXsO1A.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*I2_vOk86JV7s2IcRV4nwAw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*I2_vOk86JV7s2IcRV4nwAw.jpeg" /></p>

<p>There are many street vendors here, and the floor has celebrities’ handprints.</p>

<h4 id="makgeolli-bhc-fried-chicken--bhc-chicken-busan-nampo-branch-right-at-biff-square-">Makgeolli, <a href="https://naver.me/xww9nJx2" target="_blank">BHC Fried Chicken — BHC Chicken Busan Nampo Branch</a> (right at BIFF Square) 👍👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*ps1VVbDmZzRPf5CZTrzdtQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ps1VVbDmZzRPf5CZTrzdtQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*qf2-1dMVz6KrPmTQRXO0Pw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*qf2-1dMVz6KrPmTQRXO0Pw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*oEo33HTYo8UdkOZ60P3Gxw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*oEo33HTYo8UdkOZ60P3Gxw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*C1NraGTlXNjmzfeCg7-5RA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*C1NraGTlXNjmzfeCg7-5RA.jpeg" /></p>

<p>Finally bought and ate:</p>

<ul>
  <li>
    <p>Makgeolli: Similar to millet wine or fermented rice wine, very good.</p>
  </li>
  <li>
    <p>BHC Boneless Fried Chicken: The seasoning and chicken are tender and very delicious.</p>
  </li>
</ul>

<h4 id="0000-good-night-nampo">00:00 Good night, Nampo</h4>

<p><img src="/assets/8ace34a1a3d8/1*Hh1Rxl4e47yN-iFuGxer9g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Hh1Rxl4e47yN-iFuGxer9g.jpeg" /></p>

<p>Around 00:00, the street vendors all clear out.</p>

<h4 id="day-5-0802-sat-lotte-department-store-yeongdo-bridge-opening-only-on-saturdays">Day 5 08/02 (Sat) Lotte Department Store, Yeongdo Bridge Opening (Only on Saturdays)</h4>

<h4 id="1045-lotte-department-store-gwangbok-branch-lotte-supermarket--lotte-dept-store-gwangbok-branch-aqua-mall">10:45 <a href="https://naver.me/xWziztAc" target="_blank">Lotte Department Store Gwangbok Branch, Lotte Supermarket — Lotte Dept. Store Gwangbok Branch Aqua Mall</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*cCougQTjZSMEe_nhgbQF2w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*cCougQTjZSMEe_nhgbQF2w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*CsRLupeh763WBqVnJ8pQDA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*CsRLupeh763WBqVnJ8pQDA.jpeg" /></p>

<p>Leave around 10:XX, Lotte Department Store opens at 10:30, walk there leisurely.</p>

<p><img src="/assets/8ace34a1a3d8/1*9fHXV2Og6JodL5Pjad3JDg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9fHXV2Og6JodL5Pjad3JDg.jpeg" /></p>

<p>First, head to the B1 food court cafe to grab a casual breakfast. (So-so, the coffee was Lungo)</p>

<h4 id="1130-start-wandering-around-the-department-store-randomly">11:30 Start wandering around the department store randomly</h4>

<p><img src="/assets/8ace34a1a3d8/1*A75a3-GEjeoMSxnuqlFt-w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*A75a3-GEjeoMSxnuqlFt-w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Nk9RtTQ2z7ruxWDgKhhIrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Nk9RtTQ2z7ruxWDgKhhIrg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*-usgnffatzz53RtQHbC6PA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*-usgnffatzz53RtQHbC6PA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*J957_He9MA3RC16vdzSjbA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*J957_He9MA3RC16vdzSjbA.jpeg" /></p>

<ul>
  <li>
    <p>Bought a few clothes at the Kodak store (on average NT$300–$500 cheaper than buying through Taiwan resellers).<br />
The store offers direct tax refund.</p>
  </li>
  <li>
    <p>Bought a handheld fan<br />
Use the receipt to find the tax refund machine in the B1 food court to print the tax refund form, then claim the cash refund at the airport. (You can get back about NT$60)</p>
  </li>
</ul>

<p><img src="/assets/8ace34a1a3d8/1*QxFRNLVX2vM0ISZf3vM2-g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QxFRNLVX2vM0ISZf3vM2-g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*TxOTSeqke8P2TE0MWWgiPA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*TxOTSeqke8P2TE0MWWgiPA.jpeg" /></p>

<ul>
  <li>The water dance show starts every hour from 11 AM at the department store and is quite impressive.</li>
</ul>

<h4 id="1230-b1-downtown-burger">12:30 B1 Downtown Burger</h4>

<p><img src="/assets/8ace34a1a3d8/1*i3upBh9hp-ZjdfMxeskBnQ.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*i3upBh9hp-ZjdfMxeskBnQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*oK7jL0JkiRD69PAlv-fO5Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*oK7jL0JkiRD69PAlv-fO5Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*1BHaKNhjT3BIOkW4-qnE4g.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*1BHaKNhjT3BIOkW4-qnE4g.jpeg" /></p>

<p>After getting tired from walking, we went to B1 for Downtown Burgers at noon; I get it, but American burgers also offer kimchi!</p>

<p><img src="/assets/8ace34a1a3d8/1*TvleF3CnUri6-JtSSah9mg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*TvleF3CnUri6-JtSSah9mg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*czdiuWsarXuyo7IfATd7hg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*czdiuWsarXuyo7IfATd7hg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*SCYBfHs65_54TAWjMMssnQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*SCYBfHs65_54TAWjMMssnQ.jpeg" /></p>

<p>After eating, stroll around the department store rooftop observatory (very sunny and hot).</p>

<h4 id="1330-walk-out-of-the-department-store-and-wait-outside-by-yeongdo-bridge-for-the-bridge-to-open">~=13:30 Walk out of the department store and wait outside by Yeongdo Bridge for the bridge to open</h4>

<p><img src="/assets/8ace34a1a3d8/1*SY6E9kWJ4lQ4WfXaX9Lvww.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*SY6E9kWJ4lQ4WfXaX9Lvww.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*PB-U5oD_Iw1PaNIj9vu1Rw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PB-U5oD_Iw1PaNIj9vu1Rw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ml5meRbBGO-wiXskpKv7FQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ml5meRbBGO-wiXskpKv7FQ.jpeg" /></p>

<p>It’s very sunny outside, and more people gather as it gets closer to 14:00; at this time, <strong>an ajumma came over and told us that from 8/2 to 8/30, due to the heat, the event is moved to 20:00!! Shocking!!</strong> Then people started spreading the news that it was changed to 20:00, and everyone slowly began to leave.</p>

<p><img src="/assets/8ace34a1a3d8/1*GQondkcuwv5QwfzkKZ7QPw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*GQondkcuwv5QwfzkKZ7QPw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Lyms5xvH6lwxp0hPFF2DuA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Lyms5xvH6lwxp0hPFF2DuA.jpeg" /></p>

<p>Later, I saw an announcement banner on the railing. (The board on the left shows the regular schedule)</p>

<h4 id="1350-return-to-lotte-department-store-and-lotte-mart-to-continue-wandering-around">13:50 Return to Lotte Department Store and Lotte Mart to continue wandering around</h4>

<p><img src="/assets/8ace34a1a3d8/1*O6oXHk-CkB8vJ-vxvVIfSQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*O6oXHk-CkB8vJ-vxvVIfSQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*0fkOrRbJw84kNZ_Eu_zuZA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*0fkOrRbJw84kNZ_Eu_zuZA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*e2LIsRj5B6KtM1tS_Pvldw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*e2LIsRj5B6KtM1tS_Pvldw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ndyL7-V4wbHHLtu5uYqxlQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ndyL7-V4wbHHLtu5uYqxlQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Vi3D5-dWbMN0XVogyhAYBA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Vi3D5-dWbMN0XVogyhAYBA.jpeg" /></p>

<p>Lotte Supermarket also sells some souvenirs and skincare products (similar to Carrefour).</p>

<h4 id="b1-tax-refund-machine">B1 Tax Refund Machine</h4>

<p><img src="/assets/8ace34a1a3d8/1*VJW26Pss8GIKP_EdOKhPbQ.webp" alt="" loading="lazy" decoding="async" width="930" height="1147" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzAiIGhlaWdodD0iMTE0NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*VJW26Pss8GIKP_EdOKhPbQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*QpYPLAIoTGVwYHZoYljzGQ.webp" alt="" loading="lazy" decoding="async" width="948" height="1199" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDgiIGhlaWdodD0iMTE5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*QpYPLAIoTGVwYHZoYljzGQ.png" /></p>

<p>It took a long time to find the tax refund machine, located on a small path next to Din Tai Fung.</p>

<ul>
  <li>If it’s not an immediate tax refund, you need to scan the receipt barcode here to exchange it for a tax refund slip, and then claim the money back at the airport. (A bit troublesome)</li>
</ul>

<blockquote>
  <p><strong><em>I just learned that Korea has different tax refund agencies: Global Blue, GLOBAL TAX FREE, Easy Tax Refund, CubeRefund, and eTAX FREE. You can only use the corresponding refund machines</em></strong> <em>; I saw a cash refund machine at Olive Young that was GLOBAL TAX FREE, but my receipt was CubeRefund, so I still had to get the refund at the airport.</em></p>
</blockquote>

<h4 id="1635-return-to-the-hotel-to-rest">16:35 Return to the hotel to rest</h4>

<p><img src="/assets/8ace34a1a3d8/1*ngRJ5SY55QsOVeEuesGIXg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ngRJ5SY55QsOVeEuesGIXg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*IoTCxYVKEGU4JpUwsCMEUQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*IoTCxYVKEGU4JpUwsCMEUQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*hA48uOURihuc-sbE-Qrn3Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*hA48uOURihuc-sbE-Qrn3Q.jpeg" /></p>

<p>When tired from walking, I went to B1 to buy Korean red bean and peach snacks, along with a convenience store hand-shaken drink made with an old recipe (iced cup + banana milk + unsweetened black coffee), then took the underground street back to the hotel to rest.</p>

<h4 id="1900-leave-the-hotel-to-visit-biff-olive-young-for-a-stroll">19:00 Leave the hotel to visit BIFF Olive Young for a stroll</h4>

<p><img src="/assets/8ace34a1a3d8/1*LVHpdVT1I-X7_BStMRVfnQ.webp" alt="" loading="lazy" decoding="async" width="932" height="1192" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzIiIGhlaWdodD0iMTE5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*LVHpdVT1I-X7_BStMRVfnQ.png" /></p>

<p>I didn’t get to explore Haeundae at all OY, so I took the time to check it out. This store is very large, with many friendly staff.</p>

<p><img src="/assets/8ace34a1a3d8/1*urwdAXWOvdtMGRzUZ2cMWw.webp" alt="Souvenirs" loading="lazy" decoding="async" width="1400" height="1049" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*urwdAXWOvdtMGRzUZ2cMWw.png" /></p>

<p>Souvenirs</p>

<h4 id="1940-arrived-at-yeongdo-bridge-waiting-for-the-bridge-to-open">19:40 Arrived at Yeongdo Bridge, waiting for the bridge to open</h4>

<p><img src="/assets/8ace34a1a3d8/1*JG_o0tkChN-UKCtRdkx8Mw.webp" alt="" loading="lazy" decoding="async" width="1200" height="872" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*JG_o0tkChN-UKCtRdkx8Mw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZOT3YuHUnfvKjglbTllFRQ.webp" alt="" loading="lazy" decoding="async" width="896" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTYiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZOT3YuHUnfvKjglbTllFRQ.png" /></p>

<p>Under the bridge, there is a booth for composite souvenir photos.</p>

<p>Traffic control will start around 19:50.</p>

<h4 id="2000-bridge-opening">20:00 Bridge Opening</h4>

<p><img src="/assets/8ace34a1a3d8/1*Zpgsucjjm5BNpq_D4zfh1A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Zpgsucjjm5BNpq_D4zfh1A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*NTI7_uUkEmOE2SLCN3fclQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*NTI7_uUkEmOE2SLCN3fclQ.jpeg" /></p>

<p>First, watch from under the bridge. Around 20:05, when it’s almost done, start moving up.</p>

<p><img src="/assets/8ace34a1a3d8/1*h8neY7OqYx6WG5QVgxAEGQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*h8neY7OqYx6WG5QVgxAEGQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*n_oF3q1EJ4xy7iF-_mNIYA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*n_oF3q1EJ4xy7iF-_mNIYA.jpeg" /></p>

<p>There was a projection introducing the history of Yeongdo Bridge, but the projector angle on the first day seemed off, making it hard to see clearly.</p>

<h4 id="2015-the-bridge-lowered-again-resuming-traffic-flow">20:15 The bridge lowered again, resuming traffic flow</h4>

<p><img src="/assets/8ace34a1a3d8/1*CsyWpHHwp2x8lLZfbvXvvA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*CsyWpHHwp2x8lLZfbvXvvA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*gV_RKoppNxGGwl8BMErUkw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*gV_RKoppNxGGwl8BMErUkw.jpeg" /></p>

<h4 id="2030-dinner--ton-black--tempura-nine">20:30 Dinner — <a href="https://naver.me/Fm3VOYcH" target="_blank">Ton Black &amp; Tempura Nine</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*VEY9B-esGVFWEHU-1y5OoA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*VEY9B-esGVFWEHU-1y5OoA.jpeg" /></p>

<ul>
  <li>
    <p>On the way back to the hotel at night, I casually found a pork cutlet restaurant to eat (the pork cutlet was just as tender and delicious).</p>
  </li>
  <li>
    <p>One actual charge of NT$366</p>
  </li>
</ul>

<h4 id="2150-return-to-the-hotel-to-rest">~=21:50 Return to the hotel to rest</h4>

<p><img src="/assets/8ace34a1a3d8/1*fd1YC6o2Gytnh_shoUXxdg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fd1YC6o2Gytnh_shoUXxdg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*O-5YaMfjKPHJ3oCMVfn1UQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*O-5YaMfjKPHJ3oCMVfn1UQ.jpeg" /></p>

<h3 id="day-6-0803-sun-lotte-department-store-bupyeong-canned-fish-market-baeksan-bay-cultural-village-bokcheon-temple-night-view">Day 6 08/03 (Sun) Lotte Department Store, Bupyeong Canned Fish Market, Baeksan Bay Cultural Village, Bokcheon Temple Night View</h3>

<blockquote>
  <p><strong><em>Today was just wandering around to kill time. The content is not recommended for reference, purely for record.</em></strong></p>
</blockquote>

<h4 id="1000-head-out-and-eat-first--egg-drop">10:00 Head out and eat first — <a href="https://naver.me/5gYr3qdc" target="_blank">Egg Drop</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*Kh1xpKnZV-MMtVanIicSyA.webp" alt="" loading="lazy" decoding="async" width="942" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Kh1xpKnZV-MMtVanIicSyA.png" /></p>

<ul>
  <li>Located in the shopping street opposite Lotte Department Store.</li>
</ul>

<p><img src="/assets/8ace34a1a3d8/1*aRSQiMUWKNfjDtDVZ8V3oQ.webp" alt="" loading="lazy" decoding="async" width="945" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*aRSQiMUWKNfjDtDVZ8V3oQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*MnFVcEjz135UauqnyR5H3Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*MnFVcEjz135UauqnyR5H3Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*BD26TaxsDjAO3wb9rbeFwQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*BD26TaxsDjAO3wb9rbeFwQ.jpeg" /></p>

<ul>
  <li>
    <p>Order at the machine on the first floor, take a number ticket, then go upstairs to find a seat (make sure to check for available seats first).</p>
  </li>
  <li>
    <p>Waited about 20 minutes</p>
  </li>
  <li>
    <p>Ordered a ham and cheese omelette toast + hash browns + iced coffee: 8,400 KRW</p>
  </li>
</ul>

<blockquote>
  <p><em>It’s good, but I think some brunch spots in Taiwan taste better than this.</em></p>
</blockquote>

<h4 id="1100-eat-well-before-setting-off-again">11:00 Eat well before setting off again</h4>

<p><img src="/assets/8ace34a1a3d8/1*p3YDCVRrvV7QQgx6UomwYA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*p3YDCVRrvV7QQgx6UomwYA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*MQgkZeRyQFfcU5Zti1s4eA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*MQgkZeRyQFfcU5Zti1s4eA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*zl6x8Pt3pxiIR94C8KwkIg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*zl6x8Pt3pxiIR94C8KwkIg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*zB10iTHzqS8mD4T678Lgnw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*zB10iTHzqS8mD4T678Lgnw.jpeg" /></p>

<p>On the way to Lotte Department Store, there is a “Big Shop” Busan souvenir store that specializes in Boogi and Busan-themed merchandise.</p>

<h4 id="lotte-department-store-gwangbok-branch-lotte-supermarket--lotte-dept-store-gwangbok-branch-aqua-mall"><a href="https://naver.me/xWziztAc" target="_blank">Lotte Department Store Gwangbok Branch, Lotte Supermarket — Lotte Dept. Store Gwangbok Branch Aqua Mall</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*IlBfn7L4in16Sqh0S9u89A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*IlBfn7L4in16Sqh0S9u89A.jpeg" /></p>

<p>Bored, continued browsing Lotte Department Store.</p>

<h4 id="1145-b1-mukguk-table-steamed-cuisine-lotte-dept-store-gwangbok-branch">11:45 B1 <a href="https://naver.me/5YSwvcB0" target="_blank">Mukguk Table Steamed Cuisine Lotte Dept. Store Gwangbok Branch</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*OebNrqD4ge6viEehCRbprA.webp" alt="" loading="lazy" decoding="async" width="1200" height="851" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*OebNrqD4ge6viEehCRbprA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*TYSdoPRJBzjaNXE9MImmng.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*TYSdoPRJBzjaNXE9MImmng.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*M1rt5vPWP34qVzr4Rwq8OQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*M1rt5vPWP34qVzr4Rwq8OQ.jpeg" /></p>

<p>After eating heavy-flavored food for several days, I wanted something lighter for lunch.</p>

<ul>
  <li>
    <p>Rice must be ordered separately.</p>
  </li>
  <li>
    <p>It has little to no flavor.</p>
  </li>
</ul>

<p>After eating, we strolled around a bit more.</p>

<h4 id="1250-leave-lotte-department-store-and-head-to-sky-eye-observatory--yeongju-haneul-nun-observatory">12:50 Leave Lotte Department Store and head to <a href="https://naver.me/xYvTULtB" target="_blank">Sky Eye Observatory — Yeongju Haneul Nun Observatory</a></h4>

<p>Just wandered around nearby and found an <a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/">observation deck I missed last year</a>. There’s a direct bus (21 minutes), so I went there.</p>

<p><img src="/assets/8ace34a1a3d8/1*vzM0d49ldD5u40myXdTgug.webp" alt="" loading="lazy" decoding="async" width="382" height="721" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzODIiIGhlaWdodD0iNzIxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*vzM0d49ldD5u40myXdTgug.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*T8YCaXpAOoHzt08VBNO2zQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*T8YCaXpAOoHzt08VBNO2zQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*zlS5hxZG5BXyZNASUNzXMA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*zlS5hxZG5BXyZNASUNzXMA.jpeg" /></p>

<ul>
  <li>
    <p>Buses in Busan are almost all brand new electric buses with very cool air conditioning.</p>
  </li>
  <li>
    <p>Passing by <a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/">Last year’s stop: Busan Station</a></p>
  </li>
</ul>

<h4 id="1300-sky-eye-observatory--yeongju-haneul-nun-observatory">13:00 <a href="https://naver.me/xYvTULtB" target="_blank">Sky Eye Observatory — Yeongju Haneul Nun Observatory</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*clTNOo-ZktYUKpKKh75heg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*clTNOo-ZktYUKpKKh75heg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*EQMOhh1KhjP0i9bwys0YuQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*EQMOhh1KhjP0i9bwys0YuQ.jpeg" /></p>

<p>After getting off at the bus stop, walk across the street and down to see it.</p>

<p><img src="/assets/8ace34a1a3d8/1*YDfNJ4--5A0Kt0fiScCvVQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*YDfNJ4--5A0Kt0fiScCvVQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*fzBzk65b4cALhHQ3_Ej2ow.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fzBzk65b4cALhHQ3_Ej2ow.jpeg" /></p>

<p>The view here is great, offering a full overlook of Busan’s Nampo area.</p>

<h4 id="heading-down-to-bupyeong-can-market--first-buy-the-colleague-recommended-the-liter-hand-shaken-drink">Heading down to Bupyeong Can Market — <a href="https://naver.me/FMT6cQrZ" target="_blank">First, buy the colleague-recommended The Liter hand-shaken drink</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*-B44RvmQkP8ROxbo_ycOpA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*-B44RvmQkP8ROxbo_ycOpA.jpeg" /></p>

<ul>
  <li>Only recommend: Blueberry Yogurt Smoothie, available in 600ML or 1,000ML sizes; refreshing and not too sweet, won’t make you feel more thirsty. Price is about 4,300 KRW.</li>
</ul>

<h4 id="bupyeong-kkangtong-market"><a href="https://naver.me/Goi5D3F6" target="_blank">Bupyeong Kkangtong Market</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*V2RiLAAeaBdJUF4RVKtZCg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*V2RiLAAeaBdJUF4RVKtZCg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*o9T02VtSuMwlGadw7Mq1Jg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*o9T02VtSuMwlGadw7Mq1Jg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*9Zbj66ueqAwQvyD6OTvZqQ.webp" alt="" loading="lazy" decoding="async" width="940" height="1196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDAiIGhlaWdodD0iMTE5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9Zbj66ueqAwQvyD6OTvZqQ.png" /></p>

<p>Inside the canned food market, there are many stalls selling dry goods, groceries, and seafood (like a traditional market). The area is quite large.</p>

<h4 id="bupyeong-can-market--milgot-red-bean-cream-mugwort-rice-cake-">Bupyeong Can Market — <a href="https://naver.me/GEiuXPsf" target="_blank">Milgot</a> Red Bean Cream Mugwort Rice Cake 👍</h4>

<p><img src="/assets/8ace34a1a3d8/1*Wj_6WUHzrJCVCSyo-gsGSw.webp" alt="" loading="lazy" decoding="async" width="951" height="1180" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTEiIGhlaWdodD0iMTE4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Wj_6WUHzrJCVCSyo-gsGSw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*2Nj6AKmSZkzlQ5SO013W7w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2Nj6AKmSZkzlQ5SO013W7w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*R14q-E0U_yWC9iXarLhRZg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*R14q-E0U_yWC9iXarLhRZg.jpeg" /></p>

<p>It is probably the most famous store in the canned market, known for its specialty — red bean cream mugwort rice cake.</p>

<ul>
  <li>
    <p>Since I don’t speak Korean, I just took a photo and showed it to the staff to order this.</p>
  </li>
  <li>
    <p>The staff said to keep it refrigerated when you get home, and it’s best to thaw it for half an hour to an hour before eating.</p>
  </li>
  <li>
    <p>Also bought a savory bread, which was pretty good!</p>
  </li>
</ul>

<p><img src="/assets/8ace34a1a3d8/1*qm0Wh7MPbrWfst0Y_4eF-A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*qm0Wh7MPbrWfst0Y_4eF-A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*GJ8R6Is85xYOA8HOlA_bQg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*GJ8R6Is85xYOA8HOlA_bQg.jpeg" /></p>

<p>Later, I took it back to the hotel to eat. The mugwort rice cake has a chewy texture paired with soybean powder and red bean cream filling. I found it quite unique and slightly sweet. If you pass by, give it a try!</p>

<p><img src="/assets/8ace34a1a3d8/1*XQYb4xKnzk0rEMbMJWlglQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*XQYb4xKnzk0rEMbMJWlglQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*cwJikydjyNJW3SDbudKmOA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*cwJikydjyNJW3SDbudKmOA.jpeg" /></p>

<p>Also bought another box of <a href="https://naver.me/xww9nJx2" target="_blank">BHC Fried Chicken</a> to have for dinner.</p>

<h4 id="1830-depart-again">18:30 Depart Again</h4>

<p>After resting in the evening, we went out again, mainly to visit Bokcheon Temple for night views. First, we checked out Baeksan Bay Cultural Village.</p>

<h4 id="1845-baekcheon-bay-cultural-village">18:45 <a href="https://naver.me/FgTvHYSL" target="_blank">Baekcheon Bay Cultural Village</a></h4>

<blockquote>
  <p><em>The most famous spot here is — <a href="https://naver.me/52chRHd4" target="_blank">Enjoy the view, coffee, and foot bath — VIEW, FOOT BATH CAFE</a> foot bath café.</em></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*wFYAic5cJEcssp2DziS85Q.webp" alt="" loading="lazy" decoding="async" width="945" height="1234" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTIzNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*wFYAic5cJEcssp2DziS85Q.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*9xd7-RARtJnwkQUwLqoz2w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*9xd7-RARtJnwkQUwLqoz2w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*_bg1OXnenjgxXpfoX3xwog.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*_bg1OXnenjgxXpfoX3xwog.jpeg" /></p>

<p>But since it was late, we just took a quick look around; there are many cafes and small shops along the coast, but maybe because it was late, there weren’t many people. The coastal walkway below was under maintenance and inaccessible. The weather was cloudy today, so the scenery wasn’t great.</p>

<h4 id="1900-head-to-bokcheonsa-temple">19:00 Head to <a href="https://naver.me/5L7oHSSB" target="_blank">Bokcheonsa Temple</a></h4>

<p>After leaving Baishan Bay, head up to Fuchuan Temple.</p>

<p><img src="/assets/8ace34a1a3d8/1*kCU_DP31_Q8oVxfZPA7hOw.webp" alt="" loading="lazy" decoding="async" width="1136" height="908" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTM2IiBoZWlnaHQ9IjkwOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*kCU_DP31_Q8oVxfZPA7hOw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*7BgsQXSUyo7X3sQnbDpgCQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*7BgsQXSUyo7X3sQnbDpgCQ.jpeg" /></p>

<p>Originally, the map said the bus was only one stop away, so I thought I could just walk. After a few steps, I gave up; the map showed the bus was 1 stop (1 minute), but walking actually took about 20 minutes (all steep and shaky hills).</p>

<ul>
  <li>Yeongdogu 1 or 5 are both fine</li>
</ul>

<p><strong>Take only one stop to the next station <a href="https://naver.me/xzlLlZhM" target="_blank">Sinseon Elementary School</a>, then you have to walk the rest:</strong></p>

<p><img src="/assets/8ace34a1a3d8/1*O0Q9eorhHVOa5e9W69pK2A.webp" alt="" loading="lazy" decoding="async" width="688" height="310" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODgiIGhlaWdodD0iMzEwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*O0Q9eorhHVOa5e9W69pK2A.png" /></p>

<p>After getting off, I was stunned; it was still a very steep and shaky hillside. From here, I walked all the way up:</p>

<p><img src="/assets/8ace34a1a3d8/1*KF-K6yfIdIQ_oxTPMFu6TA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*KF-K6yfIdIQ_oxTPMFu6TA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*A232HHlbcIoIpCEE770RhA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*A232HHlbcIoIpCEE770RhA.jpeg" /></p>

<p>After passing the elementary school, you will see the entrance to Fuchuan Temple ahead:</p>

<p><img src="/assets/8ace34a1a3d8/1*8ISDSkUNQK1J6SJokF48Zg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*8ISDSkUNQK1J6SJokF48Zg.jpeg" /></p>

<p>This is just the beginning; you still have to climb a very shaky hillside all the way up to reach Bogwansa Temple:</p>

<p><img src="/assets/8ace34a1a3d8/1*f7K27vuhRDimn9H-2KB6Hw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*f7K27vuhRDimn9H-2KB6Hw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*DIL09kj3M9GtyrJSHP9G0A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*DIL09kj3M9GtyrJSHP9G0A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*JBWBSHF6GYn6fIuWPd4Dsw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*JBWBSHF6GYn6fIuWPd4Dsw.jpeg" /></p>

<blockquote>
  <p><strong><em>Cars will be passing here, please pay attention to safety ⚠️</em></strong></p>
</blockquote>

<h4 id="1925-arrive-at-bogwangsa-temple">19:25 Arrive at Bogwangsa Temple</h4>

<p><img src="/assets/8ace34a1a3d8/1*a9Rw-kNrof1PF3jtwV5Y1A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*a9Rw-kNrof1PF3jtwV5Y1A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ojagYvbJbUhKBz1JOVOlwA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*ojagYvbJbUhKBz1JOVOlwA.jpeg" /></p>

<blockquote>
  <p><em>Arrived at Bokcheon Temple around 19:25 (almost exhausted). The map said 10 minutes, but since it was all uphill with breaks, it actually took about 25 minutes.</em></p>
</blockquote>

<p>But the more tragic thing is — it closed! It closed! It closed!</p>

<p>That’s right, Bokcheon Temple is not really a public tourist spot; it’s a quiet temple that closes its gates at a certain time. According to colleagues, after around 18:00, you can only enter but not exit. They don’t rush people out, but once the gates close, no one is allowed in.</p>

<p><img src="/assets/8ace34a1a3d8/1*DgPxNoCjrJWvL1uURfNIJA.webp" alt="" loading="lazy" decoding="async" width="944" height="1248" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDQiIGhlaWdodD0iMTI0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*DgPxNoCjrJWvL1uURfNIJA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*VsLe3FUcx9XaJ8WTVWjlIg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*VsLe3FUcx9XaJ8WTVWjlIg.jpeg" /></p>

<p>Tragic, we could only view the scenery from the loft outside the station. Later, some visitors who came because of its fame also stood there.</p>

<blockquote>
  <p><strong><em>However, seeing us sweating heavily, the abbot threw two cans of ice water to us from inside. We were very grateful.</em></strong></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*5lpfrhdVVFrsN-O0qrTmQw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*5lpfrhdVVFrsN-O0qrTmQw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*lEg8_dHkPXgVIPbMLGpAVQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*lEg8_dHkPXgVIPbMLGpAVQ.jpeg" /></p>

<p>View of the cityscape from here.</p>

<p><img src="/assets/8ace34a1a3d8/1*fFLE2v0oSj9z5snHg_KINQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*fFLE2v0oSj9z5snHg_KINQ.jpeg" /></p>

<p>Finally, I casually took a photo of the night view before heading down the mountain. I decided to come here after seeing photos from other bloggers:</p>

<p><img src="/assets/8ace34a1a3d8/1*gcXOAkYiOkfJZLVpHVqQnw.webp" alt="This is what it should look like inside" loading="lazy" decoding="async" width="789" height="743" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODkiIGhlaWdodD0iNzQzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*gcXOAkYiOkfJZLVpHVqQnw.png" /></p>

<p><a href="https://judyer.com/bstp/" target="_blank">This is what it should really look like inside</a></p>

<p>Going downhill all the way, everything is dark:</p>

<p><img src="/assets/8ace34a1a3d8/1*uYq1hyMs27u89dlhoSsz7A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*uYq1hyMs27u89dlhoSsz7A.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*qAWrK94KUe0KxtQDv0kAsQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*qAWrK94KUe0KxtQDv0kAsQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*KQ_lF81lFAdeVBQ5FiexTQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*KQ_lF81lFAdeVBQ5FiexTQ.jpeg" /></p>

<p>It is a very quiet village. After walking down, I took the bus back to Jagalchi Station.</p>

<p><img src="/assets/8ace34a1a3d8/1*X0odkWacFr5MXbc1iBV-tg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*X0odkWacFr5MXbc1iBV-tg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*2wUeYbwRYkMnLrNhrTMWLg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*2wUeYbwRYkMnLrNhrTMWLg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*3nwYcNLllFSmQ8zjmiKxjA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*3nwYcNLllFSmQ8zjmiKxjA.jpeg" /></p>

<p>Before returning to the hotel, I bought the <a href="https://naver.me/5eZTZe4y" target="_blank">Seed Sugar Cake</a> at BIFF Square, where many people were lining up. I found it greasy and too sweet, not very impressive.</p>

<p><img src="/assets/8ace34a1a3d8/1*dPAGNfr1hlXfTu3CnFl7ng.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*dPAGNfr1hlXfTu3CnFl7ng.jpeg" /></p>

<p>Around 22:00, it started raining in Busan! Luckily, our trip was almost over.</p>

<h3 id="day-7-0804-mon-busan-seomyeon-shinsegae-department-store-korean-beef">Day 7 08/04 (Mon) Busan Seomyeon, Shinsegae Department Store, Korean Beef</h3>

<p>In the morning, head out and move to Seomyeon Station.</p>

<h4 id="1045-arrive-at-seomyeon-station">10:45 Arrive at Seomyeon Station</h4>

<p><img src="/assets/8ace34a1a3d8/1*o5yuGlxr94cLNs65fiuL5A.webp" alt="" loading="lazy" decoding="async" width="1400" height="984" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*o5yuGlxr94cLNs65fiuL5A.png" /></p>

<p>After arriving, I thought about storing my luggage directly at the subway station. Other places like Lotte Department Store also offer luggage storage.</p>

<blockquote>
  <p><strong><em>Luggage storage here is very expensive: drop off at 10:45 AM, pick up at 8:10 PM.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Charged 6,000 + 7,000 KRW (about NT$280).</em></strong></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*Wc20JDECq1yHn3CgB1qXFA.webp" alt="" loading="lazy" decoding="async" width="1400" height="998" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Wc20JDECq1yHn3CgB1qXFA.png" /></p>

<p>There is also an Olive Young in the underground shopping area at this station, so I restocked again.</p>

<h4 id="1100-lunch-korean-style-knife-cut-noodles--gijang-sonkalguksu">11:00 Lunch Korean Style <a href="https://naver.me/Fr0l0gR5" target="_blank">Knife-cut Noodles — Gijang Sonkalguksu</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*sd_qEyJXkxRxFOBwd40Tvg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*sd_qEyJXkxRxFOBwd40Tvg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*GUBEh-TqX2boCuU2I78HKA.webp" alt="" loading="lazy" decoding="async" width="1200" height="846" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*GUBEh-TqX2boCuU2I78HKA.png" /></p>

<p>After shopping until nearly noon, head to a nearby market to eat Korean-style knife-cut noodles.</p>

<p><img src="/assets/8ace34a1a3d8/1*eELPbEa7ehdgUH6X47-C9A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*eELPbEa7ehdgUH6X47-C9A.jpeg" /></p>

<p>The menu choices are simple, only knife-cut noodles and seaweed rolls.</p>

<p><img src="/assets/8ace34a1a3d8/1*EsG2Zr3hz1BvZ6ZYMtfplA.webp" alt="" loading="lazy" decoding="async" width="1200" height="861" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*EsG2Zr3hz1BvZ6ZYMtfplA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*B3iHfLVRtZ7zTuzh5pmOag.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*B3iHfLVRtZ7zTuzh5pmOag.jpeg" /></p>

<blockquote>
  <p><em>We ordered seaweed rolls to share, and I had the knife-cut cold noodles. The noodles were very chewy and refreshing; the downside was the lack of toppings (meat), making it a plain carb-only dish.</em></p>
</blockquote>

<p>After eating, walk back to Lotte Department Store.</p>

<h4 id="the-liter-seo-myeon-medical-street-branch"><a href="https://naver.me/FytxtsbC" target="_blank">The Liter Seo-myeon Medical Street Branch</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*2yLbWMPpyCtAruEp2Cz46g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*2yLbWMPpyCtAruEp2Cz46g.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*i4mbPG5JVPgWfVZvr40MJg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*i4mbPG5JVPgWfVZvr40MJg.jpeg" /></p>

<p>Bought another Liter of blueberry smoothie yogurt to refresh the palate.</p>

<ul>
  <li>The ajumma here is very enthusiastic.</li>
</ul>

<h4 id="lotte-department-store--busan-main-branch"><a href="https://naver.me/GA868sJB" target="_blank">Lotte Department Store — Busan Main Branch</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*VmJEjJZMfCoxu_HLUxnTIA.webp" alt="" loading="lazy" decoding="async" width="952" height="1186" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTIiIGhlaWdodD0iMTE4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*VmJEjJZMfCoxu_HLUxnTIA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*JnKRh6VL4zQAj5DN0QjOPw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*JnKRh6VL4zQAj5DN0QjOPw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*lBdiaq_smyUqUNRW1ZTLYg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*lBdiaq_smyUqUNRW1ZTLYg.jpeg" /></p>

<p>I found it hard to shop here, or rather, it’s even harder to shop than at Lotte Department Store - Gwangbok Branch yesterday. There are fewer floors and fewer stores; <strong>the only advantage is the large public rest area upstairs where you can charge your devices and rest.</strong></p>

<p><img src="/assets/8ace34a1a3d8/1*FIKndhK0XDcFCTNeGi9myA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*FIKndhK0XDcFCTNeGi9myA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*HrvD0lkdT605U-UVMyqdnw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*HrvD0lkdT605U-UVMyqdnw.jpeg" /></p>

<p>There is also a Busan souvenir shop here, but it has far fewer items than the one in Nampo.</p>

<p><img src="/assets/8ace34a1a3d8/1*hSXu5dj5gWdLB99e5TaHXA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*hSXu5dj5gWdLB99e5TaHXA.jpeg" /></p>

<p>After visiting the LEGO store, not sure where to go next, so I took the bus to explore Shinsegae Department Store.</p>

<blockquote>
  <p><em>Another NC Department Store in Seomyeon has closed. <strong>If you want to shop elsewhere, you can try Jeonpo Coffee Street, which has many small shops (similar to the Dong District vibe), but it may not be very interesting for men.</strong></em></p>
</blockquote>

<h4 id="busan-is-good">Busan is good!</h4>

<p><img src="/assets/8ace34a1a3d8/1*FtpmNOEbpZm_wqMEE0XZXA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*FtpmNOEbpZm_wqMEE0XZXA.jpeg" /></p>

<blockquote>
  <p><em>The slogan “Busan is good” is seen everywhere in Busan. Indeed, the travel experience was excellent—there are many buses, transportation is convenient, buses are new and cool, prices are cheap, and the food is delicious.</em></p>
</blockquote>

<h4 id="shinsegae-department-store--shinsegae-centum-city-"><a href="https://naver.me/xfYRYAfu" target="_blank">Shinsegae Department Store — Shinsegae Centum City</a> 👍👍👍</h4>

<p>A few days ago, I only visited Spa Land and didn’t explore the rest. This time, I’m making up for it.</p>

<p><img src="/assets/8ace34a1a3d8/1*_Z3co8etvvt8uJ6IW2WBZg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*_Z3co8etvvt8uJ6IW2WBZg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*U0WsMct8QALuKAQXMVp-fQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*U0WsMct8QALuKAQXMVp-fQ.jpeg" /></p>

<blockquote>
  <p><strong><em>Shinsegae Department Store is huge (Guinness World Record holder for the largest)</em></strong> <em>and has a lot to explore.</em></p>
</blockquote>

<blockquote>
  <p><em>If you haven’t had enough shopping, next door is also <a href="https://naver.me/5b0d0jtI" target="_blank">Lotte Dept. Store Centum City Branch</a>.</em></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*QGugJUaJZPoHgPEOElZ4uA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*QGugJUaJZPoHgPEOElZ4uA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*wFK6WxUwNb_DeaQyLu_dtA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*wFK6WxUwNb_DeaQyLu_dtA.jpeg" /></p>

<p>Consisting of two buildings, each with several floors, and each floor is quite spacious; I casually noted down the prices at La Lebo in Busan (located on the first floor).</p>

<p><img src="/assets/8ace34a1a3d8/1*yCeQQeere4hq9i_JBXxVug.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*yCeQQeere4hq9i_JBXxVug.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*HCpT3BycowwHj4YH7T3kZg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*HCpT3BycowwHj4YH7T3kZg.jpeg" /></p>

<p>There is a free dinosaur park called ZOORAJI on the rooftop of this building, perfect for taking kids.</p>

<p><img src="/assets/8ace34a1a3d8/1*oFC6DVRe9pA7T-R5TUG0HA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*oFC6DVRe9pA7T-R5TUG0HA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZRWtwouGGpGv9F2cvFCz_A.webp" alt="" loading="lazy" decoding="async" width="922" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZRWtwouGGpGv9F2cvFCz_A.png" /></p>

<p>Strolled around casually; basically, all brands and all types of counters are available.</p>

<p><strong>On the first floor of another building, I found Shake Shack, so I had to try it:</strong></p>

<h4 id="shake-shack-busan-centum"><a href="https://naver.me/GCgugmaP" target="_blank">SHAKE SHACK BUSAN CENTUM</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*kuCM6Mh79ZFYGXtuk2qv7Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="841" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*kuCM6Mh79ZFYGXtuk2qv7Q.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*w8wX3ItxckiitSpt-SPH9Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*w8wX3ItxckiitSpt-SPH9Q.jpeg" /></p>

<p>I ordered the crispy chicken combo. Besides their signature items, I think their chicken is tender and juicy, and really delicious.</p>

<p><img src="/assets/8ace34a1a3d8/1*aVPzCfKsq8FJ5xpyl0Wleg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*aVPzCfKsq8FJ5xpyl0Wleg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*3WaFG6qy3Eg9mm9BvXbLxg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*3WaFG6qy3Eg9mm9BvXbLxg.jpeg" /></p>

<p>The pager seemed to have some issues; it was actually ready earlier but didn’t ring.</p>

<blockquote>
  <p><em>The nostalgic good taste.</em></p>
</blockquote>

<blockquote>
  <p><em>Travelogue: <a href="https://zhgchg.li/posts/aacd5f5cacd1/#1200-shake-shack" target="_blank">Osaka Shack Shake</a> / <a href="https://zhgchg.li/posts/b7e7c0938985/#central-world" target="_blank">Bangkok Shake Shack</a></em></p>
</blockquote>

<h4 id="1700-leave-shinsegae-department-store">~=17:00 Leave Shinsegae Department Store</h4>

<h4 id="1745-return-to-jeonpo-station-seomyeon">~=17:45 Return to Jeonpo Station (Seomyeon)</h4>

<p>Preparing to have Korean beef for dinner.</p>

<p><img src="/assets/8ace34a1a3d8/1*A18rMP1xmxzXPrQ9RPTkDQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*A18rMP1xmxzXPrQ9RPTkDQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ytbdvCf_b5ARZf_PyPolmQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*ytbdvCf_b5ARZf_PyPolmQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*vS3dZ4vmhuLZLOkifzpZ4Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*vS3dZ4vmhuLZLOkifzpZ4Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6cr53hsYgiwQJNrgtETIPQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*6cr53hsYgiwQJNrgtETIPQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*3Sg_E9SpDVQi2sIZs5HR7w.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*3Sg_E9SpDVQi2sIZs5HR7w.jpeg" /></p>

<p>As soon as you exit Tampo Station, you can see the sign pointing to Tampo Coffee Street. There is a standalone Olive Young right outside.</p>

<p>There are many coffee shops and small stores in Tampo, giving it a vibe similar to the East District.</p>

<h4 id="flowerbeefhouse-tano-branch--flowerbeefhouse"><a href="https://naver.me/Gj7G7BxZ" target="_blank">Flowerbeefhouse Tano Branch — flowerbeefhouse</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*C7814gYWbA1ajdvCX24HBA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*C7814gYWbA1ajdvCX24HBA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6lXBVu2a8ooPredaeD98YA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*6lXBVu2a8ooPredaeD98YA.jpeg" /></p>

<p>It has a bit of an East District trendy café vibe. Luckily, I made a reservation in advance on <a href="https://www.catchtable.net/zh-TW/shop/busan_kosaljip?operationType=REMOTE_WAITING_GLOBAL" target="_blank">CatchTable</a>, or there would have been no seats available on site.</p>

<p><img src="/assets/8ace34a1a3d8/1*0MfOvlWLctZiRYYM9YI2Ow.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*0MfOvlWLctZiRYYM9YI2Ow.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*dODWMoS7WzgH-siJ6pjpbA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*dODWMoS7WzgH-siJ6pjpbA.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*IknycW3IPMrRvAW5jl0aZw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*IknycW3IPMrRvAW5jl0aZw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*nXobUwdMajACPXrjnt4M8w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*nXobUwdMajACPXrjnt4M8w.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*MoSNzb94xyAqw7GFRHdxYA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*MoSNzb94xyAqw7GFRHdxYA.jpeg" /></p>

<blockquote>
  <p><em>Ordered a Korean beef set + Jajangmyeon + Cass beer. The beef was tender but lacked beef flavor, so I found it just okay; plus, <strong>the actual price was NT$2,458, which is not very cost-effective</strong>. It’s not as good as the throat grill barbecue we had a few days ago.</em></p>
</blockquote>

<blockquote>
  <p><em>The advantages are mainly beautiful photo spots, friendly staff, and their ability to speak Chinese.</em></p>
</blockquote>

<h4 id="1915-walk-back-to-seomyeon-station-after-eating">19:15 Walk back to Seomyeon Station after eating</h4>

<p><img src="/assets/8ace34a1a3d8/1*6p05-utFJo4ZG1eFY6yNdQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*6p05-utFJo4ZG1eFY6yNdQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*OJJd1Cu8EacCLHW4NqKJzQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="986" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*OJJd1Cu8EacCLHW4NqKJzQ.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*b-EarSDTdr74ICykPiQrKw.webp" alt="" loading="lazy" decoding="async" width="1200" height="837" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*b-EarSDTdr74ICykPiQrKw.png" /></p>

<p>There are really many small shops, cafes, and even a ZARA from Daepo to Seomyeon.</p>

<h4 id="after-returning-to-seomyeon-station-go-to-lotte-department-store-b1-food-court-to-buy-food-then-prepare-to-go-to-the-airport-hotel">After returning to Seomyeon Station, go to Lotte Department Store B1 food court to buy food, then prepare to go to the airport hotel</h4>

<p><img src="/assets/8ace34a1a3d8/1*w_NQLTD-KKle0HD3xlyngw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*w_NQLTD-KKle0HD3xlyngw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*Rifq7iofXWw4XwV4irb2kQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="875" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Rifq7iofXWw4XwV4irb2kQ.png" /></p>

<p>There is also a supermarket here. I ended up buying this takoyaki to eat back at the hotel, choosing the Korean-flavored one on the far right.</p>

<h4 id="2030-head-to-the-airport-hotel">~=20:30 Head to the airport hotel</h4>

<p><img src="/assets/8ace34a1a3d8/1*rAoS5socLEE80cN-joYtPg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*rAoS5socLEE80cN-joYtPg.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*D0WhmbgOA3EgJVJvW0S_TQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*D0WhmbgOA3EgJVJvW0S_TQ.jpeg" /></p>

<blockquote>
  <p><em>This time, heading in the opposite direction towards Gaya University, also on a Tuesday, it felt like seeing myself arriving in Busan last Tuesday.</em></p>
</blockquote>

<h4 id="2100-arrive-at-air-sky-hotel">~=21:00 Arrive at <a href="https://naver.me/FnViVS4g" target="_blank">AIR SKY HOTEL</a></h4>

<p><img src="/assets/8ace34a1a3d8/1*Uv0KMnohG6_icObDYCwnoQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*Uv0KMnohG6_icObDYCwnoQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*A1HoGDP-gPzEhYhr7jBa-Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*A1HoGDP-gPzEhYhr7jBa-Q.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*ZO4RT_vb0kK09_OwDQCfow.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*ZO4RT_vb0kK09_OwDQCfow.jpeg" /></p>

<p>The hotel is located at the stop before the airport — Seobusan Distribution Area Station, with CU and GS25 convenience stores right across the street.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/KCmYPL1Joko" title="AIR SKY HOTEL (Busan)" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/8ace34a1a3d8/1*MmEJgGoFwwQ58ZeA01MznQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*MmEJgGoFwwQ58ZeA01MznQ.jpeg" /></p>

<blockquote>
  <p><em>This room and bathroom are the largest and best equipped for three days, but we only stayed for one day as transient guests.</em></p>
</blockquote>

<p>You can see planes taking off and landing directly outside the room window.</p>

<p><img src="/assets/8ace34a1a3d8/1*1o095_AJ41otB1aOuQrdiw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*1o095_AJ41otB1aOuQrdiw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*DqayI4Jguehj-4uKyeUkEw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*DqayI4Jguehj-4uKyeUkEw.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*s7ieNDtJQZQ8SdCa8FnYEg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*s7ieNDtJQZQ8SdCa8FnYEg.jpeg" /></p>

<p>Bought peach wine at the convenience store to go with the takoyaki I just got. I think this takoyaki is great. Although it got soft after bringing it back, it’s made with black squid ink batter, has the crispiness of Taiwanese takoyaki, and is paired with Korean spicy sauce. Very delicious. 😋</p>

<h4 id="day-8-0805-tue-busan-gimhae-international-airport-return-trip">Day 8 08/05 (Tue) Busan Gimhae International Airport, Return Trip</h4>

<p>Time flies, and the eight-day trip to Busan is coming to an end. The last day only includes a trip to the airport.</p>

<p>From the hotel, take one stop to reach the airport (about 10 minutes).</p>

<p><img src="/assets/8ace34a1a3d8/1*Dww8cdw_xtLtDoh2R13GNA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Dww8cdw_xtLtDoh2R13GNA.jpeg" /></p>

<p>After arriving at the airport, go directly to the second floor for international departures.</p>

<blockquote>
  <p><em>Still sleepy in the morning, forgot to apply for <a href="https://www.instagram.com/reel/C8MojavSlZ2/" target="_blank">SES</a> to speed up the next entry clearance.</em></p>
</blockquote>

<h4 id="tax-refund">Tax Refund</h4>

<p>If you have a tax refund slip to claim, remember to follow <a href="https://www.funliday.com/posts/2024-korea-tax-refund/" target="_blank">this article</a> for the process.<br />
I lost my tax refund slip…</p>

<p><img src="/assets/8ace34a1a3d8/1*teDaGZTayKhkL2N84UJ25A.webp" alt="" loading="lazy" decoding="async" width="1400" height="988" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*teDaGZTayKhkL2N84UJ25A.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*pW5hYSUPeitfaOU9aYqklQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*pW5hYSUPeitfaOU9aYqklQ.jpeg" /></p>

<p>There are many self check-in kiosks along the edge of the second floor where you can check in and select seats. You can also see that the third floor has many food options and shops.</p>

<p><img src="/assets/8ace34a1a3d8/1*8qabU_7i8fR_lKUelRAukw.webp" alt="" loading="lazy" decoding="async" width="1200" height="791" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*8qabU_7i8fR_lKUelRAukw.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*CqJalfOGffMcPy86EiF2NQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="689" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*CqJalfOGffMcPy86EiF2NQ.png" /></p>

<p>The counter was assigned to D at the far edge.</p>

<p><img src="/assets/8ace34a1a3d8/1*Fe08xzMZ2pZa1ZLJz0i5qQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Fe08xzMZ2pZa1ZLJz0i5qQ.jpeg" /></p>

<ul>
  <li>
    <p>Busan Air’s free baggage allowance is only: 15 KG</p>
  </li>
  <li>
    <p>Check if the power bank is placed inside a ziplock bag.</p>
  </li>
  <li>
    <p><strong>After hanging up your luggage, be sure to wait in that area for 5 minutes to see if there are any issues.</strong><br />
That day, someone was being constantly called over the broadcast because they couldn’t be found…</p>
  </li>
</ul>

<h4 id="-0930-departure-and-waiting-for-flight">~= 09:30 Departure and Waiting for Flight</h4>

<p><img src="/assets/8ace34a1a3d8/1*zp0SV0ILqoWylQLYr0S-uA.webp" alt="" loading="lazy" decoding="async" width="1400" height="958" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk1OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*zp0SV0ILqoWylQLYr0S-uA.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*5xLQvy-TjwOBT2RAmlzq6g.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*5xLQvy-TjwOBT2RAmlzq6g.png" /></p>

<p><img src="/assets/8ace34a1a3d8/1*6GHIaGkDU8ajLtBZFc5KdA.webp" alt="" loading="lazy" decoding="async" width="1200" height="1185" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjExODUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/8ace34a1a3d8/1*6GHIaGkDU8ajLtBZFc5KdA.png" /></p>

<blockquote>
  <p><em>The departure hall is small, with only one row; there are a few duty-free shops, a convenience store, and a café offering limited snacks. (If you’re very hungry, remember to eat outside before passing through departure.)</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Since it is a military-civilian airport, taking photos of the planes outside is not allowed.</em></strong></p>
</blockquote>

<p><img src="/assets/8ace34a1a3d8/1*jeZ-QLfYxlbkCFR3s9_6IQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*jeZ-QLfYxlbkCFR3s9_6IQ.jpeg" /></p>

<p><img src="/assets/8ace34a1a3d8/1*oKbFo2YD2LVe80YMI5odeQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*oKbFo2YD2LVe80YMI5odeQ.jpeg" /></p>

<p>Not sure what to buy, so I got two cans of GD liquor from the convenience store to bring back to Taiwan.</p>

<h4 id="delay-departed-at-1107-arrived-early-at-1158">Delay: Departed at 11:07, arrived early at 11:58</h4>

<p><img src="/assets/8ace34a1a3d8/1*PEiW_t5NUyQw-EGRq-SyZg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*PEiW_t5NUyQw-EGRq-SyZg.jpeg" /></p>

<h4 id="busan-trip-a-great-success">Busan Trip a Great Success!</h4>

<p><img src="/assets/8ace34a1a3d8/1*Oz4R-jMiTOqh0Qa-rh6cHg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/8ace34a1a3d8/1*Oz4R-jMiTOqh0Qa-rh6cHg.jpeg" /></p>

<h4 id="souvenirs">Souvenirs</h4>

<p><img src="/assets/8ace34a1a3d8/1*mANxdMpisGZuM1PYkslrxg.webp" alt="" loading="lazy" decoding="async" width="968" height="812" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjgiIGhlaWdodD0iODEyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/8ace34a1a3d8/1*mANxdMpisGZuM1PYkslrxg.jpeg" /></p>

<p>Didn’t buy much.</p>

<h4 id="thank-you-for-reading">Thank you for reading</h4>

<blockquote>
  <p>If you enjoy my travelogue, feel free to purchase the <a href="https://www.kkday.com/zh-tw/product/138477-visit-busan-pass-discount-free-attractions?cid=19365" target="_blank">Korea Busan Pass VISIT BUSAN PASS</a> through my referral link. I will receive a small commission. Thank you.</p>
</blockquote>

<h3 id="more-travelogues">More Travelogues</h3>

<ul>
  <li>
    <p><a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/"><strong>[Travelogue] 2024 Second Visit to Kyushu 9-Day Free Travel, Entering via Busan → Hakata Cruise</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/san-in-kansai-7-day-solo-trip-explore-izumo-matsue-tottori-himeji-osaka-kobe-aacd5f5cacd1/">[Travelogue] 2024 San’in Region Shimane Izumo Matsue Tottori Himeji Osaka Kobe 7-Day Solo Free Travel</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/kyushu-10-day-solo-travel-guide-explore-fukuoka-nagasaki-kumamoto-efficiently-d78e0b15a08a/">[Travelogue] 2023 Kyushu 10-Day Solo Trip</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">[Travelogue] 6-Day Hiroshima and Okayama Free Trip in 2023</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/nagoya-one-day-quick-trip-peach-aviation-flight-experience-and-travel-tips-7b8a0563c157/">[Travelogue] 9/11 Nagoya One-Day Quick Trip</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">2023 Tokyo 5-Day Free Trip</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">2023 Kyoto-Osaka-Kobe 8-Day Free Trip</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">Google Offerwall Ads｜Boost Content Creator Revenue with Minimal Restrictions</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/google-offerwall-ads-boost-content-creator-revenue-with-minimal-restrictions-ba132457e6a5/" rel="alternate" type="text/html" title="Google Offerwall Ads｜Boost Content Creator Revenue with Minimal Restrictions" />
    <published>2025-07-24T16:15:09+08:00</published>
    <updated>2025-07-24T16:26:29+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/ba132457e6a5</id><summary type="html">Content creators struggling to monetize traffic can convert existing content into micro-support or ad-view access using Google Offerwall Ads, enhancing revenue streams efficiently and with low barriers.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="google-ads" /><category term="offerwall" /><category term="google-adsense" /><category term="jekyll" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/ba132457e6a5/1*fBIJDsQ994Wn0JNyPDJt8Q.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/google-offerwall-ads-boost-content-creator-revenue-with-minimal-restrictions-ba132457e6a5/"><![CDATA[<h3 id="google-offerwall-ads--a-new-revenue-option-for-content-creators">Google Offerwall Ads — A New Revenue Option for Content Creators</h3>

<p>A Brief Introduction and Trial of Google Offerwall Ads, Quickly Converting Existing Traffic Content into Access Restricted by Small Payments or Ad Viewing.</p>

<h3 id="live-demo"><a href="/posts/zrealm-dev/google-offerwall-ads-boost-content-creator-revenue-with-minimal-restrictions-ba132457e6a5/">Live Demo</a></h3>

<p><img src="/assets/ba132457e6a5/1*V4LD8JJoor7SusoHX01_eQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="994" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*V4LD8JJoor7SusoHX01_eQ.png" /></p>

<p><img src="/assets/ba132457e6a5/1*fBIJDsQ994Wn0JNyPDJt8Q.webp" alt="https://zhgchg.li/posts/ba132457e6a5/" loading="lazy" decoding="async" width="1400" height="973" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*fBIJDsQ994Wn0JNyPDJt8Q.png" /></p>

<p><a href="/posts/zrealm-dev/google-offerwall-ads-boost-content-creator-revenue-with-minimal-restrictions-ba132457e6a5/">https://zhgchg.li/posts/ba132457e6a5/</a></p>

<blockquote>
  <p><strong><em>Live Demo -&gt; <a href="/posts/zrealm-dev/google-offerwall-ads-boost-content-creator-revenue-with-minimal-restrictions-ba132457e6a5/">https://zhgchg.li/posts/ba132457e6a5/</a></em></strong></p>
</blockquote>

<h3 id="google-offerwall-ads">Google Offerwall Ads</h3>

<p><img src="/assets/ba132457e6a5/1*DNW0ylu9LHLs4ztWVzw3UQ.webp" alt="&lt;https://support.google.com/admanager/answer/13860694?hl=zh-Hant&gt;" loading="lazy" decoding="async" width="1400" height="976" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*DNW0ylu9LHLs4ztWVzw3UQ.png" /></p>

<p><a href="https://support.google.com/admanager/answer/13860694?hl=zh-Hant" target="_blank">https://support.google.com/admanager/answer/13860694?hl=zh-Hant</a></p>

<p><a href="https://support.google.com/admanager/answer/13860694?hl=zh-Hant" target="_blank">Google Offerwall</a> is what I consider a <strong>reward-based Offerwall ad</strong> that combines features of rewarded ads. Developers can easily <strong>require users to complete tasks to access specific website content pages</strong>. The <strong>tasks are Offerwall reward tasks</strong>, which can be set as completing surveys, watching ads, making small payments, subscribing to newsletters, or <a href="https://support.google.com/admanager/answer/13566866?hl=zh-Hant&amp;ref_topic=13821812&amp;sjid=14925592883586634734-NC" target="_blank">integrating tasks from your own system</a>. The rewards grant access permissions that can be unlimited for a few hours or days, or limited by quantity. All of this can be achieved simply by embedding Google Adsense dynamic ad code, without the need to develop your own system.</p>

<h4 id="-advantages">✅ Advantages</h4>

<ul>
  <li>
    <p><strong>Easy Implementation</strong>: Just embed the <a href="https://support.google.com/adsense/answer/9189560?hl=zh-Hant" target="_blank">dynamic ad code</a> on your website once, and use it for life.</p>
  </li>
  <li>
    <p><strong>Direct Profit</strong>: Like Google AdSense, there is no need to build a complex ad system or data tracking system; nor do you need to find advertisers yourself. Everything is managed and operated by the platform.</p>
  </li>
  <li>
    <p><strong>Flexible and Diverse</strong>: Offers more flexible and diverse content and traffic monetization methods.</p>
  </li>
  <li>
    <p><strong>Cross-platform</strong>: Supports both mobile and desktop versions.</p>
  </li>
</ul>

<h4 id="-disadvantages">❌ Disadvantages</h4>

<ul>
  <li>
    <p><strong>Low Bids</strong>: This is also a common issue with Google AdSense, where ad bids are generally low.</p>
  </li>
  <li>
    <p><strong>Easy to block</strong>: Since Google Offerwall is a pure front-end overlay ad, it can be easily bypassed by ad blockers or users using developer tools; <strong>therefore, it is not recommended to implement it on sensitive, important, or valuable content.</strong></p>
  </li>
</ul>

<h4 id="-technical-requirements">📝 Technical Requirements</h4>

<ul>
  <li>
    <p>You need to apply and get approved by <a href="https://adsense.google.com/" target="_blank">Google Adsense</a> first.<br />
<strong>The threshold is lower now; as long as you have a website with 5–10 articles, you can basically get approved.</strong></p>
  </li>
  <li>
    <p>The website must be able to embed dynamic ad code in the HTML Head <a href="https://support.google.com/adsense/answer/9189560?hl=zh-Hant" target="_blank">Embed dynamic ad code</a></p>
  </li>
</ul>

<h4 id="️-suitable-scenarios">❤️ Suitable Scenarios</h4>

<ul>
  <li>
    <p><strong>For Advertisers</strong>: Like Google Adsense, it is suitable for small to medium content websites or personal blogs without self-selling ad spaces, such as sites hosted on <a href="https://wordpress.com/zh-tw/" target="_blank">Wordpress</a> or <a href="https://pages.github.com/" target="_blank">GitHub Pages</a>. Besides displaying regular ad spaces, you can also set up Offerwall rewarded ads for valuable content articles.<br />
<strong>This article uses For Advertisers as a case study; currently, all features except advertising are still in Beta.</strong></p>
  </li>
  <li>
    <p><strong>For surveys, micro-payments, and newsletter subscriptions</strong>: Any website can quickly create pop-up ads using this mechanism.</p>
  </li>
</ul>

<h3 id="installation-and-activation-steps">Installation and Activation Steps</h3>

<h4 id="embed-dynamic-ad-code">Embed Dynamic Ad Code</h4>

<p>First, make sure the website has embedded the Google Adsense ad code. If dynamic ads were used before, you can skip this step.</p>

<p><strong>Go to <a href="https://adsense.google.com/" target="_blank">Google Adsense</a> -&gt; Ads -&gt; By site -&gt; Get code:</strong></p>

<p><img src="/assets/ba132457e6a5/1*CYX4r932I3qkkOgKRVYTSg.webp" alt="" loading="lazy" decoding="async" width="1200" height="636" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYzNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*CYX4r932I3qkkOgKRVYTSg.png" /></p>

<p><img src="/assets/ba132457e6a5/1*JAEN6wEMLaSgu5wpUkRTnw.webp" alt="" loading="lazy" decoding="async" width="1400" height="609" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjYwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*JAEN6wEMLaSgu5wpUkRTnw.png" /></p>

<p>Paste the code inside the website’s <code class="language-plaintext highlighter-rouge">&lt;head&gt;&lt;/head&gt;</code> section:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;head&gt;</span>
  //...
  <span class="nt">&lt;script</span> <span class="err">async</span> <span class="na">src=</span><span class="s">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3184248473087645"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;/head&gt;</span>
</code></pre></div></div>

<h4 id="creating-an-offerwall-ad">Creating an Offerwall Ad</h4>

<p><strong>Go to <a href="https://adsense.google.com/" target="_blank">Google Adsense</a> -&gt; Privacy &amp; messages -&gt; Manage -&gt; Create message:</strong></p>

<p><img src="/assets/ba132457e6a5/1*aQ3svqP3lo16CGH8R-28yw.webp" alt="" loading="lazy" decoding="async" width="1200" height="1171" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjExNzEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/ba132457e6a5/1*aQ3svqP3lo16CGH8R-28yw.png" /></p>

<p><img src="/assets/ba132457e6a5/1*ziurWzmp3N6S5-P4O9GO7w.webp" alt="" loading="lazy" decoding="async" width="1200" height="868" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*ziurWzmp3N6S5-P4O9GO7w.png" /></p>

<blockquote>
  <p><em>You can also check ad revenue on this page.</em></p>
</blockquote>

<p><strong>Setting up Offerwall Ads:</strong></p>

<p><img src="/assets/ba132457e6a5/1*QfG2HVUy728iSMOdKb7rLw.webp" alt="" loading="lazy" decoding="async" width="1400" height="743" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijc0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*QfG2HVUy728iSMOdKb7rLw.png" /></p>

<p><img src="/assets/ba132457e6a5/1*E0Pkk8T4e9YVBxJ8xnv1ow.webp" alt="" loading="lazy" decoding="async" width="826" height="960" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MjYiIGhlaWdodD0iOTYwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/ba132457e6a5/1*E0Pkk8T4e9YVBxJ8xnv1ow.png" /></p>

<p><img src="/assets/ba132457e6a5/1*J6wnirCtE8Qp5VsnsTAn_w.webp" alt="" loading="lazy" decoding="async" width="980" height="778" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODAiIGhlaWdodD0iNzc4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/ba132457e6a5/1*J6wnirCtE8Qp5VsnsTAn_w.png" /></p>

<p><img src="/assets/ba132457e6a5/1*akZZ5_K3BS1FVSomf8AvvA.webp" alt="3, 5, 6" loading="lazy" decoding="async" width="1200" height="723" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcyMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*akZZ5_K3BS1FVSomf8AvvA.png" /></p>

<ol>
  <li><strong>Advantages</strong></li>
</ol>

<ul>
  <li>Easy to implement: Just embed the Google AdSense code.</li>
  <li>Direct monetization: Earn revenue directly from user interactions.</li>
  <li>Flexible and diverse: Supports various tasks like watching ads, surveys, or micro-payments.</li>
  <li>Cross-platform support: Works on websites, apps, and other platforms.</li>
</ul>

<ol>
  <li><strong>Technical Requirements</strong></li>
</ol>

<ul>
  <li>Apply for a Google AdSense account.</li>
  <li>Embed dynamic Google Offerwall ad code into your website or app.</li>
  <li>Set up tasks and rewards according to your content strategy.</li>
</ul>

<ol>
  <li><strong>Setup and Testing</strong></li>
</ol>

<ul>
  <li>Insert the Google Offerwall ad script into your site.</li>
  <li>Configure tasks such as watching ads or completing surveys.</li>
  <li>Test the functionality to ensure users can unlock content after completing tasks.</li>
</ul>

<p><img src="/assets/ba132457e6a5/1*g_PkT5TqCEFYajdT4ugPCQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="842" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*g_PkT5TqCEFYajdT4ugPCQ.png" /></p>

<p><img src="/assets/ba132457e6a5/1*wnbt-BkLE-0Bk3KGr9ED7Q.webp" alt="4, 8" loading="lazy" decoding="async" width="1400" height="747" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijc0NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*wnbt-BkLE-0Bk3KGr9ED7Q.png" /></p>

<ol>
  <li><strong>Technical Requirements</strong></li>
</ol>

<ul>
  <li>Apply for a Google AdSense account.</li>
  <li>Embed the Google Offerwall dynamic ad code into your site.</li>
</ul>

<ol>
  <li><strong>Testing</strong></li>
</ol>

<p>After embedding the Offerwall ad code, test it on your site to ensure the tasks and rewards work correctly. Use different devices and browsers to verify compatibility.</p>

<ol>
  <li>
    <p><strong>Ad Name</strong></p>
  </li>
  <li>
    <p><strong>Your Website</strong>: Set the website to apply</p>
  </li>
  <li>
    <p><strong>Included and Excluded Pages</strong>: You can set which page URLs to include or exclude<br />
<em>Here, I set it so that only URLs under the <code class="language-plaintext highlighter-rouge">https://zhgchg.li/posts/</code> path will trigger.</em></p>
  </li>
  <li>
    <p><strong>Default Language</strong>: Set the default language and other supported languages. You can switch languages at the top left to edit content in different languages.</p>
  </li>
  <li>
    <p><strong>Measurement</strong>: You can set after how many page views the trigger occurs<br />
<em>Here, I set it to 0, meaning it triggers on the first time.</em></p>
  </li>
  <li>
    <p><strong>Rewarded Ads</strong>: You can set rewards that unlock content after completing tasks, such as unlimited views within a time frame or a limited number of page views. After the reward expires, the task must be completed again.<br />
<em>Here, I set it so that completing one task allows unlimited viewing for 24 hours.</em></p>
  </li>
  <li>
    <p><strong>Copy Settings:</strong> Specify the copy content, and remember to upload at least a logo to enhance brand strength</p>
  </li>
  <li>
    <p><strong>Style Settings:</strong> After clicking on the 7 texts, you can also set the text style and color here.<br />
You can refer to <a href="https://support.google.com/admanager/answer/13860694?hl=zh-Hant#zippy=%2C%E5%A6%82%E4%BD%95%E6%B8%AC%E8%A9%A6-offerwall" target="_blank">Testing Method</a> and add <code class="language-plaintext highlighter-rouge">?fc=alwaysshow&amp;fctype=monetization</code> to the page URL to preview the result.</p>
  </li>
  <li>
    <p><strong>Publish Changes</strong>: Remember to click publish after everything is set up.</p>
  </li>
</ol>

<p><img src="/assets/ba132457e6a5/1*fRqD1tZ-513FjKsTTwDYkw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1047" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNDciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/ba132457e6a5/1*fRqD1tZ-513FjKsTTwDYkw.png" /></p>

<p>The post-completion reward message cannot be changed at this time.</p>

<p><img src="/assets/ba132457e6a5/1*fjfArCVeVNus7J48rEHcaQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="781" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc4MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*fjfArCVeVNus7J48rEHcaQ.png" /></p>

<p>Confirm that the ad is in a published state.</p>

<h4 id="testing-offerwall-ads">Testing Offerwall Ads</h4>

<p>Use an incognito browser and make sure no ad blockers or anti-tracking extensions are enabled, then visit the webpage within the rules:</p>

<p><img src="/assets/ba132457e6a5/1*OSEzFBhd-wXSAdm-41aLjQ.webp" alt="https://zhgchg.li/posts/c008a9e8ceca/" loading="lazy" decoding="async" width="1400" height="1349" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEzNDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/ba132457e6a5/1*OSEzFBhd-wXSAdm-41aLjQ.png" /></p>

<p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/">https://zhgchg.li/posts/c008a9e8ceca/</a></p>

<blockquote>
  <p>Success 🙌🙌🙌</p>
</blockquote>

<p>Try to use Chrome Incognito mode for testing. In Safari, you need to disable ad blockers and anti-tracking, which can be more troublesome.</p>

<h4 id="earnings-aspect">Earnings Aspect</h4>

<p>Because my traffic is low, the bid per click is also not high, roughly between USD $0.01 and $0.07 per click.</p>

<h3 id="medium-to-github-pages-x-google-offerwall">Medium To Github Pages x Google Offerwall</h3>

<p>You can refer to my previous article “<a href="/posts/zrealm-dev/seamlessly-migrate-medium-content-to-self-hosted-sites-github-pages-with-jekyll-chirpy-a0c08d579ab1/">Effortlessly Migrate Medium to a Self-Hosted Site</a>” to mirror your Medium articles to a static website hosted on GitHub Pages. Then, add Google Offerwall to the site to monetize your content.</p>

<p>Or directly refer to “<a href="/posts/zrealm-life/medium-partner-program-global-access-for-writers-including-taiwan-earn-revenue-from-your-articles-cefdf4d41746/">Medium Partner Program is finally open to writers worldwide (including Taiwan)!</a>” to add a paywall to your Medium articles and earn revenue.</p>

<blockquote>
  <p><em>Because Medium’s paywall requires readers to subscribe monthly to access articles, which I find unfriendly to information sharing, I have not joined the paywall program; Google Offerwall perfectly fills this gap, allowing users to support content creators by simply watching ads without mandatory monthly fees, while creators still earn revenue—benefiting both sides!</em></p>
</blockquote>

<h3 id="supplement-explanation-of-offerwall-reward-wall-ads-and-rewarded-ad-types"><strong><em>[Supplement] Explanation of Offerwall Reward Wall Ads and Rewarded Ad Types</em></strong></h3>

<h4 id="offerwall--what-is-reward-wall-advertising">Offerwall — What is Reward Wall Advertising?</h4>

<p>Compared to traditional fixed-position ads, offerwall ads emphasize interaction and purpose with users. They integrate content display, reward incentives, and user behavior, allowing users not only to choose to pay to unlock premium items but also to gain extra resources in a more engaging and participatory way. Creators can earn additional revenue, achieving a win-win outcome.</p>

<p><strong>Example — Line Points Offerwall</strong></p>

<p><img src="/assets/ba132457e6a5/1*SWF8K0p5asGiDoPMhZRAWQ.webp" alt="" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*SWF8K0p5asGiDoPMhZRAWQ.jpeg" /></p>

<p>A classic everyday example is Line Points. Besides directly purchasing Line Points, users can also choose tasks they like from the Offerwall reward wall (adding friends, registering accounts, filling out surveys, watching ads, etc.) and earn corresponding rewards upon completion.</p>

<h4 id="rewarded-ads--rewarded-ads">Rewarded Ads — <strong>Rewarded Ads</strong></h4>

<p>Rewarded ads are a common ad format in games and mobile apps, where users voluntarily complete specific actions, such as <strong>watching a video, clicking interactive content, or trying a feature</strong>, to earn rewards like virtual currency, extra lives, or game items.</p>

<p><strong>Example — Candy Crush</strong></p>

<p><img src="/assets/ba132457e6a5/1*D9PdoZg-mlCbtO19roltdQ.webp" alt="" loading="lazy" decoding="async" width="700" height="1400" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iMTQwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/ba132457e6a5/1*D9PdoZg-mlCbtO19roltdQ.png" /></p>

<p>A classic example is the “continue playing after death” ad in games. This type of ad not only boosts user engagement and satisfaction but also allows developers to generate revenue without harming the user experience, achieving a win-win outcome.</p>]]></content>
  </entry><entry>
    <title type="html">CI/CD Guide｜Integrate Google Apps Script Web App with GitHub Actions for Free Packaging Platform</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/" rel="alternate" type="text/html" title="CI/CD Guide｜Integrate Google Apps Script Web App with GitHub Actions for Free Packaging Platform" />
    <published>2025-07-10T20:30:51+08:00</published>
    <updated>2025-11-19T23:59:42+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/4273e57e7148</id><summary type="html">Developers facing complex CI/CD setups can streamline workflows by connecting Google Apps Script Web App with GitHub Actions, Slack, and Firebase APIs to build a free, collaborative packaging tool platform that enhances cross-team efficiency.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="cicd" /><category term="google-apps-script" /><category term="web-development" /><category term="tools" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/4273e57e7148/1*kJhD4PCaIphZ9G1BG_dtNw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><![CDATA[<h3 id="cicd-practical-guide-part-4-using-google-apps-script-web-app-to-connect-github-actions-and-build-a-free-easy-to-use-packaging-tool-platform">CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</h3>

<p>GAS Web App Integrates GitHub, Slack, Firebase, or Asana/Jira APIs to Build a Relay Station, Providing a Cross-Team Shared Packaging Tool Platform</p>

<p><img src="/assets/4273e57e7148/1*kJhD4PCaIphZ9G1BG_dtNw.webp" alt="Photo by Lee Campbell" loading="lazy" decoding="async" width="1200" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*kJhD4PCaIphZ9G1BG_dtNw.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@leecampbell?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Lee Campbell</a></p>

<h3 id="preface">Preface</h3>

<p>In the previous article “<a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing iOS App CI and CD Workflows with GitHub Actions</strong></a>,” we completed the foundational CI/CD setup for the iOS app project. Now, CI can automatically run tests and CD can handle packaging and deployment. However, in real product development workflows, <strong>packaging and deployment are mostly done to deliver builds to other functional teams</strong> for QA (Quality Assurance) testing. At this point, the CD process is no longer limited to the engineering side; it may involve QA, PM, design (Design QA), or even the boss wanting to try it out first.</p>

<p>GitHub Actions <code class="language-plaintext highlighter-rouge">workflow_dispatch</code> manual trigger events provide a simple form for users to start builds. However, this is unfriendly for non-engineers who may not understand: what is a branch? Should fields be filled? How to know when the build is done? How to download it once ready? …etc.</p>

<p>There is also a permission control issue. To let other team members use GitHub Actions for builds, you have to add their accounts to the repo. <strong>This is insecure and unreasonable from a security perspective</strong>, as they get access to the entire source code just to operate the build form.</p>

<p>Unlike Jenkins, which has an independent web tool platform, GitHub Actions only provides this functionality.</p>

<p><img src="/assets/4273e57e7148/1*qDD8HAAHxDxPEU3vJPEhjA.webp" alt="`workflow_dispatch form style`" loading="lazy" decoding="async" width="342" height="445" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNDIiIGhlaWdodD0iNDQ1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*qDD8HAAHxDxPEU3vJPEhjA.png" /></p>

<p><code class="language-plaintext highlighter-rouge">workflow_dispatch form style</code></p>

<p>Therefore, <strong>we need an intermediary packaging platform to serve other functional users</strong>, integrating Asana/Jira task tickets so that users can directly package the app using the task ticket and view the progress and download the packaging results on it.</p>

<p><img src="/assets/4273e57e7148/1*bHYawmSnhqwB4TzIJVfGow.webp" alt="GAS Web App Relay Station" loading="lazy" decoding="async" width="1400" height="1037" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMzciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4273e57e7148/1*bHYawmSnhqwB4TzIJVfGow.png" /></p>

<p>GAS Web App Relay Station</p>

<p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/">Previous article</a> focused on the core GitHub Actions CI/CD workflow development on the right side; this article focuses on the left side—the end-user packaging tool platform and enhancing user experience.</p>

<h4 id="google-apps-script--web-app-packaging-tool-platform-result-image">Google Apps Script — Web App Packaging Tool Platform Result Image</h4>

<p><img src="/assets/4273e57e7148/1*yXMeaOELhqdvMCxIJ5ElBw.gif" alt="" loading="lazy" decoding="async" width="1048" height="822" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ4IiBoZWlnaHQ9IjgyMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<p><img src="/assets/4273e57e7148/1*EM0goWpuDeHGVkZoybLm8g.webp" alt="" loading="lazy" decoding="async" width="980" height="899" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODAiIGhlaWdodD0iODk5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*EM0goWpuDeHGVkZoybLm8g.png" /></p>

<p><img src="/assets/4273e57e7148/1*haoGMvAUroz7rUMDEez9gA.webp" alt="" loading="lazy" decoding="async" width="984" height="826" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODQiIGhlaWdodD0iODI2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*haoGMvAUroz7rUMDEez9gA.png" /></p>

<p><img src="/assets/4273e57e7148/1*afCwKITlerG1g6swB_2wZA.webp" alt="" loading="lazy" decoding="async" width="955" height="758" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTUiIGhlaWdodD0iNzU4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*afCwKITlerG1g6swB_2wZA.png" /></p>

<p><img src="/assets/4273e57e7148/1*mvwpHBdrC73_PjL8H32nkw.webp" alt="" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*mvwpHBdrC73_PjL8H32nkw.jpeg" /></p>

<p><img src="/assets/4273e57e7148/1*BbbEd_thhUOdbAQqsCt-Tw.webp" alt="" loading="lazy" decoding="async" width="1046" height="585" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ2IiBoZWlnaHQ9IjU4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*BbbEd_thhUOdbAQqsCt-Tw.png" /></p>

<p><img src="/assets/4273e57e7148/1*HXjfMpUPssaw3sJgH6KKJQ.webp" alt="" loading="lazy" decoding="async" width="312" height="292" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTIiIGhlaWdodD0iMjkyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*HXjfMpUPssaw3sJgH6KKJQ.png" /></p>

<ul>
  <li>
    <p><strong>Packaging Form:</strong> Integrate project management tools to fetch ticket numbers and GitHub to retrieve open Pull Requests.</p>
  </li>
  <li>
    <p><strong>Package Records:</strong> Show packaging history, current task progress, and allow clicking to get download links by fetching Firebase App Distribution links and related information.</p>
  </li>
  <li>
    <p><strong>Runner Status:</strong> Display the status of the Self-hosted Runner.</p>
  </li>
  <li>
    <p><strong>Slack build progress notifications.</strong></p>
  </li>
  <li>
    <p>Support Mobile Version.</p>
  </li>
  <li>
    <p>Support restricting usage to accounts within the organization team.</p>
  </li>
</ul>

<p><strong>Main Responsibilities</strong></p>

<p>0 state, 0 database, <strong>purely a relay exchange station</strong>, integrating and displaying data from various APIs (e.g. Asana/Jira/GitHub) and forwarding form requests to GitHub Actions.</p>

<p><strong>Operation Requirements:</strong> Support both mobile and desktop devices.</p>

<p><strong>Permission Requirements:</strong> Access should be restricted to team organization members only.</p>

<h4 id="online-demo-web-app"><a href="https://script.google.com/macros/s/AKfycbwNW6N5ozKbIz_E1HK6yFEUtA8KQrUciS-jcPsQptvIKlARmKgLxbQzNu8ksVeg-BmEfg/exec" target="_blank">Online Demo Web App</a></h4>

<ul>
  <li>For first-time use, please refer to the authorization in the image below (Only For Demo App):</li>
</ul>

<p><img src="/assets/4273e57e7148/0*7pJ876yQbcakQvdO.webp" alt="" loading="lazy" decoding="async" width="700" height="607" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNjA3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/0*7pJ876yQbcakQvdO.png" /></p>

<p>Project source code: <a href="https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit" target="_blank">https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit</a></p>

<h3 id="technology-choices">Technology Choices</h3>

<p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/">The first article</a> mentioned this, here is a detailed summary again.</p>

<h4 id="integrate-with-slack">Integrate with Slack</h4>

<p>We tried using Slack as the packaging platform by developing our own backend service hosted on GCP. It integrated with Slack API and Asana API, then forwarded forms submitted via Slack to GitHub API to trigger subsequent GitHub Actions. The experience was very smooth and unified within the team’s collaboration tools, making it easy to use. However, <strong>the downside was the high development and maintenance costs</strong>. Since the backend was built with Ktor, app engineers had to handle both backend development and Google service OAuth integration. Some features, like submission for review, had to be implemented here, making it complex. If new team members didn’t properly take over this part, it became nearly impossible to maintain. Additionally, there was a monthly $15 USD GCP server fee.</p>

<p>Initially, we also tried using FaaS services to connect with the Slack API, such as Cloud Functions. However, Slack API requires the connected service to respond within 3 seconds; otherwise, it is considered a failure. FaaS suffers from the <a href="https://www.cloudflare.com/zh-tw/learning/serverless/what-is-serverless/" target="_blank">cold start issue</a>, where the service goes to sleep after a period of inactivity and takes longer to respond (≥ 5 seconds) when called again. This causes the Slack packaging form to be unstable and often results in Timeout Errors.</p>

<h4 id="integration-into-internal-systems">Integration into Internal Systems</h4>

<p>This is obviously the optimal solution. If the team has web or backend developers, directly integrating with existing systems is the best and safest approach.</p>

<blockquote>
  <p><em>The premise of this article is: None, the app is self-reliant.</em></p>
</blockquote>

<h4 id="google-apps-script--web-app">Google Apps Script — Web App</h4>

<p>Google Apps Script has been our longtime partner. We have used it in many RPA projects to schedule and trigger tasks, such as: “<a href="/posts/zrealm-robotic-process-automation/crashlytics-google-analytics-automate-crash-free-user-rate-queries-with-google-apps-script-793cb8f89b72/">Crashlytics + Google Analytics Auto Query for App Crash-Free Users Rate</a>” and “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Using Google Apps Script to Automate Daily Data Report RPA</a>”. This time, I recalled it again; GAS has a feature to deploy as a Web (App) to serve directly as a web service.</p>

<p><strong>Advantages of Google Apps Script:</strong></p>

<ul>
  <li>
    <p>✅ Free, <a href="https://developers.google.com/apps-script/guides/services/quotas?hl=zh-tw" target="_blank">almost no limits under normal use</a></p>
  </li>
  <li>
    <p>✅ Functions as a Service means no need to set up or maintain your own server</p>
  </li>
  <li>
    <p>✅ Permission control integrates with Google Workspace, allowing access only to Google accounts within the organization</p>
  </li>
  <li>
    <p>✅ Seamless integration with Google ecosystem services (e.g. Firebase, GA, etc.) and data (no need to handle OAuth yourself)</p>
  </li>
  <li>
    <p>✅ Programming language uses JavaScript, easy to learn (V8 Runtime supports ES6+)</p>
  </li>
  <li>
    <p>✅ Fast writing, fast deployment, fast usage</p>
  </li>
  <li>
    <p>✅ Stable and long-lasting service (launched over 16 years ago)</p>
  </li>
  <li>
    <p><strong>✅ AI Can Help! Tested Using ChatGPT for Development Assistance, Achieving Up to 95% Accuracy</strong></p>
  </li>
</ul>

<p><strong>Disadvantages of Google Apps Script:</strong></p>

<ul>
  <li>
    <p>❌ Built-in version control is problematic</p>
  </li>
  <li>
    <p>❌ No built-in support for file storage, data storage, or key/certificate management</p>
  </li>
  <li>
    <p>❌ Web App cannot achieve a 100% responsive web design (RWD) experience</p>
  </li>
  <li>
    <p>❌ The project can only be linked to a personal account, not an organization</p>
  </li>
  <li>
    <p>❌ Although Google continues development and maintenance, overall feature updates are slow</p>
  </li>
  <li>
    <p>❌ Network request <code class="language-plaintext highlighter-rouge">UrlFetchApp</code> does not support setting User-Agent</p>
  </li>
  <li>
    <p>❌ Web App <code class="language-plaintext highlighter-rouge">doGet</code> / <code class="language-plaintext highlighter-rouge">doPost</code> does not support retrieving Headers information</p>
  </li>
  <li>
    <p>❌ FaaS <a href="https://www.cloudflare.com/zh-tw/learning/serverless/what-is-serverless/" target="_blank">Cold Start Issue</a></p>
  </li>
  <li>
    <p>❌ <strong>Does not support simultaneous multi-developer collaboration</strong><br />
However, this has little impact on the Web App, at most causing a few extra seconds delay when loading the page.</p>
  </li>
</ul>

<p>The above are the pros and cons of the GAS service itself, which have little impact on building a packaging tool web app. Compared to a Slack-based solution, this approach is faster, lighter, and easier to hand over and learn. <strong>The downside is that the team needs to know the tool’s URL and how to use it, and due to GAS’s limited library functions</strong> (e.g., no built-in encryption algorithms), it can basically only serve as a pure relay platform. For example, for submission requests, it can only forward the approval request to GitHub Actions.</p>

<blockquote>
  <p><strong><em>Also only suitable for teams using Google Workspace environment;</em></strong> <em>after weighing resources and needs, Google Apps Script — Web App was chosen to implement the packaging tool platform.</em></p>
</blockquote>

<h4 id="ui-framework">UI Framework</h4>

<p><a href="https://getbootstrap.com/" target="_blank"><img src="https://getbootstrap.com/docs/5.3/assets/brand/bootstrap-social.png" alt="" /></a></p>

<p>We directly use the Bootstrap CDN, otherwise managing CSS styles ourselves would be too troublesome. Asking AI how to combine and use Bootstrap is also more accurate and convenient.</p>

<h3 id="hands-on-practice">Hands-on Practice</h3>

<p>The entire platform architecture has been open-sourced here. Everyone can customize this version according to their team’s needs.</p>

<h4 id="open-source-example-project">Open Source Example Project</h4>

<p>View the project directly on GAS:</p>

<p><a href="https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit" target="_blank"><img src="https://www.gstatic.com/devrel-devsite/prod/v78ce60439c72b9da3632137223a86ae38b78a872a1f6dee1b5c1c8cfa57fe81d/developers/images/opengraph/white.png" alt="" /></a></p>

<p><img src="/assets/4273e57e7148/1*Xzh2eBpVR92uZq-8POPegw.webp" alt="" loading="lazy" decoding="async" width="760" height="860" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjAiIGhlaWdodD0iODYwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*Xzh2eBpVR92uZq-8POPegw.png" /></p>

<p>GitHub Repo Backup:</p>

<p><a href="https://github.com/ZhgChgLi/google-apps-script-cd-web-app-demo" target="_blank"><img src="https://opengraph.githubassets.com/3d7c517cd747bc49656701ef275357816667a72ebe2b7744c22e8a34c82b4a26/ZhgChgLi/google-apps-script-cd-web-app-demo" alt="" /></a></p>

<h4 id="file-structure">File Structure</h4>

<p><img src="/assets/4273e57e7148/1*ohVakhlaflRXCI_3j2-cOA.webp" alt="" loading="lazy" decoding="async" width="227" height="716" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMjciIGhlaWdodD0iNzE2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*ohVakhlaflRXCI_3j2-cOA.png" /></p>

<p><img src="/assets/4273e57e7148/1*AyIEF0wqEFdMDBuRzQXDEQ.webp" alt="" loading="lazy" decoding="async" width="1177" height="991" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc3IiBoZWlnaHQ9Ijk5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*AyIEF0wqEFdMDBuRzQXDEQ.png" /></p>

<p>I wrote a very simple class-based MVC architecture. If you need adjustments or don’t understand a feature, you can ask AI for accurate answers.</p>

<p><strong>System</strong></p>

<ul>
  <li>
    <p>appsscript.json: Metadata configuration file for the GAS system<br />
<strong>The key point is the “oauthScopes” variable, which declares the external permissions this Script will use.</strong></p>
  </li>
  <li>
    <p>Entrypoint.gs: Define the doGet() entry point</p>
  </li>
</ul>

<p><strong>Controller</strong></p>

<ul>
  <li>Controller_iOS.gs: iOS packaging tool page Controller, responsible for fetching data for the View display</li>
</ul>

<p><strong>View</strong></p>

<ul>
  <li>
    <p>View_index.html: The entire packaging tool framework and homepage</p>
  </li>
  <li>
    <p>View_iOS.html: iOS Packaging Tool Page Skeleton</p>
  </li>
  <li>
    <p>View_iOS_Runs.html: iOS Packaging Tool — Packaging Record Details Page</p>
  </li>
  <li>
    <p>View_iOS_Form.html: iOS Packaging Tool — Packaging Form Page</p>
  </li>
  <li>
    <p>View_iOS_Runners.html: iOS Packaging Tool — Self-hosted Runner Status Page</p>
  </li>
</ul>

<p><strong>Model(Lib)</strong></p>

<ul>
  <li>
    <p>Credentials.gs: Define key content<br />
(⚠️ Please note, using GCP IAM with GAS can be quite complex, so we directly define the keys here. <strong>Therefore, this GAS project contains sensitive information; do not share project view or edit permissions casually.</strong>)</p>
  </li>
  <li>
    <p>StubData.gs: Stub methods and data for the Online Demo.</p>
  </li>
  <li>
    <p>Settings.gs: Some common settings and lib initialization.</p>
  </li>
  <li>
    <p>GitHub.gs: Wrapper for GitHub API operations.</p>
  </li>
  <li>
    <p>Slack.gs: Wrapper for Slack API operations.</p>
  </li>
  <li>
    <p>Firebase.gs: Firebase — Wrapper for App Distribution API operations.</p>
  </li>
</ul>

<h4 id="build-your-own-packaging-platform">Build Your Own Packaging Platform</h4>

<ol>
  <li>Create a <a href="https://script.google.com/home" target="_blank">Google Apps Script project</a> and name it</li>
</ol>

<p><img src="/assets/4273e57e7148/1*_uVqy6cDNvFSEL0feOS-pw.webp" alt="" loading="lazy" decoding="async" width="919" height="369" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTkiIGhlaWdodD0iMzY5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*_uVqy6cDNvFSEL0feOS-pw.png" /></p>

<p>Go to Project Settings → Check “Show ‘appsscript.json’ manifest file in editor” to display the “appsscript.json” metadata file.</p>

<p><img src="/assets/4273e57e7148/1*tvXsQufQs5-5UzV0bg5WLA.webp" alt="" loading="lazy" decoding="async" width="736" height="466" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MzYiIGhlaWdodD0iNDY2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*tvXsQufQs5-5UzV0bg5WLA.png" /></p>

<ol>
  <li>Refer to my <a href="https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit" target="_blank">open source project files</a> and create all files exactly as in the example, then copy the content directly.</li>
</ol>

<p><img src="/assets/4273e57e7148/1*PxZvHNeW6PFaaqmZqifFfg.webp" alt="" loading="lazy" decoding="async" width="572" height="272" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzIiIGhlaWdodD0iMjcyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*PxZvHNeW6PFaaqmZqifFfg.png" /></p>

<blockquote>
  <p><strong><em>Very silly, but no choice.</em></strong></p>
</blockquote>

<blockquote>
  <p><em>Copy StubData.gs first; it can be used for the initial deployment test.</em></p>
</blockquote>

<blockquote>
  <p><em>Another method is to use <a href="https://developers.google.com/apps-script/guides/clasp?hl=zh-tw" target="_blank">clasp (Google Apps Script CLI)</a> to git clone the demo project and then push the code.</em></p>
</blockquote>

<p><img src="/assets/4273e57e7148/1*FLM2SGIhAI7Z7OWIJpeT9A.webp" alt="" loading="lazy" decoding="async" width="1150" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTUwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*FLM2SGIhAI7Z7OWIJpeT9A.png" /></p>

<p>After copying, it will look exactly the same as the sample project.</p>

<ol>
  <li>First Deployment of the “Web App” and Checking the Result</li>
</ol>

<p><img src="/assets/4273e57e7148/1*KbWpaEinVZh6M8o8bCCl6A.webp" alt="" loading="lazy" decoding="async" width="833" height="239" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MzMiIGhlaWdodD0iMjM5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*KbWpaEinVZh6M8o8bCCl6A.png" /></p>

<p><img src="/assets/4273e57e7148/1*2RrwNxZVidodX6Kq037Yvw.webp" alt="" loading="lazy" decoding="async" width="761" height="601" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjEiIGhlaWdodD0iNjAxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*2RrwNxZVidodX6Kq037Yvw.png" /></p>

<p><img src="/assets/4273e57e7148/1*CZSIfASW4hmQeXwTS3x7Fg.webp" alt="" loading="lazy" decoding="async" width="761" height="602" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjEiIGhlaWdodD0iNjAyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*CZSIfASW4hmQeXwTS3x7Fg.png" /></p>

<p>In the top right corner of the project, click “Deploy” → “New deployment” → select type “Web app”:</p>

<p><strong>Execution Identity:</strong></p>

<ul>
  <li>
    <p>I <code class="language-plaintext highlighter-rouge">always run the script using your account identity.</code></p>
  </li>
  <li>
    <p>The user accessing the web app <code class="language-plaintext highlighter-rouge">will run the script under their currently logged-in Google account.</code></p>
  </li>
</ul>

<p><strong>Who can access:</strong></p>

<ul>
  <li>
    <p>Only me</p>
  </li>
  <li>
    <p><strong>All users in the same XXX organization</strong> <code class="language-plaintext highlighter-rouge">Only users with the same organization and logged-in Google accounts can access.</code></p>
  </li>
  <li>
    <p>All signed-in Google account users<br />
<code class="language-plaintext highlighter-rouge">All signed-in Google account users can access.</code></p>
  </li>
  <li>
    <p>Everyone <code class="language-plaintext highlighter-rouge">does not need to sign in with a Google account, and everyone can access it publicly.</code></p>
  </li>
</ul>

<blockquote>
  <p><em>If it is an internal tool: you can choose “<strong>Who has access: All users in the XXX organization</strong>” + “Execute as: User accessing the web app” for secure control.</em></p>
</blockquote>

<p>The URL of the “Web App” after deployment is your Web App packaging tool URL, which you can share with your team members. (The URL might look ugly, so you can use a URL shortening service to tidy it up. <strong>Updating the deployment content will not change the URL</strong>.)</p>

<h4 id="user-consent-required-on-first-use">User Consent Required on First Use</h4>

<p>The first time you click the Web App URL, you need to grant authorization.</p>

<p><img src="/assets/4273e57e7148/1*lckRkGtc7EVqz9aTZaBIOw.webp" alt="" loading="lazy" decoding="async" width="519" height="491" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTkiIGhlaWdodD0iNDkxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*lckRkGtc7EVqz9aTZaBIOw.png" /></p>

<p><img src="/assets/4273e57e7148/1*l1t290T_4xs1ly-RjjZXtg.webp" alt="" loading="lazy" decoding="async" width="766" height="674" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjYiIGhlaWdodD0iNjc0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*l1t290T_4xs1ly-RjjZXtg.png" /></p>

<p><img src="/assets/4273e57e7148/1*O9ItCSBNoBcuqepd-6ygCA.webp" alt="" loading="lazy" decoding="async" width="722" height="630" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjIiIGhlaWdodD0iNjMwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*O9ItCSBNoBcuqepd-6ygCA.png" /></p>

<ul>
  <li>
    <p>Review Permission → Select the account identity to use this Web App</p>
  </li>
  <li>
    <p>Unverified warning window, click “Advanced” to expand → click Proceed to “XXX” (unsafe)</p>
  </li>
</ul>

<p><img src="/assets/4273e57e7148/1*cYloLsBROLbZDPIciYUm5g.webp" alt="" loading="lazy" decoding="async" width="1054" height="876" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDU0IiBoZWlnaHQ9Ijg3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*cYloLsBROLbZDPIciYUm5g.png" /></p>

<ul>
  <li>Click “Allow”</li>
</ul>

<blockquote>
  <p><em>No need to reauthorize if the script permissions remain unchanged.</em></p>
</blockquote>

<p><strong>After completing the authorization consent, you will enter the packaging tool homepage:</strong></p>

<p><img src="/assets/4273e57e7148/1*vG5SsVFHPPDukc4ej0hqLA.webp" alt="" loading="lazy" decoding="async" width="1149" height="1113" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ5IiBoZWlnaHQ9IjExMTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4273e57e7148/1*vG5SsVFHPPDukc4ej0hqLA.png" /></p>

<blockquote>
  <p>Demo packaging tool deployed successfully 🎉🎉🎉</p>
</blockquote>

<p>Note: The “This app was created by a Google Apps Script user” message cannot be hidden automatically.</p>

<h4 id="update-deployment">Update Deployment</h4>

<blockquote>
  <p>⚠️ All code changes require redeployment to take effect.</p>
</blockquote>

<blockquote>
  <p>️️⚠️All code changes require redeployment to take effect.</p>
</blockquote>

<blockquote>
  <p>⚠️ All code changes require redeployment to take effect.</p>
</blockquote>

<p>Note that saving code changes does not immediately update the Web App, so if refreshing has no effect, this is the reason; <strong>you need to go to “Deploy” → “Manage Deployments” → “Edit” → create a new version → click “Deploy” → “Done”</strong>.</p>

<p><img src="/assets/4273e57e7148/1*VsfCEfwnPlx9RbQ8DtXpnA.webp" alt="" loading="lazy" decoding="async" width="298" height="229" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTgiIGhlaWdodD0iMjI5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*VsfCEfwnPlx9RbQ8DtXpnA.png" /></p>

<p><img src="/assets/4273e57e7148/1*n62MVd6o8W3hUtQpfn9Q0w.webp" alt="" loading="lazy" decoding="async" width="761" height="603" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjEiIGhlaWdodD0iNjAzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*n62MVd6o8W3hUtQpfn9Q0w.png" /></p>

<blockquote>
  <p><em>After updating and deploying, refresh the webpage to see the changes take effect.</em></p>
</blockquote>

<h4 id="add-test-deployment-for-easier-development">Add Test Deployment for Easier Development</h4>

<p><img src="/assets/4273e57e7148/1*4fLoW6jf8z1AIXvULW-AOA.webp" alt="" loading="lazy" decoding="async" width="413" height="235" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTMiIGhlaWdodD0iMjM1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*4fLoW6jf8z1AIXvULW-AOA.png" /></p>

<p><img src="/assets/4273e57e7148/1*yvTxeaImQ4Mr7FwESsuf5A.webp" alt="" loading="lazy" decoding="async" width="761" height="599" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjEiIGhlaWdodD0iNTk5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*yvTxeaImQ4Mr7FwESsuf5A.png" /></p>

<p>As mentioned earlier, all changes require redeployment to take effect; this is inconvenient during development. Therefore, we can use a “test deployment” during development to quickly verify if the changes are correct.</p>

<p><strong>Go to “Deploy” → “Test Deployments” → get the test “Web App” URL.</strong></p>

<p><img src="/assets/4273e57e7148/1*Rt0XEw9uAev58isLEnsoPA.webp" alt="" loading="lazy" decoding="async" width="762" height="603" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjIiIGhlaWdodD0iNjAzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*Rt0XEw9uAev58isLEnsoPA.png" /></p>

<p>During development, we can directly use this URL to save. After saving file changes, refresh the page at this development URL to see the results!</p>

<blockquote>
  <p><strong><em>After all development is complete, update and deploy as described earlier, then release it for users to use.</em></strong></p>
</blockquote>

<h3 id="modify-the-demo-example-project-to-connect-real-data">Modify the Demo Example Project to Connect Real Data</h3>

<p>Next is the key part: connecting real data. The GitHub Actions Workflow refers to the <a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD process established in the previous article</strong></a>. You can also adjust the parameters according to your actual Actions Workflow.</p>

<h4 id="️-please-note-before-making-changes">⚠️ Please Note Before Making Changes</h4>

<p>The Google Apps Script platform does not support multiple users or multiple editing windows well. A pitfall I encountered was accidentally opening two editor windows; after editing in window A, I later edited in window B, causing all changes to be overwritten by B’s older version. Therefore, <strong>it is recommended that only one person edits the Script in one window at a time</strong>.</p>

<h4 id="github-integration">GitHub Integration</h4>

<p><strong>Inject GitHub API Token:</strong></p>

<p>GitHub -&gt; Account -&gt; Settings -&gt; Developer Settings -&gt; Fine-grained personal access tokens or Personal access tokens (classic).</p>

<p>It is recommended to use Fine-grained personal access tokens for better security (but they have an expiration).</p>

<p><strong>The required permissions for Fine-grained personal access tokens are as follows:</strong></p>

<p><img src="/assets/4273e57e7148/1*lCNQHwC4EMU4gNXYC-zs2A.webp" alt="" loading="lazy" decoding="async" width="821" height="579" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MjEiIGhlaWdodD0iNTc5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*lCNQHwC4EMU4gNXYC-zs2A.png" /></p>

<ul>
  <li>
    <p>Repo: Remember to select the Repo you want to operate on</p>
  </li>
  <li>
    <p>Permissions: <code class="language-plaintext highlighter-rouge">Actions (read/write)</code> and <code class="language-plaintext highlighter-rouge">Administration (read only)</code></p>
  </li>
</ul>

<blockquote>
  <p><em>If you don’t want to rely on someone’s personal account, it is recommended to create a dedicated team GitHub account and use its Token.</em></p>
</blockquote>

<p>Go to the GAS project → <code class="language-plaintext highlighter-rouge">Credentials.gs</code> → Insert the token into the <code class="language-plaintext highlighter-rouge">githubToken</code> variable.</p>

<p><strong>Replace GithubStub with GitHub:</strong></p>

<p>Go to the GAS project → <code class="language-plaintext highlighter-rouge">Settings.gs</code> → change:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">iOSGitHub</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GitHubStub</span><span class="p">(</span><span class="nx">githubToken</span><span class="p">,</span> <span class="nx">iOSRepoPath</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Change to</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">iOSGitHub</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GitHub</span><span class="p">(</span><span class="nx">githubToken</span><span class="p">,</span> <span class="nx">iOSRepoPath</span><span class="p">);</span>
</code></pre></div></div>

<p>Save the file.</p>

<p><strong>Refresh the test “Web App” URL to check if the changes are correct:</strong></p>

<p><img src="/assets/4273e57e7148/1*30k1iDALT9WdupmuBiG2uQ.webp" alt="" loading="lazy" decoding="async" width="958" height="859" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTgiIGhlaWdodD0iODU5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*30k1iDALT9WdupmuBiG2uQ.png" /></p>

<blockquote>
  <p><em>Correct data display means: <strong>GitHub is successfully connected to real data</strong> 🎉🎉🎉</em></p>
</blockquote>

<p>You can also easily switch to the “Runner Status” tab to check if the Self-hosted Runner status is being fetched correctly:</p>

<p><img src="/assets/4273e57e7148/1*K3yzISn5J7o1e1F5zhIQpg.webp" alt="" loading="lazy" decoding="async" width="972" height="394" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzIiIGhlaWdodD0iMzk0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*K3yzISn5J7o1e1F5zhIQpg.png" /></p>

<p>Note: My Runner is off… so it is offline.</p>

<h4 id="slack-integration">Slack Integration</h4>

<p>To integrate Slack notifications, we first need to go back to the Repo → GitHub Actions and add a notification container Action that wraps the package build Action Workflow.</p>

<p><strong>CD-Deploy-Form.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow(Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">CD-Deploy-Form</span>

<span class="c1"># Actions Log title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[CD-Deploy-Form]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="c1"># Cancel any running job in the same concurrency group if a new one starts</span>
<span class="c1"># For example, if the same branch's build job is triggered repeatedly, cancel the previous one</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.ref }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Manual form trigger</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="c1"># Form input fields</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># App version number</span>
      <span class="na">VERSION_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Version</span><span class="nv"> </span><span class="s">Number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1.0.0).</span><span class="nv"> </span><span class="s">Auto-detect</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">Xcode</span><span class="nv"> </span><span class="s">project</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App build number</span>
      <span class="na">BUILD_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Build</span><span class="nv"> </span><span class="s">number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1).</span><span class="nv"> </span><span class="s">Will</span><span class="nv"> </span><span class="s">use</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">timestamp</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App release note</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Release</span><span class="nv"> </span><span class="s">notes</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">deployment.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># Slack User ID of the triggerer</span>
      <span class="na">SLACK_USER_ID</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Slack</span><span class="nv"> </span><span class="s">user</span><span class="nv"> </span><span class="s">id.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># Email of the triggerer</span>
      <span class="na">AUTHOR</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Trigger</span><span class="nv"> </span><span class="s">author</span><span class="nv"> </span><span class="s">email.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
        
<span class="c1"># Job tasks</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Send Slack message when starting build</span>
  <span class="c1"># Job ID</span>
  <span class="na">start-message</span><span class="pi">:</span>
    <span class="c1"># Run this small job on GitHub Hosted Runner, low usage</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    
    <span class="c1"># Set max timeout to avoid endless waiting in abnormal cases</span>
    <span class="c1"># Normally should not exceed 5 minutes</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">5</span>

    <span class="c1"># Job steps</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post a Start Slack Message</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">slack</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.0.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ inputs.SLACK_USER_ID }}</span>
            <span class="s">text</span><span class="err">:</span> <span class="s2">"</span><span class="s">Build</span><span class="nv"> </span><span class="s">request</span><span class="nv"> </span><span class="s">received.</span><span class="se">\n</span><span class="s">ID:</span><span class="nv"> </span><span class="s">&lt;${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}</span><span class="se">\\</span><span class="s">|${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}&gt;</span><span class="se">\n</span><span class="s">Branch:</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref_name</span><span class="nv"> </span><span class="s">}}</span><span class="se">\n</span><span class="s">cc'ed</span><span class="nv"> </span><span class="s">&lt;@${{</span><span class="nv"> </span><span class="s">inputs.SLACK_USER_ID</span><span class="nv"> </span><span class="s">}}&gt;"</span>
    <span class="c1"># Job output for following jobs</span>
    <span class="c1"># ts = Slack message ID, so later notifications can reply in the same thread</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">ts</span><span class="pi">:</span> <span class="s">${{ steps.slack.outputs.ts }}</span>

  <span class="na">deploy</span><span class="pi">:</span>
    <span class="c1"># Jobs run concurrently by default, use needs to wait for start-message to finish before running</span>
    <span class="c1"># Execute build and deploy task</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">start-message</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/CD-Deploy.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">VERSION_NUMBER</span><span class="pi">:</span> <span class="s">${{ inputs.VERSION_NUMBER }}</span>
      <span class="na">BUILD_NUMBER</span><span class="pi">:</span> <span class="s">${{ inputs.BUILD_NUMBER }}</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span> <span class="s">${{ inputs.RELEASE_NOTE }}</span>
      <span class="na">AUTHOR</span><span class="pi">:</span> <span class="s">${{ inputs.AUTHOR }}</span>

  <span class="c1"># Success message for build and deploy task</span>
  <span class="na">end-message-success</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">start-message</span><span class="pi">,</span> <span class="nv">deploy</span><span class="pi">]</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.deploy.result == 'success' }}</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">5</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post a Success Slack Message</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.0.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ inputs.SLACK_USER_ID }}</span>
            <span class="s">thread_ts</span><span class="err">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">needs.start-message.outputs.ts</span><span class="nv"> </span><span class="s">}}"</span>
            <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">✅</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">deploy</span><span class="nv"> </span><span class="s">succeeded.</span><span class="se">\n\n</span><span class="s">cc'ed</span><span class="nv"> </span><span class="s">&lt;@${{</span><span class="nv"> </span><span class="s">inputs.SLACK_USER_ID</span><span class="nv"> </span><span class="s">}}&gt;"</span>

  <span class="c1"># Failure message for build and deploy task</span>
  <span class="na">end-message-failure</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">deploy</span><span class="pi">,</span> <span class="nv">start-message</span><span class="pi">]</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.deploy.result == 'failure' }}</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">5</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post a Failure Slack Message</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.0.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ inputs.SLACK_USER_ID }}</span>
            <span class="s">thread_ts</span><span class="err">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">needs.start-message.outputs.ts</span><span class="nv"> </span><span class="s">}}"</span>
            <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">❌</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">deploy</span><span class="nv"> </span><span class="s">failed,</span><span class="nv"> </span><span class="s">please</span><span class="nv"> </span><span class="s">check</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">execution</span><span class="nv"> </span><span class="s">status</span><span class="nv"> </span><span class="s">or</span><span class="nv"> </span><span class="s">try</span><span class="nv"> </span><span class="s">again</span><span class="nv"> </span><span class="s">later.</span><span class="se">\n\n</span><span class="s">cc'ed</span><span class="nv"> </span><span class="s">&lt;@${{</span><span class="nv"> </span><span class="s">inputs.SLACK_USER_ID</span><span class="nv"> </span><span class="s">}}&gt;"</span>

  <span class="c1"># Cancelled message for build and deploy task</span>
  <span class="na">end-message-cancelled</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">deploy</span><span class="pi">,</span> <span class="nv">start-message</span><span class="pi">]</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.deploy.result == 'cancelled' }}</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">5</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post a Cancelled Slack Message</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.0.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ inputs.SLACK_USER_ID }}</span>
            <span class="s">thread_ts</span><span class="err">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">needs.start-message.outputs.ts</span><span class="nv"> </span><span class="s">}}"</span>
            <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">:black_square_for_stop:</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">deploy</span><span class="nv"> </span><span class="s">cancelled.</span><span class="se">\n\n</span><span class="s">cc'ed</span><span class="nv"> </span><span class="s">&lt;@${{</span><span class="nv"> </span><span class="s">inputs.SLACK_USER_ID</span><span class="nv"> </span><span class="s">}}&gt;"</span>
</code></pre></div></div>

<p><strong>Full Code:</strong> <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/CD-Deploy-Form.yml" target="_blank">CD-Deploy-Form.yml</a></p>

<p><img src="/assets/4273e57e7148/1*NatyC_Oid4BrKYk4nehuKA.webp" alt="" loading="lazy" decoding="async" width="952" height="287" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTIiIGhlaWdodD0iMjg3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*NatyC_Oid4BrKYk4nehuKA.png" /></p>

<p>This Action is just a container that connects Slack notifications, actually reusing the <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/CD-Deploy.yml" target="_blank">CD-Deploy.yml</a> Action written in the <a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/">previous article</a>.</p>

<ul>
  <li>
    <p>For creating the Slack Bot App and setting message permissions, please refer to my <a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">previous article</a>.</p>
  </li>
  <li>
    <p>Remember to add the corresponding <code class="language-plaintext highlighter-rouge">SLACK_BOT_TOKEN</code> in Repo → Secrets and enter the Slack Bot App Token value.</p>
  </li>
</ul>

<p>Back to the GAS project → <code class="language-plaintext highlighter-rouge">Credentials.gs</code> → Insert the Token into the <code class="language-plaintext highlighter-rouge">slackBotToken</code> variable.</p>

<p>Go to the GAS project → <code class="language-plaintext highlighter-rouge">Settings.gs</code> → change:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">slack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SlackStub</span><span class="p">(</span><span class="nx">slackBotToken</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Change to</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">slack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Slack</span><span class="p">(</span><span class="nx">slackBotToken</span><span class="p">);</span>
</code></pre></div></div>

<p>Save the file.</p>

<blockquote>
  <p><em>If you don’t have a ready Slack Bot App for notifications and don’t want to create one, you can skip all the steps here and remove any Slack-related usage from the GAS project.</em></p>
</blockquote>

<h4 id="github-integration--build-form">GitHub Integration — Build Form</h4>

<p>Go to the GAS project → <code class="language-plaintext highlighter-rouge">Controller_iOS.gs</code> → modify the <code class="language-plaintext highlighter-rouge">View_iOS_Form.html</code> content:<br />
Remove the fake Asana Tasks integration method:</p>

<pre><code class="language-php-template">      &lt;? tasks.forEach(function(task) { ?&gt;
      &lt;option value="&lt;?=task.githubBranch?&gt;"&gt;[&lt;?=task.id?&gt;] &lt;?=task.title?&gt;&lt;/option&gt;
      &lt;? }) ?&gt;
</code></pre>

<p>You can also adjust the default branch here (it’s <code class="language-plaintext highlighter-rouge">main</code> in this case).</p>

<p>—</p>

<p>Go to the GAS project → <code class="language-plaintext highlighter-rouge">Controller_iOS.gs</code> → modify the content of <code class="language-plaintext highlighter-rouge">iOSLoadForm()</code>:</p>

<ul>
  <li>
    <p>Remove the line <code class="language-plaintext highlighter-rouge">template.tasks = Stubable.fetchStubAsanaTasks();</code> which mocks the Asana connection.<br />
If you want to connect to Asana/Jira, you can directly ask ChatGPT to help generate the integration method.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">template.prs = iOSGitHub.fetchOpenPRs();</code> actually calls the GitHub API to fetch the list of opened PRs and can be kept as needed.</p>
  </li>
</ul>

<p><strong>Post-Submission Handling</strong> Inside <code class="language-plaintext highlighter-rouge">iOSSubmitForm()</code>:</p>

<p>You can adjust according to the actual GitHub Actions Workflow file name and the <code class="language-plaintext highlighter-rouge">workflow_dispatch</code> inputs parameters:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nx">iOSGitHub</span><span class="p">.</span><span class="nf">dispatchWorkflow</span><span class="p">(</span><span class="dl">"</span><span class="s2">CD-Deploy-Form.yml</span><span class="dl">"</span><span class="p">,</span> <span class="nx">branch</span><span class="p">,</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">BUILD_NUMBER</span><span class="dl">"</span><span class="p">:</span> <span class="nx">buildNumber</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">VERSION_NUMBER</span><span class="dl">"</span><span class="p">:</span> <span class="nx">versionNumber</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">VERSION_NUMBER</span><span class="dl">"</span><span class="p">:</span> <span class="nx">versionNumber</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">RELEASE_NOTE</span><span class="dl">"</span><span class="p">:</span> <span class="nx">releaseNote</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">AUTHOR</span><span class="dl">"</span><span class="p">:</span> <span class="nx">email</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">SLACK_USER_ID</span><span class="dl">"</span><span class="p">:</span> <span class="nx">slack</span><span class="p">.</span><span class="nf">fetchUserID</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span>
  <span class="p">});</span>
</code></pre></div></div>

<p>You can also add your own required field validations. Here, it only checks that the branch must be filled in; otherwise, an error message will appear.</p>

<p>If you think this is not secure enough, you can add password verification or restrict access to specific accounts.</p>

<blockquote>
  <p><em>The last line <strong>Slack notification requires proper Slack setup</strong>. If you don’t have a Slack Bot App or don’t want to integrate Slack, you can directly use <code class="language-plaintext highlighter-rouge">iOSGitHub.dispatchWorkflow("CD-Deploy.yml")</code> in the <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions" target="_blank">Demo Actions Repo</a> and remove the <code class="language-plaintext highlighter-rouge">SLACK_USER_ID</code> parameter.</em></p>
</blockquote>

<p><strong>Refresh the test “Web App” URL to check if the changes are correct:</strong></p>

<p><img src="/assets/4273e57e7148/1*OkJJrssZBcJPss_cMW-Qew.webp" alt="" loading="lazy" decoding="async" width="754" height="711" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NTQiIGhlaWdodD0iNzExIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*OkJJrssZBcJPss_cMW-Qew.png" /></p>

<p>You can see that the packaging form now only has the Opened PR List left.</p>

<p><strong>Fill in the information and click “Submit Request” to test the packaging form:</strong></p>

<p><img src="/assets/4273e57e7148/1*dl-g3j6GH0AnYL8dqvSyYA.webp" alt="" loading="lazy" decoding="async" width="523" height="252" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MjMiIGhlaWdodD0iMjUyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*dl-g3j6GH0AnYL8dqvSyYA.png" /></p>

<p><strong>A successful submission means everything is fine, and you can see the task started running in the build records <em>🎉</em></strong>:</p>

<p><img src="/assets/4273e57e7148/1*cByPF6T8WdXKionifRj1Ew.webp" alt="" loading="lazy" decoding="async" width="959" height="383" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTkiIGhlaWdodD0iMzgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*cByPF6T8WdXKionifRj1Ew.png" /></p>

<blockquote>
  <p><em>Repeated packaging records can update progress.</em></p>
</blockquote>

<p><strong>Common Submission Errors:</strong></p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">Required input ‘SLACK_USER_ID’ not provided</code> <em>: The SLACK_USER_ID field is required in GitHub Actions but was not provided. This may be due to Slack configuration failure or the current User Email not matching any Slack UID.</em></p>
</blockquote>

<blockquote>
  <p><em><code class="language-plaintext highlighter-rouge">Workflow does not have ‘workflow_dispatch’ trigger</code> and <code class="language-plaintext highlighter-rouge">Branch is outdated, please update the xxx branch</code>: The selected branch cannot find the corresponding Action Workflow file (the file specified by iOSGitHub.dispatchWorkflow).</em></p>
</blockquote>

<blockquote>
  <p><em><code class="language-plaintext highlighter-rouge">No ref found for</code>, <code class="language-plaintext highlighter-rouge">Branch not found</code> : The branch could not be found.</em></p>
</blockquote>

<h3 id="firebase-app-distribution--get-download-link-integration">Firebase App Distribution — Get Download Link Integration</h3>

<p>The last small feature is integrating Firebase App Distribution to directly get download info and links, making it easy to open the packaging platform tool on a mobile device and click to download and install.</p>

<blockquote>
  <p><em>Previously introduced “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/">Google Apps Script x Google APIs Quick Integration Method</a>“—GAS can quickly and easily integrate with Firebase.</em></p>
</blockquote>

<h4 id="integration-principle"><strong>Integration Principle</strong></h4>

<blockquote>
  <p><strong><em>Before integration, let’s first explain the “Tricky” integration principle.</em></strong></p>
</blockquote>

<p>Our packaging platform has no database and serves purely as an API relay station; so in practice, when we run the packaging job in <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/workflows/CD-Deploy.yml" target="_blank"><strong>GitHub Actions CD-Deploy.yml</strong></a>, we include the Job Run ID in the Release Note (of course, it can also be included in the Build Number):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">ID</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="p">{ github.run_id </span><span class="k">}</span><span class="s2">}"</span> // Job Run ID
<span class="nv">COMMIT_SHA</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="p">{ github.sha </span><span class="k">}</span><span class="s2">}"</span>
<span class="nv">BRANCH_NAME</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="p">{ github.ref_name </span><span class="k">}</span><span class="s2">}"</span>
<span class="nv">AUTHOR</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="p">{ env.AUTHOR </span><span class="k">}</span><span class="s2">}"</span>

<span class="c"># Compose Release Note</span>
<span class="nv">RELEASE_NOTE</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="p">{ env.RELEASE_NOTE </span><span class="k">}</span><span class="s2">}
ID: </span><span class="k">${</span><span class="nv">ID</span><span class="k">}</span><span class="s2">
Commit SHA: </span><span class="k">${</span><span class="nv">COMMIT_SHA</span><span class="k">}</span><span class="s2">
Branch: </span><span class="k">${</span><span class="nv">BRANCH_NAME</span><span class="k">}</span><span class="s2">
Author: </span><span class="k">${</span><span class="nv">AUTHOR</span><span class="k">}</span><span class="s2">
"</span>

<span class="c"># Run Fastlane build &amp; deploy lane</span>
bundle <span class="nb">exec </span>fastlane beta release_notes:<span class="s2">"</span><span class="k">${</span><span class="nv">RELEASE_NOTE</span><span class="k">}</span><span class="s2">"</span> version_number:<span class="s2">"</span><span class="k">${</span><span class="nv">VERSION_NUMBER</span><span class="k">}</span><span class="s2">"</span> build_number:<span class="s2">"</span><span class="k">${</span><span class="nv">BUILD_NUMBER</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<p>This way, the Firebase App Distribution Release Notes will include the Job Run ID.</p>

<p>The GAS Web App packaging tool platform connects to the GitHub API to get GitHub Actions run records. We directly use the Job Run ID provided by the API to query the Firebase App Distribution API. If the Release Notes contain a version with <code class="language-plaintext highlighter-rouge">*ID: XXX*</code>, the corresponding packaging record can be found.</p>

<p><strong>Map two tool platforms without using any database.</strong></p>

<p><img src="/assets/4273e57e7148/1*AJRMWv_rqu64H0ZrY4q6QA.webp" alt="" loading="lazy" decoding="async" width="559" height="347" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTkiIGhlaWdodD0iMzQ3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*AJRMWv_rqu64H0ZrY4q6QA.png" /></p>

<p><img src="/assets/4273e57e7148/1*Sdzt1MDXh7TNnMDkpc5uJg.webp" alt="" loading="lazy" decoding="async" width="1055" height="702" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDU1IiBoZWlnaHQ9IjcwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*Sdzt1MDXh7TNnMDkpc5uJg.png" /></p>

<h4 id="project-integration-settings">Project Integration Settings</h4>

<p>Go to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:</p>

<p><img src="/assets/4273e57e7148/1*klclBbiBQXNBzbzzj1jH0Q.webp" alt="" loading="lazy" decoding="async" width="1140" height="1115" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQwIiBoZWlnaHQ9IjExMTUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4273e57e7148/1*klclBbiBQXNBzbzzj1jH0Q.png" /></p>

<p><img src="/assets/4273e57e7148/1*gv8m_v5O_yyUrq8uQoverQ.webp" alt="" loading="lazy" decoding="async" width="1086" height="632" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDg2IiBoZWlnaHQ9IjYzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*gv8m_v5O_yyUrq8uQoverQ.png" /></p>

<p>Enter the Firebase project ID you want to connect.</p>

<p><img src="/assets/4273e57e7148/1*OEaxA6SZWYbiVstOK60hQg.webp" alt="" loading="lazy" decoding="async" width="760" height="444" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjAiIGhlaWdodD0iNDQ0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*OEaxA6SZWYbiVstOK60hQg.png" /></p>

<blockquote>
  <p><strong><em>Initial setup may show errors</em></strong> <em>“To change the project, please configure the OAuth consent screen. Set up the OAuth consent screen details.” If it does not appear, you can skip the following steps.</em></p>
</blockquote>

<p><strong>Click the “OAuth consent screen details” link → Click “Configure consent screen”:</strong></p>

<p><img src="/assets/4273e57e7148/1*oiZeO5mEqH-D3tHrJsnE1A.webp" alt="" loading="lazy" decoding="async" width="1149" height="676" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ5IiBoZWlnaHQ9IjY3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*oiZeO5mEqH-D3tHrJsnE1A.png" /></p>

<p><strong>Click “Start”:</strong></p>

<p><img src="/assets/4273e57e7148/1*NK9RG0kaXoxJB5X-B8vhww.webp" alt="" loading="lazy" decoding="async" width="762" height="550" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjIiIGhlaWdodD0iNTUwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*NK9RG0kaXoxJB5X-B8vhww.png" /></p>

<p><strong>Application Information:</strong></p>

<ul>
  <li>
    <p>Application Name: <code class="language-plaintext highlighter-rouge">Enter Your Tool Name</code></p>
  </li>
  <li>
    <p>User Support Email: <code class="language-plaintext highlighter-rouge">Select Email</code></p>
  </li>
</ul>

<p><strong>Target Audience:</strong></p>

<ul>
  <li>
    <p>Internal: For use by organization members only</p>
  </li>
  <li>
    <p>External: Available to all Google account users after consent to authorization</p>
  </li>
</ul>

<p><strong>Contact Information:</strong></p>

<ul>
  <li>Enter the email address to receive notifications</li>
</ul>

<p><strong>Check to agree to the 《 <a href="https://developers.google.com/terms/api-services-user-data-policy?hl=zh_TW" target="_blank">Google API Services: User Data Policy</a> 》。</strong></p>

<p>Finally, click “<strong>Create</strong>”.</p>

<p>—</p>

<p>Back to GAS → Project Settings → Google Cloud Platform (GCP) Project → Change Project:</p>

<p>Re-enter the Firebase project ID and click “Change Project”.</p>

<p><img src="/assets/4273e57e7148/1*g-Lo04WjhaDjtXeXthNtDg.webp" alt="" loading="lazy" decoding="async" width="779" height="272" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzkiIGhlaWdodD0iMjcyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*g-Lo04WjhaDjtXeXthNtDg.png" /></p>

<p><strong>If no errors appear, the binding is complete.</strong></p>

<p>—</p>

<p><strong>If you choose “External,” you may also need to complete the following settings:</strong></p>

<p>Click “Project Number” → expand the left sidebar → “APIs &amp; Services” → “OAuth consent screen”</p>

<p><img src="/assets/4273e57e7148/1*RJNQv8v3KZoTcVwUbsXSHQ.webp" alt="" loading="lazy" decoding="async" width="808" height="719" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MDgiIGhlaWdodD0iNzE5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*RJNQv8v3KZoTcVwUbsXSHQ.png" /></p>

<p>Select “Target Audience” → Test, click “Deploy as web app” → Done.</p>

<p><img src="/assets/4273e57e7148/1*8vqrxhchsILPy4eSdvXfNg.webp" alt="" loading="lazy" decoding="async" width="626" height="445" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjYiIGhlaWdodD0iNDQ1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*8vqrxhchsILPy4eSdvXfNg.png" /></p>

<blockquote>
  <p><em>Users can follow the previous step “<strong>User must authorize on first use</strong>” to complete authorization and start using it!</em></p>
</blockquote>

<p><strong>If the above steps are not configured, users will encounter the following error:</strong></p>

<p><img src="/assets/4273e57e7148/1*2omUPFoubsrXVLHBPFkPbg.webp" alt="Access to &quot;XXX&quot; blocked due to incomplete Google verification process" loading="lazy" decoding="async" width="766" height="673" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjYiIGhlaWdodD0iNjczIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*2omUPFoubsrXVLHBPFkPbg.png" /></p>

<p>Access to “XXX” blocked due to incomplete Google verification process</p>

<hr />

<h4 id="integrate-project">Integrate Project</h4>

<p>Back to the integration, Firebase directly uses <code class="language-plaintext highlighter-rouge">ScriptApp.getOAuthToken()</code> to dynamically obtain the token based on the execution identity, so no token setup is needed.</p>

<p>Only up to the GAS project → <code class="language-plaintext highlighter-rouge">Settings.gs</code> → change:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">iOSFirebase</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FirebaseStub</span><span class="p">(</span><span class="nx">iOSFirebaseProject</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Change to</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">iOSFirebase</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Firebase</span><span class="p">(</span><span class="nx">iOSFirebaseProject</span><span class="p">);</span>
</code></pre></div></div>

<p>Done.</p>

<p><strong>Refresh the test “Web App” URL to the build records → Find a record and click “Get Download Link”:</strong></p>

<p><img src="/assets/4273e57e7148/1*9z-KsIAL5jyEzQvJv3YLgg.webp" alt="" loading="lazy" decoding="async" width="913" height="452" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTMiIGhlaWdodD0iNDUyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*9z-KsIAL5jyEzQvJv3YLgg.png" /></p>

<p><img src="/assets/4273e57e7148/1*q0HUYN2W3UonQLzwxZKXdQ.webp" alt="" loading="lazy" decoding="async" width="1179" height="2556" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc5IiBoZWlnaHQ9IjI1NTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4273e57e7148/1*q0HUYN2W3UonQLzwxZKXdQ.png" /></p>

<p>If the corresponding Job Run Id is found in the Firebase App Distribution Release Notes, the download information will be displayed directly, and clicking the download button will take you straight to the download page.</p>

<blockquote>
  <p>Done! 🎉🎉🎉</p>
</blockquote>

<h3 id="results">Results</h3>

<p><img src="/assets/4273e57e7148/1*znvPmqsaivk3KhsE26sFwA.webp" alt="Demo Web App" loading="lazy" decoding="async" width="1010" height="766" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDEwIiBoZWlnaHQ9Ijc2NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4273e57e7148/1*znvPmqsaivk3KhsE26sFwA.png" /></p>

<p><a href="https://script.google.com/macros/s/AKfycbwNW6N5ozKbIz_E1HK6yFEUtA8KQrUciS-jcPsQptvIKlARmKgLxbQzNu8ksVeg-BmEfg/exec" target="_blank">Demo Web App</a></p>

<p>By now, you have adapted the examples into your actual usable packaging tool. Remaining custom features, additional third-party API integrations, and more forms can be extended on your own (discuss with ChatGPT).</p>

<blockquote>
  <p><em>Finally, don’t forget that after development and testing are complete, you need to follow the previous steps — <strong>updating the deployment is required for changes to take effect!</strong></em></p>
</blockquote>

<h3 id="integration-extensions">Integration Extensions</h3>

<p>Continuing the spirit of our “relay station” role, here are some quick integration CheatSheets:</p>

<p><a href="https://developers.asana.com/reference/gettasksforproject" target="_blank"><strong>Asana API — Get Tasks:</strong></a></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">function</span> <span class="nf">asanaAPI</span><span class="p">(</span><span class="nx">endPoint</span><span class="p">,</span> <span class="nx">method</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span> <span class="nx">data</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">method</span><span class="dl">"</span> <span class="p">:</span> <span class="nx">method</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">headers</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
          <span class="dl">"</span><span class="s2">Authorization</span><span class="dl">"</span><span class="p">:</span>  <span class="dl">"</span><span class="s2">Bearer </span><span class="dl">"</span><span class="o">+</span><span class="nx">asanaToken</span>
      <span class="p">},</span>
      <span class="dl">"</span><span class="s2">payload</span><span class="dl">"</span> <span class="p">:</span> <span class="nx">data</span>
    <span class="p">};</span>

    <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://app.asana.com/api/1.0</span><span class="dl">"</span><span class="o">+</span><span class="nx">endPoint</span><span class="p">;</span>
    <span class="kd">var</span> <span class="nx">res</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">var</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">asanaAPI</span><span class="p">(</span><span class="dl">"</span><span class="s2">/projects/{project_gid}/tasks</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>

<p><a href="https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#about" target="_blank"><strong>Jira API — Get Tickets (JQL):</strong></a></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// jql = filter criteria</span>
<span class="kd">function</span> <span class="nf">jiraTickets</span><span class="p">(</span><span class="nx">jql</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="s2">`https://xxx.atlassian.net/rest/api/3/search`</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">maxResults</span> <span class="o">=</span> <span class="mi">100</span><span class="p">;</span>

  <span class="kd">let</span> <span class="nx">allIssues</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="kd">let</span> <span class="nx">startAt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="kd">let</span> <span class="nx">total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

  <span class="k">do</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">queryParams</span> <span class="o">=</span> <span class="p">{</span>
      <span class="na">jql</span><span class="p">:</span> <span class="nx">jql</span><span class="p">,</span>
      <span class="na">startAt</span><span class="p">:</span> <span class="nx">startAt</span><span class="p">,</span>
      <span class="na">maxResults</span><span class="p">:</span> <span class="nx">maxResults</span><span class="p">,</span>
      <span class="na">fields</span><span class="p">:</span> <span class="dl">"</span><span class="s2">assignee,summary,status</span><span class="dl">"</span>
    <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">queryString</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">keys</span><span class="p">(</span><span class="nx">queryParams</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nx">key</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">key</span><span class="p">)}</span><span class="s2">=</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">queryParams</span><span class="p">[</span><span class="nx">key</span><span class="p">])}</span><span class="s2">`</span><span class="p">)</span>
      <span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">"</span><span class="s2">&amp;</span><span class="dl">"</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">Authorization</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Basic </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">jiraToken</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
      <span class="p">},</span>
      <span class="na">muteHttpExceptions</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">?</span><span class="p">${</span><span class="nx">queryString</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getResponseCode</span><span class="p">()</span> <span class="o">!=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Failed to fetch Jira issues.</span><span class="dl">"</span><span class="p">);</span> 
    <span class="p">}</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">issues</span> <span class="o">&amp;&amp;</span> <span class="nx">json</span><span class="p">.</span><span class="nx">issues</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">allIssues</span> <span class="o">=</span> <span class="nx">allIssues</span><span class="p">.</span><span class="nf">concat</span><span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">issues</span><span class="p">);</span>
      <span class="nx">total</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">total</span><span class="p">;</span>
      <span class="nx">startAt</span> <span class="o">+=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">issues</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">while </span><span class="p">(</span><span class="nx">startAt</span> <span class="o">&lt;</span> <span class="nx">total</span><span class="p">);</span>

  <span class="kd">var</span> <span class="nx">groupIssues</span> <span class="o">=</span> <span class="p">{};</span>
  <span class="k">for</span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">allIssues</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">issue</span> <span class="o">=</span> <span class="nx">allIssues</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">groupIssues</span><span class="p">[</span><span class="nx">issue</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">status</span><span class="p">.</span><span class="nx">name</span><span class="p">]</span> <span class="o">==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">groupIssues</span><span class="p">[</span><span class="nx">issue</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">status</span><span class="p">.</span><span class="nx">name</span><span class="p">]</span> <span class="o">=</span> <span class="p">[];</span>
    <span class="p">}</span>
    <span class="nx">groupIssues</span><span class="p">[</span><span class="nx">issue</span><span class="p">.</span><span class="nx">fields</span><span class="p">.</span><span class="nx">status</span><span class="p">.</span><span class="nx">name</span><span class="p">].</span><span class="nf">push</span><span class="p">(</span><span class="nx">issue</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">groupIssues</span><span class="p">;</span>
<span class="p">}</span>

<span class="nf">jiraTickets</span><span class="p">(</span><span class="s2">`project IN(App)`</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>If a database is really needed, Google Sheets can be used as a substitute:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Saveable</span> <span class="p">{</span>
  <span class="nf">constructor</span><span class="p">(</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// https://docs.google.com/spreadsheets/d/Sheet-ID/edit</span>
    <span class="kd">const</span> <span class="nx">spreadsheet</span> <span class="o">=</span> <span class="nx">SpreadsheetApp</span><span class="p">.</span><span class="nf">openById</span><span class="p">(</span><span class="dl">"</span><span class="s2">Sheet-ID</span><span class="dl">"</span><span class="p">);</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">sheet</span> <span class="o">=</span> <span class="nx">spreadsheet</span><span class="p">.</span><span class="nf">getSheetByName</span><span class="p">(</span><span class="dl">"</span><span class="s2">Data</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// Sheet Name</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">type</span> <span class="o">=</span> <span class="nx">type</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nf">write</span><span class="p">(</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">sheet</span><span class="p">.</span><span class="nf">appendRow</span><span class="p">([</span>
      <span class="k">this</span><span class="p">.</span><span class="nx">type</span><span class="p">,</span>
      <span class="nx">key</span><span class="p">,</span>
      <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
    <span class="p">]);</span>
  <span class="p">}</span>

  <span class="nf">read</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">sheet</span><span class="p">.</span><span class="nf">getDataRange</span><span class="p">().</span><span class="nf">getValues</span><span class="p">();</span>
    <span class="kd">const</span> <span class="nx">row</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">===</span> <span class="k">this</span><span class="p">.</span><span class="nx">type</span> <span class="o">&amp;&amp;</span> <span class="nx">r</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="nx">key</span><span class="p">);</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">row</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">row</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">let</span> <span class="nx">saveable</span> <span class="o">=</span> <span class="nc">Saveable</span><span class="p">(</span><span class="dl">"</span><span class="s2">user</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// Write</span>
<span class="nx">saveable</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="dl">"</span><span class="s2">birthday_zhgchgli</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">0718</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// Read</span>
<span class="nx">saveable</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="dl">"</span><span class="s2">birthday_zhgchgli</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// -&gt; 0718</span>
</code></pre></div></div>

<p><a href="https://api.slack.com/methods/chat.postMessage" target="_blank"><strong>Slack API &amp; Sending Messages:</strong></a></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">slackSendMessage</span><span class="p">(</span><span class="nx">channel</span><span class="p">,</span> <span class="nx">text</span> <span class="o">=</span> <span class="dl">""</span><span class="p">,</span> <span class="nx">blocks</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">channel</span><span class="p">:</span> <span class="nx">channel</span><span class="p">,</span>
    <span class="na">unfurl_links</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="na">unfurl_media</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
    <span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
    <span class="na">blocks</span><span class="p">:</span> <span class="nx">blocks</span>
  <span class="p">};</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat.postMessage</span><span class="dl">"</span><span class="p">,</span> <span class="nx">content</span><span class="p">);</span>
    <span class="k">return</span> <span class="nx">response</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`Failed to send Slack message: </span><span class="p">${</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">contentType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">slackBotToken</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="dl">'</span><span class="s1">X-Slack-No-Retry</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">},</span>
    <span class="na">payload</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://slack.com/api/</span><span class="dl">"</span><span class="o">+</span><span class="nx">path</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">responseData</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">responseData</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">responseData</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`Slack: </span><span class="p">${</span><span class="nx">responseData</span><span class="p">.</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="more-google-apps-script-examples">More Google Apps Script Examples:</h4>

<ul>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/">Quick Integration Method for Google Apps Script x Google APIs</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/">Simple 3 Steps — Build a Free GA4 Automated Data Notification Bot</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Using Google Apps Script to Automate Daily Data Report RPA</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">Slack &amp; ChatGPT Integration</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-3-step-guide-to-build-free-github-repo-star-notifier-382218e15697/">Create a Free Github Repo Star Notifier in Three Steps Using Google Apps Script</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/crashlytics-google-analytics-automate-crash-free-user-rate-queries-with-google-apps-script-793cb8f89b72/">Crashlytics + Google Analytics Automated Query for App Crash-Free Users Rate</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/crashlytics-bigquery-integration-for-real-time-crash-tracking-and-alerts-e77b80cc6f89/">Crashlytics + Big Query: Building a More Real-Time and Convenient Crash Tracking Tool</a></p>
  </li>
</ul>

<h3 id="summary">Summary</h3>

<p>Thank you for your patience and participation. The CI/CD from 0 to 1 series ends here; I hope it helps you and your team build a solid CI/CD workflow to improve efficiency and product stability. If you have any implementation questions, feel free to leave a comment. These four articles took about 14+ days to write. <strong>If you found them helpful, please follow me on Medium and share with your friends and colleagues.</strong></p>

<blockquote>
  <p>Thank you.</p>
</blockquote>

<h4 id="-buy-me-a-beer-on-paypal"><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></h4>

<blockquote>
  <p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank"><strong><em>This series took a lot of time and effort to write. If the content helps you or improves your team’s work efficiency and product quality, please consider buying me a coffee. Thank you for your support!</em></strong></a></p>
</blockquote>

<p><img src="/assets/4273e57e7148/1*QJj54G9gOjtQS-rbHVT1SQ.webp" alt="Buy me a coffee" loading="lazy" decoding="async" width="700" height="700" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNzAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4273e57e7148/1*QJj54G9gOjtQS-rbHVT1SQ.png" /></p>

<p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></p>

<h4 id="series-articles">Series Articles:</h4>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Complete Guide to Using and Building GitHub Actions with Self-hosted Runner</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</strong></a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">GitHub Actions｜iOS App CI/CD Workflow Automation for Faster Builds and Deployments</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/" rel="alternate" type="text/html" title="GitHub Actions｜iOS App CI/CD Workflow Automation for Faster Builds and Deployments" />
    <published>2025-07-07T23:02:24+08:00</published>
    <updated>2026-01-04T11:38:01+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/4b001d2e8440</id><summary type="html">iOS developers facing manual build and deployment delays can automate their CI/CD pipeline using GitHub Actions to streamline app build, testing, and deployment processes, achieving faster releases and improved reliability.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="cicd" /><category term="github-actions" /><category term="firebase" /><category term="cicd-pipeline" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/4b001d2e8440/1*0LK6m6CTImL6rcsrliiOQA.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><![CDATA[<h3 id="cicd-practical-guide-3-implementing-ios-app-ci-and-cd-workflows-with-github-actions">CI/CD Practical Guide (3): Implementing iOS App CI and CD Workflows with GitHub Actions</h3>

<p>Complete Guide to Automating iOS App Build, Test, and Deployment with GitHub Actions</p>

<p><img src="/assets/4b001d2e8440/1*0LK6m6CTImL6rcsrliiOQA.webp" alt="Photo by Robs" loading="lazy" decoding="async" width="1200" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*0LK6m6CTImL6rcsrliiOQA.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@robinne?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Robs</a></p>

<h4 id="preface">Preface</h4>

<p>In the previous article “<a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Comprehensive Use and Setup of GitHub Actions and Self-hosted Runner</strong></a>,” we introduced the basics of GitHub Actions, its workflow, and how to use your own machine as a Runner, guiding you through three simple automated Actions. <strong>This article will focus deeply on building an App (iOS) CI/CD workflow with GitHub Actions in real-world scenarios</strong>, walking you through each step while supplementing relevant GitHub Actions knowledge.</p>

<h3 id="app-cicd-workflow-diagram">App CI/CD Workflow Diagram</h3>

<p><img src="/assets/4b001d2e8440/1*VRygfRAkBRNEDAC4RyGzRA.webp" alt="" loading="lazy" decoding="async" width="1400" height="669" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjY2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*VRygfRAkBRNEDAC4RyGzRA.png" /></p>

<p>This article focuses on the GitHub Actions CI/CD setup section. The next article, “<a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect with GitHub Actions to Build a Free and Easy Packaging Tool Platform</a>,” will cover the right-side part about using Google Apps Script Web App to build a cross-team collaboration packaging platform.</p>

<h4 id="workflow">Workflow:</h4>

<ol>
  <li>
    <p>GitHub Actions Triggered by Pull Request, Form Trigger, or Scheduled Trigger</p>
  </li>
  <li>
    <p>Run the Corresponding Workflow Jobs/Steps</p>
  </li>
  <li>
    <p>Step Execute the corresponding Fastlane (iOS) or (Android Gradle) script</p>
  </li>
  <li>
    <p>Fastlane Executes Corresponding xcodebuild (iOS) Commands</p>
  </li>
  <li>
    <p>Get the Execution Result</p>
  </li>
  <li>
    <p>Subsequent Workflow Jobs/Steps Handle Results</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
</ol>

<h4 id="github-actions-result-images">GitHub Actions Result Images</h4>

<p>Let’s start by showing the final result to give everyone some practical motivation!</p>

<p><img src="/assets/4b001d2e8440/1*5gnQYdVAOtGR-bMK4ZrOhA.webp" alt="CI Testing" loading="lazy" decoding="async" width="911" height="566" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTEiIGhlaWdodD0iNTY2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*5gnQYdVAOtGR-bMK4ZrOhA.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">CI Testing</a></p>

<p><img src="/assets/4b001d2e8440/1*u6A77KwkXS2SY5-DPPPR9A.webp" alt="" loading="lazy" decoding="async" width="1200" height="802" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*u6A77KwkXS2SY5-DPPPR9A.png" /></p>

<p><img src="/assets/4b001d2e8440/1*t9PrQfcTANyvG7gfXXC-bw.webp" alt="CI Nightly Build, CD Deploy" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*t9PrQfcTANyvG7gfXXC-bw.jpeg" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/runs/16119750747" target="_blank">CI Nightly Build, CD Deploy</a></p>

<h3 id="github-actions-x-self-hosted-runner-basics">GitHub Actions x Self-hosted Runner Basics</h3>

<p>If you are not yet familiar with GitHub Actions and setting up a Self-hosted Runner, it is highly recommended to first read the previous article “<a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Complete Usage and Setup of GitHub Actions and Self-hosted Runner</strong></a>” or implement it together with the knowledge from that article.</p>

<blockquote>
  <p><strong><em>Implementation begins!</em></strong></p>
</blockquote>

<h3 id="infra-architecture-of-the-ios-demo-project">Infra Architecture of the iOS Demo Project</h3>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo" target="_blank"><img src="https://opengraph.githubassets.com/eb233d74ad1bc6b0eceb9494487579557fb7f17066a3981d20b52fde2c00ef45/ZhgChgLi/github-actions-ci-cd-demo" alt="" /></a></p>

<p><strong>The iOS project content used in this article, including test items, is generated by AI</strong>, so there is no need to focus on the iOS code details. The discussion is solely about Infra &amp; CI/CD.</p>

<blockquote>
  <p><strong><em>The following tools are based on past experience. For new projects, consider using the newer <a href="https://github.com/jdx/mise" target="_blank">mise</a> and <a href="https://github.com/tuist/tuist" target="_blank">tuist</a>.</em></strong></p>
</blockquote>

<h4 id="mint">Mint</h4>

<p><a href="https://github.com/yonaskolb/Mint" target="_blank"><img src="https://opengraph.githubassets.com/21a1c9531478638f34289d108bd478e5d7afec08dce1aa16123feb0e226a47a2/yonaskolb/Mint" alt="" /></a></p>

<p>The Mint tool helps us manage the versions of dependency tools consistently (Gemfile can only manage Ruby Gems), such as XcodeGen, SwiftFormat, SwiftLint, and Periphery.</p>

<p>…etc</p>

<p><strong>Mintfile:</strong></p>

<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">yonaskolb/Mint@0.17.5</span><span class="w">
</span><span class="err">yonaskolb/XcodeGen@2.35.0</span><span class="w">
</span><span class="err">nicklockwood/SwiftFormat@0.51.13</span><span class="w">
</span></code></pre></div></div>

<p>Here we only use three.</p>

<blockquote>
  <p><strong><em>If it feels too complicated, you can skip it and directly install the required tools using brew install in the Action Workflow Step.</em></strong></p>
</blockquote>

<h4 id="bundle">Bundle</h4>

<p><strong>Gemfile:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> <span class="s1">'https://rubygems.org'</span>
gem <span class="s1">'cocoapods'</span>, <span class="s1">'~&gt;1.16.0'</span>
gem <span class="s1">'fastlane'</span>, <span class="s1">'~&gt;2.228.0'</span>

plugins_path <span class="o">=</span> File.join<span class="o">(</span>File.dirname<span class="o">(</span>__FILE__<span class="o">)</span>, <span class="s1">'Product'</span>, <span class="s1">'fastlane'</span>, <span class="s1">'Pluginfile'</span><span class="o">)</span>
eval_gemfile<span class="o">(</span>plugins_path<span class="o">)</span> <span class="k">if </span>File.exist?<span class="o">(</span>plugins_path<span class="o">)</span>
</code></pre></div></div>

<p>Managing Ruby (Gems) dependencies, the two most commonly used in iOS projects are <code class="language-plaintext highlighter-rouge">cocoapods</code> and <code class="language-plaintext highlighter-rouge">fastlane</code>.</p>

<h4 id="cocoapods">Cocoapods</h4>

<p><strong>Product/podfile:</strong></p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">platform</span> <span class="ss">:ios</span><span class="p">,</span> <span class="s1">'13.0'</span>
<span class="n">use_frameworks!</span>

<span class="n">target</span> <span class="s1">'app-ci-cd-github-actions-demo'</span> <span class="k">do</span>
  <span class="n">pod</span> <span class="s1">'SnapKit'</span>
<span class="k">end</span> 
</code></pre></div></div>

<p>Although it has been announced as <a href="https://blog.cocoapods.org/CocoaPods-Specs-Repo/" target="_blank">deprecated</a>, Cocoapods is still common in older iOS projects. Here, we simply add Snapkit as a demo.</p>

<h4 id="xcodegen">XCodeGen</h4>

<p>To avoid conflicts caused by changes to <code class="language-plaintext highlighter-rouge">.xcodeproj</code> / <code class="language-plaintext highlighter-rouge">.xcworkspace</code> during multi-developer collaboration, use <code class="language-plaintext highlighter-rouge">Project.yaml</code> to define the Xcode Project content consistently, then generate the Project files locally (do not commit them to Git).</p>

<p><strong>Product/project.yaml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demo</span>
<span class="na">options</span><span class="pi">:</span>
  <span class="na">bundleIdPrefix</span><span class="pi">:</span> <span class="s">com.example</span>
  <span class="na">deploymentTarget</span><span class="pi">:</span>
    <span class="na">iOS</span><span class="pi">:</span> <span class="s1">'</span><span class="s">13.0'</span>
  <span class="na">usesTabs</span><span class="pi">:</span> <span class="kc">false</span>
  <span class="na">indentWidth</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">tabWidth</span><span class="pi">:</span> <span class="m">2</span>

<span class="na">configs</span><span class="pi">:</span>
  <span class="na">Debug</span><span class="pi">:</span> <span class="s">debug</span>
  <span class="na">Release</span><span class="pi">:</span> <span class="s">release</span>

<span class="na">targets</span><span class="pi">:</span>
  <span class="na">app-ci-cd-github-actions-demo</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">app-ci-cd-github-actions-demo</span>
    <span class="na">resources</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">app-ci-cd-github-actions-demo/Assets.xcassets</span>
      <span class="pi">-</span> <span class="s">app-ci-cd-github-actions-demo/Base.lproj</span>
    <span class="na">info</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demo/Info.plist</span>
      <span class="na">properties</span><span class="pi">:</span>
        <span class="na">CFBundleIdentifier</span><span class="pi">:</span> <span class="s">$(PRODUCT_BUNDLE_IDENTIFIER)</span>
    <span class="na">settings</span><span class="pi">:</span>
      <span class="na">base</span><span class="pi">:</span>
        <span class="na">PRODUCT_BUNDLE_IDENTIFIER</span><span class="pi">:</span> <span class="s">com.test.appcicdgithubactionsdemo</span>
    <span class="na">cocoapods</span><span class="pi">:</span> <span class="kc">true</span>

  <span class="na">app-ci-cd-github-actions-demoTests</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bundle.unit-test</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">app-ci-cd-github-actions-demoTests</span>
    <span class="na">dependencies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demo</span>
    <span class="na">info</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demoTests/Info.plist</span>
    <span class="na">settings</span><span class="pi">:</span>
      <span class="na">base</span><span class="pi">:</span>
        <span class="na">PRODUCT_BUNDLE_IDENTIFIER</span><span class="pi">:</span> <span class="s">com.test.appcicdgithubactionsdemo.tests</span>

  <span class="na">app-ci-cd-github-actions-demoUITests</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bundle.ui-testing</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">app-ci-cd-github-actions-demoUITests</span>
    <span class="na">dependencies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demo</span>
    <span class="na">info</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demoUITests/Info.plist</span>
    <span class="na">settings</span><span class="pi">:</span>
      <span class="na">base</span><span class="pi">:</span>
        <span class="na">PRODUCT_BUNDLE_IDENTIFIER</span><span class="pi">:</span> <span class="s">com.test.appcicdgithubactionsdemo.uitests</span>

  <span class="na">app-ci-cd-github-actions-demoSnapshotTests</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bundle.unit-test</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">path</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demoSnapshotTests</span>
        <span class="na">excludes</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s2">"</span><span class="s">**/__Snapshots__/**"</span>
    <span class="na">dependencies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demo</span>
      <span class="pi">-</span> <span class="na">product</span><span class="pi">:</span> <span class="s">SnapshotTesting</span>
        <span class="na">package</span><span class="pi">:</span> <span class="s">SnapshotTesting</span>
    <span class="na">info</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">app-ci-cd-github-actions-demoSnapshotTests/Info.plist</span>
      <span class="na">settings</span><span class="pi">:</span>
        <span class="na">base</span><span class="pi">:</span>
          <span class="na">PRODUCT_BUNDLE_IDENTIFIER</span><span class="pi">:</span> <span class="s">com.test.appcicdgithubactionsdemo.snapshottests</span>

<span class="na">packages</span><span class="pi">:</span>
  <span class="na">SnapshotTesting</span><span class="pi">:</span>
    <span class="na">url</span><span class="pi">:</span> <span class="s">https://github.com/pointfreeco/swift-snapshot-testing</span>
    <span class="na">from</span><span class="pi">:</span> <span class="s">1.18.4</span>
</code></pre></div></div>

<p>SnapshotTesting: Managed with Swift Package Manager.</p>

<h4 id="fastlane">Fastlane</h4>

<p>Encapsulate complex steps such as xcodebuild commands, and integrating with services like App Store Connect API and Firebase API.</p>

<p><strong>Product/fastlane/Fastfile:</strong></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nf">default_platform</span><span class="p">(</span><span class="o">:</span><span class="n">ios</span><span class="p">)</span>

<span class="n">platform</span> <span class="o">:</span><span class="n">ios</span> <span class="k">do</span>
  <span class="n">desc</span> <span class="s2">"Run all tests (Unit Tests + UI Tests)"</span>
  <span class="n">lane</span> <span class="o">:</span><span class="n">run_all_tests</span> <span class="k">do</span> <span class="err">\\</span><span class="o">|</span><span class="n">options</span><span class="err">\\</span><span class="o">|</span>
    <span class="n">device</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">device</span><span class="p">]</span>
    <span class="nf">scan</span><span class="p">(</span>
      <span class="n">scheme</span><span class="o">:</span> <span class="s2">"app-ci-cd-github-actions-demo"</span><span class="p">,</span>
      <span class="n">device</span><span class="o">:</span> <span class="n">device</span><span class="p">,</span>
      <span class="n">clean</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="n">output_directory</span><span class="o">:</span> <span class="s2">"fastlane/test_output"</span><span class="p">,</span>
      <span class="n">output_types</span><span class="o">:</span> <span class="s2">"junit"</span>
    <span class="p">)</span>
  <span class="n">end</span>

  <span class="n">desc</span> <span class="s2">"Run only Unit Tests"</span>
  <span class="n">lane</span> <span class="o">:</span><span class="n">run_unit_tests</span> <span class="k">do</span> <span class="err">\\</span><span class="o">|</span><span class="n">options</span><span class="err">\\</span><span class="o">|</span>
    <span class="n">device</span> <span class="o">=</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">device</span><span class="p">]</span>
    <span class="nf">scan</span><span class="p">(</span>
      <span class="n">scheme</span><span class="o">:</span> <span class="s2">"app-ci-cd-github-actions-demo"</span><span class="p">,</span>
      <span class="n">device</span><span class="o">:</span> <span class="n">device</span><span class="p">,</span>
      <span class="n">clean</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="n">only_testing</span><span class="o">:</span> <span class="p">[</span>
        <span class="s2">"app-ci-cd-github-actions-demoTests"</span>
      <span class="p">],</span>
      <span class="n">output_directory</span><span class="o">:</span> <span class="s2">"fastlane/test_output"</span><span class="p">,</span>
      <span class="n">output_types</span><span class="o">:</span> <span class="s2">"junit"</span>
    <span class="p">)</span>
  <span class="n">end</span>

  <span class="n">desc</span> <span class="s2">"Build and upload to Firebase App Distribution"</span>
  <span class="n">lane</span> <span class="o">:</span><span class="n">beta</span> <span class="k">do</span> <span class="err">\\</span><span class="o">|</span><span class="n">options</span><span class="err">\\</span><span class="o">|</span>
    
    <span class="k">if</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">version_number</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">version_number</span><span class="p">]</span><span class="mf">.</span><span class="n">to_s</span><span class="mf">.</span><span class="n">strip</span> <span class="o">!=</span> <span class="s2">""</span>
      <span class="nf">increment_version_number</span><span class="p">(</span><span class="n">version_number</span><span class="o">:</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">version_number</span><span class="p">])</span>
    <span class="n">end</span>

    <span class="k">if</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">build_number</span><span class="p">]</span> <span class="o">&amp;&amp;</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">build_number</span><span class="p">]</span><span class="mf">.</span><span class="n">to_s</span><span class="mf">.</span><span class="n">strip</span> <span class="o">!=</span> <span class="s2">""</span>
      <span class="nf">increment_build_number</span><span class="p">(</span><span class="n">build_number</span><span class="o">:</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">build_number</span><span class="p">])</span>
    <span class="n">end</span>

    <span class="nf">update_code_signing_settings</span><span class="p">(</span>
      <span class="n">use_automatic_signing</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
      <span class="n">path</span><span class="o">:</span> <span class="s2">"app-ci-cd-github-actions-demo.xcodeproj"</span><span class="p">,</span>
      <span class="n">team_id</span><span class="o">:</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'TEAM_ID'</span><span class="p">],</span>
      <span class="n">code_sign_identity</span><span class="o">:</span> <span class="s2">"iPhone Developer"</span><span class="p">,</span>
      <span class="n">sdk</span><span class="o">:</span> <span class="s2">"iphoneos*"</span><span class="p">,</span>
      <span class="n">profile_name</span><span class="o">:</span> <span class="s2">"cicd"</span>
    <span class="p">)</span>

    <span class="nf">gym</span><span class="p">(</span>
      <span class="n">scheme</span><span class="o">:</span> <span class="s2">"app-ci-cd-github-actions-demo"</span><span class="p">,</span>
      <span class="n">clean</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
      <span class="n">export_method</span><span class="o">:</span> <span class="s2">"development"</span><span class="p">,</span>
      <span class="n">output_directory</span><span class="o">:</span> <span class="s2">"fastlane/build"</span><span class="p">,</span>
      <span class="n">output_name</span><span class="o">:</span> <span class="s2">"app-ci-cd-github-actions-demo.ipa"</span><span class="p">,</span>
      <span class="n">export_options</span><span class="o">:</span> <span class="p">{</span>
          <span class="n">provisioningProfiles</span><span class="o">:</span> <span class="p">{</span>
            <span class="s2">"com.test.appcicdgithubactionsdemo"</span> <span class="o">=&gt;</span> <span class="s2">"cicd"</span><span class="p">,</span>
          <span class="p">},</span>
      <span class="p">}</span>
    <span class="p">)</span>

    <span class="nf">firebase_app_distribution</span><span class="p">(</span>
      <span class="n">app</span><span class="o">:</span> <span class="s2">"1:127683058219:ios:98896929fa131c7a80686e"</span><span class="p">,</span>
      <span class="n">firebase_cli_token</span><span class="o">:</span> <span class="no">ENV</span><span class="p">[</span><span class="s2">"FIREBASE_CLI_TOKEN"</span><span class="p">],</span>
      <span class="n">release_notes</span><span class="o">:</span> <span class="n">options</span><span class="p">[</span><span class="o">:</span><span class="n">release_notes</span><span class="p">]</span> <span class="err">\\</span><span class="o">|</span><span class="err">\\</span><span class="o">|</span> <span class="s2">"New beta build"</span>
    <span class="p">)</span>
  <span class="n">end</span>
<span class="n">end</span>
</code></pre></div></div>

<p>Note: provisioningProfiles and profile_name correspond to the <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Profiles certificate names in App Developer</a>. (If you use match, these specifications are not needed.)</p>

<p><img src="/assets/4b001d2e8440/1*WXqqnErto3nn8rnNg6TXgw.webp" alt="" loading="lazy" decoding="async" width="1117" height="616" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE3IiBoZWlnaHQ9IjYxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*WXqqnErto3nn8rnNg6TXgw.png" /></p>

<p><strong>Fastlane is an essential part of iOS CI/CD</strong>, allowing you to quickly develop the actual CI/CD execution steps using its pre-built methods; we only need to focus on the overall script design without dealing with complex API integration or command writing.</p>

<p>For example, Fastlane only requires writing <code class="language-plaintext highlighter-rouge">scan(xxx)</code> to run tests, while using xcodebuild needs <code class="language-plaintext highlighter-rouge">xcodebuild -workspace ./xxx.xcworkspace -scheme xxx -derivedDataPath xxx ‘platform=iOS Simulator,id=xxx’ clean build test</code>. Packaging and deployment are even more complicated, requiring manual integration with App Store Connect/Firebase APIs, and the key authentication alone can take over 10 lines of code.</p>

<p><strong>The demo project has only three lanes:</strong></p>

<ul>
  <li>
    <p>run_all_tests: Run all types of tests (Snapshot + Unit)</p>
  </li>
  <li>
    <p>run_unit_tests: Run Unit Tests Only (Unit)</p>
  </li>
  <li>
    <p>beta: Build and Deploy to Firebase App Distribution</p>
  </li>
</ul>

<h4 id="fastlane--match">Fastlane — Match</h4>

<p>Due to the demo project’s limitations, Match is not used here to manage team development and deployment certificates. However, it is recommended to use Match to manage all development and deployment certificates for the team, making control and updates easier.</p>

<blockquote>
  <p><em>With Match, you can use commands like <code class="language-plaintext highlighter-rouge">match all</code> directly in the project setup step to install all the development certificates needed with one click.</em></p>
</blockquote>

<ul>
  <li><strong>Fastlane Match uses another private repo to manage certificate keys. In GitHub Actions, you need to set up the SSH Agent to clone the other private repo.</strong><br />
(Please refer to the supplement at the end)</li>
</ul>

<p><strong>[2026/02 Update] Further Reading — Additional Details on iOS Certificates, Fastlane Match, and CI/CD Usage:</strong></p>

<ul>
  <li>“<a href="/posts/zrealm-dev/ios-certificates-identifiers-profiles-explained-fastlane-match-for-unified-certificate-management-in-ci-cd-823ac523ccc8/">What are iOS Certificates, Identifiers &amp; Profiles and Notes on Managing Certificates with Fastlane Match for CI/CD</a>”</li>
</ul>

<p>— — —</p>

<h4 id="makefile">Makefile</h4>

<p><img src="/assets/4b001d2e8440/1*jYACUYFP3MkeHCgeUY7j3w.webp" alt="Makefile" loading="lazy" decoding="async" width="1200" height="423" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQyMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*jYACUYFP3MkeHCgeUY7j3w.png" /></p>

<p>Makefile</p>

<p>Let both developers and CI/CD use Makefile to run commands, making it easier to package the same environment, paths, and operations.</p>

<blockquote>
  <p><em>A common case is that some people use the locally installed <code class="language-plaintext highlighter-rouge">pod install</code>, while others use <code class="language-plaintext highlighter-rouge">bundle exec pod install</code> managed by Bundler. Differences in versions may cause discrepancies.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>If you find it too complicated and prefer not to use it, you can simply write the commands to execute directly in the Action Workflow Step.</em></strong></p>
</blockquote>

<p><strong>Makefile:</strong></p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!make
</span><span class="nv">PRODUCT_FOLDER</span> <span class="o">=</span> ./Product/
<span class="nv">SHELL</span>         <span class="o">:=</span> /bin/zsh
<span class="nv">.DEFAULT_GOAL</span> <span class="o">:=</span> <span class="nb">install</span>
<span class="nv">MINT_DIRECTORY</span> <span class="o">:=</span> ./mint/

<span class="k">export </span><span class="nv">MINT_PATH</span><span class="o">=</span><span class="p">$(</span>MINT_DIRECTORY<span class="p">)</span>

<span class="c">## 👇 Help function
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">help</span>
<span class="nl">help</span><span class="o">:</span>
 <span class="err">@echo</span> <span class="s2">""</span>
 <span class="nl">@echo "📖 Available commands</span><span class="o">:</span><span class="nf">"</span>
 <span class="nl">@grep -E '^[a-zA-Z_-]+</span><span class="o">:</span><span class="nf">.*?</span><span class="c">##</span><span class="nf"> .*$$' $(MAKEFILE_LIST) </span>\\<span class="nf">| </span>\
<span class="nf">  awk 'BEGIN {FS = ":.*?</span><span class="c">##</span><span class="nf"> "}; {printf "  </span>\0<span class="nf">33[36m%-20s</span>\0<span class="nf">33[0m %s</span>\n<span class="nf">"</span><span class="p">,</span><span class="nf"> $$1</span><span class="p">,</span><span class="nf"> $$2}'</span>
 <span class="err">@echo</span> <span class="s2">""</span>

<span class="c">## Setup
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">setup</span>
<span class="nl">setup</span><span class="o">:</span> <span class="nf">check-mint </span><span class="c">##</span><span class="nf"> Install Ruby and Mint dependencies</span>
 <span class="err">@echo</span> <span class="s2">"🔨 Installing Ruby dependencies..."</span>
 <span class="err">bundle</span> <span class="err">config</span> <span class="err">set</span> <span class="err">path</span> <span class="s1">'vendor/bundle'</span>
 <span class="err">bundle</span> <span class="err">install</span>
 <span class="err">@echo</span> <span class="s2">"🔨 Installing Mint dependencies..."</span>
 <span class="err">mint</span> <span class="err">bootstrap</span>

<span class="c">## Install
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">install</span>
<span class="nl">install</span><span class="o">:</span> <span class="nf">XcodeGen PodInstall </span><span class="c">##</span><span class="nf"> Run XcodeGen and CocoaPods install</span>

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">XcodeGen</span>
<span class="nl">XcodeGen</span><span class="o">:</span> <span class="nf">check-mint </span><span class="c">##</span><span class="nf"> Generate .xcodeproj using XcodeGen</span>
 <span class="err">@echo</span> <span class="s2">"🔨 Execute XcodeGen"</span>
 <span class="err">cd</span> <span class="err">$(PRODUCT_FOLDER)</span> <span class="err">&amp;&amp;</span> <span class="err">\</span>
 <span class="err">mint</span> <span class="err">run</span> <span class="err">yonaskolb/XcodeGen</span> <span class="err">--quiet</span>

<span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">PodInstall</span>
<span class="nl">PodInstall</span><span class="o">:</span> <span class="c">##</span><span class="nf"> Install CocoaPods dependencies</span>
 <span class="err">@echo</span> <span class="s2">"📦 Installing CocoaPods dependencies..."</span>
 <span class="err">cd</span> <span class="err">$(PRODUCT_FOLDER)</span> <span class="err">&amp;&amp;</span> <span class="err">\</span>
 <span class="err">bundle</span> <span class="err">exec</span> <span class="err">pod</span> <span class="err">install</span>

<span class="c">### Mint
</span><span class="nl">check-mint</span><span class="o">:</span> <span class="nf">check-brew </span><span class="c">##</span><span class="nf"> Check if Mint is installed</span><span class="p">,</span><span class="nf"> install if missing</span>
 <span class="err">@if</span> <span class="err">!</span> <span class="err">command</span> <span class="err">-v</span> <span class="err">mint</span> <span class="err">&amp;&gt;</span> <span class="err">/dev/null;</span> <span class="err">then</span> <span class="err">\</span>
  <span class="err">echo</span> <span class="s2">"🔨 Installing mint..."</span><span class="err">;</span> <span class="err">\</span>
  <span class="err">brew</span> <span class="err">install</span> <span class="err">mint;</span> <span class="err">\</span>
 <span class="err">fi</span>

<span class="c">### Brew
</span><span class="nl">check-brew</span><span class="o">:</span> <span class="c">##</span><span class="nf"> Check if Homebrew is installed</span><span class="p">,</span><span class="nf"> install if missing</span>
 <span class="err">@if</span> <span class="err">!</span> <span class="err">command</span> <span class="err">-v</span> <span class="err">brew</span> <span class="err">&amp;&gt;</span> <span class="err">/dev/null;</span> <span class="err">then</span> <span class="err">\</span>
  <span class="err">echo</span> <span class="s2">"🔨 Installing Homebrew..."</span><span class="err">;</span> <span class="err">\</span>
  <span class="nl">/bin/bash -c "$$(curl -fsSL https</span><span class="o">:</span><span class="nf">//raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; </span>\
<span class="nf"> fi</span>

<span class="c">## Format only git swift files
</span><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">format</span>
<span class="nl">format</span><span class="o">:</span> <span class="nf">check-mint </span><span class="c">##</span><span class="nf"> Format all Swift files under Product/</span>
 <span class="err">mint</span> <span class="err">run</span> <span class="err">swiftformat</span> <span class="err">$(PRODUCT_FOLDER)</span>
</code></pre></div></div>

<p>To avoid polluting the entire system or other projects, we try to specify the paths of dependency packages (e.g., mint, bundle, etc.) within the project directory (combined with <code class="language-plaintext highlighter-rouge">.gitignore</code> exclusion).</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">├──</span> <span class="nt">mint</span> <span class="o">(</span><span class="nt">Mint</span> <span class="nt">dependencies</span><span class="o">)</span>
<span class="err">│</span>   <span class="err">└──</span> <span class="nt">packages</span>
<span class="err">├──</span> <span class="nt">Product</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demo</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demo</span><span class="nc">.xcodeproj</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demo</span><span class="nc">.xcworkspace</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demoSnapshotTests</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demoTests</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">app-ci-cd-github-actions-demoUITests</span>
<span class="err">│</span>   <span class="err">├──</span> <span class="nt">fastlane</span>
<span class="err">│</span>   <span class="err">└──</span> <span class="nt">Pods</span> <span class="o">(</span><span class="nt">Cocoapods</span> <span class="nt">dependencies</span><span class="o">)</span>
<span class="err">└──</span> <span class="nt">vendor</span> <span class="o">(</span><span class="nt">Bundle</span> <span class="nt">dependencies</span><span class="o">)</span>
    <span class="err">└──</span> <span class="nt">bundle</span>
</code></pre></div></div>

<p><img src="/assets/4b001d2e8440/1*F1KFntT8bCZzyYm9JahiuA.webp" alt="make help" loading="lazy" decoding="async" width="449" height="142" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NDkiIGhlaWdodD0iMTQyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*F1KFntT8bCZzyYm9JahiuA.png" /></p>

<p>make help</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">.PHONY</span><span class="o">:</span> <span class="nf">help</span>
<span class="nl">help</span><span class="o">:</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"Usage: make &lt;target&gt;"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"Targets:"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  setup          # Setup environment (Mint, Bundle, Pods, XcodeGen)"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  test           # Run unit tests"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  snapshot       # Run snapshot tests"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  build          # Build the app"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  archive        # Archive the app"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  export         # Export the archive"</span>
	<span class="p">@</span><span class="nb">echo</span> <span class="s2">"  upload         # Upload the build to Firebase App Distribution"</span>
</code></pre></div></div>

<p><strong>Unified Project Setup Steps Using Makefile:</strong></p>

<ol>
  <li>
    <p><code class="language-plaintext highlighter-rouge">git clone repo</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">cd ./repo</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">make setup</code><br />
Install necessary tool dependencies (<strong>brew</strong>, <strong>mint</strong>, <strong>bundle</strong>, xcodegen, swiftformat, …)</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">make install</code><br />
Generate the project (run pod install, xcodegen)</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
  <li>
    <p>Open and Run the Project</p>
  </li>
</ol>

<blockquote>
  <p><strong><em>Whether for CI/CD or onboarding new members, the project is built following the above steps.</em></strong></p>
</blockquote>

<h3 id="github-actions-cicd-examples-in-this-article">GitHub Actions CI/CD Examples in This Article</h3>

<p>This article introduces three GitHub Actions CI/CD workflow examples. You can also refer to these steps to build a CI/CD process that fits your team’s workflow.</p>

<ol>
  <li>
    <p>CI — Run Unit Tests on Pull Request Submission</p>
  </li>
  <li>
    <p>CD — Build + Deploy to Firebase App Distribution</p>
  </li>
  <li>
    <p>CI + CD — Nightly Build Running Snapshot + Unit Tests + Build + Deploy to Firebase App Distribution</p>
  </li>
</ol>

<blockquote>
  <p><em>Due to demo limitations, this article only covers packaging and deployment to Firebase App Distribution. Packaging for Testflight or the App Store follows the same steps, with only different scripts in Fastlane. Feel free to customize as needed.</em></p>
</blockquote>

<h3 id="ci--run-unit-tests-on-pull-request">CI — Run Unit Tests on Pull Request</h3>

<h4 id="workflow-1">Workflow</h4>

<p>The Develop branch <strong>cannot be pushed to directly</strong>; updates must be done via Pull Request. All Pull Requests <strong>require review approval and passing unit tests before merging</strong>, and new commits pushed will trigger retesting.</p>

<h4 id="ci-testingyml">CI-Testing.yml</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow (Action) Name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">CI-Testing</span>

<span class="c1"># Actions Log Title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[CI-Testing]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.event.pull_request.title</span><span class="nv"> </span><span class="se">\\</span><span class="s">|</span><span class="se">\\</span><span class="s">|</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="c1"># Cancel running jobs in the same concurrency group if a new job starts</span>
<span class="c1"># For example, if a new push commit triggers a job before the previous one finishes, the previous job will be canceled</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number \\|\\| github.ref }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># PR events</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="c1"># PR - opened, reopened, or new push commit</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">,</span> <span class="nv">synchronize</span><span class="pi">,</span> <span class="nv">reopened</span><span class="pi">]</span>
  <span class="c1"># Manual trigger via form</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="c1"># Form inputs</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># Fastlane lane to run tests</span>
      <span class="na">TEST_LANE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Test</span><span class="nv"> </span><span class="s">Lane'</span>
        <span class="na">default</span><span class="pi">:</span> <span class="s1">'</span><span class="s">run_unit_tests'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">choice</span>
        <span class="na">options</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="s">run_unit_tests</span>
          <span class="pi">-</span> <span class="s">run_all_tests</span>
  <span class="c1"># Triggered by other workflows</span>
  <span class="c1"># Used by Nightly Build</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
    <span class="c1"># Form inputs</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># Fastlane lane to run tests</span>
      <span class="na">TEST_LANE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Test</span><span class="nv"> </span><span class="s">Lane'</span>
        <span class="na">default</span><span class="pi">:</span> <span class="s1">'</span><span class="s">run_unit_tests'</span>
        <span class="c1"># workflow_call inputs do not support choice</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="na">BRANCH</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Branch'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
  
<span class="c1"># Job items</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">testing</span><span class="pi">:</span>
    <span class="c1"># Job name (optional, improves log readability)</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Testing</span>
    
    <span class="c1"># Runner label - use GitHub Hosted Runner macos-15 to run the job</span>
    <span class="c1"># Note: This is a Public Repo, so usage is free and unlimited</span>
    <span class="c1"># Note: This is a Public Repo, so usage is free and unlimited</span>
    <span class="c1"># Note: This is a Public Repo, so usage is free and unlimited</span>
    <span class="c1"># For Private Repo, usage is metered and macOS machines are the most expensive (10x),</span>
    <span class="c1"># running about 10 times may reach the 2,000 minutes free limit</span>
    <span class="c1"># Self-hosted Runner is recommended</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">macos-15</span>

    <span class="c1"># Set maximum timeout to prevent endless wait on abnormal situations</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">30</span>

    <span class="c1"># use zsh</span>
    <span class="c1"># Optional, I prefer zsh; default is bash</span>
    <span class="na">defaults</span><span class="pi">:</span>
      <span class="na">run</span><span class="pi">:</span>
        <span class="na">shell</span><span class="pi">:</span> <span class="s">zsh {0}</span>
          
    <span class="c1"># Job steps</span>
    <span class="c1"># Steps run sequentially  </span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># git clone current repo &amp; checkout the target branch</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout repository</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="c1"># Git Large File Storage, not needed for our test environment</span>
          <span class="c1"># default: false</span>
          <span class="na">lfs</span><span class="pi">:</span> <span class="kc">false</span>
          
          <span class="c1"># Checkout specified branch if provided, otherwise use default (current branch)</span>
          <span class="c1"># on: schedule event only runs on main branch, so to do Nightly Build on master branch, specify here</span>
          <span class="c1"># e.g. on: schedule -&gt; main branch, Nightly Build master branch</span>
          <span class="na">ref</span><span class="pi">:</span> <span class="s">${{ github.event.inputs.BRANCH \\|\\| '' }}</span>

      <span class="c1"># ========== Env Setup Steps ==========</span>
      
      <span class="c1"># Read project specified Xcode version</span>
      <span class="c1"># Later we manually specify Xcode_x.x.x.app to use</span>
      <span class="c1"># Not using xcversion because it is deprecated and unstable</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Read .xcode-version</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">read_xcode_version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">XCODE_VERSION=$(cat .xcode-version)</span>
          <span class="s">echo "XCODE_VERSION</span><span class="err">:</span> <span class="s">${XCODE_VERSION}"</span>
          <span class="s">echo "xcode_version=${XCODE_VERSION}" &gt;&gt; $GITHUB_OUTPUT</span>

          <span class="s"># You can also set global Xcode version here to avoid specifying DEVELOPER_DIR later</span>
          <span class="s"># But this requires sudo rights; self-hosted runner must have sudo permission</span>
          <span class="s"># sudo xcode-select -s "/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer"</span>

      <span class="c1"># Read project specified Ruby version</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Read .ruby-version</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">read_ruby_version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">RUBY_VERSION=$(cat .ruby-version)</span>
          <span class="s">echo "RUBY_VERSION</span><span class="err">:</span> <span class="s">${RUBY_VERSION}"</span>
          <span class="s">echo "ruby_version=${RUBY_VERSION}" &gt;&gt; $GITHUB_OUTPUT</span>

      <span class="c1"># Install or set up Ruby version on Runner to project specified version</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Ruby</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">ruby/setup-ruby@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">ruby-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">steps.read_ruby_version.outputs.ruby_version</span><span class="nv"> </span><span class="s">}}"</span>

      <span class="c1"># Optional: previously when running multiple self-hosted runners on same machine,</span>
      <span class="c1"># cocoapods repos share the same directory causing conflicts during pod install</span>
      <span class="c1"># This solves rare conflicts by isolating each runner's cocoapods repos folder</span>
      <span class="c1"># GitHub Hosted Runner does not need this setting</span>
      <span class="c1"># - name: Change Cocoapods Repos Folder</span>
      <span class="c1">#   if: contains(runner.labels, 'self-hosted')</span>
      <span class="c1">#   run: \\|</span>
      <span class="c1">#     # Each runner uses its own .cocoapods folder to prevent resource conflicts</span>
      <span class="c1">#     mkdir -p "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/"</span>
      <span class="c1">#     export CP_HOME_DIR="$HOME/.cocoapods-${{ env.RUNNER_NAME }}"</span>
      <span class="c1">#     rm -f "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/repos/cocoapods/.git/index.lock"</span>

      <span class="c1"># ========== Cache Setting Steps ==========</span>
      <span class="c1"># Note: Even for self-hosted, cache is cloud cache and counts usage</span>
      <span class="c1"># Rules: auto delete after 7 days no hit, max 10 GB per cache, cache only on successful actions</span>
      <span class="c1"># Public Repo: free unlimited</span>
      <span class="c1"># Private Repo: from 5 GB</span>
      <span class="c1"># Self-hosted can implement own cache &amp; restore strategies via shell scripts or other tools</span>
      
      <span class="c1"># Bundle Cache (Gemfile)</span>
      <span class="c1"># Matches Makefile specifying Bundle install path ./vendor</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache Bundle</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">./vendor</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-bundle-${{ hashFiles('Gemfile.lock') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-bundle-</span>

      <span class="c1"># CocoaPods Cache (Podfile)</span>
      <span class="c1"># Default is project/Pods folder</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache CocoaPods</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">./Product/Pods</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-cocoapods-${{ hashFiles('Product/Podfile.lock') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-cocoapods-</span>

      <span class="c1"># Mint cache</span>
      <span class="c1"># Matches Makefile specifying Mint install path ./mint</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache Mint</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./mint</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-mint-${{ hashFiles('Mintfile') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-mint-</span>

      <span class="c1"># ====================</span>

      <span class="c1"># Project Setup &amp; Dependency Installation</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup &amp; Install Dependency</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># Run Makefile's setup command, roughly equivalent to</span><span class="err">:</span>
          <span class="c1"># brew install mint</span>
          <span class="c1"># bundle config set path 'vendor/bundle'</span>
          <span class="c1"># bundle install</span>
          <span class="c1"># mint bootstrap</span>
          <span class="c1"># ...</span>
          <span class="s">make setup</span>

          <span class="s"># Run Makefile's install command, roughly equivalent to</span><span class="err">:</span>
          <span class="c1"># mint run yonaskolb/XcodeGen --quiet</span>
          <span class="c1"># bundle exec pod install</span>
          <span class="c1"># ...</span>
          <span class="s">make install</span>

      <span class="c1"># Run Fastlane Unit Test Lane</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Run Tests</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">testing</span>
        <span class="c1"># Set working directory so no need to cd later</span>
        <span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Product/</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="c1"># Test plan: run all or only unit tests</span>
          <span class="c1"># If triggered by PR, use run_unit_tests; otherwise use inputs.TEST_LANE or default run_all_tests</span>
          <span class="na">TEST_LANE</span><span class="pi">:</span> <span class="s">${{ github.event_name == 'pull_request' &amp;&amp; 'run_unit_tests' \\|\\| github.event.inputs.TEST_LANE \\|\\| 'run_all_tests' }}</span>
          
          <span class="c1"># Use the Xcode version specified by XCode_x.x.x</span>
          <span class="na">DEVELOPER_DIR</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/Applications/Xcode_${{</span><span class="nv"> </span><span class="s">steps.read_xcode_version.outputs.xcode_version</span><span class="nv"> </span><span class="s">}}.app/Contents/Developer"</span>
          
          <span class="c1"># Repo -&gt; Settings -&gt; Actions secrets and variables -&gt; variables</span>
          <span class="c1"># Simulator name to use</span>
          <span class="na">SIMULATOR_NAME</span><span class="pi">:</span> <span class="s">${{ vars.SIMULATOR_NAME }}</span>
          <span class="c1"># Simulator iOS version</span>
          <span class="na">SIMULATOR_IOS_VERSION</span><span class="pi">:</span> <span class="s">${{ vars.SIMULATOR_IOS_VERSION }}</span>

          <span class="c1"># Current runner name</span>
          <span class="na">RUNNER_NAME</span><span class="pi">:</span> <span class="s">${{ runner.name }}</span>
          
          <span class="c1"># Increase Xcodebuild command timeout and retry count</span>
          <span class="c1"># Because machine load can cause failure after 3 tries</span>
          <span class="na">FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT</span><span class="pi">:</span> <span class="m">60</span>
          <span class="na">FASTLANE_XCODEBUILD_SETTINGS_RETRIES</span><span class="pi">:</span> <span class="m">10</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>

          <span class="s"># On self-hosted runners running multiple runners on same machine,</span>
          <span class="s"># there is a simulator contention issue (explained later)</span>
          <span class="s"># To avoid this, name simulators after runner name and assign one simulator per runner</span>
          <span class="s"># This prevents conflicts causing test failures</span>
          <span class="s"># e.g. bundle exec fastlane run_unit_tests device:"${RUNNER_NAME} (${SIMULATOR_IOS_VERSION})"</span>
          <span class="s"># Here using GitHub Hosted Runner, no such issue, so use device:"${SIMULATOR_NAME} (${SIMULATOR_IOS_VERSION})"</span>

          <span class="s"># Do not exit immediately on error; write all output to temp/testing_output.txt</span>
          <span class="s"># Later analyze file to distinguish Build Failed or Test Failed and comment accordingly on PR</span>
          <span class="s">set +e</span>
          
          <span class="s"># EXIT_CODE stores exit code of execution.</span>
          <span class="s"># 0 = OK</span>
          <span class="s"># 1 = exit</span>
          <span class="s">EXIT_CODE=0</span>
          
          <span class="s"># Write all output to file</span>
          <span class="s">bundle exec fastlane ${TEST_LANE} device:"${SIMULATOR_NAME} (${SIMULATOR_IOS_VERSION})" \\| tee "$RUNNER_TEMP/testing_output.txt"</span>
          <span class="s"># If current EXIT_CODE is 0, set EXIT_CODE to bundle exec fastlane command exit code</span>
          <span class="s">[[ $EXIT_CODE -eq 0 ]] &amp;&amp; EXIT_CODE=${PIPESTATUS[0]}</span>

          <span class="s"># Restore exit on error</span>
          <span class="s">set -e</span>

          <span class="s"># Check Testing Output</span>
          <span class="s"># If output contains "Error building", set is_build_error=true for Actions env (build failed)</span>
          <span class="s"># If output contains "Tests have failed", set is_test_error=true for Actions env (test failed)</span>
          
          <span class="s">if grep -q "Error building" "$RUNNER_TEMP/testing_output.txt"; then</span>
            <span class="s">echo "is_build_error=true" &gt;&gt; $GITHUB_OUTPUT</span>
            <span class="s">echo "❌ Detected Build Error"</span>
          <span class="s">elif grep -q "Tests have failed" "$RUNNER_TEMP/testing_output.txt"; then</span>
            <span class="s">echo "is_test_error=true" &gt;&gt; $GITHUB_OUTPUT</span>
            <span class="s">echo "❌ Detected Test Error"</span>
          <span class="s">fi</span>

          <span class="s"># Restore exit code output</span>
          <span class="s">exit $EXIT_CODE</span>
          
      <span class="c1"># ========== Handle Result Steps ==========</span>
      
      <span class="c1"># Parse *.junit test reports, mark results, comment (if PR)</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Publish Test Report</span>
        <span class="c1"># Reuse existing .junit Parser Action: https://github.com/mikepenz/action-junit-report</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">mikepenz/action-junit-report@v5</span>
        <span class="c1"># if:</span>
        <span class="c1"># Previous step (Testing) success or</span>
        <span class="c1"># Previous step (Testing) failed and is_test_error (skip if build failed)</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">${{ (failure() &amp;&amp; steps.testing.outputs.is_test_error == 'true') \\|\\| success() }}</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">check_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Testing</span><span class="nv"> </span><span class="s">Report"</span>
          <span class="na">comment</span><span class="pi">:</span> <span class="kc">true</span>
          <span class="na">updateComment</span><span class="pi">:</span> <span class="kc">false</span>
          <span class="na">require_tests</span><span class="pi">:</span> <span class="kc">true</span>
          <span class="na">detailed_summary</span><span class="pi">:</span> <span class="kc">true</span>
          <span class="na">report_paths</span><span class="pi">:</span> <span class="s2">"</span><span class="s">./Product/fastlane/test_output/*.junit"</span>

      <span class="c1"># Comment on build failure</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build Failure Comment</span>
        <span class="c1"># if:</span>
        <span class="c1"># Previous step (Testing) failed and is_build_error and PR number exists</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">${{ failure() &amp;&amp; steps.testing.outputs.is_build_error == 'true' &amp;&amp; github.event.pull_request.number }}</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v6</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">action_url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}/attempts/${{</span><span class="nv"> </span><span class="s">github.run_attempt</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">with</span><span class="pi">:</span>
            <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
              <span class="s">const action_url = process.env.action_url</span>
              <span class="s">const pullRequest = context.payload.pull_request \\|\\| {}</span>
              <span class="s">const commitSha = pullRequest.head?.sha \\|\\| context.sha</span>
              <span class="s">const creator = pullRequest.user?.login \\|\\| context.actor</span>
        
              <span class="s">const commentBody = [</span>
                <span class="s">`# Project or Test Build Failed ❌`,</span>
                <span class="s">`Please ensure your Pull Request compiles and runs tests correctly.`,</span>
                <span class="s">``,</span>
                <span class="s">`🔗 **Action**</span><span class="err">:</span> <span class="pi">[</span><span class="nv">View Workflow Run</span><span class="pi">]</span><span class="s">(${action_url})`,</span>
                <span class="s">`📝 **Commit**</span><span class="err">:</span> <span class="s">${commitSha}`,</span>
                <span class="s">`👤 **Author**</span><span class="err">:</span> <span class="err">@</span><span class="s">${creator}`</span>
              <span class="s">].join('\n')</span>
        
              <span class="s">await github.rest.issues.createComment({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">issue_number</span><span class="err">:</span> <span class="s">context.payload.pull_request.number,</span>
                <span class="s">body</span><span class="err">:</span> <span class="s">commentBody</span>
              <span class="s">})</span>
</code></pre></div></div>

<p><strong>Technical Highlights:</strong></p>

<ul>
  <li>
    <p>runs-on: It is recommended to use a self-hosted Runner, as <a href="https://docs.github.com/en/billing/managing-billing-for-your-products/about-billing-for-github-actions#per-minute-rates" target="_blank">GitHub Hosted Runner macOS is very expensive</a>.</p>
  </li>
  <li>
    <p>Manually read the <code class="language-plaintext highlighter-rouge">.xcode-version</code> file to get the specified Xcode version and set the <code class="language-plaintext highlighter-rouge">DEVELOPER_DIR</code> env in steps that require a specific Xcode. This allows easy Xcode switching without using Sudo.</p>
  </li>
  <li>
    <p>Cache: It can speed up dependency installation, but note that even self-hosted Runners still use GitHub Cloud Cache and are subject to billing limits.</p>
  </li>
  <li>
    <p>Use <code class="language-plaintext highlighter-rouge">set +e</code> so the script won’t exit immediately on command failure + redirect all output to a file + read the file to determine if it’s Build Failed or Test Failed; otherwise, the message will always show as Test Failed.<br />
You can also extend this to detect other errors, for example: <code class="language-plaintext highlighter-rouge">Underlying Error: Unable to boot the Simulator.</code> means the simulator failed to start; please try again.</p>
  </li>
  <li>
    <p>Checkout Code can accept a specified branch: Since the <code class="language-plaintext highlighter-rouge">on: schedule</code> event can only trigger on the main (Default Branch), if we want the schedule to operate on another branch, we need to specify the branch.</p>
  </li>
  <li>
    <p>Specifying the .cocoapods Repo path is optional. Previously, we encountered an issue where two Runners on the same self-hosted machine got stuck at pod install because both were operating on the .cocoapods Repo simultaneously, causing a git lock.<br />
(Though the chance of this happening is very low.)</p>
  </li>
  <li>
    <p><strong>If you have a Private Pods Repo that requires SSH Agent to have permission to clone.</strong><br />
<strong>(Please refer to the supplement at the end)</strong></p>
  </li>
  <li>
    <p>Remember to add the following in Repo -&gt; Settings -&gt; Actions secrets and variables -&gt; variables:<br />
<code class="language-plaintext highlighter-rouge">SIMULATOR_IOS_VERSION</code> iOS version for the simulator<br />
<code class="language-plaintext highlighter-rouge">SIMULATOR_NAME</code> name of the simulator</p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*Vj37P9vZ6KL5wWNvU3Cq3Q.webp" alt="" loading="lazy" decoding="async" width="1156" height="549" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTU2IiBoZWlnaHQ9IjU0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Vj37P9vZ6KL5wWNvU3Cq3Q.png" /></p>

<p><strong>Commit files to the repo main branch and manually trigger a verification:</strong></p>

<p><img src="/assets/4b001d2e8440/1*tA2nKehTJ7aURSGgU7MM0g.webp" alt="" loading="lazy" decoding="async" width="1200" height="413" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQxMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*tA2nKehTJ7aURSGgU7MM0g.png" /></p>

<p><img src="/assets/4b001d2e8440/1*Qap3KmhIJrLov2O7GneV3w.webp" alt="" loading="lazy" decoding="async" width="1031" height="910" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDMxIiBoZWlnaHQ9IjkxMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Qap3KmhIJrLov2O7GneV3w.png" /></p>

<p>Continue with the correct subsequent settings.</p>

<h4 id="github-workflow-configuration">GitHub Workflow Configuration</h4>

<p>Repo → Settings → Rules → Rulesets.</p>

<p><img src="/assets/4b001d2e8440/1*cf_M6NnakdcbzvC2VHSfpw.webp" alt="" loading="lazy" decoding="async" width="1202" height="964" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAyIiBoZWlnaHQ9Ijk2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*cf_M6NnakdcbzvC2VHSfpw.png" /></p>

<ul>
  <li>
    <p>Ruleset Name: Ruleset Name</p>
  </li>
  <li>
    <p>Enforcement status: Enable/Disable this rule restriction</p>
  </li>
  <li>
    <p>Target branches: The target base branches. Setting the Default Branch means all branches merging into main or develop are subject to this rule.</p>
  </li>
  <li>
    <p>Bypass list: You can specify special identities or Teams to be exempt from this limit</p>
  </li>
  <li>
    <p>Branch rules:</p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*Hzd9CCTsu18kyDR1goRWag.webp" alt="" loading="lazy" decoding="async" width="792" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Hzd9CCTsu18kyDR1goRWag.png" /></p>

<ul>
  <li>
    <p>Restrict deletions: Prohibit branch deletion</p>
  </li>
  <li>
    <p>Require a pull request before merging: Only allow merging through PR Merge<br />
Required approvals: Limit the number of required approvals</p>
  </li>
  <li>
    <p>Require status checks to pass: Restrict which checks must pass before merging<br />
Click + Add checks, type <code class="language-plaintext highlighter-rouge">Testing</code>, and select the one with the GitHub Actions icon.<br />
<strong>There is a small issue here: if Suggestions</strong> <strong>can’t find <code class="language-plaintext highlighter-rouge">Testing</code>, you need to go back to Actions and trigger it once (try opening a PR) so it appears here.</strong></p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*MRPwUEVVbbz8nH0sS2hFfQ.webp" alt="" loading="lazy" decoding="async" width="834" height="394" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MzQiIGhlaWdodD0iMzk0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*MRPwUEVVbbz8nH0sS2hFfQ.png" /></p>

<ul>
  <li>Block force pushes: Prevent force pushes</li>
</ul>

<p>After saving and confirming the Enforcement status is Active, the rule will take effect.</p>

<p><strong>After everything is set up, open a PR to test it:</strong></p>

<p><img src="/assets/4b001d2e8440/1*iPETBWx6Boq12rY1fHXmuQ.webp" alt="" loading="lazy" decoding="async" width="919" height="546" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTkiIGhlaWdodD0iNTQ2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*iPETBWx6Boq12rY1fHXmuQ.png" /></p>

<ul>
  <li>If you see CI-Testing ( <strong>Required</strong> ), merging is blocked, and at least X approving reviews are required by reviewers with write access in Checks, it means the setup is successful.</li>
</ul>

<p><strong>If the project build fails (Build Failed), it will Comment:</strong></p>

<p><img src="/assets/4b001d2e8440/1*4E35VRPo--pI8uqrR4UZNA.webp" alt="" loading="lazy" decoding="async" width="915" height="295" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTUiIGhlaWdodD0iMjk1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*4E35VRPo--pI8uqrR4UZNA.png" /></p>

<p><strong>If the project builds successfully but test cases fail (Test Failed), it will Comment:</strong></p>

<p><img src="/assets/4b001d2e8440/1*wYFzsn5a_yvi8CbZxi-QdQ.webp" alt="" loading="lazy" decoding="async" width="921" height="389" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjEiIGhlaWdodD0iMzg5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*wYFzsn5a_yvi8CbZxi-QdQ.png" /></p>

<p><strong>If the project build and tests succeed (Test Success), it will Comment:</strong></p>

<p><img src="/assets/4b001d2e8440/1*WqPK1629jYdYTEWCWBHWqg.webp" alt="" loading="lazy" decoding="async" width="915" height="321" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTUiIGhlaWdodD0iMzIxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*WqPK1629jYdYTEWCWBHWqg.png" /></p>

<p><strong>After completing Review Approve + passing Check tests:</strong></p>

<p><img src="/assets/4b001d2e8440/1*5gnQYdVAOtGR-bMK4ZrOhA.webp" alt="Demo PR" loading="lazy" decoding="async" width="911" height="566" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTEiIGhlaWdodD0iNTY2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*5gnQYdVAOtGR-bMK4ZrOhA.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<p>then you can merge the PR.</p>

<ul>
  <li>If there is a Push New Commit, the Checks tests will automatically rerun.</li>
</ul>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/CI-Testing.yml" target="_blank">CI-Testing.yml</a></strong></p>

<p><strong>Auto-merge:</strong></p>

<p>Additionally, you can enable the following in Repo Settings → General → Pull Request:</p>

<p><img src="/assets/4b001d2e8440/1*hl6mfqnnv_zAA3YVrI_M0w.webp" alt="" loading="lazy" decoding="async" width="856" height="257" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NTYiIGhlaWdodD0iMjU3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*hl6mfqnnv_zAA3YVrI_M0w.png" /></p>

<ul>
  <li>
    <p>Automatically delete head branches: Automatically delete branches after merging PRs</p>
  </li>
  <li>
    <p>Allow Auto-merge: When checks pass and the required approvals are met, the PR will be automatically merged.<br />
<strong>The Enable auto-merge button only appears if conditions are set and the current conditions do not yet allow merging.</strong></p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*gi0LaaMVXit-DGKnsxS1lQ.webp" alt="" loading="lazy" decoding="async" width="927" height="431" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjciIGhlaWdodD0iNDMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*gi0LaaMVXit-DGKnsxS1lQ.png" /></p>

<h3 id="cd--build-and-deploy-to-firebase-app-distribution">CD — Build and Deploy to Firebase App Distribution</h3>

<h4 id="workflow-2">Workflow</h4>

<p>Using GitHub Actions form to trigger the build job allows specifying the version number and Release Notes. After building, the app is automatically uploaded to Firebase App Distribution for the team to download and test.</p>

<h4 id="cd-deployyml">CD-Deploy.yml</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow(Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">CD-Deploy</span>

<span class="c1"># Actions Log title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[CD-Deploy]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="c1"># Cancel running jobs in the same concurrency group if a new job starts</span>
<span class="c1"># For example, if the same branch's packaging job is triggered repeatedly, the previous job will be canceled</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Manual trigger via form</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="c1"># Form inputs</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># App version number</span>
      <span class="na">VERSION_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Version</span><span class="nv"> </span><span class="s">Number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1.0.0).</span><span class="nv"> </span><span class="s">Auto-detect</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">Xcode</span><span class="nv"> </span><span class="s">project</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App Build Number</span>
      <span class="na">BUILD_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Build</span><span class="nv"> </span><span class="s">number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1).</span><span class="nv"> </span><span class="s">Will</span><span class="nv"> </span><span class="s">use</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">timestamp</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App Release Note</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Release</span><span class="nv"> </span><span class="s">notes</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">deployment.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
  <span class="c1"># Triggered by other workflows calling this workflow</span>
  <span class="c1"># Used by Nightly Build</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># App version number</span>
      <span class="na">VERSION_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Version</span><span class="nv"> </span><span class="s">Number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1.0.0).</span><span class="nv"> </span><span class="s">Auto-detect</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">Xcode</span><span class="nv"> </span><span class="s">project</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App Build Number</span>
      <span class="na">BUILD_NUMBER</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Build</span><span class="nv"> </span><span class="s">number</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">(e.g.,</span><span class="nv"> </span><span class="s">1).</span><span class="nv"> </span><span class="s">Will</span><span class="nv"> </span><span class="s">use</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">timestamp</span><span class="nv"> </span><span class="s">if</span><span class="nv"> </span><span class="s">left</span><span class="nv"> </span><span class="s">blank.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># App Release Note</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Release</span><span class="nv"> </span><span class="s">notes</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">deployment.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="na">BRANCH</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Branch'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>


<span class="c1"># Define global static variables</span>
<span class="na">env</span><span class="pi">:</span>
  <span class="na">APP_STORE_CONNECT_API_KEY_FILE_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">app_store_connect_api_key.json"</span>

<span class="c1"># Job definitions</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="c1"># Job name (optional, improves log readability)</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy - Firebase App Distribution</span>
    
    <span class="c1"># Runner Label - use GitHub Hosted Runner macos-15 to run the job</span>
    <span class="c1"># Note: This project is a Public Repo and can use unlimited free minutes</span>
    <span class="c1"># Note: This project is a Public Repo and can use unlimited free minutes</span>
    <span class="c1"># Note: This project is a Public Repo and can use unlimited free minutes</span>
    <span class="c1"># For Private Repos, billing applies; macOS runners are the most expensive (10x),</span>
    <span class="c1"># running 10 times can reach the 2,000 free minutes limit</span>
    <span class="c1"># Self-hosted Runner is recommended</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">macos-15</span>

    <span class="c1"># Set maximum timeout to prevent endless waiting on errors</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">30</span>

    <span class="c1"># use zsh</span>
    <span class="c1"># Optional, just my preference; default is bash</span>
    <span class="na">defaults</span><span class="pi">:</span>
      <span class="na">run</span><span class="pi">:</span>
        <span class="na">shell</span><span class="pi">:</span> <span class="s">zsh {0}</span>

    <span class="c1"># Job steps</span>
    <span class="c1"># Steps execute sequentially</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># git clone current repo &amp; checkout the running branch</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout repository</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="c1"># Git Large File Storage, not needed in our test environment</span>
          <span class="c1"># default: false</span>
          <span class="na">lfs</span><span class="pi">:</span> <span class="kc">false</span>
          
          <span class="c1"># Checkout specified branch if provided, else default (current branch)</span>
          <span class="c1"># Because on: schedule event only runs on main branch, to do Nightly Build etc. we need to specify branch</span>
          <span class="c1"># e.g. on: schedule -&gt; main branch, Nightly Build master branch</span>
          <span class="na">ref</span><span class="pi">:</span> <span class="s">${{ github.event.inputs.BRANCH \\|\\| '' }}</span>

      <span class="c1"># ========== Certificates Steps ==========</span>
      
      <span class="c1"># Recommended to use Fastlane - Match to manage development certificates and run match in lanes</span>
      <span class="c1"># Match uses another private repo to manage certificates, requires SSH Agent setup for access</span>
      <span class="c1"># ref: https://stackoverflow.com/questions/57612428/cloning-private-github-repository-within-organisation-in-actions</span>
      <span class="c1">#</span>
      <span class="c1">#</span>
      <span class="c1"># --- Below is the approach without using Fastlane - Match, directly download &amp; import certificates to runner ---</span>
      <span class="c1"># ref: https://docs.github.com/en/actions/how-tos/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development</span>
      <span class="c1">#</span>
      <span class="c1"># GitHub Actions Secrets cannot store files, so all certificates must be Base64 encoded and stored as text secrets</span>
      <span class="c1"># In the step, decode and write to TEMP files, then move to correct paths for system use</span>
      <span class="c1"># See article for more details</span>
      <span class="c1">#</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install the Apple certificate and provisioning profile</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">BUILD_CERTIFICATE_BASE64</span><span class="pi">:</span> <span class="s">${{ secrets.BUILD_CERTIFICATE_BASE64 }}</span>
          <span class="na">P12_PASSWORD</span><span class="pi">:</span> <span class="s">${{ secrets.BUILD_CERTIFICATE_P12_PASSWORD }}</span>
          <span class="na">BUILD_PROVISION_PROFILE_BASE64</span><span class="pi">:</span> <span class="s">${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}</span>
          <span class="c1"># GitHub Hosted Runner: custom string</span>
          <span class="c1"># Self-hosted Runner: machine login password</span>
          <span class="na">KEYCHAIN_PASSWORD</span><span class="pi">:</span> <span class="s">${{ secrets.KEYCHAIN_PASSWORD }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># create variables</span>
          <span class="s">CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12</span>
          <span class="s">PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision</span>
          <span class="s">KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db</span>

          <span class="s"># import certificate and provisioning profile from secrets</span>
          <span class="s">echo -n "$BUILD_CERTIFICATE_BASE64" \\| base64 --decode -o $CERTIFICATE_PATH</span>
          <span class="s">echo -n "$BUILD_PROVISION_PROFILE_BASE64" \\| base64 --decode -o $PP_PATH</span>

          <span class="s"># create temporary keychain</span>
          <span class="s">security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH</span>
          <span class="s">security set-keychain-settings -lut 21600 $KEYCHAIN_PATH</span>
          <span class="s">security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH</span>

          <span class="s"># import certificate to keychain</span>
          <span class="s">security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH</span>
          <span class="s">security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH</span>
          <span class="s">security list-keychain -d user -s $KEYCHAIN_PATH</span>

          <span class="s"># apply provisioning profile</span>
          <span class="s">mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles</span>
          <span class="s">cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles</span>

      <span class="c1"># App Store Connect API Fastlane JSON Key</span>
      <span class="c1"># Another almost essential item in packaging environment: App Store Connect API Fastlane JSON Key (.json)</span>
      <span class="c1"># format: .json content format: https://docs.fastlane.tools/app-store-connect-api/</span>
      <span class="c1"># Contains App Store Connect API .p8 Key</span>
      <span class="c1"># Passed to Fastlane later for uploading to Testflight, App Store API usage</span>
      <span class="c1">#</span>
      <span class="c1"># GitHub Actions Secrets cannot store files, so all certificates must be Base64 encoded and stored as text secrets</span>
      <span class="c1"># Decode and write to TEMP file in the step for other steps to use</span>
      <span class="c1"># See article for details</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Read and Write Apple Store Connect API Key to Temp</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">APP_STORE_CONNECT_API_KEY_BASE64</span><span class="pi">:</span> <span class="s">${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }}</span>
          <span class="na">APP_STORE_CONNECT_API_KEY_PATH</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">runner.temp</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">env.APP_STORE_CONNECT_API_KEY_FILE_NAME</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># import certificate and provisioning profile from secrets</span>
          <span class="s">echo -n "$APP_STORE_CONNECT_API_KEY_BASE64" \\| base64 --decode -o $APP_STORE_CONNECT_API_KEY_PATH</span>

      <span class="c1"># ========== Env Setup Steps ==========</span>
      
      <span class="c1"># Read project specified Xcode version</span>
      <span class="c1"># Later we manually specify the Xcode_x.x.x.app to use</span>
      <span class="c1"># Not using xcversion because it is deprecated and unstable</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Read .xcode-version</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">read_xcode_version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">XCODE_VERSION=$(cat .xcode-version)</span>
          <span class="s">echo "XCODE_VERSION</span><span class="err">:</span> <span class="s">${XCODE_VERSION}"</span>
          <span class="s">echo "xcode_version=${XCODE_VERSION}" &gt;&gt; $GITHUB_OUTPUT</span>

          <span class="s"># You can also directly specify global Xcode version here to avoid setting DEVELOPER_DIR later</span>
          <span class="s"># But this command requires sudo privileges; if self-hosted runner, ensure runner has sudo rights</span>
          <span class="s"># sudo xcode-select -s "/Applications/Xcode_${XCODE_VERSION}.app/Contents/Developer"</span>

      <span class="c1"># Read project specified Ruby version</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Read .ruby-version</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">read_ruby_version</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">RUBY_VERSION=$(cat .ruby-version)</span>
          <span class="s">echo "RUBY_VERSION</span><span class="err">:</span> <span class="s">${RUBY_VERSION}"</span>
          <span class="s">echo "ruby_version=${RUBY_VERSION}" &gt;&gt; $GITHUB_OUTPUT</span>

      <span class="c1"># Install or set runner Ruby version to project specified version</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Ruby</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">ruby/setup-ruby@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">ruby-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">steps.read_ruby_version.outputs.ruby_version</span><span class="nv"> </span><span class="s">}}"</span>

      <span class="c1"># Optional: previously when running multiple self-hosted runners on same machine,</span>
      <span class="c1"># CocoaPods repos shared directory caused rare conflicts during simultaneous pod install</span>
      <span class="c1"># GitHub Hosted Runner does not need this setting</span>
      <span class="c1"># - name: Change Cocoapods Repos Folder</span>
      <span class="c1">#   if: contains(runner.labels, 'self-hosted')</span>
      <span class="c1">#   run: \\|</span>
      <span class="c1">#     # Each runner uses its own .cocoapods folder to prevent conflicts</span>
      <span class="c1">#     mkdir -p "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/"</span>
      <span class="c1">#     export CP_HOME_DIR="$HOME/.cocoapods-${{ env.RUNNER_NAME }}"</span>
      <span class="c1">#     rm -f "$HOME/.cocoapods-${{ env.RUNNER_NAME }}/repos/cocoapods/.git/index.lock"</span>

      <span class="c1"># ========== Cache Setting Steps ==========</span>
      <span class="c1"># Note: Even for self-hosted, Cache is cloud-based and usage counts</span>
      <span class="c1"># Rules: auto delete after 7 days no hits, max 10 GB per cache, cache only on successful actions</span>
      <span class="c1"># Public Repo: free unlimited</span>
      <span class="c1"># Private Repo: starts at 5 GB</span>
      <span class="c1"># Self-hosted can implement own cache &amp; restore strategy via shell scripts or other tools</span>
      
      <span class="c1"># Bundle Cache (Gemfile)</span>
      <span class="c1"># Corresponds to Makefile specifying Bundle install path ./vendor</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache Bundle</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">./vendor</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-bundle-${{ hashFiles('Gemfile.lock') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-bundle-</span>

      <span class="c1"># CocoaPods Cache (Podfile)</span>
      <span class="c1"># Default is project/Pods</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache CocoaPods</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">./Product/Pods</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-cocoapods-${{ hashFiles('Product/Podfile.lock') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-cocoapods-</span>

      <span class="c1"># Mint cache</span>
      <span class="c1"># Corresponds to Makefile specifying Mint install path ./mint</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache Mint</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./mint</span>
          <span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-mint-${{ hashFiles('Mintfile') }}</span>
          <span class="na">restore-keys</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">${{ runner.os }}-mint-</span>

      <span class="c1"># ====================</span>

      <span class="c1"># Project Setup &amp; Dependency Installation</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup &amp; Install Dependency</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># Run Makefile encapsulated Setup command, roughly</span><span class="err">:</span>
          <span class="c1"># brew install mint</span>
          <span class="c1"># bundle config set path 'vendor/bundle'</span>
          <span class="c1"># bundle install</span>
          <span class="c1"># mint bootstrap</span>
          <span class="c1"># ...</span>
          <span class="c1"># other setup commands</span>
          <span class="s">make setup</span>

          <span class="s"># Run Makefile encapsulated Install command, roughly</span><span class="err">:</span>
          <span class="c1"># mint run yonaskolb/XcodeGen --quiet</span>
          <span class="c1"># bundle exec pod install</span>
          <span class="c1"># ...</span>
          <span class="c1"># other install commands</span>
          <span class="s">make install</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Beta</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">deploy</span>
        <span class="c1"># Set working directory so later commands don't need cd ./Product/</span>
        <span class="na">working-directory</span><span class="pi">:</span> <span class="s">./Product/</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="c1"># Packaging input parameters</span>
          <span class="na">VERSION_NUMBER</span><span class="pi">:</span> <span class="s">${{ inputs.VERSION_NUMBER \\|\\| '' }}</span>
          <span class="na">BUILD_NUMBER</span><span class="pi">:</span> <span class="s">${{ inputs.BUILD_NUMBER \\|\\| '' }}</span>
          <span class="na">RELEASE_NOTE</span><span class="pi">:</span> <span class="s">${{ inputs.RELEASE_NOTE \\|\\| '' }}</span>
          <span class="na">AUTHOR</span><span class="pi">:</span> <span class="s">${{ github.actor }}</span>

          <span class="c1"># Repo -&gt; Settings -&gt; Actions secrets and variables -&gt; secrets</span>
          <span class="c1"># Firebase CLI Token (see article for how to get)</span>
          <span class="na">FIREBASE_CLI_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.FIREBASE_CLI_TOKEN }}</span>
          <span class="c1"># Apple Developer Program Team ID</span>
          <span class="na">TEAM_ID</span><span class="pi">:</span> <span class="s">${{ secrets.TEAM_ID }}</span>
                    
          <span class="c1"># Specify this job to use XCode_x.x.x version</span>
          <span class="na">DEVELOPER_DIR</span><span class="pi">:</span> <span class="s2">"</span><span class="s">/Applications/Xcode_${{</span><span class="nv"> </span><span class="s">steps.read_xcode_version.outputs.xcode_version</span><span class="nv"> </span><span class="s">}}.app/Contents/Developer"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># Get current timestamp</span>
          <span class="s">BUILD_TIMESTAMP=$(date +'%Y%m%d%H%M%S')</span>

          <span class="s"># If BUILD_NUMBER is empty, use timestamp as app build number</span>
          <span class="s">BUILD_NUMBER="${BUILD_NUMBER:-$BUILD_TIMESTAMP}"</span>
  
          <span class="s">ID="${{ github.run_id }}"</span>
          <span class="s">COMMIT_SHA="${{ github.sha }}"</span>
          <span class="s">BRANCH_NAME="${{ github.ref_name }}"</span>
          <span class="s">AUTHOR="${{ env.AUTHOR }}"</span>

          <span class="s"># Compose Release Note</span>
          <span class="s">RELEASE_NOTE="${{ env.RELEASE_NOTE }}</span>
          <span class="s">ID</span><span class="err">:</span> <span class="s">${ID}</span>
          <span class="s">Commit SHA</span><span class="err">:</span> <span class="s">${COMMIT_SHA}</span>
          <span class="s">Branch</span><span class="err">:</span> <span class="s">${BRANCH_NAME}</span>
          <span class="s">Author</span><span class="err">:</span> <span class="s">${AUTHOR}</span>
          <span class="s">"</span>

          <span class="s"># Run Fastlane lane for packaging &amp; deployment</span>
          <span class="s">bundle exec fastlane beta release_notes:"${RELEASE_NOTE}" version_number:"${VERSION_NUMBER}" build_number:"${BUILD_NUMBER}"</span>

      <span class="c1"># GitHub Actions recommended self-hosted security cleanup:</span>
      <span class="c1"># ref: https://docs.github.com/en/actions/how-tos/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development#required-clean-up-on-self-hosted-runners</span>
      <span class="c1"># Corresponds to Step: Install the Apple certificate and provisioning profile</span>
      <span class="c1"># Purpose: delete downloaded keychain certificates on machine</span>
      <span class="c1"># If using Match, rewrite to Match's clean</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Clean up keychain and provisioning profile</span>
        <span class="na">if</span><span class="pi">:</span> <span class="s">${{ always() &amp;&amp; contains(runner.labels, 'self-hosted') }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">security delete-keychain $RUNNER_TEMP/app-signing.keychain-db</span>
          <span class="s">rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision</span>
</code></pre></div></div>

<ul>
  <li>Remember to add a <code class="language-plaintext highlighter-rouge">TEAM_ID</code> secret in Repo -&gt; Settings -&gt; Actions secrets and variables -&gt; secrets, containing your Apple Developer Team ID string.</li>
</ul>

<p><img src="/assets/4b001d2e8440/1*dxdmT_N_w_VUd56f6GRLSQ.webp" alt="" loading="lazy" decoding="async" width="1181" height="498" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTgxIiBoZWlnaHQ9IjQ5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*dxdmT_N_w_VUd56f6GRLSQ.png" /></p>

<p><strong>Commit files to the repo’s main branch to test the build function:</strong></p>

<p><img src="/assets/4b001d2e8440/1*Z_UAfWAsJSIoWxeTRMvtDQ.webp" alt="" loading="lazy" decoding="async" width="1072" height="685" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDcyIiBoZWlnaHQ9IjY4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Z_UAfWAsJSIoWxeTRMvtDQ.png" /></p>

<blockquote>
  <p><em>Please note that if other branches want to use this Action, they need to first merge the CD-Deploy.yml file from the main branch.</em></p>
</blockquote>

<p><strong>Waiting for the task to complete:</strong></p>

<p><img src="/assets/4b001d2e8440/1*Q-c2IUlJpssooiqcqcm_Bg.webp" alt="" loading="lazy" decoding="async" width="1386" height="1288" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzg2IiBoZWlnaHQ9IjEyODgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4b001d2e8440/1*Q-c2IUlJpssooiqcqcm_Bg.png" /></p>

<p><img src="/assets/4b001d2e8440/1*W8PBkatfsITMDFSlp7xpjg.webp" alt="Demo" loading="lazy" decoding="async" width="1200" height="975" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*W8PBkatfsITMDFSlp7xpjg.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/runs/16114046420" target="_blank">Demo</a></p>

<blockquote>
  <p><strong><em>Build + Deployment Successful ✅</em></strong></p>
</blockquote>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/CD-Deploy.yml" target="_blank">CD-Deploy.yml</a></strong></p>

<h4 id="technical-details--obtaining-and-setting-firebase-cli-token">Technical Details — Obtaining and Setting Firebase CLI Token</h4>

<p>According to the <a href="https://firebase.google.com/docs/cli?hl=zh-tw#install-cli-mac-linux" target="_blank">Firebase official documentation steps</a>:</p>

<p>First, install the Firebase CLI tool:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-sL</span> https://firebase.tools <span class="se">\\</span>| bash
</code></pre></div></div>

<p>Execute:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>firebase login:ci
</code></pre></div></div>

<p>Complete login and authorization:</p>

<p><img src="/assets/4b001d2e8440/1*Jex5aYzRSs7Trfx6z_e1Aw.webp" alt="" loading="lazy" decoding="async" width="1063" height="701" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDYzIiBoZWlnaHQ9IjcwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Jex5aYzRSs7Trfx6z_e1Aw.png" /></p>

<p><img src="/assets/4b001d2e8440/1*S-1ckn9WC6j0DIqZGSdUUg.webp" alt="" loading="lazy" decoding="async" width="427" height="213" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MjciIGhlaWdodD0iMjEzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*S-1ckn9WC6j0DIqZGSdUUg.png" /></p>

<p>Back to Terminal, copy the Firebase CLI Token:</p>

<p><img src="/assets/4b001d2e8440/1*sa_h8L08JbeC2nA0kACQtQ.webp" alt="" loading="lazy" decoding="async" width="682" height="483" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODIiIGhlaWdodD0iNDgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*sa_h8L08JbeC2nA0kACQtQ.png" /></p>

<p>Go to Repo → Settings → Secrets and variables → Actions → add a new Secret: <code class="language-plaintext highlighter-rouge">FIREBASE_CLI_TOKEN</code> and paste the Firebase CLI Token.</p>

<p><img src="/assets/4b001d2e8440/1*Lc6yGPu7L_0z6u1HH2PB5A.webp" alt="" loading="lazy" decoding="async" width="1171" height="515" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTcxIiBoZWlnaHQ9IjUxNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Lc6yGPu7L_0z6u1HH2PB5A.png" /></p>

<blockquote>
  <p><strong><em>This Token = Your login identity</em></strong> <em>Please keep it safe. If the account holder leaves, it must be replaced.</em></p>
</blockquote>

<h4 id="technical-details--install-the-apple-certificate-and-provisioning-profile">Technical Details — Install the Apple certificate and provisioning profile</h4>

<p>Additional steps for importing development certificates into the Runner.</p>

<p>Since GitHub Actions Secrets cannot store files, all certificate files must first be converted to Base64 encoded text and stored in Secrets. During the GitHub Actions step, the text is dynamically read, written to a TEMP file, and then moved to the correct location for the system to access.</p>

<p><strong>Packaging Development requires two key certificates:</strong></p>

<ul>
  <li>
    <p><a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Provision Profile (.mobileprovision)</a></p>
  </li>
  <li>
    <p><a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Development Certificate (.p12)</a></p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*WXqqnErto3nn8rnNg6TXgw.webp" alt="cicd.mobileprovision" loading="lazy" decoding="async" width="1117" height="616" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE3IiBoZWlnaHQ9IjYxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*WXqqnErto3nn8rnNg6TXgw.png" /></p>

<p>cicd.mobileprovision</p>

<p><img src="/assets/4b001d2e8440/1*RkeZ1PkXeY9Nt1kSRJKEQw.webp" alt="development.cer" loading="lazy" decoding="async" width="1078" height="738" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDc4IiBoZWlnaHQ9IjczOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*RkeZ1PkXeY9Nt1kSRJKEQw.png" /></p>

<p>development.cer</p>

<p>The Certificate downloaded from <a href="https://developer.apple.com/account/resources/certificates/list" target="_blank">Apple Developer</a> is in .cer format, but we need the .p12 format. You can double-click the downloaded .cer file to install it into Keychain, then open Keychain, right-click the certificate, and choose Export.</p>

<p><img src="/assets/4b001d2e8440/1*HJMxwM3IDjxT-UGqnoUtWw.webp" alt="" loading="lazy" decoding="async" width="924" height="526" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjQiIGhlaWdodD0iNTI2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*HJMxwM3IDjxT-UGqnoUtWw.png" /></p>

<p>File name: cicd.p12, format .p12</p>

<p>P12 Key Password: Enter a secure custom string (the example is a bad practice, using <code class="language-plaintext highlighter-rouge">123456</code>)</p>

<p><img src="/assets/4b001d2e8440/1*tyH0XqDVPGPWFJPL4dxksw.webp" alt="" loading="lazy" decoding="async" width="572" height="356" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzIiIGhlaWdodD0iMzU2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*tyH0XqDVPGPWFJPL4dxksw.png" /></p>

<p><img src="/assets/4b001d2e8440/1*qKeZWel3w_5wW7meMtHmLA.webp" alt="" loading="lazy" decoding="async" width="556" height="381" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTYiIGhlaWdodD0iMzgxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*qKeZWel3w_5wW7meMtHmLA.png" /></p>

<p><strong>Now two files are ready:</strong> cicd.p12 and cicd.mobileprovision</p>

<p><strong>Convert to BASE64 string and save to Repo Secrets:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">base64</span> <span class="nt">-i</span> cicd.mobileprovision <span class="se">\\</span>| pbcopy
</code></pre></div></div>

<p>Go to Repo → Settings → Secrets and variables → Actions → Add a new Secret: <code class="language-plaintext highlighter-rouge">BUILD_PROVISION_PROFILE_BASE64</code> and paste the above content.</p>

<p>-</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">base64</span> <span class="nt">-i</span> cicd.p12 <span class="se">\\</span>| pbcopy
</code></pre></div></div>

<p>Go to Repo → Settings → Secrets and variables → Actions → add a new Secret: <code class="language-plaintext highlighter-rouge">BUILD_CERTIFICATE_BASE64</code> and paste the above content.</p>

<p>-</p>

<p>Go to Repo → Settings → Secrets and variables → Actions → add a new Secret: <code class="language-plaintext highlighter-rouge">P12_PASSWORD</code> with the password used when exporting the P12 key.</p>

<p>-
Go to Repo → Settings → Secrets and variables → Actions → Add a new Secret: <code class="language-plaintext highlighter-rouge">KEYCHAIN_PASSWORD</code>:
If using GitHub Hosted Runner, enter any random string. <strong>If using a Self-hosted Runner, this should be the macOS Runner user’s login password</strong>.</p>

<p><img src="/assets/4b001d2e8440/1*JZCkFUJCQsggqYtW8acjTw.webp" alt="" loading="lazy" decoding="async" width="1088" height="493" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDg4IiBoZWlnaHQ9IjQ5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*JZCkFUJCQsggqYtW8acjTw.png" /></p>

<h4 id="technical-details--app-store-connect-api-key">Technical Details — App Store Connect API Key</h4>

<p>Fastlane packaging and deployment to App Store, Testflight require the <a href="https://docs.fastlane.tools/app-store-connect-api/" target="_blank">mandatory .json key</a>. Since GitHub Actions Secrets can only store strings, not files, we convert the key content into a Base64 string. Then, in a GitHub Actions step, we dynamically read and write it into a TEMP file and provide the file path for Fastlane to use.</p>

<p><strong>First, go to <a href="/posts/zrealm-dev/app-store-connect-api-manage-customer-reviews-and-subscriptions-efficiently-f1365e51902c/">App Store Connect to create &amp; download the App Store Connect API Key</a> (.p8):</strong></p>

<pre><code class="language-vbnet">-----BEGIN PRIVATE KEY-----
sss
axzzvcxz
zxzvzcxv
vzxcvzxvczxcvz
-----END PRIVATE KEY-----
</code></pre>

<p>Add a new <code class="language-plaintext highlighter-rouge">app_store_connect_api.json</code> file ( <a href="https://docs.fastlane.tools/app-store-connect-api/" target="_blank">reference content</a> ):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">{</span>
  <span class="s2">"key_id"</span>: <span class="s2">"The Key ID from App Store Connect"</span>,
  <span class="s2">"issuer_id"</span>: <span class="s2">"The Issuer ID from App Store Connect"</span>,
  <span class="s2">"key"</span>: <span class="s2">"-----BEGIN PRIVATE KEY-----Remember to replace line breaks with </span><span class="se">\n</span><span class="s2">-----END PRIVATE KEY-----"</span>,
  <span class="s2">"duration"</span>: 1200, <span class="c"># optional (maximum 1200)</span>
  <span class="s2">"in_house"</span>: <span class="nb">false</span> <span class="c"># optional but may be required if using match/sigh</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Execute after saving the file:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">base64</span> <span class="nt">-i</span> app_store_connect_api.json <span class="se">\\</span>| pbcopy
</code></pre></div></div>

<p>Paste the string content into Repo → Settings → Secrets and variables → Actions → Add a new Secret: <code class="language-plaintext highlighter-rouge">APP_STORE_CONNECT_API_KEY_BASE64</code> and paste the above content.</p>

<p><img src="/assets/4b001d2e8440/1*QxRuxEPEfWbJ383hhnxGkA.webp" alt="" loading="lazy" decoding="async" width="1159" height="549" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTU5IiBoZWlnaHQ9IjU0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*QxRuxEPEfWbJ383hhnxGkA.png" /></p>

<p>After the <code class="language-plaintext highlighter-rouge">Read and Write Apple Store Connect API Key to Temp</code> step is completed, just pass the env <code class="language-plaintext highlighter-rouge">APP_STORE_CONNECT_API_KEY_PATH</code> in the following steps:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy</span>
  <span class="na">env</span><span class="pi">:</span>
    <span class="na">APP_STORE_CONNECT_API_KEY_PATH</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">runner.temp</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">env.APP_STORE_CONNECT_API_KEY_FILE_NAME</span><span class="nv"> </span><span class="s">}}"</span>
  <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
    <span class="s">....</span>
</code></pre></div></div>

<p>Fastlane can automatically obtain and use it.</p>

<h4 id="technical-extension--reuse-action-workflow-to-separate-build-and-deploy-steps">Technical Extension — Reuse Action Workflow to Separate Build and Deploy Steps</h4>

<p>In this case, we directly use the Fastlane <code class="language-plaintext highlighter-rouge">beta</code> lane to perform both packaging and deployment.</p>

<p>In real cases, we may need to deploy the same build to different platforms (Firebase, Testflight, etc.). Therefore, a better approach is to have packaging as one Action and deployment as another Action, to avoid running the build twice. This also aligns better with the responsibility division in CI/CD.</p>

<blockquote>
  <p><strong><em>The following is an example introduction:</em></strong></p>
</blockquote>

<p><strong>CI-Build.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Build</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">main</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
     <span class="na">inputs</span><span class="pi">:</span>
        <span class="na">RELEASE_NOTE</span><span class="pi">:</span>
          <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Release</span><span class="nv"> </span><span class="s">notes</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">deployment.'</span>
          <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
          <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">macos-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout Code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Dependencies</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">make steup</span>
          <span class="s">make instal</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build Project</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec fastlane build</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload Build Artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">build-artifact</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./fastlane/build/</span>
</code></pre></div></div>

<p><strong>CD-Deploy-Firebase.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Firebase</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Automatically trigger when the Build Action completes</span>
  <span class="na">workflow_run</span><span class="pi">:</span>
    <span class="na">workflows</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">Build"</span><span class="pi">]</span>
    <span class="na">types</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">completed</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="c1"># Deploy only if the build is completed and successful</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ github.event.workflow_run.conclusion == 'success' }}</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout Code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Dependencies</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">make steup</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download Build Artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">build-artifact</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./fastlane/build/</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Production</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec fastlane deploy-firebase</span>
</code></pre></div></div>

<p><strong>CD-Deploy-Testflight.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Testflight</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Automatically trigger when the Build Action is completed</span>
  <span class="na">workflow_run</span><span class="pi">:</span>
    <span class="na">workflows</span><span class="pi">:</span> <span class="pi">[</span><span class="s2">"</span><span class="s">Build"</span><span class="pi">]</span>
    <span class="na">types</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">completed</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="c1"># Only run deployment if the build is completed and successful</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ github.event.workflow_run.conclusion == 'success' }}</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout Code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Dependencies</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">make steup</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download Build Artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">build-artifact</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./fastlane/build/</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Production</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec fastlane deploy-testflight</span>
</code></pre></div></div>

<p><strong>You can also use <a href="https://docs.github.com/en/actions/how-tos/sharing-automations/reusing-workflows" target="_blank">Reusing Workflow</a> :</strong></p>

<p><strong>CD-Deploy-Firebase.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Firebase</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Any trigger condition, here using manual form trigger as an example</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Release</span><span class="nv"> </span><span class="s">notes</span><span class="nv"> </span><span class="s">of</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">deployment.'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">Build</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/CD-Build.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span> <span class="s">${{ inputs.RELEASE_NOTE }}</span>

  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="c1"># Jobs run concurrently by default, use needs to wait for build to finish</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">build</span><span class="pi">]</span>
    <span class="c1"># Deploy only if successful</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ always() &amp;&amp; needs.deploy.result == 'success' }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout Code</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Dependencies</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">make steup</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download Build Artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/download-artifact@v4</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">build-artifact</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s">./fastlane/build/</span>

      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to Production</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec fastlane deploy-firebase</span>
</code></pre></div></div>

<h4 id="github-actions--artifact">GitHub Actions — Artifact</h4>

<p>Regarding caching, currently <strong>even the Self-hosted Runner Artifact feature still uses GitHub Cloud</strong>, which is subject to usage limits ( <a href="https://docs.github.com/en/billing/managing-billing-for-your-products/about-billing-for-github-actions#included-storage-and-minutes" target="_blank">Free accounts start at 500MB</a> ).</p>

<blockquote>
  <p><em>To achieve similar results with a self-hosted runner, you can create a shared host directory yourself or find alternative tools.</em></p>
</blockquote>

<p>Therefore, I currently use Artifacts only to store small data, such as Snapshot Test errors, test reports, and so on.</p>

<h3 id="ci-nightly-build-runs-snapshot--unit-tests--build--cd-deployment-to-firebase-app-distribution">CI— Nightly Build Runs Snapshot + Unit Tests + Build + CD Deployment to Firebase App Distribution</h3>

<h4 id="workflow-3">Workflow</h4>

<p>Automatically run all tests (unit + snapshot tests) every day at 3 AM on the main (develop or master) branch. If the tests fail, send a failure notification to the Slack workspace; if successful, build and deploy a version to Firebase App Distribution. Both build success and failure will trigger Slack notifications.</p>

<h4 id="ci-nightly-build-and-deployyml">CI-Nightly-Build-And-Deploy.yml</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow(Action) Name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">CI-Nightly Build And Deploy</span>

<span class="c1"># Actions Log Title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[CI-Nightly</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">And</span><span class="nv"> </span><span class="s">Deploy]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="c1"># Trigger Events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Scheduled automatic execution</span>
  <span class="c1"># https://crontab.guru/</span>
  <span class="c1"># UTC time</span>
  <span class="na">schedule</span><span class="pi">:</span>
    <span class="c1"># 19:00 UTC = 03:00 UTC+8 daily</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0</span><span class="nv"> </span><span class="s">19</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*'</span>
  <span class="c1"># Manual trigger</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="c1"># Job Items</span>
<span class="c1"># Jobs run concurrently by default</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Testing job</span>
  <span class="na">testing</span><span class="pi">:</span>
    <span class="c1"># Reuse Workflow (workflow_call)</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/CI-Testing.yml</span>
    <span class="c1"># Pass all Secrets to CD-Testing.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="c1"># Run all tests</span>
      <span class="na">TEST_LANE</span><span class="pi">:</span> <span class="s2">"</span><span class="s">run_all_tests"</span>
      <span class="c1"># Target branch: main, develop or master...etc</span>
      <span class="na">BRANCH</span><span class="pi">:</span> <span class="s2">"</span><span class="s">main"</span>

  <span class="na">deploy-env</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">DATE_STRING</span><span class="pi">:</span> <span class="s">${{ steps.get_date.outputs.DATE_STRING }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get Date String</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">get_date</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">VERSION_DATE=$(date -u '+%Y%m%d')</span>
          <span class="s">echo "${VERSION_DATE}"</span>
          <span class="s">echo "DATE_STRING=${VERSION_DATE}" &gt;&gt; $GITHUB_ENV</span>
          <span class="s">echo "DATE_STRING=${VERSION_DATE}" &gt;&gt; $GITHUB_OUTPUT</span>
    
  <span class="na">deploy</span><span class="pi">:</span>
    <span class="c1"># Jobs run concurrently by default, use needs to wait for testing and deploy-env to finish before running</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">testing</span><span class="pi">,</span> <span class="nv">deploy-env</span><span class="pi">]</span>
    <span class="c1"># Run only if testing succeeded</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.testing.result == 'success' }}</span>
    <span class="c1"># Reuse Workflow (workflow_call)</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">./.github/workflows/CD-Deploy.yml</span>
    <span class="c1"># Pass all Secrets to CD-Deploy.yml</span>
    <span class="na">secrets</span><span class="pi">:</span> <span class="s">inherit</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">VERSION_NUMBER</span><span class="pi">:</span> <span class="s">NightlyBuild-${{ needs.deploy-env.outputs.DATE_STRING }}</span>
      <span class="na">RELEASE_NOTE</span><span class="pi">:</span> <span class="s">NightlyBuild-${{ needs.deploy-env.outputs.DATE_STRING }}</span>
      <span class="c1"># Target branch: main, develop or master...etc</span>
      <span class="na">BRANCH</span><span class="pi">:</span> <span class="s2">"</span><span class="s">main"</span>

<span class="c1"># ----- Slack Notify -----</span>
  <span class="na">testing-failed-slack-notify</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">testing</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.testing.result == 'failure' }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post text to a Slack channel</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.1.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ vars.SLACK_TEAM_CHANNEL_ID }}</span>
            <span class="s">text</span><span class="err">:</span> <span class="s2">"</span><span class="s">:x:</span><span class="nv"> </span><span class="s">Nightly</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">-</span><span class="nv"> </span><span class="s">Testing</span><span class="nv"> </span><span class="s">failed</span><span class="se">\n</span><span class="s">Workflow:</span><span class="nv"> </span><span class="s">&lt;${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}</span><span class="se">\\</span><span class="s">|View</span><span class="nv"> </span><span class="s">Run&gt;"</span>

  <span class="na">deploy-failed-slack-notify</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">deploy</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.deploy.result == 'failure' }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post text to a Slack channel</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.1.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ vars.SLACK_TEAM_CHANNEL_ID }}</span>
            <span class="s">text</span><span class="err">:</span> <span class="s2">"</span><span class="s">:x:</span><span class="nv"> </span><span class="s">Nightly</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">Deploy</span><span class="nv"> </span><span class="s">failed</span><span class="se">\n</span><span class="s">Workflow:</span><span class="nv"> </span><span class="s">&lt;${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}</span><span class="se">\\</span><span class="s">|View</span><span class="nv"> </span><span class="s">Run&gt;"</span>

  <span class="na">deploy-success-slack-notify</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">deploy</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">${{ needs.deploy.result == 'success' }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post text to a Slack channel</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.1.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ vars.SLACK_TEAM_CHANNEL_ID }}</span>
            <span class="s">text</span><span class="err">:</span> <span class="s2">"</span><span class="s">:white_check_mark:</span><span class="nv"> </span><span class="s">Nightly</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">Deploy</span><span class="nv"> </span><span class="s">succeeded</span><span class="se">\n</span><span class="s">Workflow:</span><span class="nv"> </span><span class="s">&lt;${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}</span><span class="se">\\</span><span class="s">|View</span><span class="nv"> </span><span class="s">Run&gt;"</span>
</code></pre></div></div>

<p><strong>Commit files to the Repo main branch and manually trigger test and build to see the results:</strong></p>

<p><img src="/assets/4b001d2e8440/1*l71fL8oLoeAv-JX_FgkqzQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="564" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjU2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*l71fL8oLoeAv-JX_FgkqzQ.png" /></p>

<blockquote>
  <p><em>Will be triggered automatically every day in the future.</em></p>
</blockquote>

<p><img src="/assets/4b001d2e8440/1*u6A77KwkXS2SY5-DPPPR9A.webp" alt="Demo" loading="lazy" decoding="async" width="1200" height="802" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*u6A77KwkXS2SY5-DPPPR9A.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/runs/16119750747" target="_blank">Demo</a></p>

<p><img src="/assets/4b001d2e8440/1*6DQL_v4eahSqSuVJZRPrEA.webp" alt="" loading="lazy" decoding="async" width="310" height="92" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMTAiIGhlaWdodD0iOTIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4b001d2e8440/1*6DQL_v4eahSqSuVJZRPrEA.png" /></p>

<p>After waiting for the testing tasks, packaging and deployment tasks, and notification tasks to complete, check the results.</p>

<p><img src="/assets/4b001d2e8440/1*4UVgyCQljqZQgzxHpXPB4g.webp" alt="" loading="lazy" decoding="async" width="903" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDMiIGhlaWdodD0iOTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*4UVgyCQljqZQgzxHpXPB4g.png" /></p>

<p><img src="/assets/4b001d2e8440/1*t9PrQfcTANyvG7gfXXC-bw.webp" alt="" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*t9PrQfcTANyvG7gfXXC-bw.jpeg" /></p>

<p>We can directly install the Nightly Build version on the phone for early access testing.</p>

<h4 id="technical-details">Technical Details</h4>

<p>This Action directly reuses the previously designed CI-Testing and CD-Deploy, combining them into our Nightly Build. It’s very flexible and easy to use!</p>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/workflows/CI-Nightly-Build-And-Deploy.yml" target="_blank">CI-Nightly-Build-And-Deploy.yml</a></strong></p>

<h3 id="self-hosted-runner-notes">Self-hosted Runner Notes</h3>

<p>This article uses GitHub Hosted macOS Runners since it is a public repo. However, in real work scenarios, our repos are always private. Using GitHub Hosted Runners directly is very expensive and not cost-effective (you could buy a Mac Mini in about a month and run it at the office with unlimited usage). Each machine can run multiple Runners concurrently depending on its performance to handle tasks in parallel.</p>

<blockquote>
  <p><strong><em>For details, please refer to the <a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/">previous article</a> section “ <a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/">Setting up and Switching to Self-hosted Runner</a>”.</em> After installing XCode and the basic environment on your local machine, register and activate the Runner, then change <code class="language-plaintext highlighter-rouge">runs-on</code> to <code class="language-plaintext highlighter-rouge">[self-hosted]</code> in the Action Workflow YAML.</strong>*</p>
</blockquote>

<p>Issues with multiple runners on the same machine have mostly been resolved in the Actions above, such as changing all shared dependency directories to local directories. Another problem to solve during testing is the simulator conflict: “<strong>When two test jobs are run simultaneously by two runners on the same machine, specifying the same simulator causes interference and leads to test failures.</strong>”</p>

<p>The solution is simple: assign a separate simulator to each individual Runner.</p>

<h4 id="simulator-settings-for-multiple-runners-on-the-same-machine">Simulator Settings for Multiple Runners on the Same Machine</h4>

<p>Assuming I have <strong>two Runners on the same machine</strong> receiving tasks concurrently:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">ZhgChgLideMacBook-Pro-Runner-A</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">ZhgChgLideMacBook-Pro-Runner-B</code></p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*3ptg9Tl5fBbEIYB4kgh0kw.webp" alt="" loading="lazy" decoding="async" width="819" height="333" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MTkiIGhlaWdodD0iMzMzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*3ptg9Tl5fBbEIYB4kgh0kw.png" /></p>

<p>In XCode Simulator settings, we need to add two simulators:</p>

<p><img src="/assets/4b001d2e8440/1*k9bR2C12Wk11HAKKiYJ2zg.webp" alt="" loading="lazy" decoding="async" width="1037" height="695" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDM3IiBoZWlnaHQ9IjY5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*k9bR2C12Wk11HAKKiYJ2zg.png" /></p>

<p><img src="/assets/4b001d2e8440/1*iigArewZEW0063Q6xwZn7g.webp" alt="" loading="lazy" decoding="async" width="322" height="281" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMjIiIGhlaWdodD0iMjgxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*iigArewZEW0063Q6xwZn7g.png" /></p>

<ul>
  <li>Model, iOS Version, and Test Environment</li>
</ul>

<p><strong>Change the test steps in CI-Testing.yml to:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Run Fastlane Unit Test Lane</span>
      - name: Run Tests
        <span class="nb">id</span>: testing
        <span class="c"># Set working directory so subsequent commands don't need to cd ./Product/</span>
        working-directory: ./Product/
        <span class="nb">env</span>:
          <span class="c"># ...</span>
          <span class="c"># Repo -&gt; Settings -&gt; Actions secrets and variables -&gt; variables</span>
          <span class="c"># iOS version of the simulator</span>
          SIMULATOR_IOS_VERSION: <span class="k">${</span><span class="p">{ vars.SIMULATOR_IOS_VERSION </span><span class="k">}</span><span class="o">}</span>

          <span class="c"># Current Runner name</span>
          RUNNER_NAME: <span class="k">${</span><span class="p">{ runner.name </span><span class="k">}</span><span class="o">}</span>
          
          <span class="c"># ...</span>
        run: <span class="se">\\</span>|

          <span class="c"># ...</span>
          bundle <span class="nb">exec </span>fastlane <span class="k">${</span><span class="nv">TEST_LANE</span><span class="k">}</span> device:<span class="s2">"</span><span class="k">${</span><span class="nv">RUNNER_NAME</span><span class="k">}</span><span class="s2"> (</span><span class="k">${</span><span class="nv">SIMULATOR_IOS_VERSION</span><span class="k">}</span><span class="s2">)"</span> <span class="se">\\</span>| <span class="nb">tee</span> <span class="s2">"</span><span class="nv">$RUNNER_TEMP</span><span class="s2">/testing_output.txt"</span>
          <span class="c"># ...</span>
</code></pre></div></div>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">device</code> <strong>changed to</strong> <code class="language-plaintext highlighter-rouge">${RUNNER_NAME} (${SIMULATOR_IOS_VERSION})</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">SIMULATOR_IOS_VERSION</code> is still consistently referenced from the Repo variables.</p>
  </li>
</ul>

<p><strong>The combined result will be (using 18.4 as an example):</strong></p>

<ul>
  <li>
    <p>Runner: <code class="language-plaintext highlighter-rouge">ZhgChgLideMacBook-Pro-Runner-A</code><br />
Simulator: <strong>ZhgChgLideMacBook-Pro-Runner-A(18.4)</strong></p>
  </li>
  <li>
    <p>Runner: <code class="language-plaintext highlighter-rouge">ZhgChgLideMacBook-Pro-Runner-B</code><br />
Simulator: <strong>ZhgChgLideMacBook-Pro-Runner-B(18.4)</strong></p>
  </li>
</ul>

<p>This way, when two Runners run tests simultaneously, two simulators will launch and run independently.</p>

<h4 id="complete-project-repo">Complete Project Repo</h4>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo" target="_blank"><img src="https://opengraph.githubassets.com/eb233d74ad1bc6b0eceb9494487579557fb7f17066a3981d20b52fde2c00ef45/ZhgChgLi/github-actions-ci-cd-demo" alt="" /></a></p>

<h3 id="additional-ssh-agent-setup--for-fastlane-match-or-private-cocoapods-repo">Additional SSH Agent Setup — For Fastlane Match or Private CocoaPods Repo</h3>

<p>When using Fastlane Match or a Private CocoaPods Repo, since they are in another private repo, the current repo/action environment cannot directly git clone. You need to set up an SSH agent to grant permission during the action execution.</p>

<h4 id="step-1-generate-ssh-key">Step 1. Generate SSH Key</h4>

<p><img src="/assets/4b001d2e8440/1*kPWl9hombBgQ15i-n2sY2A.webp" alt="" loading="lazy" decoding="async" width="682" height="483" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODIiIGhlaWdodD0iNDgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*kPWl9hombBgQ15i-n2sY2A.png" /></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-C</span> <span class="s2">"zhgchgli@gmail.com"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Enter file in which to save the key (/Users/zhgchgli/.ssh/id_ed25519):</code> /Users/zhgchgli/Downloads/zhgchgli</p>

<ul>
  <li>Input the download path to make it easier for us to copy the content</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">Enter passphrase for “/Users/zhgchgli/Downloads/zhgchgli” (leave empty if no passphrase):</code></p>

<ul>
  <li>
    <p><strong>Leave Blank:</strong> For CI/CD use, you cannot enter the Passphrase interactively in the CLI, <strong>so please leave it blank</strong></p>
  </li>
  <li>
    <p>Generation completed (.pub/private_key)</p>
  </li>
</ul>

<p><img src="/assets/4b001d2e8440/1*aF5P6EK9Hfv-CI3MRZ8MCA.webp" alt="" loading="lazy" decoding="async" width="220" height="51" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMjAiIGhlaWdodD0iNTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4b001d2e8440/1*aF5P6EK9Hfv-CI3MRZ8MCA.png" /></p>

<h4 id="step-2-set-deploy-key-in-private-repo">Step 2. Set Deploy Key in Private Repo</h4>

<p><img src="/assets/4b001d2e8440/1*3K6JvCHa23QfvWoNq2xdBQ.webp" alt="github-actions-ci-cd-demo-certificates Repo" loading="lazy" decoding="async" width="1172" height="781" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTcyIiBoZWlnaHQ9Ijc4MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*3K6JvCHa23QfvWoNq2xdBQ.jpeg" /></p>

<p>github-actions-ci-cd-demo-certificates Repo</p>

<p>Settings → Security → Deploy keys → Add deploy key.</p>

<ul>
  <li>
    <p>Title: Enter Key Name</p>
  </li>
  <li>
    <p>Key: <code class="language-plaintext highlighter-rouge">Paste the .pub Key content</code></p>
  </li>
</ul>

<p>Completed.</p>

<p><img src="/assets/4b001d2e8440/1*Xo_g-AchEk0j4SGXOmBuxg.webp" alt="" loading="lazy" decoding="async" width="1168" height="814" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTY4IiBoZWlnaHQ9IjgxNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Xo_g-AchEk0j4SGXOmBuxg.jpeg" /></p>

<h4 id="step-3-set-ssh-private-key-to-secrets-in-the-actions-repo">Step 3. Set SSH Private Key to Secrets in the Action’s Repo</h4>

<p><img src="/assets/4b001d2e8440/1*Jm92pQHMOImQzZtmmvd1ng.webp" alt="github-actions-ci-cd-demo Repo" loading="lazy" decoding="async" width="1174" height="899" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc0IiBoZWlnaHQ9Ijg5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*Jm92pQHMOImQzZtmmvd1ng.jpeg" /></p>

<p>github-actions-ci-cd-demo Repo</p>

<p>Settings → Secrets and variables → Actions → New repository secret.</p>

<ul>
  <li>
    <p>Name: Enter the secret variable name <code class="language-plaintext highlighter-rouge">SSH_PRIVATE_KEY</code></p>
  </li>
  <li>
    <p>Secret: <code class="language-plaintext highlighter-rouge">Paste the private_key content</code></p>
  </li>
</ul>

<p>Completed.</p>

<h4 id="step-4-ssh-agent-setup-completed-now-verify-git-clone-private-repo-access-rights">Step 4. SSH Agent setup completed, now verify Git Clone Private Repo access rights</h4>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/actions/workflows/Demo-Git-Clone-Private-Repo.yml" target="_blank"><strong>Demo-Git-Clone-Private-Repo.yml</strong></a> <strong>:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Demo Git Clone Private Repo</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">clone-private-repo</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Git Clone Private Repo</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">30</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># 🔐 Enable SSH Agent and add the private key</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup SSH Agent</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">webfactory/ssh-agent@v0.9.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">ssh-private-key</span><span class="pi">:</span> <span class="s">${{ secrets.SSH_PRIVATE_KEY }}</span>

      <span class="c1"># 🛡️ Add github.com to known_hosts to avoid host verification errors</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Add GitHub to known_hosts</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">mkdir -p ~/.ssh</span>
          <span class="s">ssh-keyscan github.com &gt;&gt; ~/.ssh/known_hosts</span>
      <span class="c1"># 📦 Clone private repo via SSH and verify</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Clone and Verify Private Repo</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">git clone git@github.com:ZhgChgLi/github-actions-ci-cd-demo-certificates.git ./fakeMatch/</span>
          <span class="s">if [ -d "./fakeMatch/.git" ]; then</span>
            <span class="s">echo "✅ Repo cloned successfully into ./fakeMatch/"</span>
            <span class="s">cd ./fakeMatch</span>
            <span class="s">echo "📌 Current commit</span><span class="err">:</span> <span class="s">$(git rev-parse --short HEAD)"</span>
          <span class="s">else</span>
            <span class="s">echo "❌ Clone failed. SSH Agent may not be configured properly."</span>
            <span class="s">exit </span><span class="m">1</span>
          <span class="s">fi</span>
</code></pre></div></div>

<p>You can use the above Action to verify if the setup is successful.</p>

<p><img src="/assets/4b001d2e8440/1*YLbr7l0FBSOYx_fQT-tiCA.webp" alt="" loading="lazy" decoding="async" width="1400" height="805" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjgwNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4b001d2e8440/1*YLbr7l0FBSOYx_fQT-tiCA.png" /></p>

<p>Success. Subsequent <code class="language-plaintext highlighter-rouge">fastlane match</code> or <code class="language-plaintext highlighter-rouge">pod install</code> for private pods should now run correctly.</p>

<h3 id="summary">Summary</h3>

<p>This article provides a detailed record of developing a complete iOS CI/CD pipeline using GitHub Actions. The next article will focus on optimizing the user experience (engineers/PMs/designers) by <strong>enhancing Slack notifications and integrating Google Apps Script Web App with GitHub Actions to create a free and easy-to-use cross-team packaging platform tool.</strong></p>

<h3 id="series-articles">Series Articles:</h3>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Comprehensive Use and Setup of GitHub Actions and Self-hosted Runner</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</strong></a></p>
  </li>
</ul>

<h4 id="-buy-me-a-beer-on-paypal"><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></h4>

<blockquote>
  <p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank"><strong><em>This series of articles took a lot of time and effort to write. If the content helps you or improves your team’s work efficiency and product quality, feel free to buy me a coffee. Thank you for your support!</em></strong></a></p>
</blockquote>

<p><img src="/assets/4b001d2e8440/1*QJj54G9gOjtQS-rbHVT1SQ.webp" alt="Buy me a coffee" loading="lazy" decoding="async" width="700" height="700" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNzAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4b001d2e8440/1*QJj54G9gOjtQS-rbHVT1SQ.png" /></p>

<p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></p>]]></content>
  </entry><entry>
    <title type="html">GitHub Actions｜Self-hosted Runner Setup and Usage Guide for Efficient CI/CD</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/" rel="alternate" type="text/html" title="GitHub Actions｜Self-hosted Runner Setup and Usage Guide for Efficient CI/CD" />
    <published>2025-07-02T20:22:32+08:00</published>
    <updated>2025-07-12T22:49:01+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/404bd5c70040</id><summary type="html">Discover step-by-step instructions to implement GitHub Actions and Self-hosted Runner, resolving CI/CD deployment challenges and accelerating your automation workflows for reliable and scalable software delivery.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="cicd" /><category term="github-actions" /><category term="github" /><category term="self-hosted" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/404bd5c70040/1*_vGYh_XSI3ZDbdeT8xCihA.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><![CDATA[<h3 id="cicd-practical-guide-part-2-complete-guide-to-using-and-building-github-actions-and-self-hosted-runner">CI/CD Practical Guide (Part 2): Complete Guide to Using and Building GitHub Actions and Self-hosted Runner</h3>

<p>A Complete Guide to Understanding GitHub Actions and Self-hosted Runner Operation with Step-by-Step Tutorials.</p>

<p><img src="/assets/404bd5c70040/1*_vGYh_XSI3ZDbdeT8xCihA.webp" alt="Photo by Dan Taylor" loading="lazy" decoding="async" width="1400" height="933" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*_vGYh_XSI3ZDbdeT8xCihA.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@theoneandonlydantaylor?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Dan Taylor</a></p>

<h4 id="preface">Preface</h4>

<p>In the previous article “<a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a>”, we introduced what CI/CD is, its benefits, and tool selection. <strong>This article will focus on the architecture and usage of GitHub Actions and Self-hosted Runner</strong>, and guide you step-by-step to create several interesting automation workflows to help you get started.</p>

<h3 id="github-actions-architecture-flowchart">GitHub Actions Architecture Flowchart</h3>

<p>Before we begin, let’s first clarify the operational architecture, workflow relationships, and responsibilities of GitHub Actions.</p>

<p><img src="/assets/404bd5c70040/1*iacfyTX_b3YTSzMcn2ldjw.webp" alt="" loading="lazy" decoding="async" width="1400" height="937" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*iacfyTX_b3YTSzMcn2ldjw.png" /></p>

<h4 id="github-repo"><strong>GitHub Repo</strong></h4>

<ul>
  <li>In the world of GitHub Actions, all Actions (Workflow YAML files) must be stored within a Git Repo (<code class="language-plaintext highlighter-rouge">REPO/.github/workflows/</code>).</li>
</ul>

<h4 id="github-repo--actions-secrets">GitHub Repo — Actions Secrets</h4>

<p>Repo → Settings → Secrets and variables → Actions → Secrets.</p>

<ul>
  <li>
    <p>Store Secret Keys and Tokens used in Actions steps<br />
e.g. Slack Bot Token, Apple Store Connect API .p8 Key</p>
  </li>
  <li>
    <p><strong>Secrets cannot be viewed in the Action Log and are automatically masked with **</strong>**</p>
  </li>
  <li>
    <p>Secrets cannot be viewed or edited; they can only be overwritten.</p>
  </li>
  <li>
    <p>Secrets <strong>currently only support plain text content and cannot upload files</strong><br />
<strong>-</strong> For binary keys, please refer to the <a href="https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/using-secrets-in-github-actions#storing-base64-binary-blobs-as-secrets" target="_blank">official steps to store after Base64 encoding</a>.</p>
  </li>
  <li>
    <p>The method for storing iOS development certificates can be found in the official guide: <a href="https://docs.github.com/en/actions/how-tos/use-cases-and-examples/deploying/installing-an-apple-certificate-on-macos-runners-for-xcode-development" target="_blank">Installing an Apple certificate on macOS runners for Xcode development</a></p>
  </li>
  <li>
    <p>You can store organization-level Secrets to share across Repos.</p>
  </li>
</ul>

<h4 id="github-repo--actions-variables">GitHub Repo — Actions Variables</h4>

<p>Repo → Settings → Secrets and variables → Actions → Variables.</p>

<ul>
  <li>
    <p>Store commonly used variables in Actions steps<br />
e.g. Simulator iOS version, working directory</p>
  </li>
  <li>
    <p>Variable contents can be viewed and edited</p>
  </li>
  <li>
    <p>Variable contents can be output in the Action Log</p>
  </li>
  <li>
    <p>Variables only support plain text. You can also store JSON strings and parse them yourself for use.</p>
  </li>
  <li>
    <p>You can store organization-level Variables and share them across Repos.</p>
  </li>
</ul>

<h4 id="github-actions--trigger">GitHub Actions — Trigger</h4>

<ul>
  <li>
    <p><strong>The Most Important Starting Point in GitHub Actions — Trigger Events (Conditions)</strong></p>
  </li>
  <li>
    <p>Only GitHub Actions that match the trigger event will run</p>
  </li>
  <li>
    <p>The full list of events is available in the <a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows" target="_blank">official documentation</a>.</p>
  </li>
  <li>
    <p>It basically covers all event scenarios in CI/CD and automation.<br />
But <strong>if a special scenario lacks a specific event, you must use other events combined with Job conditions or use a Schedule to manually check</strong>.<br />
For example, if there is no PR Merged event, you can use <code class="language-plaintext highlighter-rouge">pull_request: closed</code> + Job <code class="language-plaintext highlighter-rouge">if: github.event.pull_request.merged == true</code> to achieve this.</p>
  </li>
</ul>

<p><strong>Common Events:</strong></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">schedule</code> (cron): Scheduled execution (same as crontab)<br />
Can be used for automation: periodic PR checks, scheduled builds, running automation scripts on a schedule<br />
<strong>All run on main / develop (Default Branch).</strong></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">pull_request:</code>: PR related events<br />
When a PR is opened, assigned, labeled, new commits are pushed, etc.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">issues</code>, <code class="language-plaintext highlighter-rouge">issue_comment</code>: Issue-related events<br />
When an Issue is opened, a new comment is added, etc.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">workflow_dispatch</code>: Manual trigger; you can set required input fields, and GitHub Actions provides a simple form for users to fill in information.<br />
e.g.:</p>
  </li>
</ul>

<p><img src="/assets/404bd5c70040/1*XogIJsCbrNPerWBto_PG8w.webp" alt="" loading="lazy" decoding="async" width="332" height="432" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMzIiIGhlaWdodD0iNDMyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*XogIJsCbrNPerWBto_PG8w.png" /></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">workflow_call</code>: Triggers another Action (Workflow) to execute a task.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">workflow_run</code>: Triggers this task when another Action (Workflow) runs a job.</p>
  </li>
</ul>

<blockquote>
  <p><em>For more event types and configuration details, please refer to the <a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows" target="_blank">official documentation</a>.</em></p>
</blockquote>

<h4 id="github-actions--workflow"><strong>GitHub Actions — Workflow</strong></h4>

<ul>
  <li>
    <p>a.k.a Action</p>
  </li>
  <li>
    <p>Write .yaml files using YAML, and place all files under <code class="language-plaintext highlighter-rouge">REPO/.github/workflows/</code>.</p>
  </li>
  <li>
    <p><strong>Use the Workflow YAML file in the main branch as the source</strong><br />
If you can’t see the Action under development or encounter errors on other branches, try merging back into the main branch first.</p>
  </li>
  <li>
    <p>The most basic unit in GitHub Actions, each Workflow represents a CI/CD or automation task.</p>
  </li>
  <li>
    <p>A Workflow can call another Workflow to execute tasks<br />
(This feature can be used to separate core Workflows and calling Workflows)</p>
  </li>
  <li>
    <p>This section defines the job name, execution strategy, trigger events, job tasks, and all other Action-related settings.</p>
  </li>
  <li>
    <p>The current file structure does not support subdirectories.</p>
  </li>
  <li>
    <p>Actions are completely free (Public and Private Repo)</p>
  </li>
</ul>

<h4 id="github-actions--workflow--job">GitHub Actions — Workflow — Job</h4>

<ul>
  <li>
    <p>Execution Units in GitHub Actions</p>
  </li>
  <li>
    <p>Tasks in a Workflow include the following</p>
  </li>
  <li>
    <p>Each Workflow can have multiple Jobs</p>
  </li>
  <li>
    <p>Each Job must specify which Runner Label to use. During execution, the corresponding Runner machine will run the task.</p>
  </li>
  <li>
    <p><strong>Multiple Jobs Run Concurrently</strong> (use <code class="language-plaintext highlighter-rouge">needs</code> to enforce order if needed)</p>
  </li>
  <li>
    <p><strong>Each Job should be treated as an independent execution unit (each as a Sandbox)</strong>. If a Job produces resource files that need to be used by subsequent Jobs or Workflows, you must Upload Artifacts or move them to a shared output directory on the self-hosted runner.</p>
  </li>
  <li>
    <p>A Job can output a string for other Jobs to reference.<br />
(For example, the execution result true or false)</p>
  </li>
  <li>
    <p>If no specific workflow conditions are set, when multiple Jobs are running and one Job fails, <strong>the other Jobs will continue to run</strong>.</p>
  </li>
</ul>

<h4 id="github-actions--reuse-workflowjob">GitHub Actions — Reuse Workflow/Job</h4>

<ul>
  <li>
    <p>Defining <code class="language-plaintext highlighter-rouge">on: workflow_call</code> in a Workflow allows it to be encapsulated and reused as a Job by other Workflows.</p>
  </li>
  <li>
    <p>Sharing and usage across Repos within the same organization</p>
  </li>
  <li>
    <p>Therefore, CI/CD tasks shared across Repos can be placed in a common Repo for joint use.</p>
  </li>
</ul>

<h4 id="github-actions--workflow--job--step">GitHub Actions — Workflow — Job — Step</h4>

<ul>
  <li>
    <p>The Smallest Executable Unit in GitHub Actions</p>
  </li>
  <li>
    <p>The script that actually runs the tasks within a Job</p>
  </li>
  <li>
    <p>Each Job can have multiple Steps</p>
  </li>
  <li>
    <p><strong>Multiple Steps Execute Sequentially</strong></p>
  </li>
  <li>
    <p>A Step can output a string for subsequent Steps to reference and use.</p>
  </li>
  <li>
    <p><strong>A Step can directly write shell script code</strong><br />
You can use <a href="https://cli.github.com/manual/gh" target="_blank">gh cli</a> and current environment variables (e.g., get PR number) to do whatever you want directly.</p>
  </li>
  <li>
    <p>If no specific workflow conditions are set, a Step <strong>will stop immediately</strong> upon an error, and subsequent Steps will not run.</p>
  </li>
</ul>

<h4 id="github-actions--workflow--job--reuse-action-step">GitHub Actions — Workflow — Job — Reuse Action Step</h4>

<ul>
  <li>
    <p><strong>You can directly reuse existing workflows created by experts on the <a href="https://github.com/marketplace?type=actions" target="_blank">Marketplace</a>.</strong><br />
For example: <a href="https://github.com/marketplace/actions/comment-pull-request" target="_blank">Comment on a Pull Request</a>.</p>
  </li>
  <li>
    <p>You can also package a series of your own workflow Steps into an Action GitHub Repo for reuse in other workflows.</p>
  </li>
  <li>
    <p>Actions from Public Repos can be published to the Marketplace</p>
  </li>
</ul>

<p><strong>Packaging Actions supports:</strong></p>

<ul>
  <li>
    <p><strong>Docker Action</strong> — means GitHub Actions will pass environment variables into the Docker container, allowing you to handle them as needed, such as with shell scripts, Java, PHP, etc.</p>
  </li>
  <li>
    <p><strong>JavaScript/TypeScript Action</strong> — Write GitHub Actions logic directly using node.js, with environment variables provided for reference.<br />
e.g. <a href="https://github.com/pozil/auto-assign-issue/blob/v2/action.yml" target="_blank">pozil/auto-assign-issue</a></p>
  </li>
  <li>
    <p><strong>Composite (YAML) —</strong> Pure YAML description of task steps (same structure as GitHub Actions — Workflow — Job — Step). You can specify which steps to perform or write shell scripts directly.</p>

    <p>e.g. <a href="https://github.com/ZhgChgLi/ZReviewTender/blob/main/action.yml" target="_blank">ZhgChgLi/ZReviewTender</a></p>
  </li>
</ul>

<blockquote>
  <p><em>Due to space limitations, this article will not cover how to package a GitHub Actions Action. If interested, please refer to the official documentation: <a href="https://docs.github.com/en/actions/tutorials/creating-a-composite-action" target="_blank">tutorials/creating-a-composite-action</a>.</em></p>
</blockquote>

<h4 id="github-runner">GitHub Runner</h4>

<ul>
  <li>
    <p>GitHub dispatches corresponding Jobs to Runners based on Runner Labels.</p>
  </li>
  <li>
    <p>The Runner acts only as a listener, polling and waiting for tasks dispatched by GitHub.</p>
  </li>
  <li>
    <p>Only cares about the Job, not which Action (Workflow) it belongs to.<br />
Therefore, after Action A’s Job-1 finishes, the next one will be Action B’s Job-1, instead of completing all Jobs of Action A before moving to Action B.</p>
  </li>
  <li>
    <p>Runners can use either GitHub Hosted Runner or Self-hosted Runner.</p>
  </li>
</ul>

<h4 id="github-hosted-runner"><strong>GitHub Hosted Runner</strong></h4>

<ul>
  <li>GitHub provides Runners, which can be found in the official Repo list:</li>
</ul>

<p><a href="https://github.com/actions/runner-images" target="_blank"><img src="https://opengraph.githubassets.com/752d8c5522e9674fddc4c85962169e07ac91e262e2e3bd299cccec91a8f20b26/actions/runner-images" alt="" /></a></p>

<p><img src="/assets/404bd5c70040/1*KtQV4kDCWscEeaZ8jQI8Dg.webp" alt="List of Images for 2025/06" loading="lazy" decoding="async" width="1200" height="1154" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjExNTQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*KtQV4kDCWscEeaZ8jQI8Dg.png" /></p>

<p><a href="https://github.com/actions/runner-images" target="_blank">Images list for 2025/06</a></p>

<ul>
  <li>What is pre-installed on the Runner can be checked here:<br />
e.g. <a href="https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md" target="_blank">macos-14-arm64</a></li>
</ul>

<p><img src="/assets/404bd5c70040/1*svXtXH78-TvK1C_XXCyYLA.webp" alt="macos-14-arm64" loading="lazy" decoding="async" width="1200" height="669" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*svXtXH78-TvK1C_XXCyYLA.png" /></p>

<p><a href="https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md" target="_blank">macos-14-arm64</a></p>

<ul>
  <li>
    <p>For iOS development, prioritize using -arm64 (M-series) processors for the Runner, as they run faster.</p>
  </li>
  <li>
    <p>Just paste the YAML label from the table into the Job <code class="language-plaintext highlighter-rouge">runs-on</code> to use that Runner for the task.</p>
  </li>
  <li>
    <p><strong>Public Repo Pricing: Completely free with unlimited usage</strong></p>
  </li>
  <li>
    <p><strong>️</strong> Private Repo Free Tier:<br />
Free tier (varies by account, example given for GitHub Free):<br />
Usage: 2,000 minutes free per month<br />
Storage: 500 MB</p>
  </li>
  <li>
    <p><strong>⚠️️Private Repo Billing Method:</strong><br />
<strong>After exceeding the free quota, usage-based billing starts (with configurable limits and notifications). Prices vary depending on the operating system and cores of the machine hosting the Runner:</strong></p>
  </li>
</ul>

<p><img src="/assets/404bd5c70040/1*rkhRJN4ZRas_lDOJh-pjkQ.webp" alt="about-billing-for-github-actions" loading="lazy" decoding="async" width="1064" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDY0IiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*rkhRJN4ZRas_lDOJh-pjkQ.png" /></p>

<p><a href="https://docs.github.com/en/billing/managing-billing-for-your-products/about-billing-for-github-actions" target="_blank">about-billing-for-github-actions</a></p>

<p>You can see that the price of macOS is high due to the expensive hardware costs.</p>

<ul>
  <li>Maximum Concurrent Job Limit:</li>
</ul>

<p><img src="/assets/404bd5c70040/1*LU3zGSBe57NBVMfDwl0wdQ.webp" alt="usage-limits-billing-and-administration" loading="lazy" decoding="async" width="1200" height="490" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjQ5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*LU3zGSBe57NBVMfDwl0wdQ.png" /></p>

<p><a href="https://docs.github.com/en/actions/concepts/overview/usage-limits-billing-and-administration#usage-limits" target="_blank">usage-limits-billing-and-administration</a></p>

<blockquote>
  <p><em>This part goes off-topic; our focus is on Self-hosted Runner.</em></p>
</blockquote>

<h4 id="self-hosted-runner-on-in-house-server">Self-hosted Runner on In-house Server</h4>

<ul>
  <li>
    <p>Using Your Own Machine as a Runner</p>
  </li>
  <li>
    <p><strong>One physical machine can run multiple Runners to handle tasks concurrently</strong></p>
  </li>
  <li>
    <p><strong>Free Unlimited Usage Without Restrictions</strong><br />
Only the machine purchase cost, pay once and use endlessly!<br />
Using a 32G RAM M4 Mini (=NT$40,900) as an example, if you use GitHub Hosted Runner, it costs 500 USD per month; <strong>buying a machine and setting it up pays off in just over three months</strong>!</p>
  </li>
  <li>
    <p>Supports Windows, macOS, Linux (x64/ARM/ARM64)</p>
  </li>
  <li>
    <p><strong>Runners Can Be Shared Across Repos Within the Same Organization</strong></p>
  </li>
  <li>
    <p><strong>⚠️ Currently: actions/cache, actions/upload-artifact, and actions/download-artifact only support GitHub cloud services, meaning these contents are still uploaded to GitHub servers and count towards storage usage fees.</strong><br />
You can set up a shared directory on your own machine as an alternative.</p>
  </li>
  <li>
    <p>Self-hosted Runner also supports <a href="https://docs.github.com/en/actions/concepts/runners/about-actions-runner-controller" target="_blank">Docker, k8s</a>, but I haven’t explored it.</p>
  </li>
</ul>

<blockquote>
  <p><strong><em>Setting up a Self-hosted Runner takes only a few steps (ready within 10 minutes) to go online and start running jobs (explained later in this article).</em></strong></p>
</blockquote>

<h4 id="github-workflow-x-job-x-step-x-runner-flow-diagram">GitHub Workflow x Job x Step x Runner Flow Diagram</h4>

<p><img src="/assets/404bd5c70040/1*WHIVfXdVRoEsdXGfJhu2Rw.webp" alt="" loading="lazy" decoding="async" width="1400" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*WHIVfXdVRoEsdXGfJhu2Rw.png" /></p>

<p>Here is a diagram summarizing the workflow and relationships, assuming we have two Workflows and two Runners:</p>

<ul>
  <li>
    <p>CI — Assume R has 3 Jobs, each with several Steps. Runner Label (run-on) — <code class="language-plaintext highlighter-rouge">self-hosted-app</code></p>
  </li>
  <li>
    <p>CD — There are 4 Jobs, each with several Steps, Runner Label (run-on) — <code class="language-plaintext highlighter-rouge">self-hosted-app</code></p>
  </li>
  <li>
    <p>Runner — There are 2, and both Runner Labels are — <code class="language-plaintext highlighter-rouge">self-hosted-app</code></p>
  </li>
</ul>

<p>As mentioned before, Runners and Workflows dispatch and receive tasks based on the Runner Label. In this example, the same <code class="language-plaintext highlighter-rouge">self-hosted-app</code> Runner Label is used. Workflow Jobs run concurrently by default, while Steps are the actual execution contents of each Job and run sequentially in order. When all Steps are completed, the Job is considered finished.</p>

<blockquote>
  <p><strong><em>Therefore, the actual execution timeline of a Workflow will shuttle between two Runners to run Jobs. Jobs can run concurrently, and the next Job after the current one finishes is not necessarily the next Job in the same Workflow.</em></strong></p>
</blockquote>

<blockquote>
  <p><em>⚠️ This is why ensuring each Job is independent is very important (especially in a Self-hosted Runner environment, where the cleanup after a Job might not be thorough. We might subconsciously assume that the output of one Job can be used directly by the next Job), but it shouldn’t be used this way.</em></p>
</blockquote>

<h4 id="if-a-job-produces-output-to-be-used-by-subsequent-jobs">If a Job produces output to be used by subsequent Jobs:</h4>

<ul>
  <li>
    <p><strong>Job Output String</strong>: Output plain text to a variable for other Jobs to reference</p>
  </li>
  <li>
    <p><strong>Artifact-upload/download:</strong> The last step of a Job uploads the execution result to GitHub Artifact, and the first step of another Job downloads it for further processing.<br />
e.g. For example, Job — Packaging → upload the packaged .ipa to <strong>Artifact →</strong> Job — Deployment → download the .ipa → upload to App Store for deployment<br />
Note: <strong>Currently, even Self-hosted Runners upload to GitHub Artifact cloud storage.</strong></p>
  </li>
  <li>
    <p><strong>AWS/GCP… Cloud Storage</strong>: Same as above, but using your own cloud storage service.</p>
  </li>
  <li>
    <p><strong>[Self-hosted Only] Shared Drive and Directory:</strong> If Self-hosted Runners all mount a shared directory, you can create folders based on UUIDs in this directory to store output results. Subsequent Jobs can then read the previous Job’s Output UUID to locate and retrieve the corresponding storage.<br />
Note that Runners on different machines must all mount the same shared directory.</p>
  </li>
  <li>
    <p>Complete everything within the Steps of the same Job.</p>
  </li>
</ul>

<h3 id="learn-github-actions-by-doing--practical-examples">Learn GitHub Actions by Doing — Practical Examples</h3>

<p>“Talk less, do more.” The above explanations and process architecture might still feel a bit unclear. Next, we will present three practical examples, guiding you through hands-on practice while explaining the encountered concepts. This way, you can learn by doing and truly understand what GitHub Actions is.</p>

<h3 id="case--1">Case — 1</h3>

<p>Automatically add a File Changes Size Label after creating a Pull Request to help Reviewers manage their review tasks.</p>

<h4 id="result-image">Result Image</h4>

<p><img src="/assets/404bd5c70040/1*vjSWeu2zB-hmVpfziMDR5Q.webp" alt="Demo PR" loading="lazy" decoding="async" width="774" height="206" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzQiIGhlaWdodD0iMjA2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*vjSWeu2zB-hmVpfziMDR5Q.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<h4 id="workflow-process">Workflow Process</h4>

<ul>
  <li>
    <p>User opens a PR, reopens a PR, or pushes new commits to the PR</p>
  </li>
  <li>
    <p>Triggering GitHub Actions Workflow</p>
  </li>
  <li>
    <p>shell script to get the number of file changes</p>
  </li>
  <li>
    <p>Determine Quantity to Label PR</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
</ul>

<h4 id="hands-on-practice">Hands-on Practice</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<p><strong>File Name:</strong> <code class="language-plaintext highlighter-rouge">Automation-PullRequest.yml</code></p>

<p>Action Workflows can have each task in a separate file, or they can be grouped in the same file based on trigger events or purpose. Since multiple Jobs run concurrently, either approach works. Additionally, <strong>because GitHub Actions currently does not support directory structures, having fewer files and using hierarchical file naming makes management easier</strong>.</p>

<p>Here, all PR-related event Actions are placed in the same Workflow.</p>

<h4 id="automation-pullrequestyml"><code class="language-plaintext highlighter-rouge">Automation-PullRequest.yml</code></h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow (Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Pull Request Automation</span>

<span class="c1"># Actions Log title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[Pull</span><span class="nv"> </span><span class="s">Request</span><span class="nv"> </span><span class="s">Automation]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.event.pull_request.title</span><span class="nv"> </span><span class="se">\\</span><span class="s">|</span><span class="se">\\</span><span class="s">|</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># PR events</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="c1"># PR - opened, reopened, or new push commit</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">,</span> <span class="nv">synchronize</span><span class="pi">,</span> <span class="nv">reopened</span><span class="pi">]</span>

<span class="c1"># If a new job starts in the same concurrency group, cancel the running one</span>
<span class="c1"># For example, if a push commit triggers a job but another push happens before it runs, the previous job is canceled</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.ref_name }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Job tasks</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">label-pr-by-file-count</span><span class="pi">:</span>
    <span class="c1"># Job name (optional, better readability in logs)</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Label PR by changes file count</span>

    <span class="c1"># If this job fails, it won't affect the entire workflow; other jobs continue</span>
    <span class="na">continue-on-error</span><span class="pi">:</span> <span class="kc">true</span>
    
    <span class="c1"># Set maximum timeout to prevent infinite waiting in abnormal cases</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">10</span>

    <span class="c1"># Runner Label - use GitHub Hosted Runner ubuntu-latest to run the job</span>
    <span class="c1"># For private repos, usage counts and may incur costs</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="c1"># Job steps</span>
    <span class="c1"># Steps run sequentially</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Step name</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get changed file count and apply label</span>
        <span class="c1"># Step ID (optional, only needed if outputs are referenced later)</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">get-changed-files-count-by-gh</span>
        <span class="c1"># Inject external environment variables into runtime</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="c1"># secrets.GITHUB_TOKEN is automatically generated by GitHub Actions, no need to set manually; has some GitHub Repo API scopes</span>
          <span class="c1"># https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows</span>
          <span class="c1"># gh (GitHub CLI) needs GH_TOKEN in ENV to have permission to operate</span>
          <span class="na">GH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.GITHUB_TOKEN }}</span>
        <span class="c1"># Shell script</span>
        <span class="c1"># GitHub Hosted Runner comes pre-installed with gh CLI, no need to install in the job</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">#   ${{ github.xxx }} is a GitHub Actions Context expression</span>
          <span class="s">#   Not a shell variable, but replaced by GitHub Actions during YAML parsing</span>
          <span class="s">#   More info</span><span class="err">:</span> <span class="s">https://docs.github.com/en/actions/learn-github-actions/contexts#github-context</span>
          
          <span class="s"># Get PR number</span><span class="err">:</span>
          <span class="s">PR_NUMBER=${{ github.event.pull_request.number }}</span>

          <span class="s"># Get Repo</span><span class="err">:</span>
          <span class="s">REPO=${{ github.repository }}</span>

          <span class="s"># Use GitHub API (gh CLI) to get the count of changed files</span>
          <span class="s">FILE_COUNT=$(gh pr view $PR_NUMBER --repo $REPO --json files --jq '.files \\| length')</span>
          
          <span class="s"># Print log</span>
          <span class="s">echo "Changed file count</span><span class="err">:</span> <span class="s">$FILE_COUNT"</span>

          <span class="s"># Label logic</span>
          <span class="s">if [ "$FILE_COUNT" -lt 5 ]; then</span>
            <span class="s">LABEL="XS"</span>
          <span class="s">elif [ "$FILE_COUNT" -lt 10 ]; then</span>
            <span class="s">LABEL="S"</span>
          <span class="s">elif [ "$FILE_COUNT" -lt 30 ]; then</span>
            <span class="s">LABEL="M"</span>
          <span class="s">elif [ "$FILE_COUNT" -lt 80 ]; then</span>
            <span class="s">LABEL="L"</span>
          <span class="s">elif [ "$FILE_COUNT" -lt 200 ]; then</span>
            <span class="s">LABEL="XL"</span>
          <span class="s">else</span>
            <span class="s">LABEL="XXL"</span>
          <span class="s">fi</span>

          <span class="s"># Use GitHub API (gh CLI) to remove current Size Labels</span>
          <span class="s">EXISTING_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')</span>
          <span class="s">for EXISTING in $EXISTING_LABELS; do</span>
            <span class="s">case "$EXISTING" in</span>
              <span class="s">XS\\|S\\|M\\|L\\|XL\\|XXL)</span>
                <span class="s">echo "🧹 Removing existing label</span><span class="err">:</span> <span class="s">$EXISTING"</span>
                <span class="s">gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$EXISTING"</span>
                <span class="s">;;</span>
            <span class="s">esac</span>
          <span class="s">done</span>

          <span class="s"># (Optional) Create label if it doesn't exist</span>
          <span class="s">if ! gh label list --repo "$REPO" \\| grep -q "^$LABEL"; then</span>
            <span class="s">echo "🆕 Creating missing label</span><span class="err">:</span> <span class="s">$LABEL"</span>
            <span class="s">gh label create "$LABEL" --repo "$REPO" --description "Size label</span><span class="err">:</span> <span class="s">$LABEL" --color "ededed"</span>
          <span class="s">else</span>
            <span class="s">echo "✅ Label '$LABEL' already exists"</span>
          <span class="s">fi</span>
          
          <span class="s"># Use GitHub API (gh CLI) to add the label</span>
          <span class="s">gh pr edit $PR_NUMBER --repo $REPO --add-label "$LABEL"</span>
</code></pre></div></div>

<p>After committing files to the repo’s main branch, opening a new PR will automatically trigger GitHub Actions:</p>

<p><img src="/assets/404bd5c70040/1*wdAztL0BgPeSZdqXxqUEXg.webp" alt="" loading="lazy" decoding="async" width="938" height="744" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iNzQ0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*wdAztL0BgPeSZdqXxqUEXg.png" /></p>

<p>Action execution status showing <strong>Queued</strong> means the task is waiting for a Runner to pick it up.</p>

<h4 id="execution-result">Execution Result</h4>

<p><img src="/assets/404bd5c70040/1*-JoD8IQYHVrDqHmLmrXyaQ.webp" alt="Demo PR" loading="lazy" decoding="async" width="1338" height="948" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzM4IiBoZWlnaHQ9Ijk0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*-JoD8IQYHVrDqHmLmrXyaQ.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<p>After execution completes successfully, the corresponding Label will be automatically added to the PR! The record will show it was labeled by <code class="language-plaintext highlighter-rouge">github-actions</code>.</p>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Automation-PullRequest.yml" target="_blank">Automation-PullRequest.yml</a></strong></p>

<h4 id="directly-use-someone-elses-prebuilt-action-step-pascalgnsize-label-action">Directly Use Someone Else’s Prebuilt Action Step: <a href="https://github.com/pascalgn/size-label-action/tree/main" target="_blank">pascalgn/size-label-action</a></h4>

<p>Earlier, it was mentioned that you can directly use Actions packaged by others. The task of labeling PR Size already has ready-made solutions available. The above example is only for teaching purposes; in practice, there’s no need to reinvent the wheel.</p>

<p>You can complete the task directly within the Action Workflow Job Step:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow (Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Pull Request Automation</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># PR events</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="c1"># PR - when opened, reopened, or new push commit</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">,</span> <span class="nv">synchronize</span><span class="pi">,</span> <span class="nv">reopened</span><span class="pi">]</span>

<span class="c1"># If a new job in the same concurrency group starts, cancel the running one</span>
<span class="c1"># For example, if a push commit triggers a job that hasn't started yet and another push happens, cancel the previous job</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.ref_name }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Job tasks</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">label-pr-by-file-count</span><span class="pi">:</span>
    <span class="c1"># Job name (optional, better for readable logs)</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Label PR by changes file count</span>

    <span class="c1"># If this job fails, it won't affect the whole workflow; other jobs continue</span>
    <span class="na">continue-on-error</span><span class="pi">:</span> <span class="kc">true</span>
    
    <span class="c1"># Set maximum timeout to prevent endless waiting in abnormal cases</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">10</span>

    <span class="c1"># Runner label - use GitHub Hosted Runner ubuntu-latest to run the job</span>
    <span class="c1"># For private repos, usage is counted and may incur costs</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="c1"># Job steps</span>
    <span class="c1"># Steps run sequentially</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Step name</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get changed file count and apply label</span>
        <span class="c1"># Step ID (optional, only needed if later steps reference this step's output)</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">get-changed-files-count-by-gh</span>
        <span class="c1"># Use a pre-built action from others</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s2">"</span><span class="s">pascalgn/size-label-action@v0.5.5"</span>
        <span class="c1"># Inject external environment variables into the runtime</span>
        <span class="c1"># Parameter names and usage refer to docs: https://github.com/pascalgn/size-label-action/tree/main</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="c1"># secrets.GITHUB_TOKEN is automatically generated by GitHub Actions (github-actions identity), no need to set it manually in Secrets, has some GitHub Repo API scopes</span>
          <span class="c1"># https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows</span>
          <span class="na">GITHUB_TOKEN</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.GITHUB_TOKEN</span><span class="nv"> </span><span class="s">}}"</span>
</code></pre></div></div>

<p><a href="https://github.com/pascalgn/size-label-action/tree/main" target="_blank"><img src="https://opengraph.githubassets.com/746d87e558bda612364f12277484f99cae4c57fbb2eb910bfd59d6865c1b6e94/pascalgn/size-label-action" alt="" /></a></p>

<p>This packaged Action is a JavaScript Action. The actual execution code can be found in the following file: <a href="https://github.com/pascalgn/size-label-action/blob/main/dist/index.js" target="_blank">dist/index.js</a> .</p>

<h4 id="additional-notes-on-name-and-run-name">Additional notes on name and run-name:</h4>

<p><img src="/assets/404bd5c70040/1*zBmjm_3AU53NN0UhTrmOBQ.webp" alt="" loading="lazy" decoding="async" width="1012" height="569" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDEyIiBoZWlnaHQ9IjU2OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*zBmjm_3AU53NN0UhTrmOBQ.png" /></p>

<ul>
  <li>
    <p>name: Name of the Action Workflow</p>
  </li>
  <li>
    <p>run-name: The title of the run record (can include PR Title, Branch, Author, etc.)<br />
For the on:pull_request event, the default is the PR Title.</p>
  </li>
</ul>

<h3 id="case--2">Case — 2</h3>

<p>Automatically assign the author and add a comment if there is no Assignee after creating a Pull Request. (This only runs on the initial creation)</p>

<h4 id="result-image-1">Result Image</h4>

<p><img src="/assets/404bd5c70040/1*EL-0nQF7jhP34d6ZoSkJAg.webp" alt="Demo PR" loading="lazy" decoding="async" width="1200" height="682" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*EL-0nQF7jhP34d6ZoSkJAg.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<h4 id="workflow-process-1">Workflow Process</h4>

<ul>
  <li>
    <p>User Opens a PR</p>
  </li>
  <li>
    <p>Triggering GitHub Actions Workflow</p>
  </li>
  <li>
    <p>GitHub script to get assignee</p>
  </li>
  <li>
    <p>If there is no assignee, assign the PR author &amp; comment a message</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
</ul>

<h4 id="hands-on-practice-1">Hands-on Practice</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<p><strong>File Name:</strong> <code class="language-plaintext highlighter-rouge">Automation-PullRequest.yml</code> (same as above)</p>

<h4 id="automation-pullrequestyml-1"><code class="language-plaintext highlighter-rouge">Automation-PullRequest.yml</code></h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow (Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Pull Request Automation</span>

<span class="c1"># Actions Log title</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Pull</span><span class="nv"> </span><span class="s">Request</span><span class="nv"> </span><span class="s">Automation</span><span class="nv"> </span><span class="s">-</span><span class="nv"> </span><span class="s">Daily</span><span class="nv"> </span><span class="s">Checker"</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># PR events</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="c1"># PR - opened, synchronized, reopened</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">,</span> <span class="nv">synchronize</span><span class="pi">,</span> <span class="nv">reopened</span><span class="pi">]</span>

<span class="c1"># If a new job in the same concurrency group starts, cancel the running one</span>
<span class="c1"># For example, if a Push Commit triggers a job that hasn't started yet and another Push Commit occurs, the previous job will be cancelled</span>
<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.ref_name }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Job tasks</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">label-pr-by-file-count</span><span class="pi">:</span>
    <span class="c1"># Please refer to previous text, omitted....</span>
  <span class="c1"># ---------</span>
  <span class="na">assign-self-if-no-assignee</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Automatically assign to self if no assignee is specified</span>
    <span class="c1"># Since the trigger event is shared, this job checks itself and only runs when the Pull Request is opened (first created), otherwise it will be skipped</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">github.event_name == 'pull_request' &amp;&amp; github.event.action == 'opened'</span>

    <span class="c1"># If this job fails, it won't affect the whole workflow, other jobs will continue</span>
    <span class="na">continue-on-error</span><span class="pi">:</span> <span class="kc">true</span>
    
    <span class="c1"># Set maximum timeout to prevent endless waiting in case of abnormal situations</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">10</span>
    
    <span class="c1"># Runner Label - use GitHub Hosted Runner ubuntu-latest to run the job</span>
    <span class="c1"># For private repos, usage is counted and may incur charges if exceeded</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Assign self if No Assignee</span>
        <span class="c1"># Use GitHub Script (JavaScript) to write the script (Node.js environment)</span>
        <span class="c1"># More convenient and cleaner than using Shell Script directly above</span>
        <span class="c1"># No need to inject environment variables or GITHUB_TOKEN manually</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">// github-script automatically injects the context variable for direct JavaScript access</span>
            <span class="s">// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context</span>

            <span class="s">const issue = context.payload.pull_request; // To support Issues too, write as context.payload.issue \\|\\| context.payload.pull_request</span>
            <span class="s">const assignees = issue.assignees \\|\\| [];</span>
            <span class="s">const me = context.actor;</span>

            <span class="s">if (assignees.length === 0) {</span>
              <span class="s">// Assign self as assignee</span>
              <span class="s">await github.rest.issues.addAssignees({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">issue_number</span><span class="err">:</span> <span class="s">issue.number,</span>
                <span class="s">assignees</span><span class="err">:</span> <span class="pi">[</span><span class="nv">me</span><span class="pi">]</span>
              <span class="err">}</span><span class="s">);</span>

              <span class="s">// Post a comment notification</span>
              <span class="s">await github.rest.issues.createComment({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">issue_number</span><span class="err">:</span> <span class="s">issue.number,</span>
                <span class="s">body</span><span class="err">:</span> <span class="err">`</span><span class="s">🔧 No assignee was set, so I have assigned this to myself (@${me}).`</span>
              <span class="s">});</span>
            <span class="s">}</span>
</code></pre></div></div>

<p>This time, we demonstrate using GitHub Script (JavaScript) to write the script, offering more flexibility and easier syntax.</p>

<p>Of course, if you want to follow what I said earlier—one file per task—you can remove the Job If condition and set the trigger directly in the Action Workflow:</p>

<p><code class="language-plaintext highlighter-rouge">Automation-PullRequest-Auto-Assign.yml</code> <strong>:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow(Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Pull Request Automation - Auto Assignee Self</span>

<span class="c1"># Trigger event</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># PR event</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="c1"># PR - when opened</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">assign-self-if-no-assignee</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Automatically assign to self if no assignee is specified</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Please refer to the previous text, omitted....</span>
</code></pre></div></div>

<p>After committing files to the repo’s main branch, opening a new PR will automatically trigger GitHub Actions:</p>

<p><img src="/assets/404bd5c70040/1*Y-A6owUibNEFBoRFHnqIYg.webp" alt="" loading="lazy" decoding="async" width="1271" height="708" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjcxIiBoZWlnaHQ9IjcwOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*Y-A6owUibNEFBoRFHnqIYg.png" /></p>

<p>Now there are two Jobs to run!</p>

<h4 id="execution-result-1">Execution Result</h4>

<p><img src="/assets/404bd5c70040/1*23muCVgUJKmZt746khBMFQ.webp" alt="Demo PR" loading="lazy" decoding="async" width="1200" height="979" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk3OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*23muCVgUJKmZt746khBMFQ.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<p>After the execution is complete and successful, if the PR has no Assignees, it will automatically assign the PR author and comment a message. (All actions are performed using the <code class="language-plaintext highlighter-rouge">github-actions</code> identity)</p>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Automation-PullRequest.yml" target="_blank">Automation-PullRequest.yml</a></strong></p>

<h4 id="test-reopened-pr">Test Reopened PR</h4>

<p><img src="/assets/404bd5c70040/1*mcDa7TZjv6mO7HtwhOE-Vw.webp" alt="" loading="lazy" decoding="async" width="960" height="407" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iNDA3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*mcDa7TZjv6mO7HtwhOE-Vw.png" /></p>

<p>You can see that only the Size Label Job runs, while the Auto Assignee Job is skipped.</p>

<blockquote>
  <p><em>This task also has a ready-made Action for direct reuse, see: <a href="https://github.com/pozil/auto-assign-issue" target="_blank">pozil/auto-assign-issue</a>.</em></p>
</blockquote>

<h3 id="case--3">Case — 3</h3>

<p>Automatically count the current number of PRs and how long they have been open every day at 9 AM, then send a notification message to the Slack workspace. Also, automatically close PRs that have been open for more than 3 months.</p>

<h4 id="result-image-2">Result Image</h4>

<p><img src="/assets/404bd5c70040/1*0stX9KpZi6PcXpG-90wyIg.webp" alt="" loading="lazy" decoding="async" width="508" height="147" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDgiIGhlaWdodD0iMTQ3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*0stX9KpZi6PcXpG-90wyIg.png" /></p>

<p><img src="/assets/404bd5c70040/1*Nbg3r1zzhx24YIBEjPJnaw.webp" alt="" loading="lazy" decoding="async" width="928" height="187" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iMTg3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*Nbg3r1zzhx24YIBEjPJnaw.png" /></p>

<ul>
  <li>
    <p>Slack workspace receives daily morning reports automatically</p>
  </li>
  <li>
    <p>Automatically Close PRs Older Than 90 Days</p>
  </li>
</ul>

<h4 id="workflow-process-2">Workflow Process</h4>

<ul>
  <li>
    <p>GitHub Actions Automatically Triggered Every Day at 9 AM</p>
  </li>
  <li>
    <p>Triggering GitHub Actions Workflow</p>
  </li>
  <li>
    <p>GitHub script to get the list of open PRs and count how many days they have been open</p>
  </li>
  <li>
    <p>Send Statistical Report Message to Slack</p>
  </li>
  <li>
    <p>Close PRs Older Than 90 Days</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
</ul>

<h4 id="hands-on-practice-2">Hands-on Practice</h4>

<p>Repo → Actions → New workflow → set up a workflow yourself.</p>

<p><strong>Filename:</strong> <code class="language-plaintext highlighter-rouge">Automation-PullRequest-Daily.yml</code></p>

<h4 id="automation-pullrequest-dailyyml">Automation-PullRequest-Daily.yml</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Workflow(Action) name</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Pull Request Automation - Daily Checker</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Scheduled automatic execution</span>
  <span class="c1"># https://crontab.guru/</span>
  <span class="c1"># UTC time</span>
  <span class="na">schedule</span><span class="pi">:</span>
    <span class="c1"># 01:00 UTC = 09:00 UTC+8 daily</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0</span><span class="nv"> </span><span class="s">1</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*'</span>
  <span class="c1"># Manual trigger</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="c1"># Job tasks</span>
<span class="c1"># Jobs run concurrently</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># Job ID</span>
  <span class="na">caculate-pr-status</span><span class="pi">:</span>
    <span class="c1"># Job name (optional, better readability in logs)</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Calculate PR Status</span>
    <span class="c1"># Runner Label - use GitHub Hosted Runner ubuntu-latest to run the job</span>
    <span class="c1"># For Private Repos, usage is counted and may incur costs if exceeded</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="c1"># Job Output</span>
    <span class="na">outputs</span><span class="pi">:</span>
      <span class="na">pr_list</span><span class="pi">:</span> <span class="s">${{ steps.pr-info.outputs.pr_list }}</span>

    <span class="c1"># Job steps</span>
    <span class="c1"># Steps run sequentially</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Step name</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Fetch open PRs and calculate</span>
        <span class="c1"># Set id to reference Output externally</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">pr-info</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">const now = new Date();</span>
            <span class="s">const per_page = 100;</span>
            <span class="s">let page = 1;</span>
            <span class="s">let allPRs = [];</span>
      
            <span class="s">while (true) {</span>
              <span class="s">const { data</span><span class="err">:</span> <span class="s">prs } = await github.rest.pulls.list({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">state</span><span class="err">:</span> <span class="s1">'</span><span class="s">open'</span><span class="err">,</span>
                <span class="s">per_page,</span>
                <span class="s">page,</span>
              <span class="s">});</span>
              <span class="s">if (prs.length === 0) break;</span>
              <span class="s">allPRs = allPRs.concat(prs);</span>
              <span class="s">if (prs.length &lt; per_page) break;</span>
              <span class="s">page++;</span>
            <span class="s">}</span>
      
            <span class="s">const result = allPRs.map(pr =&gt; {</span>
              <span class="s">const created = new Date(pr.created_at);</span>
              <span class="s">const daysOpen = Math.floor((now - created) / (1000 * 60 * 60 * 24));</span>
              <span class="s">return {</span>
                <span class="s">pr</span><span class="err">:</span> <span class="s">pr.number.toString(),</span>
                <span class="s">title</span><span class="err">:</span> <span class="s">pr.title,</span>
                <span class="s">idle</span><span class="err">:</span> <span class="s">daysOpen</span>
              <span class="s">};</span>
            <span class="s">});</span>

            <span class="s">// Set output, only accepts String</span>
            <span class="s">core.setOutput('pr_list', JSON.stringify(result));</span>
  <span class="c1"># ----</span>
  <span class="na">send-pr-summary-message-to-slack</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Send PR Summary Message to Slack</span>
    <span class="c1"># Jobs run concurrently by default, use needs to wait for dependent jobs</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">caculate-pr-status</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Generate Message</span>
        <span class="c1"># Set id to reference Output externally</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">gen-msg</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">const prList = JSON.parse(`${{ needs.caculate-pr-status.outputs.pr_list }}`);</span>
            <span class="s">const blocks = [];</span>
      
            <span class="s">// Title</span>
            <span class="s">blocks.push({</span>
              <span class="s">type</span><span class="err">:</span> <span class="s2">"</span><span class="s">section"</span><span class="err">,</span>
              <span class="na">text</span><span class="pi">:</span> <span class="pi">{</span>
                <span class="nv">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">mrkdwn"</span><span class="pi">,</span>
                <span class="nv">text</span><span class="pi">:</span> <span class="err">`</span><span class="nv">📬 *Open PR Report*\nTotal</span><span class="pi">:</span> <span class="err">*</span><span class="nv">$</span><span class="pi">{</span><span class="nv">prList.length</span><span class="pi">}</span><span class="err">*</span> <span class="nv">PR(s)`</span>
              <span class="pi">}</span>
          <span class="err">  }</span><span class="s">);</span>
      
            <span class="s">// One line per PR</span>
            <span class="s">for (const pr of prList) {</span>
              <span class="s">blocks.push({</span>
                <span class="s">type</span><span class="err">:</span> <span class="s2">"</span><span class="s">section"</span><span class="err">,</span>
                <span class="na">text</span><span class="pi">:</span> <span class="pi">{</span>
                  <span class="nv">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">mrkdwn"</span><span class="pi">,</span>
                  <span class="nv">text</span><span class="pi">:</span> <span class="err">`</span><span class="nv">• &lt;https</span><span class="pi">:</span><span class="nv">//github.com/$</span><span class="pi">{</span><span class="nv">context.repo.owner</span><span class="pi">}</span><span class="nv">/$</span><span class="pi">{</span><span class="nv">context.repo.repo</span><span class="pi">}</span><span class="nv">/pull/$</span><span class="pi">{</span><span class="nv">pr.pr</span><span class="pi">}</span><span class="nv">\\|PR</span> <span class="c1">#${pr.pr}&gt; *${pr.title}* - 🕒 ${pr.idle} day(s)`</span>
                <span class="pi">}</span>
          <span class="err">    }</span><span class="s">);</span>
            <span class="s">}</span>

            <span class="s">// Set output, only accepts String</span>
            <span class="s">core.setOutput('blocks', JSON.stringify(blocks));</span>

            
      <span class="c1"># Use official Slack API GitHub Action</span>
      <span class="c1"># https://tools.slack.dev/slack-github-action/sending-techniques/sending-data-slack-api-method/</span>
      <span class="c1"># Send message</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Post text to a Slack channel</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">slackapi/slack-github-action@v2.1.0</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">method</span><span class="pi">:</span> <span class="s">chat.postMessage</span>
          <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
          <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">channel</span><span class="err">:</span> <span class="s">${{ vars.SLACK_TEAM_CHANNEL_ID }}</span>
            <span class="s">blocks</span><span class="err">:</span> <span class="s">${{ steps.gen-msg.outputs.blocks }}</span>
  <span class="c1"># ----</span>
  <span class="na">auto-close-old-prs</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Auto Close Old PRs</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">caculate-pr-status</span><span class="pi">]</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Auto close PRs opened more than 90 days</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">const prList = JSON.parse(`${{ needs.caculate-pr-status.outputs.pr_list }}`);</span>
            <span class="s">const oldPRs = prList.filter(pr =&gt; pr.idle &gt; 90);</span>

            <span class="s">for (const pr of oldPRs) {</span>
              <span class="s">await github.rest.pulls.update({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">pull_number</span><span class="err">:</span> <span class="s">parseInt(pr.pr),</span>
                <span class="s">state</span><span class="err">:</span> <span class="s1">'</span><span class="s">closed'</span>
              <span class="err">}</span><span class="s">);</span>

              <span class="s">await github.rest.issues.createComment({</span>
                <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
                <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
                <span class="s">issue_number</span><span class="err">:</span> <span class="s">parseInt(pr.pr),</span>
                <span class="s">body</span><span class="err">:</span> <span class="err">`</span><span class="s">⚠️ This pull request has been automatically closed because it has been open for more than 90 days. Please reopen if needed.`</span>
              <span class="s">});</span>
            <span class="s">}</span>
            <span class="s">console.log(`Closed ${oldPRs.length} PR(s)`);</span>
</code></pre></div></div>

<p>In this example, we use:</p>

<ul>
  <li>
    <p>on: schedule<br />
Crontab schedule for automatic triggering and workflow_dispatch for manual triggering</p>
  </li>
  <li>
    <p>Job output / Step output (both can only be strings)</p>
  </li>
  <li>
    <p>Multiple jobs run concurrently by default but can use <code class="language-plaintext highlighter-rouge">needs</code> to set time-dependent dependencies.</p>
  </li>
  <li>
    <p>Getting Settings from Repo Secrets/Variables</p>
  </li>
  <li>
    <p>Integrating Slack API</p>
  </li>
</ul>

<p><strong>Repo Secrets — Add</strong> <code class="language-plaintext highlighter-rouge">SLACK_BOT_TOKEN</code></p>

<p><img src="/assets/404bd5c70040/1*YC4DBHtKmX5XGgcSSzaE5A.webp" alt="" loading="lazy" decoding="async" width="1200" height="970" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk3MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*YC4DBHtKmX5XGgcSSzaE5A.png" /></p>

<ul>
  <li>For setting up a Slack App and configuring message permissions, you can refer to my <a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">previous article</a>.</li>
</ul>

<p><strong>Repo Variables — Add</strong> <code class="language-plaintext highlighter-rouge">SLACK_TEAM_CHANNEL_ID</code></p>

<p><img src="/assets/404bd5c70040/1*X4gZ5_IRvsiehAL0O9AleQ.webp" alt="" loading="lazy" decoding="async" width="1174" height="998" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc0IiBoZWlnaHQ9Ijk5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*X4gZ5_IRvsiehAL0O9AleQ.png" /></p>

<p>After committing files to the repo’s main branch, go back to Actions and trigger it manually to test:</p>

<blockquote>
  <p><em>It will be automatically triggered daily in the future.</em></p>
</blockquote>

<p><img src="/assets/404bd5c70040/1*WNFKXl-QC2WGyaSWep5-DA.webp" alt="" loading="lazy" decoding="async" width="1048" height="438" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ4IiBoZWlnaHQ9IjQzOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*WNFKXl-QC2WGyaSWep5-DA.png" /></p>

<p>Actions → Pull Request Automation — Daily Checker → Run workflow → Branch: main → Run workflow.</p>

<p><strong>After execution, you can click to view the execution status:</strong></p>

<p><img src="/assets/404bd5c70040/1*4mvOxnd1uS4ZlxnNoNdHaQ.webp" alt="" loading="lazy" decoding="async" width="1087" height="545" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDg3IiBoZWlnaHQ9IjU0NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*4mvOxnd1uS4ZlxnNoNdHaQ.png" /></p>

<p><img src="/assets/404bd5c70040/1*BluzFgY2tM2E7DwoVq1jQw.webp" alt="" loading="lazy" decoding="async" width="1030" height="458" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDMwIiBoZWlnaHQ9IjQ1OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*BluzFgY2tM2E7DwoVq1jQw.png" /></p>

<p>Due to the <code class="language-plaintext highlighter-rouge">needs</code> constraint, the job flow will have <code class="language-plaintext highlighter-rouge">Calculate PR Status</code> complete first, then <code class="language-plaintext highlighter-rouge">Auto Close Old PRs</code> and <code class="language-plaintext highlighter-rouge">Send PR Summary Message to Slack</code> will run concurrently.</p>

<h4 id="execution-result-2">Execution Result</h4>

<p><strong>After all tasks are successfully executed, you can check the Slack message:</strong></p>

<p><img src="/assets/404bd5c70040/1*0stX9KpZi6PcXpG-90wyIg.webp" alt="" loading="lazy" decoding="async" width="508" height="147" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDgiIGhlaWdodD0iMTQ3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*0stX9KpZi6PcXpG-90wyIg.png" /></p>

<p>Success 🚀🚀🚀</p>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Automation-PullRequest-Daily.yml" target="_blank">Automation-PullRequest-Daily.yml</a></strong></p>

<h3 id="summary">Summary</h3>

<blockquote>
  <p>I hope the three examples above give you a basic understanding of GitHub Actions and inspire your own automation ideas. You can create your own workflows (be sure to first check the <a href="https://docs.github.com/en/actions/reference/events-that-trigger-workflows" target="_blank">Trigger Events</a>), then write scripts to execute them. Also, remember to visit the <a href="https://github.com/marketplace?type=actions" target="_blank"><strong>Marketplace</strong></a> to find existing actions you can use to stand on the shoulders of giants.</p>
</blockquote>

<p>This article is just an introduction (it doesn’t even include code checkout). The next article, <a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a>, will cover more advanced and complex GitHub Actions workflows.</p>

<h4 id="github-automation-extended-topics">GitHub Automation Extended Topics</h4>

<p>GitHub can integrate with Slack to subscribe to Repo PR update notifications, Push Default Branch notifications, and more.</p>

<p><img src="/assets/404bd5c70040/1*BMTT4koRBIIsQ7_mklC86w.webp" alt="" loading="lazy" decoding="async" width="439" height="430" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MzkiIGhlaWdodD0iNDMwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*BMTT4koRBIIsQ7_mklC86w.png" /></p>

<p><strong>Issue 1. GitHub messages cannot mention people, only GitHub accounts are mentioned, Slack accounts do not receive notifications:</strong></p>

<p><img src="/assets/404bd5c70040/1*Ddp0cEtXE10hScM1rozmtg.webp" alt="" loading="lazy" decoding="async" width="1300" height="946" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzAwIiBoZWlnaHQ9Ijk0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*Ddp0cEtXE10hScM1rozmtg.png" /></p>

<p><img src="/assets/404bd5c70040/1*uHGJ7l__AdYoWT9KpvihLQ.webp" alt="" loading="lazy" decoding="async" width="1043" height="775" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQzIiBoZWlnaHQ9Ijc3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*uHGJ7l__AdYoWT9KpvihLQ.png" /></p>

<p><a href="https://slack.github.com/" target="_blank">Slack App</a> or search for GitHub in Apps → open the message window → complete the <strong>Connect GitHub Account</strong> step. This allows GitHub to know your corresponding Slack UID for tagging you.</p>

<p><strong>Question 2. Pull Request Reminder</strong></p>

<p>I remember this was originally a third-party feature, but it was later integrated directly into GitHub. Don’t foolishly write scripts yourself using GitHub Actions!</p>

<p><img src="/assets/404bd5c70040/1*BpMd2K0NBDE6xB1XzFY94Q.webp" alt="" loading="lazy" decoding="async" width="1084" height="1291" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDg0IiBoZWlnaHQ9IjEyOTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*BpMd2K0NBDE6xB1XzFY94Q.png" /></p>

<blockquote>
  <p><a href="https://docs.github.com/en/organizations/organizing-members-into-teams/managing-scheduled-reminders-for-your-team" target="_blank"><strong><em>GitHub’s built-in setting is by Team. You need to first add a Team to your organization and add the Repo to the Team before setting up Pull Request Reminders.</em></strong></a></p>
</blockquote>

<ul>
  <li>After entering the Slack Channel and rules and saving, daily reminder messages and Reviewer tagging will be sent automatically. Setup reference can be found <a href="https://michaelheap.com/github-scheduled-reminders/" target="_blank">here</a>. The result is shown below:</li>
</ul>

<p><img src="/assets/404bd5c70040/1*kvqFXkIKIxP79Wm8vIaN0Q.webp" alt="&lt;https://michaelheap.com/github-scheduled-reminders/&gt;" loading="lazy" decoding="async" width="500" height="315" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDAiIGhlaWdodD0iMzE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*kvqFXkIKIxP79Wm8vIaN0Q.png" /></p>

<p><a href="https://michaelheap.com/github-scheduled-reminders/" target="_blank">https://michaelheap.com/github-scheduled-reminders/</a></p>

<p><strong>Issue 4. GitHub PR message, author does not receive notification:</strong></p>

<p><img src="/assets/404bd5c70040/1*wmtXwXAcQZDXqInRd0ivPw.webp" alt="" loading="lazy" decoding="async" width="792" height="535" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTIiIGhlaWdodD0iNTM1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*wmtXwXAcQZDXqInRd0ivPw.png" /></p>

<blockquote>
  <p><em>This is a long-standing issue: the PR Slack message <strong>does not actually notify the author when someone replies in the threads</strong>. The Slack message sent by GitHub only mentions Reviewers, not the Assignee or the PR author.</em></p>
</blockquote>

<p>At this point, GitHub Actions becomes useful as an automation tool. We can add a Job to append the author tag at the end of the PR Description, so the message sent to Slack will also tag the author:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Workflow (Action) name</span>
name: Pull Request Automation
<span class="c"># Please refer to previous text, omitted....</span>

  <span class="c"># ---------</span>
  append-author-to-pr-description:
    name: Append author to PR description
    <span class="c"># Since this is a shared trigger event, the job itself checks to run only when the Pull Request is opened (first created), otherwise the job will be skipped</span>
    <span class="k">if</span>: github.event_name <span class="o">==</span> <span class="s1">'pull_request'</span> <span class="o">&amp;&amp;</span> github.event.action <span class="o">==</span> <span class="s1">'opened'</span>
    
    <span class="c"># If this job fails, it won't affect the entire workflow, continue with other jobs</span>
    <span class="k">continue</span><span class="nt">-on-error</span>: <span class="nb">true</span>
    
    <span class="c"># Set maximum timeout to prevent endless waiting in abnormal situations</span>
    timeout-minutes: 10
    
    <span class="c"># Runner Label - use GitHub Hosted Runner ubuntu-latest to run the job</span>
    <span class="c"># For Private Repos, usage is counted and may incur costs if exceeded</span>
    <span class="c"># But such small automation tasks rarely exceed limits</span>
    runs-on: ubuntu-latest
    steps:
      - name: Append author to PR description
        <span class="nb">env</span>:
          <span class="c"># secrets.GITHUB_TOKEN is automatically generated during GitHub Actions execution, no need to set manually in Secrets, it has some GitHub Repo API scopes</span>
          <span class="c"># gh (GitHub) CLI needs GH_TOKEN injected into ENV to have permission to operate</span>
          <span class="c"># https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows</span>
          GH_TOKEN: <span class="k">${</span><span class="p">{ secrets.GITHUB_TOKEN </span><span class="k">}</span><span class="o">}</span>

          <span class="c">#   ${{ github.xxx }} is a GitHub Actions Context expression</span>
          <span class="c">#   Not a shell variable, but replaced by GitHub Actions during YAML parsing with corresponding values</span>
          <span class="c">#   More parameters: https://docs.github.com/en/actions/learn-github-actions/contexts#github-context</span>
          PR_NUMBER: <span class="k">${</span><span class="p">{ github.event.pull_request.number </span><span class="k">}</span><span class="o">}</span>
          AUTHOR_TAG: <span class="s1">'@${{ github.event.pull_request.user.login }}'</span>
        run: <span class="se">\\</span>|
          <span class="nv">PR_BODY</span><span class="o">=</span><span class="si">$(</span>gh <span class="nb">pr </span>view <span class="nv">$PR_NUMBER</span> <span class="nt">--repo</span> <span class="k">${</span><span class="p">{ github.repository </span><span class="k">}</span><span class="o">}</span> <span class="nt">--json</span> body <span class="nt">-q</span> <span class="s2">".body"</span><span class="si">)</span>
          <span class="nv">NEW_BODY</span><span class="o">=</span><span class="si">$(</span><span class="nb">printf</span> <span class="s2">"%s</span><span class="se">\n\n</span><span class="s2">Created by %s"</span> <span class="s2">"</span><span class="nv">$PR_BODY</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$AUTHOR_TAG</span><span class="s2">"</span><span class="si">)</span>
          gh <span class="nb">pr </span>edit <span class="nv">$PR_NUMBER</span> <span class="nt">--repo</span> <span class="k">${</span><span class="p">{ github.repository </span><span class="k">}</span><span class="o">}</span> <span class="nt">--body</span> <span class="s2">"</span><span class="nv">$NEW_BODY</span><span class="s2">"</span>
</code></pre></div></div>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Automation-PullRequest.yml" target="_blank">Automation-PullRequest.yml</a></strong></p>

<p>When opening a PR, the author information will be automatically added to the description, and the Slack message will correctly tag the author:</p>

<p><img src="/assets/404bd5c70040/1*UtTDtIQJ0vJks79UzCQSAQ.webp" alt="" loading="lazy" decoding="async" width="953" height="370" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTMiIGhlaWdodD0iMzcwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*UtTDtIQJ0vJks79UzCQSAQ.png" /></p>

<p><img src="/assets/404bd5c70040/1*MjVsFqE--xAq1zSQfCNVdQ.webp" alt="" loading="lazy" decoding="async" width="416" height="244" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTYiIGhlaWdodD0iMjQ0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*MjVsFqE--xAq1zSQfCNVdQ.png" /></p>

<p><strong>Issue 5. GitHub sends too many notification emails, which is very annoying:</strong></p>

<p>After setting up notifications for both GitHub Repo and Slack, all notifications will be received in Slack. You can go to your GitHub personal settings to turn off email notifications:</p>

<p><img src="/assets/404bd5c70040/1*INgfO9B2bNFy-Nd1ZjnzOw.webp" alt="" loading="lazy" decoding="async" width="1200" height="630" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjYzMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*INgfO9B2bNFy-Nd1ZjnzOw.png" /></p>

<h4 id="others">Others</h4>

<p>Actions currently do not have a directory structure, but you can pin up to five Actions on the Actions page. You can also disable an Action to pause it.</p>

<p><img src="/assets/404bd5c70040/1*ZD-5xkparhJqtnm2REeu6A.webp" alt="" loading="lazy" decoding="async" width="1238" height="599" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjM4IiBoZWlnaHQ9IjU5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*ZD-5xkparhJqtnm2REeu6A.png" /></p>

<p>You can view GitHub Actions usage and performance in Insights:</p>

<p><img src="/assets/404bd5c70040/1*re91ZRtQ0frOF6GVJ-MKrw.webp" alt="" loading="lazy" decoding="async" width="1271" height="626" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjcxIiBoZWlnaHQ9IjYyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*re91ZRtQ0frOF6GVJ-MKrw.png" /></p>

<h3 id="reuse-workflow-supplement">Reuse Workflow Supplement</h3>

<p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">Next article</a> uses the same Repo to split Reuse Workflow. Here is an additional example of splitting Reuse Workflow across different Repos.</p>

<p>First, define a Reuse Workflow: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo-share-actions/blob/main/.github/workflows/Automation-label-pr-base-branch.yml" target="_blank"><strong>Automation-label-pr-base-branch.yml:</strong></a> <strong>in the <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo-share-actions/blob/main/.github/workflows/Automation-label-pr-base-branch.yml" target="_blank">ZhgChgLi/github-actions-ci-cd-demo-share-actions</a> repo</strong>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Automation-label-pr-base-branch</span>
<span class="na">run-name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">[Automation-label-pr-base-branch]</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">github.event.inputs.PR_NUMBER</span><span class="nv"> </span><span class="se">\\</span><span class="s">|</span><span class="se">\\</span><span class="s">|</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>

<span class="na">concurrency</span><span class="pi">:</span>
  <span class="na">group</span><span class="pi">:</span> <span class="s">${{ github.workflow }}-${{ github.event.inputs.PR_NUMBER \\|\\| github.ref }}</span>
  <span class="na">cancel-in-progress</span><span class="pi">:</span> <span class="kc">true</span>

<span class="c1"># Trigger events</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="c1"># Triggered by other workflows calling this workflow</span>
  <span class="na">workflow_call</span><span class="pi">:</span>
    <span class="c1"># Input data</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="c1"># PR Number</span>
      <span class="na">PR_NUMBER</span><span class="pi">:</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
    <span class="c1"># Secret inputs</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="na">GH_TOKEN</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s2">"</span><span class="s">GitHub</span><span class="nv"> </span><span class="s">token</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">API</span><span class="nv"> </span><span class="s">access"</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">label-pr-base-branch</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Label PR Base Branch</span>
    <span class="na">continue-on-error</span><span class="pi">:</span> <span class="kc">true</span>
    <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">10</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Label PR by Base Branch</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">label-pr-base-branch</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">GH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.GITHUB_TOKEN }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">PR_NUMBER="${{ inputs.PR_NUMBER }}"</span>
          <span class="s">REPO="${{ github.repository }}"</span>

          <span class="s">echo "📦 Processing PR</span> <span class="c1">#$PR_NUMBER in repo $REPO"</span>

          <span class="c1"># Get the base branch of the PR</span>
          <span class="s">BASE_BRANCH=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json baseRefName --jq '.baseRefName')</span>
          <span class="s">echo "🔖 PR Base Branch</span><span class="err">:</span> <span class="s">$BASE_BRANCH"</span>

          <span class="s"># Allowed base branch labels</span>
          <span class="s">BRANCH_LABELS=("develop" "main" "master")</span>

          <span class="s"># Check if label is in allowed list</span>
          <span class="s">if [[ " ${BRANCH_LABELS[@]} " =~ " ${BASE_BRANCH} " ]]; then</span>
            <span class="s">LABEL="$BASE_BRANCH"</span>
          <span class="s">else</span>
            <span class="s">echo "⚠️ Base branch '$BASE_BRANCH' not in allowed list, skipping label."</span>
            <span class="s">exit </span><span class="m">0</span>
          <span class="s">fi</span>

          <span class="s"># Remove existing base branch labels</span>
          <span class="s">EXISTING_LABELS=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json labels --jq '.labels[].name')</span>
          <span class="s">for EXISTING in $EXISTING_LABELS; do</span>
            <span class="s">if [[ " ${BRANCH_LABELS[@]} " =~ " ${EXISTING} " ]]; then</span>
              <span class="s">echo "🧹 Removing existing base branch label</span><span class="err">:</span> <span class="s">$EXISTING"</span>
              <span class="s">gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$EXISTING"</span>
            <span class="s">fi</span>
          <span class="s">done</span>

          <span class="s"># Create label if it does not exist</span>
          <span class="s">if ! gh label list --repo "$REPO" \\| grep -q "^$LABEL$"; then</span>
            <span class="s">echo "🆕 Creating missing label</span><span class="err">:</span> <span class="s">$LABEL"</span>
            <span class="s">gh label create "$LABEL" --repo "$REPO" --description "PR targeting $LABEL branch" --color "ededed"</span>
          <span class="s">else</span>
            <span class="s">echo "✅ Label '$LABEL' already exists"</span>
          <span class="s">fi</span>

          <span class="s"># Add the base branch label</span>
          <span class="s">echo "🏷️ Adding label '$LABEL' to PR</span> <span class="c1">#$PR_NUMBER"</span>
          <span class="s">gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label "$LABEL"</span>
</code></pre></div></div>

<p>Back to our <strong>main <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Demo-Reuse-Action-From-Another-Repo.yml" target="_blank">ZhgChgLi/github-actions-ci-cd-demo</a> repo,</strong> add a main <strong>Workflow <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Demo-Reuse-Action-From-Another-Repo.yml" target="_blank">Demo-Reuse-Action-From-Another-Repo.yml</a>:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Automation-label-pr-base-branch</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">pull_request</span><span class="pi">:</span>
    <span class="na">types</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">opened</span><span class="pi">]</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">call-label-pr-workflow</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">Call Base Branch Label Workflow</span>
    <span class="c1"># ✅ If the called workflow is in the same repo:</span>
    <span class="c1">#    uses: ./.github/workflows/Automation-label-pr-base-branch.yml</span>
    <span class="c1">#    Note: This method cannot specify a branch (it always uses the caller workflow's branch)</span>
    <span class="c1">#    Example references:</span>
    <span class="c1">#    - CD-Deploy-Form.yml calls a workflow in the same repo</span>
    <span class="c1">#    - CD-Deploy.yml calls a workflow across repos</span>
    <span class="c1">#</span>
    <span class="c1"># ✅ If the called workflow is in another repo:</span>
    <span class="c1">#    uses: {owner}/{repo}/.github/workflows/{file}.yml@{branch_or_tag}</span>
    <span class="c1">#    You can specify a branch or tag</span>
    <span class="c1">#    ref: https://github.com/ZhgChgLi/github-actions-ci-cd-demo-share-actions/blob/main/.github/workflows/Automation-label-pr-base-branch.yml</span>
    <span class="na">uses</span><span class="pi">:</span> <span class="s">ZhgChgLi/github-actions-ci-cd-demo-share-actions/.github/workflows/Automation-label-pr-base-branch.yml@main</span>
    <span class="na">with</span><span class="pi">:</span>
      <span class="na">PR_NUMBER</span><span class="pi">:</span> <span class="s">${{ github.event.number }}</span>
      
    <span class="c1"># To inherit all secrets from the caller workflow, use `inherit`</span>
    <span class="c1"># secrets: inherit</span>
    <span class="c1">#</span>
    <span class="c1"># To pass specific secrets only, specify individually</span>
    <span class="na">secrets</span><span class="pi">:</span>
      <span class="na">GH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.GITHUB_TOKEN }}</span>
</code></pre></div></div>

<p>Try creating a PR after committing the files.</p>

<p><strong>Workflow: The main repo’s <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Demo-Reuse-Action-From-Another-Repo.yml" target="_blank">Demo-Reuse-Action-From-Another-Repo.yml</a> → calls and passes parameters to another repo’s <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo-share-actions/blob/main/.github/workflows/Automation-label-pr-base-branch.yml" target="_blank">Automation-label-pr-base-branch.yml</a> to run the task → then returns the result to the main repo.</strong></p>

<p><img src="/assets/404bd5c70040/1*13bqlAgpBkHM7UthTJAuOg.webp" alt="" loading="lazy" decoding="async" width="1119" height="453" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE5IiBoZWlnaHQ9IjQ1MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*13bqlAgpBkHM7UthTJAuOg.png" /></p>

<p><img src="/assets/404bd5c70040/1*CAF3HzS4PHBveDQYu3H73g.webp" alt="" loading="lazy" decoding="async" width="402" height="145" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDIiIGhlaWdodD0iMTQ1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*CAF3HzS4PHBveDQYu3H73g.png" /></p>

<p>Success!</p>

<h4 id="private-repo-access-issues">Private Repo Access Issues</h4>

<p>Here is a security note: Public Repos have no issues, but if you reuse a workflow in a Private Repo, it can only be accessed and used by <strong>other repos within the same organization + those repos must also be Private + permission must be granted</strong>.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Public — ZhgChgLi/ReuseRepo</code> + <code class="language-plaintext highlighter-rouge">Private or Public-ZhgChgli or harry/myRepo</code> = ✅</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Private — ZhgChgLi/ReuseRepo</code> + <code class="language-plaintext highlighter-rouge">Private or Public— harry/myRepo</code> = ❌</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Private — ZhgChgLi/ReuseRepo</code> + <code class="language-plaintext highlighter-rouge">Public — ZhgChgLi/anotherRepo</code> = ❌</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Private — ZhgChgLi/ReuseRepo</code> + <code class="language-plaintext highlighter-rouge">Private — ZhgChgLi/anotherRepo</code> = ✅</p>
  </li>
</ul>

<p><strong>To set allowed methods, go to Reuse Workflow Repo → Settings → Actions → General → Access:</strong></p>

<p><img src="/assets/404bd5c70040/1*iZCEifgyArTJS3URMSk01g.webp" alt="" loading="lazy" decoding="async" width="725" height="300" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjUiIGhlaWdodD0iMzAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*iZCEifgyArTJS3URMSk01g.png" /></p>

<p>Change the checkbox:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Accessible from repositories in the 'ZhgChgLi' organization
Workflows in other repositories within the 'ZhgChgLi' organization can access the actions and reusable workflows in this repository. Access is allowed only from private repositories.
</code></pre></div></div>

<p>Click “Save” to save and complete.</p>

<p><strong>If you don’t have access permission, the following error message will appear:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Invalid workflow file: .github/workflows/Demo-reuse-action-from-another-repo.yml#L21
error parsing called workflow
<span class="s2">".github/workflows/Demo-reuse-action-from-another-repo.yml"</span>
-&gt; <span class="s2">"ZhgChgLi/github-actions-ci-cd-demo-share-actions/.github/workflows/Automation-label-pr-base-branch.yml@main"</span>
: workflow was not found.
</code></pre></div></div>

<h3 id="additional-github-actions-script-syntax">Additional GitHub Actions Script Syntax</h3>

<p>A supplement for the variable syntax that might confuse everyone: <code class="language-plaintext highlighter-rouge">${{ XXX }}</code>, <code class="language-plaintext highlighter-rouge">${XXX}</code>, or <code class="language-plaintext highlighter-rouge">$XXX</code>.</p>

<p><strong>Demo-Env-Vars-.yml:</strong></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Demo-Env-Vars</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">print-env-vars</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">print-env</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">env</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">context-vars-vs-vars</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">SAY_MY_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Heisenberg"</span>
          <span class="c1"># GitHub Actions Context expression, explained below</span>
          <span class="na">FROM_REF</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s"># Shell Script</span><span class="err">:</span>
          <span class="c1"># Referencing custom injected ENV variables</span>
          <span class="c1"># ${SAY_MY_NAME} or $SAY_MY_NAME both work</span>
          <span class="c1"># ${XX} is better when concatenating strings:</span>
          <span class="s">echo "HI</span><span class="err">:</span> <span class="s">${SAY_MY_NAME}"</span>
          
          <span class="s"># 💡 GitHub Actions Context expression</span>
          <span class="s"># This is not a shell variable, but replaced by GitHub Actions during YAML parsing</span>
          <span class="s"># ⚠ Running locally or outside GitHub Actions will fail</span>
          <span class="s"># 🔗 https://docs.github.com/en/actions/learn-github-actions/contexts#github-context</span>
          <span class="s">BRANCH_NAME_FROM_CONTEXT="${{ github.ref }}"</span>
          
          <span class="s"># 💡 GitHub Actions runtime environment variables</span>
          <span class="s"># These variables are automatically injected by GitHub Actions when the runner runs the shell</span>
          <span class="s"># ✅ Can be pre-defined with export or ENV in other environments</span>
          <span class="s"># 🔗 https://docs.github.com/en/actions/learn-github-actions/environment-variables</span>
          <span class="s">BRANCH_NAME_FROM_ENV_VARS="${GITHUB_REF}"</span>
          
          <span class="s">echo "FROM_REF</span><span class="err">:</span> <span class="s">${FROM_REF}"</span>
          <span class="s">echo "BRANCH_NAME_FROM_CONTEXT</span><span class="err">:</span> <span class="s">${BRANCH_NAME_FROM_CONTEXT}"</span>
          <span class="s">echo "BRANCH_NAME_FROM_ENV_VARS</span><span class="err">:</span> <span class="s">${BRANCH_NAME_FROM_ENV_VARS}"</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">print-github-script-env</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">SAY_MY_NAME</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Heisenberg"</span>
          <span class="c1"># GitHub Actions Context expression, explained above</span>
          <span class="na">FROM_REF</span><span class="pi">:</span> <span class="s2">"</span><span class="s">${{</span><span class="nv"> </span><span class="s">github.ref</span><span class="nv"> </span><span class="s">}}"</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">// GitHub Script</span><span class="na">: (JavaScript (Node.js))</span><span class="pi">:</span>
            <span class="s">// Get ENV values from process.env</span>
            <span class="s">console.log(`HI</span><span class="err">:</span> <span class="s">${process.env.SAY_MY_NAME}`);</span>
            <span class="s">console.log(`FROM_REF</span><span class="err">:</span> <span class="s">${process.env.FROM_REF}`);</span>
            <span class="s">// github-script automatically injects context variables for direct JS access</span>
            <span class="s">// https://docs.github.com/en/actions/learn-github-actions/contexts#github-context</span>
            <span class="s">const branch_name_from_context_vars = context.ref;</span>
            <span class="s">console.log(`branch_name_from_context_vars</span><span class="err">:</span> <span class="s">${branch_name_from_context_vars}`);</span>
            <span class="s">// You can also use GitHub Actions Context expression (though less meaningful)</span><span class="err">:</span>
            <span class="s">const branch_name_from_context = "${{ github.ref }}";</span>
            <span class="s">console.log(`branch_name_from_context</span><span class="err">:</span> <span class="s">${branch_name_from_context}`);</span>
            
            <span class="s">for (const [key, value] of Object.entries(process.env)) {</span>
              <span class="s">console.log(`${key}=${value}`);</span>
            <span class="s">}</span>
            <span class="s">// The github object in github-script is an Octokit REST API instance</span>
            <span class="s">// used to interact with GitHub API</span>
            <span class="s">// For example</span><span class="err">:</span>
            <span class="s">// await github.rest.pulls.list({</span>
            <span class="s">//   owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
            <span class="s">//   repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
            <span class="s">//   state</span><span class="err">:</span> <span class="s2">"</span><span class="s">open"</span>
            <span class="s">//  });</span>
      <span class="c1"># gh CLI does NOT use GITHUB_TOKEN by default; requires GH_TOKEN</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">gh CLI without GH_TOKEN (expected to fail)</span>
        <span class="na">continue-on-error</span><span class="pi">:</span> <span class="kc">true</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">PR_COUNT=$(gh pr list --repo $GITHUB_REPOSITORY --json number --jq 'length')</span>
          <span class="s">echo "Found $PR_COUNT open pull requests"</span>
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">gh CLI with GH_TOKEN (expected to succeed)</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="c1"># Assign GH_TOKEN so gh CLI can authenticate</span>
          <span class="c1"># https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows</span>
          <span class="na">GH_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.GITHUB_TOKEN }}</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">PR_COUNT=$(gh pr list --repo $GITHUB_REPOSITORY --json number --jq 'length')</span>
          <span class="s">echo "Found $PR_COUNT open pull requests"</span>
      
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">github-script auto-authentication (no GH_TOKEN needed)</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v7</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">script</span><span class="pi">:</span> <span class="s">\\|</span>
            <span class="s">// github = Octokit REST client (auto-authenticated with GITHUB_TOKEN)</span>
            <span class="s">const pulls = await github.rest.pulls.list({</span>
              <span class="s">owner</span><span class="err">:</span> <span class="s">context.repo.owner,</span>
              <span class="s">repo</span><span class="err">:</span> <span class="s">context.repo.repo,</span>
              <span class="s">state</span><span class="err">:</span> <span class="s2">"</span><span class="s">open"</span>
            <span class="err">}</span><span class="s">);</span>
            <span class="s">console.log(`Found ${pulls.data.length} open pull requests`);</span>
</code></pre></div></div>

<h4 id="-xxx--github-actions-context-expressions">${{ XXX }} GitHub Actions Context Expressions</h4>

<p><img src="/assets/404bd5c70040/1*e3jpPuwdmf005cjbaXjAwQ.webp" alt="" loading="lazy" decoding="async" width="789" height="684" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODkiIGhlaWdodD0iNjg0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*e3jpPuwdmf005cjbaXjAwQ.png" /></p>

<p><img src="/assets/404bd5c70040/1*ed5Wm7Xk48xuP8yQzHpJ9w.webp" alt="" loading="lazy" decoding="async" width="880" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODAiIGhlaWdodD0iODAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*ed5Wm7Xk48xuP8yQzHpJ9w.png" /></p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">${{ XXX }}</code> is a GitHub Actions Context expression, <strong>replaced by GitHub Actions with the corresponding value during the YAML parsing phase</strong> (as shown in the above image <code class="language-plaintext highlighter-rouge">BRANCH_NAME_FROM_CONTEXT</code>).</p>
  </li>
  <li>
    <p>A complete list of usable expressions can be found in the <a href="https://docs.github.com/en/actions/learn-github-actions/contexts#github-context" target="_blank">official documentation</a>.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">${XXX}</code> or <code class="language-plaintext highlighter-rouge">$XXX</code> are actual environment variables. <code class="language-plaintext highlighter-rouge">${XXX}</code> is more convenient for string concatenation.</p>
  </li>
  <li>
    <p>GitHub Actions injects some default environment variables. For the full list, please refer to the <a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables" target="_blank">official documentation</a>.</p>
  </li>
</ul>

<h4 id="actionsgithub-scriptv7">actions/github-script@v7</h4>

<p><img src="/assets/404bd5c70040/1*TjJQnlAAU3VRwS9_dFz1yA.webp" alt="" loading="lazy" decoding="async" width="718" height="913" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MTgiIGhlaWdodD0iOTEzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*TjJQnlAAU3VRwS9_dFz1yA.png" /></p>

<p><img src="/assets/404bd5c70040/1*5bHEdjrGsVdWnYSQgpnsZA.webp" alt="" loading="lazy" decoding="async" width="1018" height="1150" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDE4IiBoZWlnaHQ9IjExNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*5bHEdjrGsVdWnYSQgpnsZA.png" /></p>

<ul>
  <li>
    <p>In <code class="language-plaintext highlighter-rouge">github-script</code>, you can also use <code class="language-plaintext highlighter-rouge">${{ XXX }}</code>, which is a GitHub Actions Context expression, but it is not very meaningful; because <strong>in the github-script environment, context variables are injected by default and can be directly referenced in JavaScript</strong>.</p>
  </li>
  <li>
    <p>In <code class="language-plaintext highlighter-rouge">github-script</code>, the <code class="language-plaintext highlighter-rouge">github</code> object is an instance of the Octokit REST API.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">github-script</code> can also inject variables from <code class="language-plaintext highlighter-rouge">env:</code>, but you need to access them using <code class="language-plaintext highlighter-rouge">${process.env.xxx}</code>.</p>
  </li>
  <li>
    <p>Some environment variables are injected by default. For the full list, see the <a href="https://docs.github.com/en/actions/learn-github-actions/environment-variables" target="_blank">official documentation</a>. Access them via <code class="language-plaintext highlighter-rouge">${process.env.xxx}</code>; however, since there is already the <code class="language-plaintext highlighter-rouge">context</code> variable, accessing them this way is not very meaningful.</p>
  </li>
</ul>

<p><img src="/assets/404bd5c70040/1*oW85Sju0sN4caXiaLSg32A.webp" alt="" loading="lazy" decoding="async" width="735" height="408" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MzUiIGhlaWdodD0iNDA4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*oW85Sju0sN4caXiaLSg32A.png" /></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">github-script</code> automatically includes the <code class="language-plaintext highlighter-rouge">github-token</code>, no need to specify it.</li>
</ul>

<h4 id="gh-cli-gh_token">gh cli GH_TOKEN</h4>

<p><img src="/assets/404bd5c70040/1*NUfaaKIobZWVBuXrGg0TPw.webp" alt="" loading="lazy" decoding="async" width="866" height="431" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjYiIGhlaWdodD0iNDMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*NUfaaKIobZWVBuXrGg0TPw.png" /></p>

<ul>
  <li>
    <p>When using <code class="language-plaintext highlighter-rouge">gh cli</code> in GitHub Actions Shell Script, you need to specify the <code class="language-plaintext highlighter-rouge">GH_TOKEN</code> parameter</p>
  </li>
  <li>
    <p>You can directly use the default token — <code class="language-plaintext highlighter-rouge">secrets.GITHUB_TOKEN</code> (automatically generated during GitHub Actions execution)</p>
  </li>
  <li>
    <p><strong>The default token has some basic permissions. If the command requires higher permissions, use a <a href="https://docs.github.com/en/actions/how-tos/security-for-github-actions/security-guides/use-github_token-in-workflows" target="_blank">Personal Access Token</a>. For teams, it is recommended to create a PAT using a shared account.</strong></p>
  </li>
</ul>

<p><strong>Full code: <a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/blob/main/.github/workflows/Demo-Env-Vars-.yml" target="_blank">Demo-Env-Vars-.yml</a></strong></p>

<p>GitHub Actions is already developed. The next step is to replace the GitHub Hosted Runner with your own Self-hosted Runner.</p>

<blockquote>
  <p><em>GitHub Hosted Runner offers a free quota of 2,000 minutes per month (starting point). Running small automation tasks usually takes little time and runs on Linux machines at a low cost, so you might not even reach the free limit; <strong>it’s not necessary to switch to a Self-hosted Runner</strong>. Switching runners also requires ensuring the runner environment is set up correctly (for example, GitHub Hosted Runner comes with the gh CLI pre-installed, but for a self-hosted Runner, you need to install it yourself). <strong>This article switches runners purely for teaching purposes</strong>.</em></p>
</blockquote>

<blockquote>
  <p><em>Self-hosted Runner is only needed when running CI/CD tasks.</em></p>
</blockquote>

<h4 id="adding-a-self-hosted-runner">Adding a Self-hosted Runner</h4>

<blockquote>
  <p><em>This article uses <strong>macOS M1</strong> as an example.</em></p>
</blockquote>

<p><img src="/assets/404bd5c70040/1*mEInaj-tLaprMAa2OfowCw.webp" alt="" loading="lazy" decoding="async" width="1225" height="1136" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjI1IiBoZWlnaHQ9IjExMzYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*mEInaj-tLaprMAa2OfowCw.png" /></p>

<ul>
  <li>
    <p>Settings → Actions → Runners → New self-hosted runner.</p>
  </li>
  <li>
    <p><strong>Runner image:</strong> macOS</p>
  </li>
  <li>
    <p><strong>Architecture:</strong> Remember to select ARM64 on M1 for faster execution</p>
  </li>
</ul>

<p><strong>Open a Terminal on a physical computer.</strong></p>

<p><strong>Complete the following steps on your local computer according to the Download instructions:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create the Runner directory at your desired path</span>
<span class="nb">mkdir </span>actions-runner <span class="o">&amp;&amp;</span> <span class="nb">cd </span>actions-runner
<span class="c"># Download the Runner image</span>
curl <span class="nt">-o</span> actions-runner-osx-x64-2.325.0.tar.gz <span class="nt">-L</span> https://github.com/actions/runner/releases/download/v2.325.0/actions-runner-osx-x64-2.325.0.tar.gz

<span class="c"># Optional: Validate the hash</span>
<span class="nb">echo</span> <span class="s2">"0562bd934b27ca0c6d8a357df00809fbc7b4d5524d4aeb6ec152e14fd520a4c3  actions-runner-osx-x64-2.325.0.tar.gz"</span> <span class="se">\\</span>| shasum <span class="nt">-a</span> 256 <span class="nt">-c</span>

<span class="c"># Extract the archive</span>
<span class="nb">tar </span>xzf ./actions-runner-osx-x64-2.325.0.tar.gz
</code></pre></div></div>

<blockquote>
  <p><em>The above is for reference only; it is recommended to follow the steps in your settings page to get the latest version of the Runner image.</em></p>
</blockquote>

<p><img src="/assets/404bd5c70040/1*Q0k0EPjrY2V4owseESe5lg.webp" alt="" loading="lazy" decoding="async" width="1161" height="197" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTYxIiBoZWlnaHQ9IjE5NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*Q0k0EPjrY2V4owseESe5lg.png" /></p>

<p><strong>Configure:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Please refer to the setup page for the command; the token changes over time</span>
./config.sh <span class="nt">--url</span> https://github.com/ORG/REPO <span class="nt">--token</span> XXX
</code></pre></div></div>

<p>You will be asked to input sequentially:</p>

<ul>
  <li>
    <p>Enter the name of the runner group to add this runner to: [press Enter for Default] <strong>Just press Enter</strong><br />
<em>Only runners registered at the Organization level have the Group feature</em></p>
  </li>
  <li>
    <p>Enter the name of runner: [press Enter for ZhgChgLideMacBook-Pro] <strong>You can enter the desired Runner name, e.g.</strong> <code class="language-plaintext highlighter-rouge">app-runner-1</code> <strong>or just press Enter</strong></p>
  </li>
  <li>
    <p>This runner will have the following labels: ‘self-hosted’, ‘macOS’, ‘X64’<br />
Enter any additional labels (ex. label-1,label-2): [press Enter to skip]<br />
<strong>Enter the Runner labels you want to set. You can add custom labels for easier use later</strong><br />
As mentioned before, GitHub Actions/Runner picks tasks based on matching labels. <strong>If you only use the default labels, the Runner might pick up jobs meant for other runners in the organization. It’s safest to add a custom label.</strong><br />
Here, I randomly set a label <code class="language-plaintext highlighter-rouge">self-hosted-zhgchgli</code></p>
  </li>
  <li>
    <p>Enter name of work folder: [press Enter for _work] <strong>Press Enter directly</strong></p>
  </li>
</ul>

<p>When you see √ Settings Saved., it means the setup is complete.</p>

<p><img src="/assets/404bd5c70040/1*yxLfii0rWhzsiFuYVmPgFw.webp" alt="" loading="lazy" decoding="async" width="929" height="501" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjkiIGhlaWdodD0iNTAxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*yxLfii0rWhzsiFuYVmPgFw.png" /></p>

<p><strong>Start the Runner:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./run.sh
</code></pre></div></div>

<p>When you see √ Connected to GitHub and Listening for Jobs, it means the Actions runner is already listening for tasks:</p>

<p><img src="/assets/404bd5c70040/1*1RmPPOosAoCxHA9tg_WE2w.webp" alt="" loading="lazy" decoding="async" width="290" height="98" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTAiIGhlaWdodD0iOTgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*1RmPPOosAoCxHA9tg_WE2w.png" /></p>

<blockquote>
  <p><strong><em>This Terminal window will keep receiving tasks as long as it remains open.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>🚀🚀🚀You can run multiple Runners on the same computer by opening multiple Terminals in different directories.</em></strong></p>
</blockquote>

<p><strong>Back on the Repo settings page, you can also see the Runner waiting for tasks:</strong></p>

<p><img src="/assets/404bd5c70040/1*jjq5NTPYMuC3gH3ey_VTtg.webp" alt="" loading="lazy" decoding="async" width="802" height="247" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MDIiIGhlaWdodD0iMjQ3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*jjq5NTPYMuC3gH3ey_VTtg.png" /></p>

<p><strong>Status:</strong></p>

<ul>
  <li>
    <p>Idle: idle, waiting for tasks</p>
  </li>
  <li>
    <p>Active: A task is currently running</p>
  </li>
  <li>
    <p>Offline: Runner is offline</p>
  </li>
</ul>

<h4 id="workflow-github-actions-runner-switching-to-self-hosted-runner">Workflow (GitHub Actions) Runner Switching to Self-hosted Runner</h4>

<p>Using <code class="language-plaintext highlighter-rouge">Automation-PullRequest.yml</code> as an example:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Please refer to the previous text, omitted....</span>
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">label-pr-by-file-count</span><span class="pi">:</span>
    <span class="c1"># Please refer to the previous text, omitted....</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">self-hosted-zhgchgli</span><span class="pi">]</span>
    <span class="c1"># Please refer to the previous text, omitted....</span>
  <span class="c1"># ---------</span>
  <span class="na">assign-self-if-no-assignee</span><span class="pi">:</span>
    <span class="c1"># Please refer to the previous text, omitted....</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">self-hosted-zhgchgli</span><span class="pi">]</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="c1"># Please refer to the previous text, omitted....</span>
</code></pre></div></div>

<p>After committing files to the repo’s main branch, reopen the PR to trigger and verify the Actions.</p>

<p><strong>Back in the Runner Terminal, you can see a new task has arrived, showing its execution and results:</strong></p>

<p><img src="/assets/404bd5c70040/1*Hu0DxKQbenPmBEw8swv65A.webp" alt="" loading="lazy" decoding="async" width="628" height="100" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjgiIGhlaWdodD0iMTAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*Hu0DxKQbenPmBEw8swv65A.png" /></p>

<p>Failed because my local computer does not have the <a href="https://github.com/cli/cli" target="_blank">gh cli</a> environment installed:</p>

<p><img src="/assets/404bd5c70040/1*9xguacdPATIeEZdbFszmHw.webp" alt="" loading="lazy" decoding="async" width="973" height="123" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzMiIGhlaWdodD0iMTIzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*9xguacdPATIeEZdbFszmHw.png" /></p>

<p>After installing gh on the physical machine using <code class="language-plaintext highlighter-rouge">brew install gh</code>, trigger the execution again:</p>

<p><img src="/assets/404bd5c70040/1*TbkAt00K89Ysbix33dK8ZA.webp" alt="" loading="lazy" decoding="async" width="631" height="29" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MzEiIGhlaWdodD0iMjkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*TbkAt00K89Ysbix33dK8ZA.png" /></p>

<p>Success! Now this task runs entirely on our own computer, without using GitHub Hosted Runner and without any charges.</p>

<p><strong>We can click into the Action Log to check which Runner and machine the task is running on:</strong></p>

<p><img src="/assets/404bd5c70040/1*oqLcWfn6cbWsCt9fVsrikQ.webp" alt="" loading="lazy" decoding="async" width="1092" height="826" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDkyIiBoZWlnaHQ9IjgyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/404bd5c70040/1*oqLcWfn6cbWsCt9fVsrikQ.png" /></p>

<h4 id="runs-on--runner-label-setting">runs-on: [ <strong>Runner Label] Setting</strong></h4>

<p>This is AND, not OR. GitHub Runner does not currently support OR when selecting Runners to execute.</p>

<p>For example: <code class="language-plaintext highlighter-rouge">[self-hosted, macOS, app]</code> → means the Runner must have <strong>all three</strong> labels <code class="language-plaintext highlighter-rouge">self-hosted, macOS, app</code> to match and take the job.</p>

<p>If a Job wants to test results across different Runner environments simultaneously, you can use the <code class="language-plaintext highlighter-rouge">matrix</code> parameter:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">jobs</span><span class="pi">:</span>
  <span class="na">test</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">${{ matrix.runner }}</span>
    <span class="na">strategy</span><span class="pi">:</span>
      <span class="na">matrix</span><span class="pi">:</span>
        <span class="na">runner</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="pi">[</span><span class="nv">self-hosted</span><span class="pi">,</span> <span class="nv">linux</span><span class="pi">,</span> <span class="nv">high-memory</span><span class="pi">]</span>
          <span class="pi">-</span> <span class="pi">[</span><span class="nv">self-hosted</span><span class="pi">,</span> <span class="nv">macos</span><span class="pi">,</span> <span class="nv">xcode-15</span><span class="pi">]</span>

    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">echo "Running on ${{ matrix.runner }}"</span>
</code></pre></div></div>

<p>This Job will run once in parallel on each of the following two Runner Labels Runners:</p>

<ul>
  <li>
    <p>self-hosted, linux, high-memory</p>
  </li>
  <li>
    <p>self-hosted, macos, xcode-15</p>
  </li>
</ul>

<blockquote>
  <p><strong><em>Runner is currently not supported for:</em></strong></p>
</blockquote>

<blockquote>
  <p><em>- OR Selecting a Runner</em></p>
</blockquote>

<blockquote>
  <p><em>- Runner weight settings</em></p>
</blockquote>

<h4 id="register-runner-as-a-service">Register Runner as a Service</h4>

<p>You can refer to the official document “<a href="https://docs.github.com/en/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service?platform=mac" target="_blank">Configuring the self-hosted runner application as a service</a>” to register the Runner directly as a system service. This allows it to run in the background (without keeping the Terminal open) and start automatically after boot.</p>

<p>If you have multiple runners, remember to adjust the settings in <a href="https://docs.github.com/en/actions/how-tos/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service?platform=mac#customizing-the-self-hosted-runner-service-1" target="_blank">Customizing the self-hosted runner service</a> to register different names.</p>

<blockquote>
  <p><em>On the iOS side, I have an issue to investigate and exclude: <strong>after switching to a background Service, I encounter errors during Archive (suspected to be related to keychain permissions)</strong>. I didn’t have time to fix it then, so I temporarily used the foreground Terminal Runner.</em></p>
</blockquote>

<p>For a traditional foreground app to start automatically on boot, you need to add a launch configuration file in <code class="language-plaintext highlighter-rouge">~/Library/LaunchAgents</code>:</p>

<p><code class="language-plaintext highlighter-rouge">actions.runner.REPO.RUNNER_NAME.plist</code>:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="cp">&lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&gt;</span>
<span class="nt">&lt;plist</span> <span class="na">version=</span><span class="s">"1.0"</span><span class="nt">&gt;</span>
 <span class="nt">&lt;dict&gt;</span>
  <span class="nt">&lt;key&gt;</span>Label<span class="nt">&lt;/key&gt;</span>
  <span class="nt">&lt;string&gt;</span>actions.runner.REPO.RUNNER_NAME<span class="nt">&lt;/string&gt;</span>
  <span class="c">&lt;!-- Specify Terminal.app to launch --&gt;</span>
  <span class="nt">&lt;key&gt;</span>ProgramArguments<span class="nt">&lt;/key&gt;</span>
  <span class="nt">&lt;array&gt;</span>
   <span class="nt">&lt;string&gt;</span>/usr/bin/open<span class="nt">&lt;/string&gt;</span>
   <span class="nt">&lt;string&gt;</span>-a<span class="nt">&lt;/string&gt;</span>
   <span class="nt">&lt;string&gt;</span>Terminal<span class="nt">&lt;/string&gt;</span>
   <span class="nt">&lt;string&gt;</span>/Users/zhgchgli/Documents/actions-runner/run.sh<span class="nt">&lt;/string&gt;</span>
  <span class="nt">&lt;/array&gt;</span>
  <span class="nt">&lt;key&gt;</span>RunAtLoad<span class="nt">&lt;/key&gt;</span>
  <span class="nt">&lt;true/&gt;</span>
  <span class="nt">&lt;key&gt;</span>WorkingDirectory<span class="nt">&lt;/key&gt;</span>
  <span class="nt">&lt;string&gt;</span>/Users/zhgchgli/Documents/actions-runner<span class="nt">&lt;/string&gt;</span>
 <span class="nt">&lt;/dict&gt;</span>
<span class="nt">&lt;/plist&gt;</span>
</code></pre></div></div>

<blockquote>
  <p><em>For those interested in diving deeper into DevOps, you can refer to the official <a href="https://docs.github.com/en/actions/concepts/runners/about-actions-runner-controller#scaling-runners" target="_blank">k8s Runner</a> documentation.</em></p>
</blockquote>

<h4 id="complete-project-repo">Complete Project Repo</h4>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo" target="_blank"><img src="https://opengraph.githubassets.com/eb233d74ad1bc6b0eceb9494487579557fb7f17066a3981d20b52fde2c00ef45/ZhgChgLi/github-actions-ci-cd-demo" alt="" /></a></p>

<h3 id="official-documentation">Official Documentation</h3>

<p><a href="https://docs.github.com/en/actions/get-started/quickstart" target="_blank"><img src="https://docs.github.com/assets/cb-345/images/social-cards/actions.png" alt="" /></a></p>

<p>For more detailed setup instructions, please refer to the official documentation.</p>

<h4 id="ai-can-help">AI Can Help!</h4>

<p><img src="/assets/404bd5c70040/1*JecNUCqujV1oeAvHA14X-g.webp" alt="" loading="lazy" decoding="async" width="1114" height="1140" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTE0IiBoZWlnaHQ9IjExNDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/404bd5c70040/1*JecNUCqujV1oeAvHA14X-g.png" /></p>

<p>Providing ChatGPT with complete steps and timing can help you create ready-to-use GitHub Actions!</p>

<h3 id="summary-1">Summary</h3>

<p>You should now have a solid understanding of GitHub Actions + Self-hosted Runner. In the next article, I will start using an App (iOS) CI/CD case to guide you step-by-step through building the entire process.</p>

<h3 id="series-articles">Series Articles:</h3>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Complete Guide to Using and Building GitHub Actions and Self-hosted Runners</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</strong></a></p>
  </li>
</ul>

<h4 id="-buy-me-a-beer-on-paypal"><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></h4>

<blockquote>
  <p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank"><strong><em>This series of articles took a lot of time and effort to write. If the content helps you or improves your team’s work efficiency and product quality, please consider buying me a coffee. Thank you for your support!</em></strong></a></p>
</blockquote>

<p><img src="/assets/404bd5c70040/1*QJj54G9gOjtQS-rbHVT1SQ.webp" alt="Buy me a coffee" loading="lazy" decoding="async" width="700" height="700" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNzAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/404bd5c70040/1*QJj54G9gOjtQS-rbHVT1SQ.png" /></p>

<p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></p>]]></content>
  </entry><entry>
    <title type="html">CI/CD Explained: Boost iOS App Development Stability and Efficiency｜Tool Selection Guide</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/" rel="alternate" type="text/html" title="CI/CD Explained: Boost iOS App Development Stability and Efficiency｜Tool Selection Guide" />
    <published>2025-06-30T15:10:16+08:00</published>
    <updated>2025-12-18T00:24:11+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/c008a9e8ceca</id><summary type="html">iOS app teams struggling with unstable builds and slow releases can leverage CI/CD to streamline workflows, enhance code quality, and accelerate delivery. Discover practical steps and tool choices to implement CI/CD effectively and transform your development process.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="cicd" /><category term="github-actions" /><category term="jenkins" /><category term="agile" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/c008a9e8ceca/1*vokpvb4dyWHOnVnF3WGbfw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><![CDATA[<h3 id="cicd-practical-guide-part-1-what-is-cicd-how-to-build-a-stable-and-efficient-development-team-with-cicd-tool-selection">CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</h3>

<p>Using the App (iOS) Team as an example, this guide introduces CI/CD from scratch and the tangible value it brings after implementation.</p>

<p><img src="/assets/c008a9e8ceca/1*vokpvb4dyWHOnVnF3WGbfw.webp" alt="Photo by Leif Christoph Gottwald" loading="lazy" decoding="async" width="1200" height="675" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*vokpvb4dyWHOnVnF3WGbfw.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@project2204?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Leif Christoph Gottwald</a></p>

<h3 id="2026-update--pricing-changes-for-github-actions-including-self-hosted-runner">2026 Update — Pricing changes for GitHub Actions (including Self-hosted Runner)</h3>

<blockquote>
  <p><a href="https://resources.github.com/actions/2026-pricing-changes-for-github-actions/" target="_blank">TLDR — On January 1, 2026, we are lowering the price of hosted runners, and starting March 1, 2026, we will charge $0.002 per minute for self-hosted runners. Most customers will not see any change to their bill. Actions will remain free for public repositories.</a></p>
</blockquote>

<p>GitHub 2025/12/14 <a href="https://resources.github.com/actions/2026-pricing-changes-for-github-actions/" target="_blank">Latest Announcement</a>:</p>

<ul>
  <li>
    <p>January 1, 2026: GitHub-hosted Runner is now cheaper, with price reductions up to 39%.</p>
  </li>
  <li>
    <p>March 1, 2026: <strong>Using Self-hosted Runner requires additional service maintenance fees, $0.002 USD per minute (regardless of OS), which equals $1 for 500 minutes. It is still cost-effective so far.</strong> ️</p>
  </li>
  <li>
    <p>Public repositories still retain the same free tier.</p>
  </li>
</ul>

<p><strong>GitHub will also invest more resources to enhance GitHub Actions features:</strong></p>

<ul>
  <li>
    <p><strong>Scale Set Client:</strong> Simplifies the <strong>Autoscaling</strong> of Self-hosted Runners.</p>
  </li>
  <li>
    <p><strong>multi-label: Support Restart</strong>, improving the Runner label experience.</p>
  </li>
  <li>
    <p><strong>Actions Runner Controller (ARC)</strong> Update: Improved Deployment, Observability, and Version Management.</p>
  </li>
  <li>
    <p><strong>Actions Data Stream:</strong> Provides near real-time workflow/runner event data streams for monitoring and analysis.</p>
  </li>
</ul>

<p><a href="https://github.com/pricing/calculator" target="_blank"><strong>The official pricing calculator has also been updated</strong></a> <strong>:</strong></p>

<p><img src="/assets/c008a9e8ceca/1*CuNXq0t_70UiVY0N3JEacA.webp" alt="" loading="lazy" decoding="async" width="1144" height="921" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ0IiBoZWlnaHQ9IjkyMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*CuNXq0t_70UiVY0N3JEacA.png" /></p>

<ul>
  <li>
    <p>GitHub Hosted Runner — macOS 3-core(M1): $0.062 USD/minute, 1,000 minutes equals $62 USD</p>
  </li>
  <li>
    <p>Self-Hosted Runner — $0.002 USD/minute, 1,000 minutes equals $2 USD</p>
  </li>
</ul>

<blockquote>
  <p><em>Buy the latest model <a href="https://www.apple.com/tw/shop/buy-mac/mac-mini" target="_blank">Mac Mini 10-core (M4)</a> yourself for about $600 USD, place it in the office as a Self-hosted Runner, and it will pay off in about a year; it offers better and faster performance with almost unlimited usage ( <strong>maintenance cost is minimal</strong> ).</em></p>
</blockquote>

<blockquote>
  <p><em>You can also use retired old M1 MacBook Pro as a Runner = 0 cost.</em></p>
</blockquote>

<blockquote>
  <p>*The above does not include electricity and internet costs.</p>
</blockquote>

<h4 id="introduction">Introduction</h4>

<p>After two experiences building App CI/CD in different development teams, I finally have time to organize the journey from “why to do it” to “how to do it.” While I can’t guarantee this is the most standard CI/CD workflow, it is definitely a valuable starting point to help your team begin implementation, improve product stability, and boost overall development efficiency.</p>

<h4 id="chapter">Chapter</h4>

<p>This series will start by explaining “What CI/CD is and the value it brings,” followed by a hands-on guide on “How to set up a CI/CD environment using GitHub Actions + self-hosted Runner” and “Implementing CI and CD in app development as an example.” Finally, it will introduce how to “Use Google Apps Script Web App combined with GitHub Actions to create an easy-to-use app packaging platform for cross-team collaboration.” We hope this series is helpful to you.</p>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Complete Guide to Using and Setting Up GitHub Actions and Self-hosted Runner</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Integrate GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</strong></a></p>
  </li>
</ul>

<h4 id="final-outcome">Final Outcome</h4>

<p>No more words, here is the final result.</p>

<p><img src="/assets/c008a9e8ceca/1*7-abScyjHQno1XzH4aGkaw.webp" alt="Demo PR" loading="lazy" decoding="async" width="1265" height="1093" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY1IiBoZWlnaHQ9IjEwOTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/c008a9e8ceca/1*7-abScyjHQno1XzH4aGkaw.png" /></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/pull/11" target="_blank">Demo PR</a></p>

<p><img src="/assets/c008a9e8ceca/1*yXMeaOELhqdvMCxIJ5ElBw.gif" alt="Demo Web App (Please refer to the tutorial below for first-time use)" loading="lazy" decoding="async" width="1048" height="822" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQ4IiBoZWlnaHQ9IjgyMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<p><a href="https://script.google.com/macros/s/AKfycbwNW6N5ozKbIz_E1HK6yFEUtA8KQrUciS-jcPsQptvIKlARmKgLxbQzNu8ksVeg-BmEfg/exec" target="_blank">Demo Web App (Please refer to the tutorial below for first-time use)</a></p>

<p>CI/CD — Fully developed with GitHub Actions, easy to maintain and extend.</p>

<p><strong>CI:</strong></p>

<ul>
  <li>
    <p>Trigger Unit Tests Automatically on PR Submission</p>
  </li>
  <li>
    <p>Run corresponding tests based on the scope of changed files</p>
  </li>
  <li>
    <p>Merge PR only after tests have passed</p>
  </li>
</ul>

<p><strong>CD:</strong></p>

<ul>
  <li>
    <p>Google Apps Script Web App (CD Packaging Interface) allows engineers, QA, and PMs to package the app on computers or mobile devices through this website.</p>
  </li>
  <li>
    <p>GitHub Actions Self-hosted Runner: Using your own machine for unlimited CI/CD usage</p>
  </li>
  <li>
    <p>Integrate Firebase App Distribution API to directly obtain the download link for the packaged test version</p>
  </li>
</ul>

<p><strong>Automation</strong> :</p>

<ul>
  <li>
    <p>Automatically assign yourself when creating a PR</p>
  </li>
  <li>
    <p>Automatically assign reviewers randomly when creating a PR</p>
  </li>
  <li>
    <p>Mark PR size label</p>
  </li>
</ul>

<h4 id="demo-web-appproject">Demo Web App/Project</h4>

<ul>
  <li><a href="https://script.google.com/macros/s/AKfycbwNW6N5ozKbIz_E1HK6yFEUtA8KQrUciS-jcPsQptvIKlARmKgLxbQzNu8ksVeg-BmEfg/exec" target="_blank">Online Demo</a> Please refer to the authorization image below for first-time use (Only For Demo App):</li>
</ul>

<p><img src="/assets/c008a9e8ceca/1*0cD7NrUr9J9roqIe4Upfzg.webp" alt="" loading="lazy" decoding="async" width="1200" height="1041" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNDEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/c008a9e8ceca/1*0cD7NrUr9J9roqIe4Upfzg.png" /></p>

<p><a href="https://script.google.com/home/projects/1CBB39OMedqP9Ro1WSlvgDnMBin4-ksyhgly2h_KrbOuFiPHTalNgwHOp/edit?pli=1" target="_blank"><img src="https://www.gstatic.com/devrel-devsite/prod/v78ce60439c72b9da3632137223a86ae38b78a872a1f6dee1b5c1c8cfa57fe81d/developers/images/opengraph/white.png" alt="" /></a></p>

<p><a href="https://github.com/ZhgChgLi/github-actions-ci-cd-demo/" target="_blank"><img src="https://opengraph.githubassets.com/eb233d74ad1bc6b0eceb9494487579557fb7f17066a3981d20b52fde2c00ef45/ZhgChgLi/github-actions-ci-cd-demo" alt="" /></a></p>

<h3 id="what-is-cicd">What is CI/CD?</h3>

<h4 id="story--development-process-without-cicd">Story — Development Process Without CI/CD</h4>

<p>Before discussing what CI/CD actually is, let’s set aside the term “CI/CD” and first recall how a startup development team without any workflow in place would work. The general process can be roughly summarized in the diagram below:</p>

<p><img src="/assets/c008a9e8ceca/1*rg4hbs7MsYDU9HZoehrvjQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="687" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjY4NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*rg4hbs7MsYDU9HZoehrvjQ.png" /></p>

<ol>
  <li>
    <p>The product has a bug. Developer T creates a branch fix/bug-c from the main branch to fix it. After the fix, the branch is merged back into the main branch.</p>
  </li>
  <li>
    <p>Next, Developer Z branched off from the main branch to create feature/a for requirement A. Halfway through, they noticed something was wrong with the feature. <strong>After investigating, they found that the current functionality was broken and the tests were failing</strong>, so they informed Developer T to fix it.</p>
  </li>
  <li>
    <p>After all development is complete, Developer Z <strong>uses his computer to build the version for QA testing, repeatedly fixing and building</strong>. Once everything is confirmed to be fine, the feature is merged back into the main branch.</p>
  </li>
  <li>
    <p>As the Sprint quickly comes to an end, the release package needs to be delivered to users; Developer Z <strong>puts aside their current work</strong> to help package from the main branch for QA to perform regression testing, similarly <strong>repeatedly fixing issues and repackaging</strong>, and finally submitting the packaged app for review.</p>
  </li>
  <li>
    <p>Released to users after Apple/Google review is completed.</p>
  </li>
</ol>

<h4 id="problem">Problem</h4>

<p>In the above story, we can summarize two major issues.</p>

<p><strong>Question 1: There is no unified inspection mechanism for current correct feature changes.</strong></p>

<ul>
  <li>
    <p>Code That Does Not Follow the Coding Style Can Still Be Merged</p>
  </li>
  <li>
    <p>Even If the Build Fails, I Can Still Merge</p>
  </li>
  <li>
    <p>Changes can be merged even if basic Unit Tests and important checks fail.</p>
  </li>
  <li>
    <p>My environment works correctly, but others may not have the same results.</p>
  </li>
  <li>
    <p>Affects other developers currently working on the project</p>
  </li>
</ul>

<p><strong>Question 2: Spending a lot of manpower and time on packaging work.</strong></p>

<ul>
  <li>
    <p>Packaging requires engineers to manually package, interrupting their current development work.</p>
  </li>
  <li>
    <p>Switching back and forth between packaging and development incurs a high cognitive switching cost.</p>
  </li>
  <li>
    <p>Packaging wait time prevents other development work from proceeding.</p>
  </li>
  <li>
    <p>Engineers’ time cost is money.</p>
  </li>
  <li>
    <p>Manual operations may cause errors.</p>
  </li>
  <li>
    <p>QA asks engineers to package (back-and-forth communication).</p>
  </li>
</ul>

<h4 id="ci--continuous-integration-持續整合">CI — Continuous Integration 持續整合</h4>

<p>Corresponding to Question 1, “Continuous Integration” aims to ensure that all changes automatically undergo Build &amp; Test in a unified environment, guaranteeing that all modifications pass every test case and meet team standards before entering the production environment — “continuously and automatically ensuring the correct code is integrated into production.”</p>

<p>You can also add Nightly Builds and more automated testing steps to ensure stability.</p>

<h4 id="cd--continuous-delivery--deployment-continuous-delivery--deployment">CD — Continuous Delivery / Deployment Continuous Delivery / Deployment</h4>

<p>For Question 2, “Continuous Deployment” aims to ensure that after the code passes the CI stage without issues, the changes are automatically packaged and deployed through the complex process to internal testing (QA, Debug, Staging, Beta…) or external release (Production, Release…).</p>

<ul>
  <li>
    <p><strong>Continuous Deployment:</strong> Fully automated deployment directly to the Production environment</p>
  </li>
  <li>
    <p><strong>Continuous Delivery:</strong> Automatically deploy only to the Staging/Debug environment; manual verification is required before deploying to the Production environment.</p>
  </li>
</ul>

<p>In the context of App development, it leans more towards <strong>Continuous Delivery</strong>, where we want a manual check to confirm everything is flawless before the App is officially released, ensuring the release timing and functionality are accurate.</p>

<h4 id="story--building-a-stable-and-efficient-development-team-through-cicd">Story — <strong>Building a Stable and Efficient Development Team through CI/CD</strong></h4>

<p><img src="/assets/c008a9e8ceca/1*rszgT5yFKCcfCUka_t9b3g.webp" alt="" loading="lazy" decoding="async" width="1200" height="824" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*rszgT5yFKCcfCUka_t9b3g.png" /></p>

<p><strong>Looking back at our story, after implementing CI/CD:</strong></p>

<ul>
  <li>
    <p>CI<br />
All changes must pass automated tests before merging into the main branch. Additionally, a Nightly Build scheduled automated testing process is added to improve stability.</p>
  </li>
  <li>
    <p>CD<br />
All packaging uses CD uniformly, allowing Developer T and Developer Z to fully focus on business development, reducing manual communication and operational errors.</p>
  </li>
</ul>

<blockquote>
  <p>Team Work Efficiency and Product Stability 🚀🚀🚀🚀🚀</p>
</blockquote>

<h3 id="the-value-of-cicd">The Value of CI/CD</h3>

<p>Combining the core agile development principle of “small steps, fast iterations,” CI/CD provides the foundation for stability and work efficiency during frequent continuous feature iterations.</p>

<p><strong>Automatically Standardize Verification of Iteration Results</strong></p>

<ul>
  <li>Ensure all changes meet the expected results, do not affect other functions, and do not impact other team members</li>
</ul>

<p><strong>Automate Tedious Deployment Processes</strong></p>

<ul>
  <li>Allow team members to focus on core business development and reduce manual operation errors</li>
</ul>

<h4 id="the-effectiveness-of-cicd">The Effectiveness of CI/CD</h4>

<p>Looking back at the 2021 Pinkoi talk “<a href="/posts/pinkoi-engineering/high-efficiency-engineering-teams-pinkoi-tech-career-talk-insights-11f6c8568154/"><strong>2021 Pinkoi Tech Career Talk — Secrets of High-Efficiency Engineering Teams</strong></a>,” the content basically revolves around the same points: “automation, reducing dependencies between people, and focusing on core business.” Implementing CI/CD fully aligns with these three directions, so we can use the same approach to estimate its effectiveness.</p>

<p><strong>Another point to highlight is the <a href="https://zh.wikipedia.org/wiki/%E5%BF%83%E6%B5%81%E7%90%86%E8%AB%96" target="_blank">cost of context switching</a>:</strong></p>

<p><img src="/assets/c008a9e8ceca/1*xOzjG-lSiFmdT-C4GHf0JA.webp" alt="" loading="lazy" decoding="async" width="960" height="540" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iNTQwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*xOzjG-lSiFmdT-C4GHf0JA.png" /></p>

<p>After working continuously for a period, we enter a “flow” state where our thoughts and productivity peak, allowing us to produce the most effective output. However, if interrupted, it takes time to return to this flow state—here, we use 30 minutes as an example.</p>

<p><strong>In scenarios without CI/CD, it might be:</strong> Spending a lot of time only to find out something was broken and then going back to communicate and fix it (CI), QA/PM asking engineers to help package the test version of the app (CD).</p>

<h4 id="cicd-effectiveness-estimation">CI/CD Effectiveness Estimation</h4>

<p><img src="/assets/c008a9e8ceca/1*SinoHlHKbtXiRubUS8Bm9Q.webp" alt="Team size: 6 people / Calculated over one month" loading="lazy" decoding="async" width="1200" height="790" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*SinoHlHKbtXiRubUS8Bm9Q.png" /></p>

<p>Team size: 6 people / calculated per month</p>

<p>Here, we take a monthly basis as an example. Without a CI/CD process, there are 4 unexpected main branch breakages each month, resulting in about 720 minutes spent on fixing and communication. Adding the time for monthly packaging of test and release versions, plus errors caused by manual operations, the total reaches around 1,010 minutes. With an engineer’s monthly salary of 80,000, this results in approximately 13,000 in wasted costs each month.</p>

<blockquote>
  <p><em>This missed estimating <a href="https://resources.github.com/actions/2026-pricing-changes-for-github-actions/" target="_blank">Starting 2026–03–01, Self-hosted requires a maintenance fee of $0.002 USD per minute</a>, meaning running 500 minutes costs $1 USD.</em></p>
</blockquote>

<h4 id="cicd-setup-cost">CI/CD <strong>Setup Cost</strong></h4>

<ul>
  <li>
    <p>Labor Cost:<br />
Based on the implementation described in this series, it is estimated that 1 person needs about 10 days = <strong>4,800 minutes</strong> to complete the work. (~= <strong>NT$36,384</strong>)</p>
  </li>
  <li>
    <p>Equipment and Running Costs:<br />
Using GitHub Actions self-hosted Runner only requires purchasing 1–2 <a href="https://www.apple.com/tw/shop/buy-mac/mac-mini/m4" target="_blank">Mac Minis</a> upfront or directly using existing retired MacBook Pros as CI/CD Runners.<br />
For example, for a team of 6, purchasing a brand new Mac Mini with 32G RAM M4 Mini (= <strong>NT$40,900</strong>)</p>
  </li>
</ul>

<p>The total cost is about <strong>NT$80,000</strong>, with benefits starting to be realized after around six months.</p>

<blockquote>
  <p><strong><em>Disclaimer:</em></strong> <em>This is just one way to calculate benefits and may not be the most accurate; it is meant to give everyone a concept to extend, <strong>allowing management to see the value of CI/CD</strong> and authorize the implementation of the entire workflow.</em></p>
</blockquote>

<h3 id="choosing-cicd-tools">Choosing CI/CD Tools</h3>

<h4 id="cloud-services-bitrise--xcode-cloud">Cloud Services Bitrise / XCode Cloud</h4>

<ul>
  <li>
    <p><strong>Bitrise:</strong> One of the earliest cloud services focused on App CI/CD. My first experience with CI/CD was using Bitrise. It offers a user-friendly and intuitive step editor, allowing quick setup of App CI/CD pipelines.<br />
<strong>Drawbacks:</strong> Initially, it was a $99 flat fee. When Apple M-series processors first launched, they switched to usage-based billing (a form of lock-in), and our team’s monthly cost reached at least $500. So we migrated to GitHub Actions.<br />
However, recently checking their website, they now offer 1 App / 1 Concurrent / unlimited usage / $89 per month.</p>
  </li>
  <li>
    <p><strong>XCode Cloud:</strong> 100 hours / 1 month / $50 USD. The advantage is high integration with XCode and App development; however, the downside is it does not support Android and customizing some steps can be difficult. Still, for small pure iOS apps, I would consider using it again.</p>
  </li>
</ul>

<blockquote>
  <p><em>Really worried about <strong>cloud services locking you in</strong>, so I prefer to keep control in-house and consider on-premise solutions.</em></p>
</blockquote>

<h4 id="on-premise-services-jenkins--github-actions--gitlab-cicd">On-premise Services Jenkins / GitHub Actions / Gitlab CI/CD</h4>

<ul>
  <li>
    <p><strong>Gitlab CI/CD:</strong><br />
It was launched earlier and offers more complete features than GitHub Actions. However, since our project is hosted on GitHub, we do not consider using Gitlab CI/CD. Both have similar functions, and this series will use GitHub Actions as an example.</p>
  </li>
  <li>
    <p><strong>GitHub Actions</strong><br />
GitHub launched its CI/CD service in 2018, directly integrated with GitHub projects. It has been continuously updated and improved over the years, offering many pre-built steps (<a href="https://github.com/marketplace?type=actions" target="_blank">Marketplace</a>) ready to use. It supports self-hosted runners, allowing unlimited use of your own machines (effectively a hybrid cloud).</p>
  </li>
  <li>
    <p><strong>Jenkins:</strong><br />
An open-source, free tool dedicated to CI/CD, old but powerful; Jenkins covers everything from application-level task design and permission management to low-level service dispatch and execution. It also has <a href="https://plugins.jenkins.io/" target="_blank">Plugins</a> available for direct use and was an essential tool for early DevOps CI/CD.</p>
  </li>
</ul>

<h3 id="jenkins-vs-github-actions">Jenkins v.s. GitHub Actions</h3>

<h4 id="tldr"><strong>TL;DR</strong></h4>

<blockquote>
  <p><em>For App Teams without dedicated DevOps staff, having App developers build and maintain a Jenkins environment from scratch is too challenging, with few people skilled in it and potential network security issues. Choosing GitHub Actions allows App developers to focus solely on designing the CI/CD process. By briefly reviewing the official documentation on writing workflows and starting Runners, they can quickly set up a free, stable, and secure CI/CD service.</em></p>
</blockquote>

<p><img src="/assets/c008a9e8ceca/1*qg8SkzoJqNVvWPxx2Lh6uA.webp" alt="" loading="lazy" decoding="async" width="504" height="434" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDQiIGhlaWdodD0iNDM0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*qg8SkzoJqNVvWPxx2Lh6uA.png" /></p>

<blockquote>
  <p><em>The following comparison is based solely on setting up App CI/CD and may not apply to all technical scenarios.</em></p>
</blockquote>

<h4 id="setup-and-maintenance-difficulty-jenkins--github-actions"><strong>Setup and Maintenance Difficulty</strong> Jenkins &gt;&gt;&gt; GitHub Actions</h4>

<p><img src="/assets/c008a9e8ceca/1*k2XGXjV_VZEt618DnDm0lA.webp" alt="" loading="lazy" decoding="async" width="999" height="1117" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5OTkiIGhlaWdodD0iMTExNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*k2XGXjV_VZEt618DnDm0lA.png" /></p>

<p>Here is a simple layered diagram to explain the difference between the two. As mentioned earlier, Jenkins covers all functions from top to bottom, making self-hosting much more complex. In contrast, GitHub Actions only requires writing YAML workflows on GitHub. The local machine just needs to register a GitHub self-hosted Runner (completed with 5 commands), and GitHub will automatically dispatch tasks to the local machine. GitHub is responsible for maintaining everything else, including GitHub Actions/Runner version upgrades and task dispatch issues, so we don’t need to handle them.</p>

<p>Another troublesome point is that Jenkins is a service independent of Git, so communication between them must be done via APIs (e.g., GitHub API/WebHook), making the setup more complex.</p>

<p>I previously surveyed about 30 iOS developers around me. Only a handful (2) understood Jenkins, while more than 10 were using GitHub Actions. After all, you can complete CI/CD tasks just by writing some YAML.</p>

<h4 id="learning-difficulty-jenkins-github-actions">Learning Difficulty Jenkins »&gt; GitHub Actions</h4>

<p>Refer only to the official documentation to learn the available YAML commands for GitHub Actions and how to run your own Runner locally.</p>

<h4 id="stability-github-actions--jenkins">Stability: GitHub Actions &gt; Jenkins</h4>

<p>In this aspect, I think GitHub Actions slightly outperforms Jenkins.</p>

<p>Jenkins may crash due to system upgrades or installing conflicting plugins (but if it’s running fine and you don’t touch it, it usually has no issues).</p>

<p>GitHub Actions depends on <a href="https://www.githubstatus.com/" target="_blank">GitHub Service Status</a> (if GitHub goes down, it will also be affected), but outages are rare, with an average uptime of 99.9%. If issues do occur, you just wait for them to be fixed.</p>

<h4 id="security-github-actions--jenkins">Security: GitHub Actions &gt; Jenkins</h4>

<p>Considering that GitHub Actions/Runner services are maintained and automatically updated by GitHub itself, this may be safer than Jenkins, which requires manual updates.</p>

<p>Additionally, communication between Jenkins and GitHub requires opening API/WebHook ports, which is relatively risky. GitHub and GitHub Actions integrate seamlessly, and the relationship between GitHub Actions and the self-hosted Runner is observer mode. The self-hosted Runner requests tasks from GitHub, so the Runner itself does not need to open external ports.</p>

<p>However, in a fully closed network environment, Jenkins is more secure than GitHub Actions.</p>

<h4 id="permission-control-jenkins-github-actions">Permission Control Jenkins »&gt; GitHub Actions</h4>

<p>This point needs special emphasis for comparison: Jenkins allows setting up separate account login permissions for control; GitHub Actions is directly tied to the GitHub repo, so only users with repo permissions can use it.</p>

<blockquote>
  <p><strong>This is why the following article uses GAS Web App to create a cross-team operation platform.</strong></p>
</blockquote>

<h4 id="using-jenkins-extensively-github-actions">Using Jenkins Extensively »&gt; GitHub Actions</h4>

<p>For teams with a full DevOps team, Jenkins is undoubtedly still the preferred choice. After all, in other domains (such as Web, backend, Java…), Jenkins has been running the longest, offers the most useful plugins, and can unify the CI/CD setup for all teams, making management easier. It also supports complex CI/CD scenarios like backend deployment followed by automatic frontend deployment.</p>

<blockquote>
  <p><strong>GitHub Actions later also supported cross-Repo Actions/Runners.</strong></p>
</blockquote>

<h4 id="third-party-plugin-richness-jenkins--github-actions">Third-Party Plugin Richness Jenkins &gt; GitHub Actions</h4>

<p>In terms of quantity, GitHub Actions outnumbers Jenkins, but Jenkins offers deeper and more powerful CI/CD features. Many of GitHub Actions are simply automation functions.</p>

<h4 id="feature-depth-jenkins-github-actions">Feature Depth Jenkins »&gt; GitHub Actions</h4>

<p>This aspect is incomparable. Jenkins has been around for nearly 20 years, while GitHub Actions still needs to add many features; for example: permission management, secret management (currently only plain text is supported, key files must be converted to plain text first), Cache/Artifact currently only supports Cloud, and so on.</p>

<p><strong>Additionally, GitHub Self-hosted Runner also supports <a href="https://docs.github.com/en/actions/concepts/runners/about-actions-runner-controller#scaling-runners" target="_blank">Docker or k8s</a>.</strong></p>

<h4 id="customized-deep-jenkins-github-actions">Customized Deep Jenkins »&gt; GitHub Actions</h4>

<p>Jenkins is fully controlled by yourself, allowing greater customization of permissions that can affect the entire system. GitHub Actions can only customize different steps at the application level.</p>

<p>For example, since the built-in Artifacts in GitHub Actions do not support self-hosted runners, you can only use a shell script in the step to copy files to another directory, and cannot customize Artifacts implementation yourself.</p>

<blockquote>
  <p><strong>App CI/CD scenarios do not require very advanced features.</strong></p>
</blockquote>

<h4 id="usability-github-actions-jenkins">Usability GitHub Actions »&gt; Jenkins</h4>

<p>In terms of interface, GitHub Actions is a newer tool and is easier to use than Jenkins. Regarding script configuration, Jenkins uses Pipeline Scripts stored on Jenkins, while GitHub Actions uses YAML files managed with the project’s Git, making it easier to set up than Jenkins.</p>

<h4 id="cost-risk-jenkins--github-actions">Cost Risk Jenkins &gt; GitHub Actions</h4>

<p>Jenkins is fully open-source and free to use, giving you complete control. GitHub Actions is partially open-source, but task dispatching and execution run on GitHub’s closed SaaS service. Currently, GitHub Actions is completely free; you only pay when using GitHub Runners. Starting from 2026-03-01, using self-hosted Runners will also incur a maintenance fee of $0.002 USD per minute <a href="https://resources.github.com/actions/2026-pricing-changes-for-github-actions/" target="_blank">source</a>. Public repositories have free usage quotas for these fees.</p>

<h3 id="the-purpose-of-google-apps-script-web-app-and-why-it-was-chosen">The Purpose of Google Apps Script Web App and Why It Was Chosen</h3>

<p>Another tool option is Google Apps Script Web App. The reason for needing this is that GitHub Actions’ built-in form feature is too basic (the interface is too technical and static) and its execution permissions are tied to the GitHub Repo. This makes it very inconvenient if we want to provide access to other functional team members.</p>

<p><strong>As follows:</strong></p>

<p><img src="/assets/c008a9e8ceca/1*kULMefCX5D6I5z9xt71A5A.webp" alt="" loading="lazy" decoding="async" width="348" height="447" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNDgiIGhlaWdodD0iNDQ3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*kULMefCX5D6I5z9xt71A5A.png" /></p>

<p>CD packaging usually requires the operator to fill in some information, such as Release Notes.</p>

<p>Therefore, we need an “interface” tool for other team members or even our own engineers to use more conveniently.</p>

<p><strong>Required Scenario:</strong></p>

<blockquote>
  <p><em>Fill in the required information on this more user-friendly “interface,” connect to project management tools (e.g., Jira, Asana) to fetch Tasks, or directly get the PR list from GitHub. Then select from the dropdown menu and submit, triggering GitHub Actions to start the build via the GitHub API.</em></p>
</blockquote>

<h4 id="slack">Slack</h4>

<p>When we first implemented CI/CD, we chose to integrate the Slack API to achieve a similar effect:</p>

<p><img src="/assets/c008a9e8ceca/1*m85bTTlrwAmCxoVqVXo2fg.webp" alt="&lt;https://slack.com/intl/zh-tw/blog/productivity/workflow-builder-tools-automation-examples&gt;" loading="lazy" decoding="async" width="647" height="497" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDciIGhlaWdodD0iNDk3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*m85bTTlrwAmCxoVqVXo2fg.png" /></p>

<p><a href="https://slack.com/intl/zh-tw/blog/productivity/workflow-builder-tools-automation-examples" target="_blank">https://slack.com/intl/zh-tw/blog/productivity/workflow-builder-tools-automation-examples</a></p>

<ul>
  <li>Partners can directly fill out forms in Slack, trigger CD packaging, and receive Slack notifications.</li>
</ul>

<p>The operation is smooth and unified within everyday office tools (SSOT), requiring no relearning; however, the underlying issue is that <strong>development and maintenance costs are very high</strong>. One reason is that Slack Outgoing-Webhook API demands a very fast response (within 3 seconds), which basically rules out using FAAS services for simple integration (e.g., Cloud Functions, GAS, Lambda…).</p>

<p><strong>Previously, a team member interested in automation and backend developed a complete backend service using Kotlin+ktor, then set up a server on GCP for Slack integration.</strong></p>

<blockquote>
  <p><strong><em>Development and maintenance costs are extremely high, and handover is very difficult.</em></strong></p>
</blockquote>

<h4 id="google-apps-script--web-app">Google Apps Script — Web App</h4>

<p>Previously shared “<a href="/posts/zrealm-dev/google-apps-script-web-app-integrate-forms-with-github-actions-ci-cd-for-streamlined-workflows-4cb4437818f2/">Using Google Apps Script Web App Form to Connect Github Action CI/CD Workflow</a>”:</p>

<p><img src="/assets/c008a9e8ceca/1*Gr4PnV2J2AB9cVFuXMLjcA.webp" alt="" loading="lazy" decoding="async" width="1200" height="596" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*Gr4PnV2J2AB9cVFuXMLjcA.png" /></p>

<p><img src="/assets/c008a9e8ceca/1*NJRcY2ULVylZlsKnBtM27A.webp" alt="Demo Web App Form URL" loading="lazy" decoding="async" width="428" height="559" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MjgiIGhlaWdodD0iNTU5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*NJRcY2ULVylZlsKnBtM27A.png" /></p>

<p><a href="https://script.google.com/macros/s/AKfycbwNW6N5ozKbIz_E1HK6yFEUtA8KQrUciS-jcPsQptvIKlARmKgLxbQzNu8ksVeg-BmEfg/exec" target="_blank">Demo Web App Form URL</a></p>

<p><strong>The advantages of using Google Apps Script — Web App are:</strong></p>

<ul>
  <li>
    <p>Website Web</p>
  </li>
  <li>
    <p>Similar to Google Workspace enterprise account permission management, access can be restricted to only Google accounts within the organization.</p>
  </li>
  <li>
    <p><strong>Completely Free</strong></p>
  </li>
  <li>
    <p><strong>Function as a Service without the need to set up or maintain servers</strong></p>
  </li>
  <li>
    <p>Easier to maintain and hand over</p>
  </li>
  <li>
    <p>Can be operated on mobile devices</p>
  </li>
  <li>
    <p><strong>AI Can Help!</strong><br />
<strong>Whether it’s ChatGPT or other AI tools, they are very familiar with GAS and can directly help us create packaging forms and connect to the GitHub API.</strong></p>
  </li>
  <li>
    <p>Can also integrate with Jira, Asana, Slack notification APIs</p>
  </li>
</ul>

<p>For the second promotion, I switched to using GAS Web App for the team, which also received great feedback. The only difference from Slack is that you need to bookmark one more URL. When packaging is needed, just open the URL and operate the packaging through the web form.</p>

<h3 id="complete-app-cicd-tool-workflow">Complete App CI/CD Tool Workflow</h3>

<p>Here is the complete workflow. The next article will gradually introduce how to use and integrate each tool.</p>

<p><img src="/assets/c008a9e8ceca/1*pQ-2Jj6s2qlvwTrLghJSjg.webp" alt="" loading="lazy" decoding="async" width="1200" height="573" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/c008a9e8ceca/1*pQ-2Jj6s2qlvwTrLghJSjg.png" /></p>

<h4 id="tool-roles">Tool Roles:</h4>

<ul>
  <li>
    <p><strong>GitHub Actions</strong>: CI/CD workflow script code</p>
  </li>
  <li>
    <p><strong>GitHub Actions — Self-hosted Runner</strong>: The actual environment where CI/CD runs, using a self-hosted runner. You only need to cover the machine purchase cost and can run tasks without usage limits.</p>
  </li>
  <li>
    <p><strong>Google Apps Script Web App</strong>: Since packaging is not always done by engineers, a platform is needed for cross-functional team members; GAS Web App can quickly create a web tool and share the URL for others to use.</p>
  </li>
  <li>
    <p><strong>Asana/Jira</strong>: Project management tools that can integrate with the GAS Web App, allowing QA/PM to directly select the tasks they want to package.</p>
  </li>
  <li>
    <p><strong>Slack</strong>: Responsible for receiving execution result notifications</p>
  </li>
</ul>

<h4 id="scenario">Scenario:</h4>

<ul>
  <li>
    <p>End-User (QA/PM/PD/Developer): Submit the packaging form via GAS Web App (fetch the Branch corresponding to the Jira or Asana task) -&gt; GAS calls GitHub API -&gt; triggers CD packaging GitHub Actions &lt;- GitHub self-hosted runner listens for the task and pulls it to the machine for execution -&gt; upon completion, Slack notification is sent, and GAS Web App packaging status is updated.</p>
  </li>
  <li>
    <p>End-User (Developer): Open a PR, push a new commit to the PR -&gt; Trigger the CI test process &lt;- GitHub self-hosted runner listens for the task and pulls it to the machine for execution -&gt; After completion, comment the test results and update Checks.</p>
  </li>
</ul>

<h3 id="summary">Summary</h3>

<p>This article mainly provides an introduction to what CI/CD is and its benefits. Starting from the next article, we will dive into the technical aspects, guiding you step-by-step to understand and implement GitHub Actions CI/CD until achieving the final results mentioned earlier.</p>

<h3 id="series-articles">Series Articles:</h3>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-explained-boost-ios-app-development-stability-and-efficiency-tool-selection-guide-c008a9e8ceca/"><strong>CI/CD Practical Guide (Part 1): What is CI/CD? How to Build a Stable and Efficient Development Team with CI/CD? Tool Selection?</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-self-hosted-runner-setup-and-usage-guide-for-efficient-ci-cd-404bd5c70040/"><strong>CI/CD Practical Guide (Part 2): Complete Guide to Using and Setting Up GitHub Actions and Self-hosted Runner</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-actions-ios-app-ci-cd-workflow-automation-for-faster-builds-and-deployments-4b001d2e8440/"><strong>CI/CD Practical Guide (Part 3): Implementing CI and CD Workflows for App Projects Using GitHub Actions</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Integrate GitHub Actions and Build a Free, Easy-to-Use Packaging Tool Platform</strong></a></p>
  </li>
</ul>

<h4 id="-buy-me-a-beer-on-paypal"><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></h4>

<blockquote>
  <p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank"><strong><em>This series of articles took a lot of time and effort to write. If the content helps you and effectively improves your team’s work efficiency and product quality, please consider buying me a coffee. Thank you for your support!</em></strong></a></p>
</blockquote>

<p><img src="/assets/c008a9e8ceca/1*QJj54G9gOjtQS-rbHVT1SQ.webp" alt="Buy me a coffee" loading="lazy" decoding="async" width="700" height="700" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNzAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/c008a9e8ceca/1*QJj54G9gOjtQS-rbHVT1SQ.png" /></p>

<p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">🍺 Buy me a beer on PayPal</a></p>]]></content>
  </entry><entry>
    <title type="html">XCode Build Configuration Release｜Detect Ghost Crashes &amp;amp; Logic Bugs Before Upgrade</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/xcode-build-configuration-release-detect-ghost-crashes-logic-bugs-before-upgrade-7508328d8b8d/" rel="alternate" type="text/html" title="XCode Build Configuration Release｜Detect Ghost Crashes &amp; Logic Bugs Before Upgrade" />
    <published>2025-04-11T22:50:31+08:00</published>
    <updated>2025-04-11T22:50:31+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/7508328d8b8d</id><summary type="html">iOS developers facing ghost crashes or logic issues only in Release builds can identify and fix these hidden bugs before XCode upgrades, ensuring stable app performance and avoiding unexpected failures in production environments.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="xcode" /><category term="build-settings" /><category term="swift" /><category term="troubleshooting" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/7508328d8b8d/1*j4gTyeQwM-T7Ad3Fi29saQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/xcode-build-configuration-release-detect-ghost-crashes-logic-bugs-before-upgrade-7508328d8b8d/"><![CDATA[<h3 id="psychic-notes-things-you-should-test-when-upgrading-xcode">[Psychic Notes] Things You Should Test When Upgrading XCode…</h3>

<p>Encounter ghost crashes or logic issues that only appear in the Build Configuration Release (official, online version), while Debug runs perfectly fine.</p>

<p><img src="/assets/7508328d8b8d/1*j4gTyeQwM-T7Ad3Fi29saQ.webp" alt="Photo by Tommaso Pecchioli" loading="lazy" decoding="async" width="1400" height="933" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkzMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/7508328d8b8d/1*j4gTyeQwM-T7Ad3Fi29saQ.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@pecchio?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Tommaso Pecchioli</a></p>

<h3 id="tldr">TL;DR</h3>

<p>Before packaging and releasing a project with the new XCode, besides directly Build &amp; Run to check for layout issues or errors, <strong>please also remember to try</strong>:</p>

<ol>
  <li>
    <p>App Target</p>
  </li>
  <li>
    <p>Select <code class="language-plaintext highlighter-rouge">Build Settings</code></p>
  </li>
  <li>
    <p>Search for <code class="language-plaintext highlighter-rouge">Optimization Level</code></p>
  </li>
  <li>
    <p>Find the <code class="language-plaintext highlighter-rouge">Optimization Level</code> section</p>
  </li>
  <li>
    <p>Set the <code class="language-plaintext highlighter-rouge">Debug</code> environment to the same value as <code class="language-plaintext highlighter-rouge">Release</code> (e.g. <code class="language-plaintext highlighter-rouge">Fastest, Smallest [-Os]</code>)</p>
  </li>
  <li>
    <p>Build &amp; Run to check for any issues</p>
  </li>
</ol>

<p><img src="/assets/7508328d8b8d/1*CUqYYFVjyXtxGkMlyd0Suw.webp" alt="" loading="lazy" decoding="async" width="832" height="401" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MzIiIGhlaWdodD0iNDAxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/7508328d8b8d/1*CUqYYFVjyXtxGkMlyd0Suw.png" /></p>

<p>Not choosing to package directly to TestFlight for testing allows us to use breakpoints to quickly locate the root cause when issues arise.</p>

<blockquote>
  <p><em>If users report issues (crashes or abnormal behavior) in the Release (official/online) version that developers cannot reproduce locally, try changing this setting and test it locally.</em></p>
</blockquote>

<h4 id="possible-issues"><strong>Possible Issues</strong></h4>

<ul>
  <li>
    <p>Code looks correct, but results are abnormal</p>
  </li>
  <li>
    <p>The app crashes in places where the code should not crash</p>
  </li>
</ul>

<p>All the above work fine in the Debug environment with <code class="language-plaintext highlighter-rouge">Optimization Level = None [-O0]</code> and only occur at <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code>, which is the Release setting.</p>

<h4 id="solution">Solution</h4>

<p><strong>If there is an issue, it is usually not the developer’s fault</strong>; it is caused by a bug in XCode’s optimization. If you must use this version of XCode for packaging, you can only adjust your code as a workaround and wait for a new XCode version to see if the problem is resolved.</p>

<blockquote>
  <p><strong><em>It is not recommended to directly change Release to None, as it may cause more other issues.</em></strong></p>
</blockquote>

<h3 id="story-time">Story Time</h3>

<p>Here are some real problem scenarios I have encountered with this issue over the years.</p>

<h4 id="story-1--the-app-keeps-showing-the-invite-to-rate-app-popup">Story 1 — The App Keeps Showing the Invite to Rate App Popup</h4>

<p>Our app previously had a feature that would “invite users to rate the app in the App Store” when opening the app. The rule was to show the prompt three times and then stop; however, many users reported that it appears every time they open the app, lasting for a long time and becoming very annoying.</p>

<p>However, from the code perspective and when building &amp; running locally on the simulator or real device, there are no issues. We also tested various edge case scenarios but couldn’t reproduce the problem. I even wrote a UI Test that repeatedly runs the flow and clears data to retry… running thousands of times without encountering the issue.</p>

<p>I remember that time I was frustrated until past 3 a.m., feeling hopeless and unable to figure out the issue. I started aimlessly checking the project settings. Suddenly, I had an idea to change all the <code class="language-plaintext highlighter-rouge">Build Settings</code> to the <code class="language-plaintext highlighter-rouge">Release</code> values and try again. That’s when I discovered the problem could be reproduced with <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code>, which helped me locate the source of the issue.</p>

<p><strong>Pseudocode</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">invitedTimes</span> <span class="o">=</span> <span class="mi">0</span> <span class="c1">// Loaded from UserDefaults; will be saved back after update</span>
<span class="kd">func</span> <span class="nf">requestAppStoreReviewIfNeeded</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">defer</span> <span class="p">{</span>
    <span class="n">invitedTimes</span> <span class="o">+=</span> <span class="mi">1</span> <span class="c1">// Works for now, but may have unintended side effects</span>
  <span class="p">}</span>

  <span class="k">guard</span> <span class="n">invitedTimes</span> <span class="o">&lt;</span> <span class="mi">3</span> <span class="k">else</span> <span class="p">{</span>
    <span class="k">return</span>
  <span class="p">}</span>
  
  <span class="k">self</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="kt">AppStoreReviewRequestAlert</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><em>This code was developed by a predecessor. Although it has side effects, the logic is correct, it compiles properly, and previous versions ran without issues.</em></p>
</blockquote>

<p>But when I set <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code> and put a breakpoint to print the value, I found an anomaly: after <code class="language-plaintext highlighter-rouge">invitedTimes += 1</code>, it would suddenly explode into <code class="language-plaintext highlighter-rouge">-24760045646797946</code>, a huge negative number. As a result, the user gets the rating invitation every time.</p>

<p>At that time, we directly changed the defer usage here, and no users reported similar issues afterward; later, when testing subsequent XCode versions, the same code with <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code> worked fine.</p>

<h4 id="story-2--a-certain-page-crashes-immediately">Story 2 — A Certain Page Crashes Immediately</h4>

<p>During internal testing of the Release (Testflight) version, we found that a page (WebView) would crash immediately upon clicking. However, the engineers could Build &amp; Run on the simulator or device without any issues. Each time we suspected a cause, we would upload a new Testflight build with added logs or attempted fixes, which was very time-consuming and frustrating. This reminded me of past struggles, so I immediately asked a colleague to change the local setting to <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code>. After rebuilding and running locally, the crash issue finally reproduced.</p>

<p>The main issue lies in a variable within our customized WebView Obj-C code that becomes null when <code class="language-plaintext highlighter-rouge">Optimization Level = Fastest, Smallest [-Os]</code> is set. The cause is unknown, so we can only add extra checks for protection. It worked fine in previous versions, so we have to wait for a new XCode release to see if it gets fixed.</p>

<h3 id="summary">Summary</h3>

<p>Actually, I’ve been caught by this pitfall more than twice, and some I can’t even remember. In short, here’s a mindset to keep in mind:</p>

<ol>
  <li>
    <p>When packaging and releasing with a new XCode version for the first time, it’s best to test this.</p>
  </li>
  <li>
    <p>The issue only occurs in Release (official, online version). Basically, this is the problem. You can directly change the settings locally to see if it can be reproduced.</p>
  </li>
</ol>]]></content>
  </entry><entry>
    <title type="html">Medium Growth Milestone: Achieve 1,000 Followers with Proven Strategies</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/medium-growth-milestone-achieve-1-000-followers-with-proven-strategies-6fd11e9704f2/" rel="alternate" type="text/html" title="Medium Growth Milestone: Achieve 1,000 Followers with Proven Strategies" />
    <published>2025-04-03T20:51:56+08:00</published>
    <updated>2025-04-03T20:51:56+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/6fd11e9704f2</id><summary type="html">Struggling to grow your Medium audience? Discover how consistent content and engagement led to reaching 1,000 followers, boosting visibility and influence on the platform.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="medium" /><category term="writer" /><category term="followers" /><category term="milestones" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/6fd11e9704f2/1*OMahkVPjT8XjSrYpDdpu-g.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/medium-growth-milestone-achieve-1-000-followers-with-proven-strategies-6fd11e9704f2/"><![CDATA[<h3 id="a-milestone-of-1000-followers-on-medium"><strong>A Milestone of 1,000 Followers On Medium</strong></h3>

<p>Running Medium for Seven Years, Finally Reaching the 1,000 Followers Milestone!</p>

<p><img src="/assets/6fd11e9704f2/1*OMahkVPjT8XjSrYpDdpu-g.webp" alt="" loading="lazy" decoding="async" width="1200" height="556" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjU1NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6fd11e9704f2/1*OMahkVPjT8XjSrYpDdpu-g.png" /></p>

<blockquote>
  <p><em>English version down below.</em></p>
</blockquote>

<p>Running Medium for 7 years, accumulating 112 articles, about 130,000 words, and investing over 800 hours, I finally reached my dream goal — 1,000 Followers on April 2, 2025.</p>

<p>It’s a long story. Starting in 2018, I shifted from Web to App development. Everything was new to me, and I always had a thirst for knowledge. I constantly researched on my own, learning by doing, and eventually completed and launched my first App.</p>

<p>Since I was entirely self-taught, I benefited greatly from many online articles written by experienced predecessors. This inspired me to write articles with the spirit of “teaching and learning,” hoping to create a positive cycle myself.</p>

<p>At first, I was caught in the “sharing dilemma,” always worrying that my content was incomplete, fearing criticism, and even more afraid it wouldn’t help readers.<br />
But later, I changed my mindset. Writing articles is like planting a seed. Someday, a developer facing a similar problem might find help in my writing or be inspired to explore further.<br />
Even if the content might not be seen, if I don’t start, these wonderful possibilities will never happen. By persistently writing, I gradually improved my skills, enriched my articles, and increased the chances of helping others.</p>

<p>During that period, I caught the tailwind of the times as app and software engineering flourished. I kept learning daily, developing new features, and continuously sharing my experiences through articles. At the same time, a steady stream of newcomers joined the development field, so each post always gained new followers.</p>

<p>In recent years, with the industry cooling down, the impact of AI on digital content creation, and changes in Medium’s SEO policies, the growth of new followers has clearly slowed. I have adopted a more relaxed attitude toward reaching the “1K goal,” but I still hold onto the original spirit of sharing and continue to document and help friends facing similar issues.<br />
Additionally, I have started sharing some daily life, travel stories, unboxing posts, and personal interests related to automation (RPA).</p>

<p>1K is just a milestone; I look forward to your continued support.<br />
Whether or not I keep writing about iOS development, I will continue to share the problems I encounter and the lessons I learn.</p>

<p>Sincerely thank you all for your support and company over these 7 years!</p>

<p><strong>Harry,</strong><br />
<strong>2025/04/02.</strong></p>

<h4 id="english">English</h4>

<p>After seven years on Medium, publishing 112 articles, about 130,000 words, and investing over 800 hours, I finally reached my long-awaited milestone of 1,000 followers on April 2, 2025.</p>

<p>It’s been quite a journey. Back in 2018, when I switched my focus from web development to app development, everything felt new and exciting. With an insatiable curiosity, I constantly searched for resources, learned by doing, and eventually completed and published my first app.</p>

<p>As someone entirely self-taught, I gained a tremendous amount from online articles shared by many experienced developers. This inspired me to start writing myself, motivated by the idea of “learning by teaching,” hoping to create a positive cycle of knowledge sharing.</p>

<p>Initially, I was caught up in doubts about sharing — always concerned whether my content was thorough enough, whether I’d be criticized, or even worse, whether it provided no real value to readers.</p>

<p>Eventually, I shifted my mindset. Writing is like planting a seed — someday, a developer facing a similar problem might find help or inspiration through my posts. Even if my articles weren’t immediately noticed, if I never started writing, those possibilities would never exist. Through continuous writing and persistence, I gradually improved my skills, enriched my content, and increased its potential to help others.</p>

<p>Fortunately, I rode the wave of rapid growth in apps and software engineering. Day by day, I learned, built new features, and shared my experiences through writing. There was also a steady influx of new developers entering the field, and each article gained new followers.</p>

<p>In recent years, growth has notably slowed, impacted by industry cooling, AI-driven changes to digital content creation, and shifts in Medium’s SEO policies. Gradually, I adopted a more relaxed approach towards reaching the 1,000-follower mark. Yet, I never lost the initial passion for sharing and continued documenting my experiences to help those encountering similar issues. Additionally, I started sharing daily life stories, travel experiences, product reviews, and content about my personal interest in automation (RPA).</p>

<p>Reaching 1,000 followers is just a milestone, and I look forward to your continued support in the future. Regardless of whether I continue writing about iOS development, I will keep sharing the challenges I face and the knowledge I gain.</p>

<p>I sincerely thank everyone for your support and companionship over these past seven years!</p>

<p><strong>Harry,</strong><br />
<strong>2025/04/02.</strong></p>]]></content>
  </entry><entry>
    <title type="html">Google Apps Script｜Fast Integration with Google APIs Using Firebase App Distribution API</title>
    <link href="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/" rel="alternate" type="text/html" title="Google Apps Script｜Fast Integration with Google APIs Using Firebase App Distribution API" />
    <published>2025-03-20T22:12:48+08:00</published>
    <updated>2025-07-10T21:00:24+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-robotic-process-automation/71400d408dc8</id><summary type="html">Developers facing complex API integrations can streamline processes by leveraging Google Apps Script with Firebase App Distribution API, achieving faster deployment and simplified management in app distribution workflows.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Robotic Process Automation" /><category term="ios-app-development" /><category term="firebase-app-distribution" /><category term="google-apps-script" /><category term="google-api" /><category term="google-cloud-platform" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/71400d408dc8/1*O6JWKIIS5oxyrOS3q9NXRQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/google-apps-script-fast-integration-with-google-apis-using-firebase-app-distribution-api-71400d408dc8/"><![CDATA[<h3 id="quick-integration-method-for-google-apps-script-x-google-apis">Quick Integration Method for Google Apps Script x Google APIs</h3>

<p>Using Google Apps Script with Firebase App Distribution API as an example</p>

<h3 id="background">Background</h3>

<p>Previously, I wrote several articles about using Google Apps Script. Among them, “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Using Google Apps Script to Automate Daily Data Reports RPA</a>” and “<a href="/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/">Simple 3 Steps — Build a Free GA4 Automated Data Notification Bot</a>” introduced how to connect Google Analytics with Google Sheets, Web Apps, Slack, Telegram, etc., to quickly build visual data platforms and notification services. Additionally, last month’s article “<a href="/posts/zrealm-dev/google-apps-script-web-app-integrate-forms-with-github-actions-ci-cd-for-streamlined-workflows-4cb4437818f2/">Using Google Apps Script Web App Form to Connect Github Action CI/CD Workflow</a>” demonstrated directly using Google Apps Script Web App to connect with the Github API as a CI/CD GUI form service. In all these cases, whether using built-in Google Apps Script services or connecting to external services (Slack, Github…), there was no need to connect to Google APIs.</p>

<blockquote>
  <p><em>This time, while optimizing the CI/CD GUI form, I wanted to display the Firebase Distribution download link directly on the Web App after packaging the internal test version. This can only be achieved by integrating Google APIs.</em></p>
</blockquote>

<h4 id="google-apps-script-x-firebase-app-distribution-api-v1">Google Apps Script x <a href="https://firebase.google.com/docs/reference/app-distribution/rest/" target="_blank">Firebase App Distribution API v1</a></h4>

<p><img src="/assets/71400d408dc8/1*O6JWKIIS5oxyrOS3q9NXRQ.webp" alt="" loading="lazy" decoding="async" width="771" height="740" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzEiIGhlaWdodD0iNzQwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*O6JWKIIS5oxyrOS3q9NXRQ.png" /></p>

<p>As shown in the image above, unlike the built-in service “AnalyticsData” for connecting to Google Analytics, Firebase App Distribution does not offer a built-in integration service, so it requires an advanced method for connection.</p>

<blockquote>
  <p><em>Originally, I thought I had to use a Service Account to generate and exchange the Access Token myself (which is a bit complicated; you can refer to my previous <a href="https://github.com/ZhgChgLi/ZReviewTender/blob/main/lib/GoogleAPI.rb" target="_blank">review bot open-source project</a>), <strong>but it’s actually not that complicated.</strong></em></p>
</blockquote>

<h3 id="google-apps-script-x-google-apis-advanced-service-integration">Google Apps Script x Google APIs Advanced Service Integration</h3>

<h4 id="integration-setup">Integration Setup</h4>

<p><strong>Refer to the <a href="https://developers.google.com/apps-script/guides/services/advanced?hl=zh-tw#requirements" target="_blank">official documentation description</a>; we need to follow these steps for advanced setup:</strong></p>

<ol>
  <li>
    <p>You must <a href="https://developers.google.com/apps-script/guides/services/advanced?hl=zh-tw#enable_advanced_services" target="_blank">enable advanced services</a> in your script project.</p>
  </li>
  <li>
    <p>You must ensure that the API for the corresponding advanced service is enabled in the <a href="https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=zh-tw" target="_blank">Cloud Platform (GCP) project</a> used by the script.</p>
  </li>
  <li>
    <p>If your script project uses the <a href="https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=zh-tw#default_cloud_platform_projects" target="_blank">default GCP project</a> created on or after April 8, 2019, the API will be automatically enabled once you enable the advanced service and save the script project. If you have not agreed yet, you may also be prompted to accept the <a href="https://cloud.google.com/terms/?hl=zh-tw" target="_blank">Google Cloud</a> and <a href="https://developers.google.com/terms?hl=zh-tw" target="_blank">Google API</a> terms of service.</p>
  </li>
  <li>
    <p>If your script project uses a <a href="https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=zh-tw#standard_cloud_platform_projects" target="_blank">standard GCP project</a> or an older default GCP project, you must manually <a href="https://developers.google.com/apps-script/guides/cloud-platform-projects?hl=zh-tw#enabling_an_api_in_a_standard_gcp_project" target="_blank">enable the corresponding API for the advanced service</a> in the GCP project. You need edit permissions for the GCP project to make this change.</p>
  </li>
</ol>

<h4 id="1-project-setup--configure-the-associated-google-cloud-platform-gcp-project">1. Project Setup — Configure the Associated Google Cloud Platform (GCP) Project</h4>

<p>Google Apps Script x Google APIs advanced service integration requires you to create a GCP project and link it to Google Apps Script. The permission to use Google APIs is based on the GCP settings.</p>

<p>Therefore, you need to create a GCP project (the same Firebase GCP project is also fine), note the “Project Number” on the info homepage, and ensure that this GCP project has the Google APIs you want to use enabled, and that the currently logged-in account or the account you want to use has permissions for this GCP project and Google APIs.</p>

<p><img src="/assets/71400d408dc8/1*-BlhyGAEtkcDRrZ86H2BHg.webp" alt="" loading="lazy" decoding="async" width="1013" height="767" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDEzIiBoZWlnaHQ9Ijc2NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/71400d408dc8/1*-BlhyGAEtkcDRrZ86H2BHg.png" /></p>

<p><img src="/assets/71400d408dc8/1*fycqt1HNNO9qiu6RZ-WMlw.webp" alt="" loading="lazy" decoding="async" width="912" height="871" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTIiIGhlaWdodD0iODcxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*fycqt1HNNO9qiu6RZ-WMlw.png" /></p>

<blockquote>
  <p><strong><em>This article uses the <a href="https://firebase.google.com/docs/reference/app-distribution/rest/" target="_blank">Firebase App Distribution API</a> as an example.</em></strong></p>
</blockquote>

<p><img src="/assets/71400d408dc8/1*T13M_lq7z-ui0_cLZtuHXg.webp" alt="" loading="lazy" decoding="async" width="869" height="830" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjkiIGhlaWdodD0iODMwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*T13M_lq7z-ui0_cLZtuHXg.png" /></p>

<p>Enter the <code class="language-plaintext highlighter-rouge">Project Number</code> into the Google Cloud Platform project number field under the Google Apps Script project settings. If the currently logged-in account has permission for that GCP project, it will automatically bind and set up successfully.</p>

<h4 id="2-project-setup--enable-appsscriptjson-manifest-file-display-in-the-editor">2. Project Setup — Enable “appsscript.json” Manifest File Display in the Editor</h4>

<p><img src="/assets/71400d408dc8/1*YnhdxRSyr_CYB2CFnO_31Q.webp" alt="" loading="lazy" decoding="async" width="765" height="498" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjUiIGhlaWdodD0iNDk4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*YnhdxRSyr_CYB2CFnO_31Q.png" /></p>

<p>After enabling, return to the editor, and the file list will show the <code class="language-plaintext highlighter-rouge">appsscript.json</code> configuration file:</p>

<p><img src="/assets/71400d408dc8/1*r8bheyYWj-TX7vFIum2GIQ.webp" alt="" loading="lazy" decoding="async" width="845" height="358" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDUiIGhlaWdodD0iMzU4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*r8bheyYWj-TX7vFIum2GIQ.png" /></p>

<p>Make sure the oauthScopes include the following two descriptions:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="nl">"oauthScopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"https://www.googleapis.com/auth/script.external_request"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://www.googleapis.com/auth/cloud-platform"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>If not, please manually paste and save.</p>

<h4 id="3-writing-integration-code">3. Writing Integration Code</h4>

<p>After setting up the GCP project, we can start writing the integration code by directly referring to the official documentation of the Google APIs we want to connect to, such as <a href="https://firebase.google.com/docs/reference/app-distribution/rest/" target="_blank">Firebase App Distribution API v1</a>:</p>

<p><img src="/assets/71400d408dc8/1*N0Zg4TEZQwTTLGCsaTdx8A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1193" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExOTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/71400d408dc8/1*N0Zg4TEZQwTTLGCsaTdx8A.png" /></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">project</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">projects/[Firebase Project ID]/apps/[Firebase APP ID]</span><span class="dl">"</span><span class="p">;</span> <span class="c1">// Please replace with your Project ID &amp; App ID</span>

<span class="kd">function</span> <span class="nf">debug</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">releases</span> <span class="o">=</span> <span class="nf">firebaseDistribution</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>

  <span class="nx">releases</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">release</span><span class="p">)</span> <span class="p">{</span>
    <span class="c1">// Release Object: https://firebase.google.com/docs/reference/app-distribution/rest/v1/projects.apps.releases?hl=zh-tw#Release</span>
    <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">release</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2"> Download URL: </span><span class="p">${</span><span class="nx">release</span><span class="p">.</span><span class="nx">testingUri</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
  <span class="p">});</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">firebaseDistribution</span><span class="p">(</span><span class="nx">releaseNote</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://firebaseappdistribution.googleapis.com/v1/</span><span class="dl">"</span><span class="o">+</span><span class="nx">project</span><span class="o">+</span><span class="dl">"</span><span class="s2">/releases?filter=releaseNotes.text%3D*</span><span class="dl">"</span><span class="o">+</span><span class="nx">releaseNote</span><span class="o">+</span><span class="dl">"</span><span class="s2">*</span><span class="dl">"</span><span class="p">;</span>
  <span class="c1">// Filter: https://firebase.google.com/docs/reference/app-distribution/rest/v1/projects.apps.releases/list?hl=zh-tw</span>
  <span class="kd">const</span> <span class="nx">headers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json; charset=UTF-8</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">Authorization</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Bearer </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">ScriptApp</span><span class="p">.</span><span class="nf">getOAuthToken</span><span class="p">(),</span> <span class="c1">// Use the current account to get the token directly</span>
  <span class="p">};</span>
  
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">method</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">headers</span><span class="dl">"</span><span class="p">:</span> <span class="nx">headers</span>
  <span class="p">};</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">()).</span><span class="nx">releases</span><span class="p">;</span>
  
  <span class="k">if </span><span class="p">(</span><span class="nx">result</span> <span class="o">==</span> <span class="kc">undefined</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">[];</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">[Firebase Project ID]</code> <strong>and <code class="language-plaintext highlighter-rouge">[Firebase APP ID]</code> can be found in the Firebase project settings:</strong></p>

<p><img src="/assets/71400d408dc8/1*oql_FnmO7_ct8S3cOhP8lg.webp" alt="" loading="lazy" decoding="async" width="1200" height="1051" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/71400d408dc8/1*oql_FnmO7_ct8S3cOhP8lg.png" /></p>

<p>After pasting the code, the first run requires completing the permission authorization.</p>

<p><img src="/assets/71400d408dc8/1*sW3ZjQA3dKS8HhAOCDsmBA.webp" alt="" loading="lazy" decoding="async" width="1125" height="647" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTI1IiBoZWlnaHQ9IjY0NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/71400d408dc8/1*sW3ZjQA3dKS8HhAOCDsmBA.png" /></p>

<p><img src="/assets/71400d408dc8/0*p-Zl0cob4mPrsNO6.webp" alt="" loading="lazy" decoding="async" width="700" height="557" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNTU3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/0*p-Zl0cob4mPrsNO6.png" /></p>

<ul>
  <li>
    <p>Click “Review Permissions”</p>
  </li>
  <li>
    <p>Choose the account to execute, usually the current Google Apps Script account.</p>
  </li>
</ul>

<p><img src="/assets/71400d408dc8/0*m7enb51ZiNlWYeoT.webp" alt="" loading="lazy" decoding="async" width="700" height="557" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNTU3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/0*m7enb51ZiNlWYeoT.png" /></p>

<p><img src="/assets/71400d408dc8/0*SN-0owiePlKXIxLk.webp" alt="" loading="lazy" decoding="async" width="700" height="557" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDAiIGhlaWdodD0iNTU3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/0*SN-0owiePlKXIxLk.png" /></p>

<ul>
  <li>
    <p>Select “Advanced” to expand -&gt; Click “Go to XXX”<br />
This is an app we wrote for our own use and does not require Google verification.</p>
  </li>
  <li>
    <p>Click “Allow”</p>
  </li>
</ul>

<blockquote>
  <p><em>The above screen may not always appear and can be ignored if absent.</em></p>
</blockquote>

<p><strong>Allow running the script later by clicking “Debug” or “Run”:</strong></p>

<p>If an error occurs:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Exception</span><span class="o">:</span> <span class="nc">Request</span> <span class="n">failed</span> <span class="k">for</span> <span class="n">https</span><span class="o">://</span><span class="n">firebaseappdistribution</span><span class="mf">.</span><span class="n">googleapis</span><span class="mf">.</span><span class="n">com</span> <span class="n">returned</span> <span class="n">code</span> <span class="mf">403.</span> <span class="nc">Truncated</span> <span class="n">server</span> <span class="n">response</span><span class="o">:</span> <span class="p">{</span>
  <span class="s2">"error"</span><span class="o">:</span> <span class="p">{</span>
    <span class="s2">"code"</span><span class="o">:</span> <span class="mi">403</span><span class="p">,</span>
    <span class="s2">"message"</span><span class="o">:</span> <span class="s2">"Request had insufficient authentication scopes."</span><span class="p">,</span>
    <span class="s2">"status"</span><span class="o">:</span> <span class="s2">"PERMISSION_DENIED"</span><span class="p">,</span>
    <span class="s2">"details"</span><span class="o">:...</span> <span class="p">(</span><span class="kn">use</span> <span class="n">muteHttpExceptions</span> <span class="n">option</span> <span class="n">to</span> <span class="n">examine</span> <span class="n">full</span> <span class="n">response</span><span class="p">)</span>
<span class="mf">...</span>
</code></pre></div></div>

<p>or</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception: Specified permissions are not sufficient to call UrlFetchApp.fetch. Required permissions: https://www.googleapis.com/auth/script.external_request
</code></pre></div></div>

<p>Please ensure that the <code class="language-plaintext highlighter-rouge">appsscript.json</code> oauthScopes include the following two entries (refer to Step 2):</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"oauthScopes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"https://www.googleapis.com/auth/script.external_request"</span><span class="p">,</span><span class="w">
    </span><span class="s2">"https://www.googleapis.com/auth/cloud-platform"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span></code></pre></div></div>

<p>If an error occurs:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Exception</span><span class="o">:</span> <span class="nc">Request</span> <span class="n">failed</span> <span class="k">for</span> <span class="n">https</span><span class="o">://</span><span class="n">firebaseappdistribution</span><span class="mf">.</span><span class="n">googleapis</span><span class="mf">.</span><span class="n">com</span> <span class="n">returned</span> <span class="n">code</span> <span class="mf">401.</span> <span class="nc">Truncated</span> <span class="n">server</span> <span class="n">response</span><span class="o">:</span> <span class="p">{</span>
  <span class="s2">"error"</span><span class="o">:</span> <span class="p">{</span>
    <span class="s2">"code"</span><span class="o">:</span> <span class="mi">401</span><span class="p">,</span>
    <span class="s2">"message"</span><span class="o">:</span> <span class="s2">"Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or othe... (use muteHttpExceptions option to examine full response)
</span></code></pre></div></div>

<p>Indicates that the GCP project binding is not set (see step 1), or the GCP project has not enabled the Google APIs to be used, or the current account lacks permission for the GCP / Google APIs or the Firebase App. Please check the settings.</p>

<p>If an error occurs:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception: Request failed for https://firebaseappdistribution.googleapis.com returned code 404. Truncated server response:
</code></pre></div></div>

<p>or</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Exception</span><span class="o">:</span> <span class="nc">Request</span> <span class="n">failed</span> <span class="k">for</span> <span class="n">https</span><span class="o">://</span><span class="n">firebaseappdistribution</span><span class="mf">.</span><span class="n">googleapis</span><span class="mf">.</span><span class="n">com</span> <span class="n">returned</span> <span class="n">code</span> <span class="mf">400.</span> <span class="nc">Truncated</span> <span class="n">server</span> <span class="n">response</span><span class="o">:</span> <span class="p">{</span>
  <span class="s2">"error"</span><span class="o">:</span> <span class="p">{</span>
    <span class="s2">"code"</span><span class="o">:</span> <span class="mi">400</span><span class="p">,</span>
    <span class="s2">"message"</span><span class="o">:</span> <span class="s2">"Request contains an invalid argument."</span><span class="p">,</span>
    <span class="s2">"status"</span><span class="o">:</span> <span class="s2">"INVALID_ARGUMENT"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Please verify if the Google APIs request path is correct.</p>

<h4 id="integration-successful-">Integration Successful 🎉🎉🎉</h4>

<p><img src="/assets/71400d408dc8/1*9OyoPtblFnGGIBRvlGw10Q.webp" alt="" loading="lazy" decoding="async" width="1026" height="474" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI2IiBoZWlnaHQ9IjQ3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/71400d408dc8/1*9OyoPtblFnGGIBRvlGw10Q.png" /></p>

<p>You can see that in less than 10 lines of code, you can seamlessly connect to Google APIs, which is very convenient. For those interested, you can check out the <a href="https://github.com/ZhgChgLi/ZReviewTender/blob/main/lib/GoogleAPI.rb" target="_blank">self-implemented</a> authentication token exchange steps when connecting to Google APIs, which are quite complicated in comparison.</p>

<h4 id="next-steps">Next Steps:</h4>

<p>Combined with the previous article “<a href="/posts/zrealm-dev/google-apps-script-web-app-integrate-forms-with-github-actions-ci-cd-for-streamlined-workflows-4cb4437818f2/">Using Google Apps Script Web App Form to Connect Github Action CI/CD Workflow</a>,” the Firebase App Distribution download link is also displayed on the Web App for colleagues to download directly.</p>

<p><img src="/assets/71400d408dc8/1*nK3H3e3KgHEg8qz-KZ9ZGA.webp" alt="Demo Result" loading="lazy" decoding="async" width="797" height="716" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3OTciIGhlaWdodD0iNzE2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/71400d408dc8/1*nK3H3e3KgHEg8qz-KZ9ZGA.png" /></p>

<p>Demo Result</p>

<blockquote>
  <p><em>其他 Google APIs 也可以用相同方式串接。</em></p>
</blockquote>

<h3 id="202507-update">2025/07 Update:</h3>

<p>This feature has been integrated into an actual packaging tool. You can refer to the latest case study: “<a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions for Building a Free and Easy Packaging Tool Platform</strong></a>”</p>]]></content>
  </entry><entry>
    <title type="html">Xcode Virtual Directory Issues: Streamline Your Project Structure with Open Source Tools</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/xcode-virtual-directory-issues-streamline-your-project-structure-with-open-source-tools-fd719053b376/" rel="alternate" type="text/html" title="Xcode Virtual Directory Issues: Streamline Your Project Structure with Open Source Tools" />
    <published>2025-03-02T20:21:31+08:00</published>
    <updated>2025-03-09T15:59:17+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/fd719053b376</id><summary type="html">Developers facing Xcode virtual directory chaos can simplify integration with modern tools like XcodeGen and Tuist using a proven open source solution that restores project clarity and boosts workflow efficiency.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="xcode" /><category term="swift" /><category term="tuist" /><category term="xcodegen" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/fd719053b376/1*fYk27y-BjMBjBFnwDfhxlw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/xcode-virtual-directory-issues-streamline-your-project-structure-with-open-source-tools-fd719053b376/"><![CDATA[<h3 id="exploring-the-eternal-problem-of-xcode-virtual-directories-and-my-open-source-tool-solution">Exploring the Eternal Problem of Xcode Virtual Directories and My Open Source Tool Solution</h3>

<p>Apple Developer Occupational Hazard: Early Xcode Use of Virtual Directories Caused Directory Structure Chaos and Difficulty Integrating Modern Tools Like XcodeGen and Tuist.</p>

<p><img src="/assets/fd719053b376/1*fYk27y-BjMBjBFnwDfhxlw.webp" alt="Photo by Saad Salim" loading="lazy" decoding="async" width="1200" height="832" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/fd719053b376/1*fYk27y-BjMBjBFnwDfhxlw.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@saadx?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Saad Salim</a></p>

<h4 id="english-version-of-this-post">English version of this post:</h4>

<h4 id="exploring-the-long-standing-issues-of-xcode-virtual-directories-and-my-open-source-tool-solution"><a href="/posts/zrealm-dev/xcode-virtual-directory-issues-streamline-your-project-structure-with-open-source-tools-fd719053b376/">Exploring the Long-Standing Issues of XCode Virtual Directories and My Open Source Tool Solution</a></h4>

<h3 id="background">Background</h3>

<p>As the team size and project grow, the Xcode project file (.xcodeproj) increases in size, reaching tens or even hundreds of thousands of lines depending on the project’s complexity. With multiple developers working on different branches simultaneously, conflicts are inevitable; <strong>once an XcodeProj file conflicts, it is as difficult to resolve as Storyboard/.xib conflicts</strong>. Since it is also a pure descriptor file, it’s easy to accidentally remove files added by others or bring back references to files others have deleted when resolving conflicts.</p>

<p>Another issue is that as modularization progresses, the process of creating and managing modules in the Xcode project file (.xcodeproj) is very unfriendly. Any changes to modules can only be seen through diffs of the Xcode project file, which hinders the team’s move towards modularization.</p>

<blockquote>
  <p><em>If it’s just to prevent conflicts, you can simply do <a href="https://github.com/chiahsien/sort-Xcode-project-file" target="_blank">File Sorting</a> in the pre-commit hook. There are many existing scripts on GitHub that you can directly refer to for setup.</em></p>
</blockquote>

<h4 id="xcfolder"><a href="https://github.com/ZhgChgLi/XCFolder" target="_blank">XCFolder</a></h4>

<p><a href="https://github.com/ZhgChgLi/XCFolder" target="_blank"><img src="https://repository-images.githubusercontent.com/941421589/27855f06-b938-4458-a20f-3e317b4283b7" alt="" /></a></p>

<blockquote>
  <p><strong><em>Long story short</em></strong>, I developed a tool that converts Xcode’s early virtual directories into physical directories based on the directory structure inside Xcode.</p>
</blockquote>

<p><img src="/assets/fd719053b376/1*eFNN12WDaAk6mr49OzmC_g.webp" alt="" loading="lazy" decoding="async" width="1400" height="484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjQ4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/fd719053b376/1*eFNN12WDaAk6mr49OzmC_g.jpeg" /></p>

<blockquote>
  <p><em>Scroll down to continue the story…</em></p>
</blockquote>

<h4 id="modern-xcode-project-file-management">Modern Xcode Project File Management</h4>

<p>The specific idea is similar to why we discourage using Storyboard or .xib in team development: <strong>we need a well-maintained, iterative, and code-reviewable interface to manage the “Xcode project file.”</strong> Currently, there are two popular free tools available on the market:</p>

<ul>
  <li>
    <p><a href="https://github.com/yonaskolb/XcodeGen" target="_blank">XCodeGen</a>: A classic tool that uses YAML to define Xcode project content and then generates the Xcode project file (.xcodeproj).<br />
It allows direct structure definition with YAML, has a lower learning curve, and is easier to adopt, but offers weaker support for modularization, dependency management, and dynamic YAML configuration.</p>
  </li>
  <li>
    <p><a href="https://github.com/tuist/tuist" target="_blank">Tuist</a>: A newer tool released in recent years that uses Swift DSL to define Xcode project contents, then converts them into Xcode project files (.xcodeproj).<br />
It is more stable and flexible, with built-in modularization and dependency management features, but has a higher learning and adoption curve.</p>
  </li>
</ul>

<p><strong>No matter which one, our core workflow will become:</strong></p>

<ol>
  <li>
    <p><strong>Create XCode Project Configuration File (XCodeGen <code class="language-plaintext highlighter-rouge">project.yaml</code> or Tuist <code class="language-plaintext highlighter-rouge">Project.swift</code>)</strong></p>
  </li>
  <li>
    <p>Add XcodeGen or Tuist to Developer and CI/CD Server Environments</p>
  </li>
  <li>
    <p><strong>Use XcodeGen or Tuist to Generate <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode Project Files via Configuration Files</strong></p>
  </li>
  <li>
    <p>Add <code class="language-plaintext highlighter-rouge">/*.xcodeproj</code> directory files to <code class="language-plaintext highlighter-rouge">.gitignore</code></p>
  </li>
  <li>
    <p>Adjust the developer workflow to run XcodeGen or Tuist when switching branches, generating the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file from configuration files.</p>
  </li>
  <li>
    <p>Adjust the CI/CD process to run XcodeGen or Tuist to generate the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file from configuration files.</p>
  </li>
  <li>
    <p>Completed</p>
  </li>
</ol>

<p><code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project files are generated by XcodeGen or Tuist based on YAML or Swift DSL configuration files. The same configuration file and tool version will produce identical results; therefore, we no longer need to commit <code class="language-plaintext highlighter-rouge">.xcodeproj</code> files to Git. This guarantees no more <code class="language-plaintext highlighter-rouge">.xcodeproj</code> file conflicts in the future. Any changes to the project structure or module additions are made by updating the configuration files. Since these are written in YAML or Swift, we can easily iterate and perform code reviews.</p>

<p><strong>Tuist Swift Example:</strong> <code class="language-plaintext highlighter-rouge">Project.swift</code></p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">import</span> <span class="nc">ProjectDescription</span>

<span class="n">let</span> <span class="n">project</span> <span class="o">=</span> <span class="nf">Project</span><span class="p">(</span>
    <span class="n">name</span><span class="o">:</span> <span class="s2">"MyApp"</span><span class="p">,</span>
    <span class="n">targets</span><span class="o">:</span> <span class="p">[</span>
        <span class="nf">Target</span><span class="p">(</span>
            <span class="n">name</span><span class="o">:</span> <span class="s2">"MyApp"</span><span class="p">,</span>
            <span class="n">platform</span><span class="o">:</span> <span class="mf">.</span><span class="n">iOS</span><span class="p">,</span>
            <span class="n">product</span><span class="o">:</span> <span class="mf">.</span><span class="n">app</span><span class="p">,</span>
            <span class="n">bundleId</span><span class="o">:</span> <span class="s2">"com.example.myapp"</span><span class="p">,</span>
            <span class="n">deploymentTarget</span><span class="o">:</span> <span class="mf">.</span><span class="nf">iOS</span><span class="p">(</span><span class="n">targetVersion</span><span class="o">:</span> <span class="s2">"15.0"</span><span class="p">,</span> <span class="n">devices</span><span class="o">:</span> <span class="p">[</span><span class="mf">.</span><span class="n">iphone</span><span class="p">,</span> <span class="mf">.</span><span class="n">ipad</span><span class="p">]),</span>
            <span class="n">infoPlist</span><span class="o">:</span> <span class="mf">.</span><span class="k">default</span><span class="p">,</span>
            <span class="n">sources</span><span class="o">:</span> <span class="p">[</span><span class="s2">"Sources/**"</span><span class="p">],</span>
            <span class="n">resources</span><span class="o">:</span> <span class="p">[</span><span class="s2">"Resources/**"</span><span class="p">],</span>
            <span class="n">dependencies</span><span class="o">:</span> <span class="p">[]</span>
        <span class="p">),</span>
        <span class="nf">Target</span><span class="p">(</span>
            <span class="n">name</span><span class="o">:</span> <span class="s2">"MyAppTests"</span><span class="p">,</span>
            <span class="n">platform</span><span class="o">:</span> <span class="mf">.</span><span class="n">iOS</span><span class="p">,</span>
            <span class="n">product</span><span class="o">:</span> <span class="mf">.</span><span class="n">unitTests</span><span class="p">,</span>
            <span class="n">bundleId</span><span class="o">:</span> <span class="s2">"com.example.myapp.tests"</span><span class="p">,</span>
            <span class="n">deploymentTarget</span><span class="o">:</span> <span class="mf">.</span><span class="nf">iOS</span><span class="p">(</span><span class="n">targetVersion</span><span class="o">:</span> <span class="s2">"15.0"</span><span class="p">,</span> <span class="n">devices</span><span class="o">:</span> <span class="p">[</span><span class="mf">.</span><span class="n">iphone</span><span class="p">,</span> <span class="mf">.</span><span class="n">ipad</span><span class="p">]),</span>
            <span class="n">infoPlist</span><span class="o">:</span> <span class="mf">.</span><span class="k">default</span><span class="p">,</span>
            <span class="n">sources</span><span class="o">:</span> <span class="p">[</span><span class="s2">"Tests/**"</span><span class="p">],</span>
            <span class="n">dependencies</span><span class="o">:</span> <span class="p">[</span><span class="mf">.</span><span class="nf">target</span><span class="p">(</span><span class="n">name</span><span class="o">:</span> <span class="s2">"MyApp"</span><span class="p">)]</span>
        <span class="p">)</span>
    <span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>

<p><strong>XCodeGen YAML Example:</strong> <code class="language-plaintext highlighter-rouge">project.yaml</code></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">MyApp</span>
<span class="na">options</span><span class="pi">:</span>
  <span class="na">bundleIdPrefix</span><span class="pi">:</span> <span class="s">com.example</span>
  <span class="na">deploymentTarget</span><span class="pi">:</span>
    <span class="na">iOS</span><span class="pi">:</span> <span class="s1">'</span><span class="s">15.0'</span>

<span class="na">targets</span><span class="pi">:</span>
  <span class="na">MyApp</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">application</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">Sources</span><span class="pi">]</span>
    <span class="na">resources</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">Resources</span><span class="pi">]</span>
    <span class="na">info</span><span class="pi">:</span>
      <span class="na">path</span><span class="pi">:</span> <span class="s">Info.plist</span>
      <span class="na">properties</span><span class="pi">:</span>
        <span class="na">UILaunchScreen</span><span class="pi">:</span> <span class="pi">{}</span>
    <span class="na">dependencies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">framework</span><span class="pi">:</span> <span class="s">Vendor/SomeFramework.framework</span>
      <span class="pi">-</span> <span class="na">sdk</span><span class="pi">:</span> <span class="s">UIKit.framework</span>
      <span class="pi">-</span> <span class="na">package</span><span class="pi">:</span> <span class="s">Alamofire</span>

  <span class="na">MyAppTests</span><span class="pi">:</span>
    <span class="na">type</span><span class="pi">:</span> <span class="s">bundle.unit-test</span>
    <span class="na">platform</span><span class="pi">:</span> <span class="s">iOS</span>
    <span class="na">sources</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">Tests</span><span class="pi">]</span>
    <span class="na">dependencies</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">target</span><span class="pi">:</span> <span class="s">MyApp</span>
</code></pre></div></div>

<h4 id="file-directory-structure">File Directory Structure</h4>

<p>XCodeGen or Tuist generates the XCode project file (.xcodeproj) directory structure based on the actual file directories and locations, <strong>where the actual directories correspond to the XCode project file directories</strong>.</p>

<p><img src="/assets/fd719053b376/1*rYgdRpEDwRiZoMLzM0WpSQ.webp" alt="" loading="lazy" decoding="async" width="1080" height="752" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgwIiBoZWlnaHQ9Ijc1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/fd719053b376/1*rYgdRpEDwRiZoMLzM0WpSQ.png" /></p>

<p>Therefore, the actual directory location of the files is very important, and we use it directly as the Xcode project file directory.</p>

<p>In modern Xcode/Xcode projects, it is common for these two directories to be located at the same level, but this is exactly the issue this article aims to explore.</p>

<h3 id="early-xcode-used-virtual-directories">Early Xcode Used Virtual Directories</h3>

<p>In early versions of Xcode, right-clicking on the file directory and selecting “New Group” does not create a real folder. Files are placed under the project root directory and referenced in the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file, so the directory only appears in the Xcode project file and does not exist physically.</p>

<p><img src="/assets/fd719053b376/1*LPuedcbhL4OUJuLcO0xSPw.webp" alt="" loading="lazy" decoding="async" width="1168" height="746" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTY4IiBoZWlnaHQ9Ijc0NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/fd719053b376/1*LPuedcbhL4OUJuLcO0xSPw.png" /></p>

<p>Over time, Apple gradually phased out this odd design in Xcode. Later versions introduced “New Group with Folder,” then defaulted to creating physical directories unless you chose “New Group without Folder.” Now (Xcode 16) there is only “New Group,” which automatically generates the Xcode project file directory based on the physical directory.</p>

<h4 id="problems-with-virtual-directories">Problems with Virtual Directories</h4>

<ul>
  <li>
    <p><strong>Cannot use XCodeGen or Tuist because both require physical directory locations to generate XCode project files (.xcodeproj).</strong></p>
  </li>
  <li>
    <p>Code Review Difficulty: The directory structure is not visible on the Git Web GUI, and all files appear flattened.</p>
  </li>
  <li>
    <p>DevOps and Third-Party Tool Integration Challenges: For example, Sentry and GitHub can assign warnings or auto-assign based on directories, but without directories—only files—this setup is not possible.</p>
  </li>
  <li>
    <p>The project directory structure is extremely complex, with a large number of files flattened in the root directory.</p>
  </li>
</ul>

<blockquote>
  <p><em>For an old project that didn’t address the virtual directory issue early, with over 3,000 files in virtual directories, manually moving them would make you want to quit and sell rice cakes. This can truly be called an “<strong><a href="https://x.com/1star_therapist" target="_blank">Apple Developer Occupational Hazard</a></strong> 😔”.</em></p>
</blockquote>

<h3 id="convert-xcode-project-virtual-folders-to-physical-folders">Convert Xcode Project Virtual Folders to Physical Folders</h3>

<p>For all these reasons, we urgently need to convert Xcode project’s virtual directories into physical directories; otherwise, project modernization and more efficient development workflows cannot proceed.</p>

<h4 id="-xcode-16-convert-to-folder-option">❌ Xcode 16 “Convert to folder” Option</h4>

<p><img src="/assets/fd719053b376/1*FlhWWH5Qz4aLfB4hYVUgNA.webp" alt="XCode 16" loading="lazy" decoding="async" width="285" height="562" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyODUiIGhlaWdodD0iNTYyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/fd719053b376/1*FlhWWH5Qz4aLfB4hYVUgNA.png" /></p>

<p>Xcode 16</p>

<p>When Xcode 16 was first released last year, I noticed this new menu option. I originally hoped it could automatically convert virtual directory files into physical directories for us.</p>

<p>But in reality, it requires you to first create the directories and place the files in their corresponding physical locations. Clicking “Convert to folder” will change the Xcode project directory setting to the new type, <code class="language-plaintext highlighter-rouge">PBXFileSystemSynchronizedRootGroup</code>. To be honest, <strong>this has no effect on the conversion itself; it’s more about upgrading to the new directory setting after conversion.</strong></p>

<p>If the directory is not created and files are not placed correctly, clicking “Convert to folder” will show the following error:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Missing Associated Folder
Each group must have an associated folder. The following groups are not associated with a folder:
• xxxx
Use the file inspector to associate a folder with each group, or delete the groups after moving their content to another group.
</code></pre></div></div>

<h4 id="-open-source-projects-venmo--synx">🫥 Open Source Projects <a href="https://github.com/venmo" target="_blank">venmo</a> / <a href="https://github.com/venmo/synx" target="_blank">synx</a></h4>

<p><a href="https://github.com/venmo/synx" target="_blank"><img src="https://opengraph.githubassets.com/c526221a2dff30443c7e38d86c4da063595d493a064d970245ff75a5593dc402/venmo/synx" alt="" /></a></p>

<p>After searching on GitHub for a long time, I only found this open-source tool written in Ruby that converts virtual directories to physical ones. It works to some extent, but since it hasn’t been updated for about 10 years, many files still need to be manually mapped and moved, so it can’t fully convert the structure. Therefore, I gave up on it.</p>

<blockquote>
  <p><em>However, I still appreciate the inspiration from this open-source project, which made me consider developing my own conversion tool.</em></p>
</blockquote>

<h4 id="-my-open-source-projects-zhgchgli--xcfolder">✅ My open-source projects <a href="https://github.com/ZhgChgLi" target="_blank">ZhgChgLi</a> / <a href="https://github.com/ZhgChgLi/XCFolder" target="_blank">XCFolder</a></h4>

<p><a href="https://github.com/ZhgChgLi/XCFolder" target="_blank"><img src="https://repository-images.githubusercontent.com/941421589/27855f06-b938-4458-a20f-3e317b4283b7" alt="" /></a></p>

<p>A Command Line Tool developed purely in Swift, based on <a href="https://github.com/tuist/XcodeProj" target="_blank">XcodeProj</a>, parses the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file, reads all files to get their directories, converts virtual directories into physical directories, moves files to the correct locations, and finally adjusts the directory settings in the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file to complete the conversion.</p>

<p><img src="/assets/fd719053b376/1*eFNN12WDaAk6mr49OzmC_g.webp" alt="" loading="lazy" decoding="async" width="1400" height="484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjQ4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/fd719053b376/1*eFNN12WDaAk6mr49OzmC_g.jpeg" /></p>

<p><strong>Usage</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/ZhgChgLi/XCFolder.git
<span class="nb">cd</span> ./XCFolder
swift run XCFolder YOUR_XCODEPROJ_FILE.xcodeproj ./Configuration.yaml
</code></pre></div></div>

<p><strong>For Example:</strong></p>

<div class="language-objectivec highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">swift</span> <span class="n">run</span> <span class="n">XCFolder</span> <span class="p">.</span><span class="o">/</span><span class="n">TestProject</span><span class="o">/</span><span class="n">DCDeviceTest</span><span class="p">.</span><span class="n">xcodeproj</span> <span class="p">.</span><span class="o">/</span><span class="n">Configuration</span><span class="p">.</span><span class="n">yaml</span>
</code></pre></div></div>

<p><strong>CI/CD Mode (</strong> <code class="language-plaintext highlighter-rouge">Non Interactive Mode</code> <strong>)：</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">swift</span> <span class="n">run</span> <span class="kt">XCFolder</span> <span class="kt">YOUR_XCODEPROJ_FILE</span><span class="o">.</span><span class="n">xcodeproj</span> <span class="o">./</span><span class="kt">Configuration</span><span class="o">.</span><span class="n">yaml</span> <span class="o">--</span><span class="k">is</span><span class="o">-</span><span class="n">non</span><span class="o">-</span><span class="n">interactive</span><span class="o">-</span><span class="n">mode</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Configuration.yaml</code> allows you to set the desired execution parameters:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Ignored directories, will not be parsed or converted</span>
ignorePaths:
- <span class="s2">"Pods"</span>
- <span class="s2">"Frameworks"</span>
- <span class="s2">"Products"</span>

<span class="c"># Ignored file types, will not be converted or moved</span>
ignoreFileTypes:
- <span class="s2">"wrapper.framework"</span> <span class="c"># Frameworks</span>
- <span class="s2">"wrapper.pb-project"</span> <span class="c"># Xcode project files</span>
<span class="c">#- "wrapper.application" # Applications</span>
<span class="c">#- "wrapper.cfbundle" # Bundles</span>
<span class="c">#- "wrapper.plug-in" # Plug-ins</span>
<span class="c">#- "wrapper.xpc-service" # XPC services</span>
<span class="c">#- "wrapper.xctest" # XCTest bundles</span>
<span class="c">#- "wrapper.app-extension" # App extensions</span>

<span class="c"># Only create folders and move files, do not modify .xcodeproj Xcode project folder settings</span>
moveFileOnly: <span class="nb">false</span>

<span class="c"># Prefer using git mv command to move files</span>
gitMove: <span class="nb">true</span>
</code></pre></div></div>

<p>⚠️ <strong>Please Note Before Execution:</strong></p>

<ul>
  <li>
    <p>Please make sure Git has no uncommitted changes to avoid the script accidentally polluting your project directory.<br />
(The script will check this and throw an error <code class="language-plaintext highlighter-rouge">❌ Error: There are uncommitted changes in the repository</code> if any uncommitted changes are found.)</p>
  </li>
  <li>
    <p>By default, the <code class="language-plaintext highlighter-rouge">git mv</code> command is preferred to move files to ensure complete git file log history. If the move fails or the project is not a Git repository, FileSystem Move will be used instead.</p>
  </li>
</ul>

<p><strong>Just wait for the execution to complete:</strong></p>

<p><img src="/assets/fd719053b376/1*IL_-Ht1SH5ZCYogZHXULbQ.webp" alt="" loading="lazy" decoding="async" width="638" height="439" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MzgiIGhlaWdodD0iNDM5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/fd719053b376/1*IL_-Ht1SH5ZCYogZHXULbQ.png" /></p>

<p><img src="/assets/fd719053b376/1*8JNbDbR7pvZLhcoQLU_B6A.webp" alt="" loading="lazy" decoding="async" width="682" height="483" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODIiIGhlaWdodD0iNDgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/fd719053b376/1*8JNbDbR7pvZLhcoQLU_B6A.png" /></p>

<p>⚠️ <strong>Please note after execution:</strong></p>

<ul>
  <li>
    <p>Please check if there are any missing (red) files in the project directory. If the number is small, you can fix them manually. If there are many, please verify whether the ignorePaths and ignoreFileTypes settings in Configuration.yaml are correct, or <a href="https://github.com/ZhgChgLi/XCFolder/issues" target="_blank"><strong>create an Issue</strong></a> to let me know.</p>
  </li>
  <li>
    <p>Check if related paths in Build Settings, e.g. <code class="language-plaintext highlighter-rouge">LIBRARY_SEARCH_PATHS</code>, need to be manually updated.</p>
  </li>
  <li>
    <p>Recommend doing a Clean &amp; Build to check.</p>
  </li>
  <li>
    <p><strong>If you don’t want to manage the current <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file, you can directly start using XcodeGen or Tuist to regenerate the directory and files</strong></p>
  </li>
</ul>

<p><strong>Modify Script:</strong></p>

<p>You can open the project and edit the script by directly clicking <code class="language-plaintext highlighter-rouge">./Package.swift</code>.</p>

<h4 id="other-development-notes">Other Development Notes</h4>

<ul>
  <li>
    <p>Thanks to <a href="https://github.com/tuist/XcodeProj" target="_blank">XcodeProj</a>, we can easily access the contents of <code class="language-plaintext highlighter-rouge">.xcodeproj</code> <strong>Xcode project files</strong> using Swift objects.</p>
  </li>
  <li>
    <p>Developing with the Clean Architecture pattern as well</p>
  </li>
  <li>
    <p>In PBXGroup settings, if there is no path but only a name, it is a virtual folder; otherwise, it is a physical folder.</p>
  </li>
  <li>
    <p>Xcode 16’s new directory setting <code class="language-plaintext highlighter-rouge">PBXFileSystemSynchronizedRootGroup</code> only requires declaring the root directory, which will automatically be resolved from the physical directory. There is no need to declare every folder and file inside the <code class="language-plaintext highlighter-rouge">.xcodeproj</code> Xcode project file.</p>
  </li>
  <li>
    <p>Developing Command Line Tools directly with SPM (Package.swift) is really convenient!</p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">Automate Medium Article Backup｜Mirror to GitHub Pages with Jekyll Seamlessly</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/automate-medium-article-backup-mirror-to-github-pages-with-jekyll-seamlessly-5bb7d3a4954f/" rel="alternate" type="text/html" title="Automate Medium Article Backup｜Mirror to GitHub Pages with Jekyll Seamlessly" />
    <published>2025-01-18T23:12:33+08:00</published>
    <updated>2025-03-08T22:48:25+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/5bb7d3a4954f</id><summary type="html">Discover how to efficiently backup and mirror your Medium articles to GitHub Pages using Jekyll, solving content loss risks with automated setup, maintenance, and customization for reliable personal archives.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="medium" /><category term="jekyll" /><category term="automation" /><category term="ios-app-development" /><category term="github-pages" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/5bb7d3a4954f/1*oM79EdbsiBYiWnqb0mH8QQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/automate-medium-article-backup-mirror-to-github-pages-with-jekyll-seamlessly-5bb7d3a4954f/"><![CDATA[<h3 id="automatic-backup-of-medium-articles-to-github-pages-jekyll">Automatic Backup of Medium Articles to Github Pages (Jekyll)</h3>

<p>Some Notes on Building, Maintaining, Upgrading, and Customizing a Personal Medium Article Backup Mirror Site</p>

<h4 id="preface">Preface</h4>

<p>I have been managing my Medium account for 6 years, and the total number of articles exceeded 100 last year. As time goes by and the number of articles grows, I increasingly worry that Medium might suddenly shut down or my account could have issues, causing all my work to be lost. Some articles are not very valuable, but many record technical architectures and problem-solving thoughts at the time. I often revisit my old posts to review knowledge. In recent years, I also started documenting my travel stories abroad, which are memories and perform well in traffic. Once these contents are lost, they cannot be rewritten.</p>

<h4 id="developing-a-backup-tool-independently">Developing a Backup Tool Independently</h4>

<p>I usually write articles directly on the Medium platform without my own backup. Therefore, during the 2022 Lunar New Year, I spent time developing a tool to download Medium articles and convert them into Markdown files (including article images, embedded code, and other content) — <strong><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank">ZMediumToMarkdown</a> ：</strong></p>

<p><a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank"><img src="https://repository-images.githubusercontent.com/493527574/9b5b7025-cc95-4e81-84a9-b38706093c27" alt="" /></a></p>

<p>Extend the use of this tool to deploy the downloaded Markdown as a static backup mirror site on Github Pages using <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> — <a href="https://zhgchg.li/" target="_blank">https://zhgchg.li/</a></p>

<p><img src="/assets/5bb7d3a4954f/1*oM79EdbsiBYiWnqb0mH8QQ.webp" alt="&lt;https://zhgchg.li/&gt;" loading="lazy" decoding="async" width="1400" height="658" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjY1OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*oM79EdbsiBYiWnqb0mH8QQ.png" /></p>

<p><a href="https://zhgchg.li/" target="_blank">https://zhgchg.li/</a></p>

<p>At that time, I integrated the whole set into a Github Template Repo for friends with similar needs to quickly deploy and use — <a href="https://github.com/ZhgChgLi/ZMediumToJekyll" target="_blank">ZMediumToJekyll</a>. Since then (2022), I have not updated the version or settings of <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a>. <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank"><strong>ZMediumToMarkdown</strong></a> <strong>is still maintained, and any format parsing errors found are fixed immediately. It is now quite stable.</strong></p>

<p>The version of <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> used at that time was v5.x, which worked well with all necessary features (e.g., sticky posts, categories, tags, cover images, comments…). The only issue was frequent scrolling problems where the page would sometimes not scroll, but after a few swipes it worked again. This was a flaw in user experience. Attempts to upgrade to v6.x still had the issue, and reporting it to the developers received no response. Additionally, conflicts increased with each version upgrade, so the idea of upgrading was eventually abandoned.</p>

<p>I recently decided to solve issues with <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a>, upgrade its version, and conveniently optimize the quick deployment tool <a href="https://github.com/ZhgChgLi/ZMediumToJekyll" target="_blank">ZMediumToJekyll</a>.</p>

<h3 id="new-medium-to-jekyll-starter-">New! medium-to-jekyll-starter 🎉🎉</h3>

<h4 id="medium-to-jekyll-startergithubio"><a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io" target="_blank">medium-to-jekyll-starter.github.io</a></h4>

<p><a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io" target="_blank"><img src="https://repository-images.githubusercontent.com/918538745/779cd996-4dc3-4ee0-88b7-951b39fc4463" alt="" /></a></p>

<p>I integrated the latest version v7.x of <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank"><strong>Jekyll (Chirpy Theme)</strong></a> with my <a href="https://github.com/ZhgChgLi/ZMediumToMarkdown" target="_blank"><strong>ZMediumToMarkdown</strong></a> Medium article download and conversion tool into a new Github Template Repo — <a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io" target="_blank">medium-to-jekyll-starter.github.io</a>.</p>

<p>You can directly use this starter Repo to quickly set up your own Medium mirror content backup site, <strong>with one-time setup for permanent continuous automatic backup, deployed completely free on Github Pages</strong>.</p>

<blockquote>
  <p><strong><em>For a step-by-step setup guide, please refer to this article: <a href="https://zhgchg.li/posts/medium-to-jekyll/" target="_blank">https://zhgchg.li/posts/medium-to-jekyll/</a></em></strong></p>
</blockquote>

<h4 id="results">Results</h4>

<p><img src="/assets/5bb7d3a4954f/1*Nyg7Fg93sDUAIMZQfN5QTg.webp" alt="&lt;https://zhgchg.li/&gt;" loading="lazy" decoding="async" width="1200" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*Nyg7Fg93sDUAIMZQfN5QTg.png" /></p>

<p><a href="https://zhgchg.li/" target="_blank">https://zhgchg.li/</a></p>

<blockquote>
  <p><strong>All the above articles are **automatically</strong> downloaded from my Medium, converted to Markdown format, and re-uploaded.*</p>
</blockquote>

<blockquote>
  <p><strong><em>Here is a sample conversion result of a random article for comparison:</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em><a href="https://shorturl.at/CG9ua" target="_blank">Original content on Medium</a> / <a href="/posts/pinkoi-engineering/design-patterns-practical-solutions-for-socket-io-client-library-challenges-78507a8de6a5/">Converted result on personal website</a></em></strong></p>
</blockquote>

<p>After the upgrade, the scrolling freeze issue no longer occurs. This upgrade also added customized dynamic content (displaying Medium follower count).</p>

<h3 id="some-technical-notes">Some Technical Notes</h3>

<p><a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> deployment setup on Github Pages mainly follows the official Start Repo:</p>

<p><a href="https://github.com/cotes2020/chirpy-starter/tree/main" target="_blank"><img src="https://opengraph.githubassets.com/e23e4502475ff31a4a9ea0acb4524983a049cf151ec9ef48fe0777bbc9d8edb1/cotes2020/chirpy-starter" alt="" /></a></p>

<blockquote>
  <p><em>Last month, I also referred to this project’s approach and created a new open-source project — <a href="https://github.com/ZhgChgLi/linkyee" target="_blank">Linkyee</a>, an open-source Link Tree personal link page.</em></p>
</blockquote>

<p><img src="/assets/5bb7d3a4954f/1*OTotv1Nw-KnhsflSSiNgkg.webp" alt="&lt;https://link.zhgchg.li/&gt;" loading="lazy" decoding="async" width="1400" height="854" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijg1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*OTotv1Nw-KnhsflSSiNgkg.png" /></p>

<p><a href="https://link.zhgchg.li/" target="_blank">https://link.zhgchg.li/</a></p>

<h4 id="jekyll-customization-method-1--override-html">Jekyll Customization Method (1) — Override HTML</h4>

<p><a href="https://jekyllrb.com/" target="_blank">Jekyll</a> is a powerful Ruby static site generator. <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> is a theme based on Jekyll. After comparing with other themes, <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Chirpy Theme</a> offers the best quality, user experience, and comprehensive features.</p>

<p>Jekyll pages support inheritance. We can add files with the <a href="https://github.com/cotes2020/jekyll-theme-chirpy/tree/master/_layouts" target="_blank">same page filenames as Jekyll</a> in <code class="language-plaintext highlighter-rouge">./_layouts</code>. When generating the site, the engine will use your custom page content to replace the original.</p>

<p>For example, if I want to add a line of text at the end of each post page, I first copy the original post page file (<a href="https://github.com/cotes2020/jekyll-theme-chirpy/blob/master/_layouts/post.html" target="_blank">post.html</a>) and place it in the <code class="language-plaintext highlighter-rouge">./_layouts</code> directory:</p>

<p><img src="/assets/5bb7d3a4954f/1*oDykwzZ0P5o8GEw8lc3WPQ.webp" alt="" loading="lazy" decoding="async" width="133" height="38" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzMiIGhlaWdodD0iMzgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/5bb7d3a4954f/1*oDykwzZ0P5o8GEw8lc3WPQ.png" /></p>

<p>Open post.html with an editor, add text or customizations in the appropriate place, and redeploy the site to see the customized results.</p>

<p><img src="/assets/5bb7d3a4954f/1*7ni973_1JykXoj8le78v1A.webp" alt="" loading="lazy" decoding="async" width="303" height="124" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDMiIGhlaWdodD0iMTI0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*7ni973_1JykXoj8le78v1A.png" /></p>

<p>You can also create a <code class="language-plaintext highlighter-rouge">./_include</code> directory to store some shared page content files:</p>

<p><img src="/assets/5bb7d3a4954f/1*rExx8jMcMfEQZ3LfvhwT2w.webp" alt="" loading="lazy" decoding="async" width="193" height="163" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxOTMiIGhlaWdodD0iMTYzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*rExx8jMcMfEQZ3LfvhwT2w.png" /></p>

<p>Then in <code class="language-plaintext highlighter-rouge">post.html</code>, we can directly use <code class="language-plaintext highlighter-rouge">{% include buymeacoffee.html %}</code> to include the HTML content from that file for reuse.</p>

<blockquote>
  <p><em>The advantage of overriding HTML Layout files is 100% customization—you can freely adjust the page content and layout as you wish; the downside is that during upgrades, conflicts or unexpected results may occur, requiring you to review your customizations again.</em></p>
</blockquote>

<h4 id="jekyll-customization-method-2--plugin">Jekyll Customization Method (2) — Plugin</h4>

<p>The second method is to use the <a href="https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events" target="_blank">Hook</a> feature within <a href="https://jekyllrb.com/docs/plugins/" target="_blank">Plugins</a> to inject custom content during Jekyll’s static site generation phase.</p>

<p><img src="/assets/5bb7d3a4954f/1*JI-uJ8tIKnomJyQk9cVfyQ.webp" alt="" loading="lazy" decoding="async" width="651" height="423" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTEiIGhlaWdodD0iNDIzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*JI-uJ8tIKnomJyQk9cVfyQ.png" /></p>

<p><img src="/assets/5bb7d3a4954f/1*GrUJn6HXoBqYXUQMKrnqTA.webp" alt="Built-in Hook Owners and Events" loading="lazy" decoding="async" width="647" height="285" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDciIGhlaWdodD0iMjg1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*GrUJn6HXoBqYXUQMKrnqTA.png" /></p>

<p>[Built-in Hook Owners and Events</p>

<p>Hook events](https://jekyllrb.com/docs/plugins/hooks/#built-in-hook-owners-and-events){:target=”_blank”} are many; here I only include the <code class="language-plaintext highlighter-rouge">site:pre_render</code> and <code class="language-plaintext highlighter-rouge">post:pre_render</code> hooks I used.</p>

<p>Adding a new method is also simple; just add a Ruby file in <code class="language-plaintext highlighter-rouge">./_plugins</code>.</p>

<p><img src="/assets/5bb7d3a4954f/1*1QTCNuYJbJPlfJoMrc6v5g.webp" alt="posts-lastmod-hook.rb is an existing Plugin" loading="lazy" decoding="async" width="216" height="66" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMTYiIGhlaWdodD0iNjYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/5bb7d3a4954f/1*1QTCNuYJbJPlfJoMrc6v5g.png" /></p>

<p>posts-lastmod-hook.rb is a built-in Plugin</p>

<p>I want a few “pseudo” dynamic content features. The first is to display the Medium follower count under the profile and show the last updated time of the page content in the footer.</p>

<p><img src="/assets/5bb7d3a4954f/1*6JA9ONLP_A0eNL_q-5b6yg.webp" alt="" loading="lazy" decoding="async" width="1081" height="802" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgxIiBoZWlnaHQ9IjgwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*6JA9ONLP_A0eNL_q-5b6yg.png" /></p>

<p>Created a <code class="language-plaintext highlighter-rouge">zhgchgli-customize.rb</code> under <code class="language-plaintext highlighter-rouge">./_plugins</code>:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env ruby</span>
<span class="c1">#</span>
<span class="nb">require</span> <span class="s1">'net/http'</span>
<span class="nb">require</span> <span class="s1">'nokogiri'</span>
<span class="nb">require</span> <span class="s1">'uri'</span>
<span class="nb">require</span> <span class="s1">'date'</span>


<span class="k">def</span> <span class="nf">load_medium_followers</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">limit</span> <span class="o">=</span> <span class="mi">10</span><span class="p">)</span>
  <span class="k">return</span> <span class="mi">0</span> <span class="k">if</span> <span class="n">limit</span><span class="p">.</span><span class="nf">zero?</span>

  <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
  <span class="n">response</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">get_response</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
  <span class="k">case</span> <span class="n">response</span>
  <span class="k">when</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTPSuccess</span> <span class="k">then</span>
      <span class="n">document</span> <span class="o">=</span> <span class="no">Nokogiri</span><span class="o">::</span><span class="no">HTML</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">body</span><span class="p">)</span>

      <span class="n">follower_count_element</span> <span class="o">=</span> <span class="n">document</span><span class="p">.</span><span class="nf">at</span><span class="p">(</span><span class="s1">'span.pw-follower-count &gt; a'</span><span class="p">)</span>
      <span class="n">follower_count</span> <span class="o">=</span> <span class="n">follower_count_element</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">text</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">)</span><span class="o">&amp;</span><span class="p">.</span><span class="nf">first</span>

      <span class="k">return</span> <span class="n">follower_count</span> <span class="p">\\</span><span class="o">|</span><span class="p">\\</span><span class="o">|</span> <span class="mi">0</span>
  <span class="k">when</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTPRedirection</span> <span class="k">then</span>
    <span class="n">location</span> <span class="o">=</span> <span class="n">response</span><span class="p">[</span><span class="s1">'location'</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">load_medium_followers</span><span class="p">(</span><span class="n">location</span><span class="p">,</span> <span class="n">limit</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
  <span class="k">else</span>
      <span class="k">return</span> <span class="mi">0</span>
  <span class="k">end</span>
<span class="k">end</span>

<span class="vg">$medium_url</span> <span class="o">=</span> <span class="s2">"https://medium.com/@zhgchgli"</span>
<span class="c1"># could also define in _config.yml and retrieve in Jekyll::Hooks.register :site, :pre_render do \\|site\\| site.config</span>

<span class="vg">$medium_followers</span> <span class="o">=</span> <span class="n">load_medium_followers</span><span class="p">(</span><span class="vg">$medium_url</span><span class="p">)</span>

<span class="vg">$medium_followers</span> <span class="o">=</span> <span class="mi">1000</span> <span class="k">if</span> <span class="vg">$medium_followers</span> <span class="o">==</span> <span class="mi">0</span>
<span class="vg">$medium_followers</span> <span class="o">=</span> <span class="vg">$medium_followers</span><span class="p">.</span><span class="nf">to_s</span><span class="p">.</span><span class="nf">reverse</span><span class="p">.</span><span class="nf">scan</span><span class="p">(</span><span class="sr">/\d{1,3}/</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="s1">','</span><span class="p">).</span><span class="nf">reverse</span>


<span class="no">Jekyll</span><span class="o">::</span><span class="no">Hooks</span><span class="p">.</span><span class="nf">register</span> <span class="ss">:site</span><span class="p">,</span> <span class="ss">:pre_render</span> <span class="k">do</span> <span class="p">\\</span><span class="o">|</span><span class="n">site</span><span class="p">\\</span><span class="o">|</span>

  <span class="n">tagline</span> <span class="o">=</span> <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'tagline'</span><span class="p">]</span>
  
  <span class="n">followMe</span> <span class="o">=</span> <span class="o">&lt;&lt;-</span><span class="no">HTML</span><span class="sh">
  &lt;a href="</span><span class="si">#{</span><span class="vg">$medium_url</span><span class="si">}</span><span class="sh">" target="_blank" style="display: block;text-align: center;font-style: normal;/* text-decoration: underline; */font-size: 1.2em;color: var(--heading-color);"&gt;</span><span class="si">#{</span><span class="vg">$medium_followers</span><span class="si">}</span><span class="sh">+ Followers on Medium&lt;/a&gt;
</span><span class="no">  HTML</span>

  <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'tagline'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">followMe</span><span class="si">}</span><span class="s2">"</span><span class="p">;</span>
  <span class="n">site</span><span class="p">.</span><span class="nf">config</span><span class="p">[</span><span class="s1">'tagline'</span><span class="p">]</span> <span class="o">+=</span> <span class="n">tagline</span><span class="p">;</span>

  <span class="n">meta_data</span> <span class="o">=</span> <span class="n">site</span><span class="p">.</span><span class="nf">data</span><span class="p">.</span><span class="nf">dig</span><span class="p">(</span><span class="s1">'locales'</span><span class="p">,</span> <span class="s1">'en'</span><span class="p">,</span> <span class="s1">'meta'</span><span class="p">);</span>
  <span class="c1"># only implementation in en, could implement to all langs.</span>

  <span class="k">if</span> <span class="n">meta_data</span>
    <span class="n">gmt_plus_8</span> <span class="o">=</span> <span class="no">Time</span><span class="p">.</span><span class="nf">now</span><span class="p">.</span><span class="nf">getlocal</span><span class="p">(</span><span class="s2">"+08:00"</span><span class="p">)</span>
    <span class="n">formatted_time</span> <span class="o">=</span> <span class="n">gmt_plus_8</span><span class="p">.</span><span class="nf">strftime</span><span class="p">(</span><span class="s2">"%Y-%m-%d %H:%M:%S"</span><span class="p">)</span>
    <span class="n">site</span><span class="p">.</span><span class="nf">data</span><span class="p">[</span><span class="s1">'locales'</span><span class="p">][</span><span class="s1">'en'</span><span class="p">][</span><span class="s1">'meta'</span><span class="p">]</span> <span class="o">+=</span> <span class="s2">"&lt;br/&gt;Last updated: </span><span class="si">#{</span><span class="n">formatted_time</span><span class="si">}</span><span class="s2"> +08:00"</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<ul>
  <li>
    <p>The principle is to register a Hook before the site renders, injecting an HTML block showing Medium follower count into the <code class="language-plaintext highlighter-rouge">tagline</code> profile introduction section in the config.</p>
  </li>
  <li>
    <p>The number of Medium followers is fetched every time the script runs to get the latest count.</p>
  </li>
  <li>
    <p>The logic for the footer last updated time is similar, which is to add the last updated time string to locales-&gt;en-&gt;meta when generating the site.</p>
  </li>
  <li>
    <p>Additionally, if it’s a Hook before article generation, you can access the Markdown; if it’s a Hook after article generation, you can access the generated HTML.</p>
  </li>
</ul>

<p>After saving, you can first test the result locally with <code class="language-plaintext highlighter-rouge">bundle exec jekyll s</code>:</p>

<p><img src="/assets/5bb7d3a4954f/1*T1idAZIWAJ2N9J054-PFSA.webp" alt="" loading="lazy" decoding="async" width="710" height="483" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MTAiIGhlaWdodD0iNDgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*T1idAZIWAJ2N9J054-PFSA.png" /></p>

<p>Open <code class="language-plaintext highlighter-rouge">127.0.0.1:4000</code> in your browser to see the result.</p>

<p><img src="/assets/5bb7d3a4954f/1*6JA9ONLP_A0eNL_q-5b6yg.webp" alt="" loading="lazy" decoding="async" width="1081" height="802" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDgxIiBoZWlnaHQ9IjgwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*6JA9ONLP_A0eNL_q-5b6yg.png" /></p>

<p>Finally, add a scheduled workflow in the Github Pages Repo Actions to automatically regenerate the site, and it’s done:</p>

<p><img src="/assets/5bb7d3a4954f/1*2BFHmkhnytEwHTkNHwZsTg.webp" alt="" loading="lazy" decoding="async" width="1050" height="589" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDUwIiBoZWlnaHQ9IjU4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/5bb7d3a4954f/1*2BFHmkhnytEwHTkNHwZsTg.png" /></p>

<p>In the <a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> repo project Actions, find <code class="language-plaintext highlighter-rouge">pages-deploy.yml</code> and add the following under <code class="language-plaintext highlighter-rouge">on:</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="na">schedule</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">10</span><span class="nv"> </span><span class="s">1</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span> <span class="c1"># Automatically runs once daily at 01:10 UTC, https://crontab.guru</span>
</code></pre></div></div>

<blockquote>
  <p><em>The advantage of Plugins is that they enable dynamic content (scheduled updates) without affecting the site structure or causing conflicts during upgrades; the downside is limited control over content and display positions.</em></p>
</blockquote>

<h4 id="jekyll-chirpy-theme-deployment-issues-on-github-pages-after-v7x"><a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> Deployment Issues on Github Pages after v7.x</h4>

<p>Besides the site structure adjustments, the deployment script in v.7.x has also changed; the original <a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io/blob/main/tools/deploy.sh" target="_blank">deploy.sh</a> script was removed, and Github Actions deployment steps are used directly:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># build:</span>
<span class="c1"># ...</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload site artifact</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-pages-artifact@v3</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_site${{</span><span class="nv"> </span><span class="s">steps.pages.outputs.base_path</span><span class="nv"> </span><span class="s">}}"</span>

<span class="err">  </span><span class="na">deploy</span><span class="pi">:</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">github-pages</span>
      <span class="na">url</span><span class="pi">:</span> <span class="s">${{ steps.deployment.outputs.page_url }}</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy to GitHub Pages</span>
        <span class="na">id</span><span class="pi">:</span> <span class="s">deployment</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/deploy-pages@v4</span>
</code></pre></div></div>

<p>But I encountered problems during the deployment process:</p>

<p><code class="language-plaintext highlighter-rouge">Uploaded artifact size of 1737778940 bytes exceeds the allowed size of 1 GB</code> caused the Upload Artifact to fail because my website content is too large; however, the previous deployment script worked, so I had to revert to the original <a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io/blob/main/tools/deploy.sh" target="_blank">deploy.sh</a> plus <a href="https://github.com/ZhgChgLi/zhgchgli.github.io/blob/main/.github/workflows/pages-deploy.yml" target="_blank">comment out this section above</a>.</p>

<h4 id="test-site-steps-keep-failing-during-github-pages-deployment">Test Site Steps Keep Failing During Github Pages Deployment</h4>

<p><a href="https://github.com/cotes2020/jekyll-theme-chirpy" target="_blank">Jekyll (Chirpy Theme)</a> deployment includes a step called Test Site to check if the webpage content is correct, such as verifying links and ensuring no missing HTML tags.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># build:</span>
<span class="c1"># ...</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Test site</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">bundle exec htmlproofer _site \</span>
            <span class="s">\-\-disable-external \</span>
            <span class="s">\-\-no-enforce-https \</span>
            <span class="s">\-\-ignore-empty-alt \</span>
            <span class="s">\-\-ignore-urls "/^http:\/\/127.0.0.1/,/^http:\/\/0.0.0.0/,/^http:\/\/localhost/"</span>
</code></pre></div></div>

<p>I added <code class="language-plaintext highlighter-rouge">--no-enforce-https</code> and <code class="language-plaintext highlighter-rouge">--ignore-empty-alt</code> to skip checks for HTTPS and HTML tags without alt attributes. <strong>Ignoring these two allows the check to pass (since I can’t change the content for now).</strong></p>

<p>The CLI command for <a href="https://github.com/gjtorikian/html-proofer" target="_blank">htmlproofer</a> is not mentioned in the official documentation. After searching for a long time, I finally found the rules in a <a href="https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268" target="_blank">comment</a> on an issue:</p>

<p><img src="/assets/5bb7d3a4954f/1*kn6TE3wlIqIA8Nxe8OqTww.webp" alt="&lt;https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268&gt;" loading="lazy" decoding="async" width="814" height="234" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MTQiIGhlaWdodD0iMjM0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/5bb7d3a4954f/1*kn6TE3wlIqIA8Nxe8OqTww.png" /></p>

<p><a href="https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268" target="_blank">https://github.com/gjtorikian/html-proofer/issues/727#issuecomment-1334430268</a></p>

<h4 id="other-article-supplements">Other Article Supplements</h4>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/github-pages-custom-domain-setup-replace-github-io-with-your-own-domain-483af5d93297/">Github Pages Custom Domain Tutorial</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/github-pages-create-free-custom-linktree-style-personal-link-pages-fast-70aeddb1fd9b/">Linkyee — Quickly Create a Free Personal LinkTree-like Page Using GitHub Pages</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">Medium to Jekyll Migration｜Step-by-Step Installation and Setup Guide</title>
    <link href="https://en.zhgchg.li/posts/tools/medium-to-jekyll-migration-step-by-step-installation-and-setup-guide-medium-to-jekyll/" rel="alternate" type="text/html" title="Medium to Jekyll Migration｜Step-by-Step Installation and Setup Guide" />
    <published>2025-01-17T08:00:00+08:00</published>
    <updated>2025-01-17T08:00:00+08:00</updated>
    <id>https://en.zhgchg.li/posts/tools/medium-to-jekyll</id><summary type="html">Struggling to migrate your blog from Medium to Jekyll? This guide offers a clear, step-by-step installation and setup process to smoothly transfer your content and optimize your static site efficiently.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="tools" /><category term="meidum" /><category term="github" /><category term="jekyll" /><category term="ruby" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/images/zmediumtomarkdown.jpeg" /><content type="html" xml:base="https://en.zhgchg.li/posts/tools/medium-to-jekyll-migration-step-by-step-installation-and-setup-guide-medium-to-jekyll/"><![CDATA[<h1 id="start">Start!</h1>

<p><a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io"><img src="https://opengraph.githubassets.com/91a5dd913bf4d51e6b76fbcc7442c845023bdf93cb1a0ce1ac1c8a40d554f781/ZhgChgLi/medium-to-jekyll-starter.github.io" alt="" /></a></p>

<h2 id="1-go-to-the-template-repo---medium-to-jekyll-startergithubio">1. Go to the Template Repo -&gt; <a href="https://github.com/ZhgChgLi/medium-to-jekyll-starter.github.io">medium-to-jekyll-starter.github.io</a></h2>

<p><img src="/assets/medium-to-jekyll-starter/start-6.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<p>Click the top right corner “Use this template” -&gt; “Create a new repository”</p>

<h2 id="2-create-a-new-repository">2. Create a new repository</h2>

<p><img src="/assets/medium-to-jekyll-starter/start-2.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>
    <p>Repository name: Usually <code class="language-plaintext highlighter-rouge">username or organization.github.io</code>, must end with <code class="language-plaintext highlighter-rouge">*.github.io</code>.</p>
  </li>
  <li>
    <p>The repository must be <code class="language-plaintext highlighter-rouge">Public</code> to use GitHub Pages.</p>
  </li>
</ul>

<h3 id="adjust-github-actions-execution-permissions">Adjust GitHub Actions Execution Permissions</h3>

<p><img src="/assets/medium-to-jekyll-starter/github-action-permissions.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>After creation, due to GitHub’s security settings, you need to enable GitHub Actions execution permissions in the repository settings.</li>
</ul>

<h2 id="3-create-gh-pages-branch-if-needed">3. Create gh-pages branch if needed</h2>

<p><img src="/assets/medium-to-jekyll-starter/start-3.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>
    <p>On the Repo homepage, click the “<code class="language-plaintext highlighter-rouge">main</code>” branch dropdown, type “<code class="language-plaintext highlighter-rouge">gh-pages</code>”, and if it doesn’t exist, select “Create branch <code class="language-plaintext highlighter-rouge">gh-pages</code> from <code class="language-plaintext highlighter-rouge">main</code>”.</p>
  </li>
  <li>
    <p>If the <code class="language-plaintext highlighter-rouge">gh-pages</code> branch already exists, or you see the message “Sorry, that branch already exists.” when creating it, you can skip this step.</p>
  </li>
</ul>

<h2 id="4-enable-github-pages-go-to-settings---pages---build-and-deployment">4. Enable Github Pages, go to Settings -&gt; Pages -&gt; Build and deployment</h2>

<p><img src="/assets/medium-to-jekyll-starter/start-4.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>Select the <code class="language-plaintext highlighter-rouge">gh-pages</code> branch and click <code class="language-plaintext highlighter-rouge">Save</code> to save the settings</li>
</ul>

<h3 id="run-the-first-deployment">Run the First Deployment</h3>

<p><img src="/assets/medium-to-jekyll-starter/first-deploy.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>Repo -&gt; “Actions” -&gt; “Build and Deploy” -&gt; “Run workflow” -&gt; “Branch: main, Run workflow”</li>
</ul>

<h2 id="5-wait-for-all-deployment-jobs-to-complete">5. Wait for All Deployment Jobs to Complete</h2>

<p><img src="/assets/medium-to-jekyll-starter/start-5.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>🟢 pages build and deployment</li>
</ul>

<h2 id="6-visit-the-website-to-see-the-results">6. Visit the Website to See the Results</h2>

<blockquote>
  <p>https://<code class="language-plaintext highlighter-rouge">username-or-organization.github.io</code></p>
</blockquote>

<p><img src="/assets/medium-to-jekyll-starter/done.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<h3 id="troubleshooting">Troubleshooting</h3>

<p>If the page only displays:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--- layout: home # Index page ---
</code></pre></div></div>

<p>This indicates a GitHub Pages configuration error, ongoing deployment, or cached previous page. Please use a hard refresh or open the webpage in an incognito browser window.</p>

<blockquote>
  <p>First deployment successful! 🎉🎉🎉 Please continue setting up synchronization with your Medium account.</p>
</blockquote>

<hr />

<h1 id="github-repo-github-actions-setup">Github Repo (Github Actions) Setup</h1>

<h2 id="1-go-to-your-github-repos-github-actions-page---click-zmediumtomarkdown---click-zmediumtomarkdownyml">1. Go to your Github Repo’s Github Actions page -&gt; Click “ZMediumToMarkdown” -&gt; Click “ZMediumToMarkdown.yml”</h2>

<p><img src="/assets/medium-to-jekyll-starter/github-1.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<blockquote>
  <p>https://github.com/{ORG}/{REPO_NAME}/blob/main/.github/workflows/ZMediumToMarkdown.yml</p>

  <h2 id="2-click-the-edit-button-on-the-right">2. Click the Edit Button on the Right</h2>

  <p><img src="/assets/medium-to-jekyll-starter/github-2.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>
</blockquote>

<h2 id="3-set-parameters-for-automatic-medium-article-sync">3. Set Parameters for Automatic Medium Article Sync</h2>

<p><img src="/assets/medium-to-jekyll-starter/github-3.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">ZMediumToMarkdown</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
  <span class="na">schedule</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">10</span><span class="nv"> </span><span class="s">1</span><span class="nv"> </span><span class="s">15</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span> <span class="c1"># Runs at 01:10(UTC), every day.</span>
    <span class="c1"># Set how often to auto-sync</span>
    <span class="c1"># ref: https://crontab.guru/</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">ZMediumToMarkdown</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ZMediumToMarkdown Automatic Bot</span>
      <span class="na">uses</span><span class="pi">:</span> <span class="s">ZhgChgLi/ZMediumToMarkdown@main</span>
      <span class="na">with</span><span class="pi">:</span>
        <span class="na">command</span><span class="pi">:</span> <span class="s2">"</span><span class="s">--cookie_uid</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MEDIUM_COOKIE_UID</span><span class="nv"> </span><span class="s">}}</span><span class="nv"> </span><span class="s">--cookie_sid</span><span class="nv"> </span><span class="s">${{</span><span class="nv"> </span><span class="s">secrets.MEDIUM_COOKIE_SID</span><span class="nv"> </span><span class="s">}}</span><span class="nv"> </span><span class="s">-j</span><span class="nv"> </span><span class="s">zhgchgli_test"</span>
        <span class="c1"># Replace zhgchgli_test with your Medium username</span>
        <span class="c1"># For example https://medium.com/@zhgchgli -&gt; zhgchgli</span>
        <span class="c1"># ref: https://github.com/ZhgChgLi/ZMediumToMarkdown?tab=readme-ov-file#usage</span>
</code></pre></div></div>

<h3 id="provide-medium-account-cookies-with-access-permissions">Provide Medium account cookies with access permissions.</h3>

<ul>
  <li>
    <p>If you have articles behind a paywall, you must provide</p>
  </li>
  <li>
    <p>If you find that Medium article synchronization is incomplete (missing articles), it means the synchronization was blocked by Medium’s firewall and you must also provide</p>
  </li>
</ul>

<h4 id="steps-to-obtain-medium-account-cookies-medium_cookie_uid--medium_cookie_sid">Steps to Obtain Medium Account Cookies MEDIUM_COOKIE_UID &amp; MEDIUM_COOKIE_SID:</h4>

<p><img src="/assets/medium-to-jekyll-starter/github-4.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ol>
  <li>
    <p>Log in to a Medium account with access rights and go to the <a href="https://medium.com/me/stats">Medium dashboard</a></p>
  </li>
  <li>
    <p>Right-click on the blank area</p>
  </li>
  <li>
    <p>Select “Inspect”</p>
  </li>
  <li>
    <p>After the Developer Console appears, select “Application”</p>
  </li>
  <li>
    <p>Select “Cookies” -&gt; “https://medium.com”</p>
  </li>
  <li>
    <p>Scroll down to find “<code class="language-plaintext highlighter-rouge">sid</code>” and “<code class="language-plaintext highlighter-rouge">uid</code>”</p>
  </li>
  <li>
    <p>Double-click to copy the values of these two fields</p>
  </li>
</ol>

<h4 id="securely-store-medium-account-cookies-in-github-repo-secrets">Securely Store Medium Account Cookies in GitHub Repo Secrets</h4>

<h5 id="1-go-to-github-repo-settings---secrets-and-variables---actions---new-repository-secret">1. Go to Github Repo Settings -&gt; Secrets and variables -&gt; Actions -&gt; New repository secret</h5>

<p><img src="/assets/medium-to-jekyll-starter/github-5.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>
    <p>Name: <code class="language-plaintext highlighter-rouge">MEDIUM_COOKIE_SID</code></p>
  </li>
  <li>
    <p>Secret: Paste the <code class="language-plaintext highlighter-rouge">sid</code> value of the Medium account copied in the previous step</p>
  </li>
</ul>

<blockquote>
  <p>https://github.com/{ORG}/{REPO_NAME}/settings/secrets/actions/new</p>

  <h5 id="2-new-secret---medium_cookie_sid">2. New secret - MEDIUM_COOKIE_SID</h5>

  <p><img src="/assets/medium-to-jekyll-starter/github-6.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>
</blockquote>

<h5 id="3-new-secret---medium_cookie_uid">3. New secret - MEDIUM_COOKIE_UID</h5>

<p><img src="/assets/medium-to-jekyll-starter/github-7.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<ul>
  <li>
    <p>Name: <code class="language-plaintext highlighter-rouge">MEDIUM_COOKIE_UID</code></p>
  </li>
  <li>
    <p>Secret: Paste the <code class="language-plaintext highlighter-rouge">uid</code> value copied from the previous step of the Medium account</p>
  </li>
</ul>

<h5 id="done">Done</h5>

<p>If the account is not explicitly logged out or encounters issues, the cookies will not expire.</p>

<p>If the following message appears during synchronization and the synced articles are incomplete:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This post is behind Medium's paywall. You must provide valid Medium Member login cookies to download the full post.
</code></pre></div></div>

<p>Indicates that the cookies have expired. Please follow the above steps to set them again.</p>

<h2 id="4-first-manual-sync-repo---github-actions---click-zmediumtomarkdown---click-enable-workflow">4. First Manual Sync, Repo -&gt; GitHub Actions -&gt; Click “ZMediumToMarkdown” -&gt; Click “Enable workflow”</h2>

<p>For the first run, we can manually sync once to check if the settings are correct.<br />
<img src="/assets/medium-to-jekyll-starter/github-9.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<h2 id="5-wait-for-the-article-synchronization-and-website-deployment-to-complete">5. Wait for the Article Synchronization and Website Deployment to Complete</h2>

<p><img src="/assets/medium-to-jekyll-starter/github-10.png" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<p>Wait for the following three Actions jobs to complete without errors:</p>

<ul>
  <li>
    <p>🟢 ZMediumToMarkdown</p>
  </li>
  <li>
    <p>🟢 pages build and deployment</p>
  </li>
  <li>
    <p>🟢 Build and Deploy</p>
  </li>
</ul>

<h2 id="6-refresh-the-page-to-see-the-results-enjoy">6. Refresh the page to see the results, Enjoy!</h2>

<blockquote>
  <p>⚠️ Attention! All file changes will trigger:</p>

  <ul>
    <li>🟢 pages build and deployment</li>
  </ul>

  <p>You need to wait for both deployment jobs above to complete before the website changes take effect.</p>
</blockquote>

<hr />

<h1 id="jekyll-website-configuration">Jekyll Website Configuration</h1>

<h2 id="basic-website-setup">Basic Website Setup</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">./_config.yml</code></p>

    <p><strong>Be sure to adjust</strong> the <code class="language-plaintext highlighter-rouge">url:</code> to your GitHub Pages URL and other site settings.</p>
  </li>
  <li>
    <p>Share Feature Settings: <code class="language-plaintext highlighter-rouge">./_data/share.yml</code></p>
  </li>
  <li>
    <p>Define article author information: <code class="language-plaintext highlighter-rouge">./_data/authors.yml</code></p>
  </li>
</ul>

<h2 id="left-sidebar-settings">Left Sidebar Settings</h2>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">./tabs</code></p>
  </li>
  <li>
    <p>Footer link buttons: <code class="language-plaintext highlighter-rouge">./_data/contact.yml</code></p>
  </li>
</ul>

<h2 id="footer-and-other-text-content-settings">Footer and Other Text Content Settings</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">./locales/{Lang}.yml</code> default is <code class="language-plaintext highlighter-rouge">/locales/en.yml</code></li>
</ul>

<h2 id="local-testing">Local Testing</h2>

<ol>
  <li>
    <p>Make sure your environment has Ruby version 3.1 or higher installed and in use</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">cd ./</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">bundle install</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">bundle exec jekyll s</code></p>
  </li>
  <li>
    <p>Go to <a href="http://127.0.0.1:4000/">http://127.0.0.1:4000/</a> to see the result</p>
  </li>
  <li>
    <p>Press <code class="language-plaintext highlighter-rouge">Ctrl-c</code> to stop.</p>
  </li>
</ol>

<p>*The basic site configuration files need to be re-run to take effect after adjustments.</p>]]></content>
  </entry><entry>
    <title type="html">Google Apps Script Web App｜Integrate Forms with GitHub Actions CI/CD for Streamlined Workflows</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/google-apps-script-web-app-integrate-forms-with-github-actions-ci-cd-for-streamlined-workflows-4cb4437818f2/" rel="alternate" type="text/html" title="Google Apps Script Web App｜Integrate Forms with GitHub Actions CI/CD for Streamlined Workflows" />
    <published>2025-01-11T19:19:43+08:00</published>
    <updated>2025-12-17T23:45:42+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/4cb4437818f2</id><summary type="html">Developers facing complex CI/CD workflows can streamline GitHub Actions by integrating Google Apps Script Web App forms, enhancing automation with Jira, Asana, and Slack for faster, more efficient deployments.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="google-apps-script" /><category term="github-actions" /><category term="slack" /><category term="github" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/4cb4437818f2/1*TiGXBQdPaCM6r2J1RHrgnA.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/google-apps-script-web-app-integrate-forms-with-github-actions-ci-cd-for-streamlined-workflows-4cb4437818f2/"><![CDATA[<h3 id="using-google-apps-script-web-app-form-to-connect-with-github-action-cicd-workflow">Using Google Apps Script Web App Form to Connect with Github Action CI/CD Workflow</h3>

<p>Github Action Workflow Form Optimization and Integration with Other Workflow Tools (Jira, Asana, Slack..) to Improve Development Efficiency.</p>

<p><img src="/assets/4cb4437818f2/1*TiGXBQdPaCM6r2J1RHrgnA.webp" alt="Left: Original Github Action Workflow Form / Right: Final Result (GAS Web App Form)" loading="lazy" decoding="async" width="1222" height="692" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjIyIiBoZWlnaHQ9IjY5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4cb4437818f2/1*TiGXBQdPaCM6r2J1RHrgnA.png" /></p>

<p>Left: Original Github Action Workflow Form / Right: <a href="https://script.google.com/macros/s/AKfycbw8SuK7lLLMdY86y3jxMJyzXqa5tdxJryRnteOnNi-lK--j6CmKYXj7UuU58DiS0NSVvA/exec" target="_blank">Final Result (GAS Web App Form)</a></p>

<h3 id="202507-update">2025/07 Update:</h3>

<p>This feature has been integrated into an actual packaging tool. You can refer to the latest article case: “<a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free and Easy Packaging Tool Platform</strong></a>”</p>

<h3 id="background">Background</h3>

<p>The previous team built a complete CI/CD service using GitHub Actions, Self-hosted GitHub Runner, and Slack. The overall result was good. For app developers, setup and maintenance were relatively easy—just follow the official documentation to configure the YAML parameters, and the triggers run automatically. On the machine side, it was also easy to use your own machine as a runner. Since the service is maintained by GitHub, we didn’t have to worry about version upgrades, and the runner pulls tasks from GitHub, so there was no need to open external network ports.</p>

<blockquote>
  <p><em>It combines the GUI YAML build style similar to Bitrise with the flexibility and lower build costs of using self-hosted machines like Jenkins, but without the maintenance effort required for Jenkins itself.</em></p>
</blockquote>

<blockquote>
  <p><strong>Will write a complete article on App CI/CD x GitHub Action setup process when I have time in the future.</strong></p>
</blockquote>

<h4 id="problem-github-action-cicd-gui-form">Problem: Github Action CI/CD GUI Form</h4>

<p><img src="/assets/4cb4437818f2/1*55tCLFvuHtTyyvSLSv1vMA.webp" alt="Github Action GUI Form" loading="lazy" decoding="async" width="406" height="499" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDYiIGhlaWdodD0iNDk5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*55tCLFvuHtTyyvSLSv1vMA.png" /></p>

<p>Github Action GUI Form</p>

<blockquote>
  <p><em>In app development, when triggering CD to build test versions, official releases, or submit for review, it usually requires providing some external parameters or selecting environments and branches based on needs before starting the workflow.</em></p>
</blockquote>

<p>Unlike Jenkins, which is a self-hosted service with a complete Web GUI, GitHub Actions does not have one. The only Web GUI form is a simple customizable form available when clicking “Run workflow” in Actions, allowing users to input external parameters to trigger the CI/CD workflow.</p>

<p>Users who trigger this CD packaging are not necessarily the app developers themselves, nor do they always have permissions for the project. For example, QA may need to package a specific version, or PM/Backend may need to package a development version for testing. The GitHub Action Form requires project permissions to use, but users may not have project permissions or even an engineering background.</p>

<p><strong>And we cannot create dynamic forms or data validation here.</strong></p>

<blockquote>
  <p><em>Therefore, we need to create a separate GUI service for other users to operate.</em></p>
</blockquote>

<h4 id="building-a-custom-slack-app-solution">Building a Custom Slack App Solution</h4>

<p>Previously, team members who loved automation built a complete Slack App web service using Kotlin+Ktor. It integrated Slack messages, forms, commands, and more to receive and forward CD packaging requests, trigger GitHub Actions, and send the results back to Slack.</p>

<blockquote>
  <p><em>Currently, there are no development resources to build the service using Kotlin+Ktor as before</em></p>
</blockquote>

<h4 id="writing-your-own-webiosmacos-app-tools">Writing Your Own Web/iOS/macOS App Tools</h4>

<p>Currently, the team uses Jenkins, which provides a basic web interface for other users to log in. Additionally, they developed an app that connects to Jenkins and wraps some parameters to make it easier for non-engineering users to operate.</p>

<blockquote>
  <p><strong><em>However, this entire setup was abandoned after migrating to GitHub Actions.</em></strong></p>
</blockquote>

<h4 id="-private-github-pages">❌ Private Github Pages</h4>

<p>It might be possible to directly build a GitHub Pages site as a CI/CD Web GUI, but currently only GitHub <a href="https://docs.github.com/en/enterprise-server@3.13/admin/configuration/configuring-your-enterprise/configuring-github-pages-for-your-enterprise#enabling-public-sites-for-github-pages" target="_blank">Enterprise</a> allows setting access permissions for GitHub Pages. Other options, even with Private Repos, will be public and lack security.</p>

<h4 id="-slack-app-but-built-with-google-apps-script">❌ Slack App, but built with Google Apps Script</h4>

<p>At first, we thought of using a Slack App as the CI/CD GUI form service based on our team’s previous experience. However, we currently don’t have the resources to build the service with Kotlin+Ktor like before. So, we considered quickly trying to build it using Function as a Service instead.</p>

<p>There are many types of Function as a Service. <a href="https://cloud.google.com/functions?hl=zh-tw" target="_blank">Cloud Functions</a> offer more flexibility, but due to organizational IT restrictions, we cannot freely add Public Cloud Functions and there are cost concerns. Therefore, we return to our old friend — Google Apps Script.</p>

<blockquote>
  <p><em>Previously, I have written several articles about automation using Google Apps Script. Interested readers can refer to them:</em></p>
</blockquote>

<blockquote>
  <p><em>1. <strong>“<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Automate Daily Data Reports with RPA Using Google Apps Script</a>“</strong></em></p>
</blockquote>

<blockquote>
  <p><em>2. “<a href="https://medium.com/zrealm-robotic-process-automation/%E7%B0%A1%E5%96%AE-3-%E6%AD%A5%E9%A9%9F-%E6%89%93%E9%80%A0%E5%85%8D%E8%B2%BB-ga4-%E8%87%AA%E5%8B%95%E6%95%B8%E6%93%9A%E9%80%9A%E7%9F%A5%E6%A9%9F%E5%99%A8%E4%BA%BA-1e85b8df2348?source=collection_home---6------1-----------------------" target="_blank">Simple 3 Steps — Build a Free GA4 Automated Data Notification Bot</a> “</em></p>
</blockquote>

<blockquote>
  <p><em>3. “<a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-google-analytics-%E8%87%AA%E5%8B%95%E6%9F%A5%E8%A9%A2-app-crash-free-users-rate-793cb8f89b72?source=collection_home---6------8-----------------------" target="_blank">Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate</a>”</em></p>
</blockquote>

<blockquote>
  <p><em>4. <a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-big-query-%E6%89%93%E9%80%A0%E6%9B%B4%E5%8D%B3%E6%99%82%E4%BE%BF%E5%88%A9%E7%9A%84-crash-%E8%BF%BD%E8%B9%A4%E5%B7%A5%E5%85%B7-e77b80cc6f89?source=collection_home---6------9-----------------------" target="_blank">Crashlytics + Big Query: Building a More Real-Time and Convenient Crash Tracking Tool</a></em></p>
</blockquote>

<p>In summary, Google Apps Script is another Function as a Service (FaaS) offered by Google, mainly featuring free usage and quick integration with Google services. However, it has several limitations, such as only supporting its own language, a maximum execution time of 6 minutes, limits on the number of executions, no multi-threading support, and more. For details, please refer to <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">my previous article</a>.</p>

<p>The conclusion is that it is not feasible, because:</p>

<ul>
  <li>
    <p><a href="https://www.cloudflare.com/zh-tw/learning/serverless/what-is-serverless/" target="_blank">Function as a Service cold start issue</a>.<br />
If a service is idle for a while, it goes to sleep, causing the next call to take longer to start (3 to ≥ 5 seconds); <strong>Slack Apps require API responses within 3 seconds or they are considered failures</strong>. Slack will then throw an error, and event listening will be lost, leading to repeated sends.</p>
  </li>
  <li>
    <p><strong>Google Apps Script doGet and doPost methods cannot access Headers.</strong><br />
<strong>This prevents using <a href="https://api.slack.com/authentication/verifying-requests-from-slack" target="_blank">official security verification</a> and disables the ability to turn off <a href="https://api.slack.com/authentication/verifying-requests-from-slack" target="_blank">Slack Retry</a>.</strong></p>
  </li>
  <li>
    <p>Google Apps Script single-thread limitation.<br />
If connecting to other services takes more than 3 seconds to respond, Slack will immediately mark it as failed.</p>
  </li>
</ul>

<p>Tried to use Slack messages, Block Kit, and Forms to connect the entire process, but it was too prone to the issues mentioned above, so it was eventually abandoned.</p>

<blockquote>
  <p><strong><em>If you want to implement this system, you still need to run your own server and service; do not use Function as a Service!!</em></strong></p>
</blockquote>

<h4 id="-slack-workflow-form">❌ Slack Workflow Form</h4>

<p><img src="/assets/4cb4437818f2/1*KWBMdFswvl1KPdnTLStzhQ.webp" alt="" loading="lazy" decoding="async" width="1136" height="812" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTM2IiBoZWlnaHQ9IjgxMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/4cb4437818f2/1*KWBMdFswvl1KPdnTLStzhQ.png" /></p>

<p><img src="/assets/4cb4437818f2/1*hnDPyOfGCTW_yJf71krMnA.webp" alt="Slack Workflow Form (❌ Not customizable)" loading="lazy" decoding="async" width="519" height="458" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTkiIGhlaWdodD0iNDU4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*hnDPyOfGCTW_yJf71krMnA.png" /></p>

<p><a href="https://slack.com/intl/zh-tw/help/articles/24720245025555-%E8%87%AA%E5%8B%95%E5%8C%96%EF%BC%9A%E4%BD%BF%E7%94%A8%E7%B0%A1%E6%98%93%E8%A1%A8%E5%96%AE%E6%94%B6%E9%9B%86%E8%B3%87%E8%A8%8A" target="_blank">Slack Workflow Form</a> (❌ Not customizable)</p>

<p>I also tried Slack’s built-in automation feature, Workflow Form, but it cannot create dynamic form content (e.g., fetching branches for users to select). The only customizable part is the subsequent data submission step.</p>

<h3 id="-google-apps-script-web-app-gui-form">✅ Google Apps Script Web App GUI Form</h3>

<p>If the mountain doesn’t turn, the road turns. On second thought, it’s not necessary to be stuck on integrating with Slack. Using Slack integration is the best solution because it directly fits into the existing team collaboration tool, avoiding the need to learn new tools. However, due to resource constraints, we have to settle for other stable and easy-to-use methods.</p>

<blockquote>
  <p><em>Looking back, Google Apps Script itself can be deployed as a Web App, which can respond with a GUI form during a Web doGet. After submitting the form, it triggers subsequent GitHub integration processes.</em></p>
</blockquote>

<h4 id="final-result-">Final Result 🎉</h4>

<p><img src="/assets/4cb4437818f2/1*pzW-Yki-4HbE2nYXC4q-Aw.webp" alt="Demo Web App Form" loading="lazy" decoding="async" width="653" height="672" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTMiIGhlaWdodD0iNjcyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*pzW-Yki-4HbE2nYXC4q-Aw.png" /></p>

<p><a href="https://script.google.com/macros/s/AKfycbw8SuK7lLLMdY86y3jxMJyzXqa5tdxJryRnteOnNi-lK--j6CmKYXj7UuU58DiS0NSVvA/exec" target="_blank">Demo Web App Form</a></p>

<h4 id="workflow"><strong>Workflow</strong></h4>

<p>We use Google Apps Script Web App to build a CI/CD form, directly linked to Google Workspace accounts, restricting access to users within the organization. It automatically obtains the current logged-in user’s email and uses a GitHub repo shared account (or borrows a personal access token from an authorized account) to call the GitHub API to get the branch list. Upon submission, it calls the API again to trigger the GitHub Action to start the CI/CD process.</p>

<p>Additionally, we can use the user’s email to call the Slack API via the Slack App to get the user’s Slack ID, then send messages through the Slack App to notify the status of the CI/CD tasks.</p>

<p>It can also be integrated with other tools and development processes, such as retrieving tasks from Asana or Jira first, then selecting them to search branches via the GitHub API, trigger GitHub Actions, and finally notify users through Slack.</p>

<h4 id="step-1-create-google-apps-script-web-app-form">Step 1. Create Google Apps Script Web App Form</h4>

<p>Go to &gt; <a href="https://script.google.com/home" target="_blank">Google Apps Script</a> and create a new project.</p>

<p><img src="/assets/4cb4437818f2/1*T3if7Dfo0iJaa4N5VZyA1Q.webp" alt="" loading="lazy" decoding="async" width="928" height="370" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iMzcwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*T3if7Dfo0iJaa4N5VZyA1Q.png" /></p>

<h4 id="step-2-create-form-content-and-gas-script">Step 2. Create Form Content and GAS Script</h4>

<p>Haven’t written HTML for a long time and too lazy to design styles myself, so I just asked ChatGPT to generate a nicely styled HTML form template.</p>

<p><img src="/assets/4cb4437818f2/1*IPv0afE5FAFj40F22s8Umg.webp" alt="" loading="lazy" decoding="async" width="1316" height="1139" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzE2IiBoZWlnaHQ9IjExMzkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/4cb4437818f2/1*IPv0afE5FAFj40F22s8Umg.png" /></p>

<p><img src="/assets/4cb4437818f2/1*F_HrfV_k16g_ojm1WIDDuA.webp" alt="" loading="lazy" decoding="async" width="978" height="608" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NzgiIGhlaWdodD0iNjA4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*F_HrfV_k16g_ojm1WIDDuA.png" /></p>

<p>In the GAS file list on the left, click “+” to add a new file, enter the file name <code class="language-plaintext highlighter-rouge">Form.html</code>, and paste the GPT-generated HTML form template content.</p>

<p><code class="language-plaintext highlighter-rouge">Form.html:</code></p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!--HTML &amp; Style Gen by ChatGPT 4o--&gt;</span>
<span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html&gt;</span>
<span class="nt">&lt;head&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">&gt;</span>
  <span class="nt">&lt;title&gt;</span><span class="cp">&lt;?=title?&gt;</span><span class="nt">&lt;/title&gt;</span>
  <span class="nt">&lt;style&gt;</span>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 20px;
      background-color: #f7f7f7;
    }
    .form-container {
      max-width: 600px;
      margin: auto;
      padding: 20px;
      background-color: #ffffff;
      border-radius: 8px;
      box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    }
    .form-container h2 {
      margin-bottom: 20px;
      color: #333333;
    }
    .form-group {
      margin-bottom: 15px;
    }
    .form-group label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
      color: #555555;
    }
    .form-group input,
    .form-group select,
    .form-group textarea {
      width: 95%;
      padding: 10px;
      border: 1px solid #cccccc;
      border-radius: 4px;
      font-size: 16px;
    }
    .form-group input[type="radio"] {
      width: auto;
      margin-right: 10px;
    }
    .form-group .radio-label {
      display: inline-block;
      margin-right: 20px;
    }
    .form-group button {
      background-color: #4CAF50;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
    }
    .form-group button:hover {
      background-color: #45a049;
    }
    .message {
      margin-top: 20px;
      padding: 15px;
      border-radius: 5px;
      font-size: 1em;
      text-align: center;
    }
    .message.success {
      background-color: #d4edda;
      color: #155724;
      border: 1px solid #c3e6cb;
    }
    .message.error {
      background-color: #f8d7da;
      color: #721c24;
      border: 1px solid #f5c6cb;
    }
    .hidden {
      display: none;
    }
  <span class="nt">&lt;/style&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-container"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;h2&gt;</span><span class="cp">&lt;?=title?&gt;</span><span class="nt">&lt;/h2&gt;</span>
    <span class="nt">&lt;form</span> <span class="na">id=</span><span class="s">"myForm"</span><span class="nt">&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"message-block"</span> <span class="na">class=</span><span class="s">"hidden"</span><span class="nt">&gt;&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"email"</span><span class="nt">&gt;</span>Email:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"email"</span> <span class="na">value=</span><span class="s">"&lt;?=email?&gt;"</span> <span class="err">readonly</span><span class="nt">/&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"buildNumber"</span><span class="nt">&gt;</span>Build Number:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"number"</span> <span class="na">value=</span><span class="s">"&lt;?=buildNumber?&gt;"</span><span class="nt">/&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"branch"</span><span class="nt">&gt;</span>PRs Under Review:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"branch"</span> <span class="na">name=</span><span class="s">"branch"</span><span class="nt">&gt;</span>
          <span class="nt">&lt;option&gt;</span>Please select<span class="nt">&lt;/option&gt;</span>
          <span class="cp">&lt;? pullRequests.forEach(pullRequest =&gt; { ?&gt;</span>
            <span class="nt">&lt;option</span> <span class="na">value=</span><span class="s">"&lt;?=pullRequest.head.ref?&gt;"</span><span class="nt">&gt;</span>[<span class="cp">&lt;?=pullRequest.state?&gt;</span>] <span class="cp">&lt;?=pullRequest.title?&gt;</span><span class="nt">&lt;/option&gt;</span>
          <span class="cp">&lt;? }); ?&gt;</span>
        <span class="nt">&lt;/select&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;label</span> <span class="na">for=</span><span class="s">"message"</span><span class="nt">&gt;</span>Update Details:<span class="nt">&lt;/label&gt;</span>
        <span class="nt">&lt;textarea</span> <span class="na">id=</span><span class="s">"message"</span> <span class="na">name=</span><span class="s">"message"</span> <span class="na">rows=</span><span class="s">"4"</span> <span class="na">placeholder=</span><span class="s">"Please enter your message"</span><span class="nt">&gt;&lt;/textarea&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
      <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"form-group"</span><span class="nt">&gt;</span>
        <span class="nt">&lt;button</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">&gt;</span>Submit<span class="nt">&lt;/button&gt;</span>
      <span class="nt">&lt;/div&gt;</span>
    <span class="nt">&lt;/form&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
  <span class="nt">&lt;script&gt;</span>
    function displayMessage(ok, message) {
      const messageBlock = document.getElementById('message-block');
      messageBlock.className = ok ? 'message success' : 'message error';
      messageBlock.innerHTML = message;
      messageBlock.classList.remove('hidden');
    }
    
    document.getElementById("myForm").addEventListener("submit", function(e) {
      e.preventDefault();
      const formData = new FormData(this);
      const formObject = Object.fromEntries(formData);
      google.script.run.withSuccessHandler((response) =&gt; {
        displayMessage(response.ok, response.message);
      }).processForm(formObject);
    });
  <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>The form content can be adjusted according to your needs.</p>

<p><code class="language-plaintext highlighter-rouge">Code.gs:</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">doGet</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Corresponds to the Form.html file on the left</span>
  <span class="kd">const</span> <span class="nx">htmlTemplate</span> <span class="o">=</span> <span class="nx">HtmlService</span><span class="p">.</span><span class="nf">createTemplateFromFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">Form</span><span class="dl">'</span><span class="p">);</span>
  
  <span class="kd">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="nx">Session</span><span class="p">.</span><span class="nf">getActiveUser</span><span class="p">().</span><span class="nf">getEmail</span><span class="p">();</span>
  <span class="c1">// Get the user's email, valid only if execution identity: user accessing the web app is set</span>
  
  <span class="kd">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">App CD Packaging Request Form</span><span class="dl">"</span><span class="p">;</span>
  
  <span class="kd">const</span> <span class="nx">buildNumber</span> <span class="o">=</span> <span class="nf">genBuildNumber</span><span class="p">();</span>

  <span class="nx">htmlTemplate</span><span class="p">.</span><span class="nx">email</span> <span class="o">=</span> <span class="nx">email</span><span class="p">;</span>
  <span class="nx">htmlTemplate</span><span class="p">.</span><span class="nx">title</span> <span class="o">=</span> <span class="nx">title</span><span class="p">;</span>
  <span class="nx">htmlTemplate</span><span class="p">.</span><span class="nx">pullRequests</span> <span class="o">=</span> <span class="p">[];</span> <span class="c1">// Next step: integrate with Github...</span>
  <span class="nx">htmlTemplate</span><span class="p">.</span><span class="nx">buildNumber</span> <span class="o">=</span> <span class="nx">buildNumber</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">html</span> <span class="o">=</span> <span class="nx">htmlTemplate</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">();</span>
  <span class="nx">html</span><span class="p">.</span><span class="nf">setTitle</span><span class="p">(</span><span class="nx">title</span><span class="p">);</span>
  <span class="c1">//html.setWidth(600) // Set page width</span>

  <span class="k">return</span> <span class="nx">html</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">processForm</span><span class="p">(</span><span class="nx">object</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">{</span><span class="dl">"</span><span class="s2">ok</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="dl">"</span><span class="s2">message</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Request submitted successfully!</span><span class="dl">"</span><span class="p">};</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">genBuildNumber</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">now</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">();</span>
  <span class="kd">const</span> <span class="nx">formattedDate</span> <span class="o">=</span> <span class="nx">Utilities</span><span class="p">.</span><span class="nf">formatDate</span><span class="p">(</span><span class="nx">now</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Asia/Taipei</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">yyyyMMddHHmmss</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">milliseconds</span> <span class="o">=</span> <span class="nx">now</span><span class="p">.</span><span class="nf">getMilliseconds</span><span class="p">().</span><span class="nf">toString</span><span class="p">().</span><span class="nf">padStart</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// Ensure milliseconds are 3 digits</span>
  <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">formattedDate</span><span class="p">}${</span><span class="nx">milliseconds</span><span class="p">}</span><span class="s2">`</span><span class="p">;</span> 
<span class="p">}</span>
</code></pre></div></div>

<p>In this step, we first complete the form GUI. Next, we will connect to the GitHub API to get the PR branch list.</p>

<p><img src="/assets/4cb4437818f2/1*RN9ftVBdgCA-IL8ra9jvTg.webp" alt="" loading="lazy" decoding="async" width="509" height="324" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDkiIGhlaWdodD0iMzI0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*RN9ftVBdgCA-IL8ra9jvTg.png" /></p>

<h4 id="step-2-deploy-google-apps-script-web-app-form">Step 2. Deploy Google Apps Script Web App Form</h4>

<p>Let’s first deploy the previous content and check the results.</p>

<p>In the top right corner of GAS, select “Deploy” -&gt; “New deployment” -&gt; “Web app”:</p>

<p><img src="/assets/4cb4437818f2/1*ouVO18FtOcX8vdeGCEtwCw.webp" alt="" loading="lazy" decoding="async" width="330" height="215" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMzAiIGhlaWdodD0iMjE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*ouVO18FtOcX8vdeGCEtwCw.png" /></p>

<p><img src="/assets/4cb4437818f2/1*4BuEtIA4H_-Q9WALonYxYg.webp" alt="" loading="lazy" decoding="async" width="396" height="302" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzOTYiIGhlaWdodD0iMzAyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*4BuEtIA4H_-Q9WALonYxYg.png" /></p>

<p><img src="/assets/4cb4437818f2/1*JagpVPTGD-W0lhIJ5nrRew.webp" alt="" loading="lazy" decoding="async" width="762" height="599" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjIiIGhlaWdodD0iNTk5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*JagpVPTGD-W0lhIJ5nrRew.png" /></p>

<p>The execution identity and who can access it can be set separately as:</p>

<p><strong>Execution Identity:</strong></p>

<ul>
  <li>
    <p>I <code class="language-plaintext highlighter-rouge">always run the script using your account identity.</code></p>
  </li>
  <li>
    <p>The user accessing the web app <code class="language-plaintext highlighter-rouge">will run the script as the currently signed-in Google account user.</code></p>
  </li>
</ul>

<p><strong>Who can access:</strong></p>

<ul>
  <li>
    <p>Only me</p>
  </li>
  <li>
    <p><strong>All users within the same XXX organization</strong> <code class="language-plaintext highlighter-rouge">Only users from the same organization who are logged in with a Google account can access.</code></p>
  </li>
  <li>
    <p>All signed-in Google account users <code class="language-plaintext highlighter-rouge">All signed-in Google account users can access.</code></p>
  </li>
  <li>
    <p>Everyone <code class="language-plaintext highlighter-rouge">does not need to sign in with a Google account, and the app is publicly accessible to all.</code></p>
  </li>
</ul>

<blockquote>
  <p><em>We chose “Who has access: All users in the XXX organization” + “Execute as: User accessing the web app” to <strong>automatically restrict access to only organization accounts</strong>, and run with their own identity!</em></p>
</blockquote>

<blockquote>
  <p><em>It’s a very convenient feature for permission management!</em></p>
</blockquote>

<p>After selecting, click “Deploy” at the bottom right.</p>

<p><img src="/assets/4cb4437818f2/1*dvyqYb5kcta402j3RUYJog.webp" alt="" loading="lazy" decoding="async" width="770" height="605" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzAiIGhlaWdodD0iNjA1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*dvyqYb5kcta402j3RUYJog.png" /></p>

<p>The URL in the web application is the Web App access URL.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://script.google.com/macros/s/AKfycbw8SuK7lLLMdY86y3jxMJyzXqa5tdxJryRnteOnNi-lK--j6CmKYXj7UuU58DiS0NSVvA/exec
</code></pre></div></div>

<blockquote>
  <p><em>The URL is long and ugly, but there’s no choice; just have to use a URL shortener.</em></p>
</blockquote>

<p><strong>Click the link to open the page and see the effect:</strong></p>

<p><img src="/assets/4cb4437818f2/1*jbx4IO4DEhqYfwI_UTQk5Q.webp" alt="" loading="lazy" decoding="async" width="709" height="641" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDkiIGhlaWdodD0iNjQxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*jbx4IO4DEhqYfwI_UTQk5Q.png" /></p>

<p><strong>Here are two more GAS limitations to mention:</strong></p>

<ul>
  <li>
    <p>Warning message at the top of GAS Web App cannot be hidden by default</p>
  </li>
  <li>
    <p>GAS Web App uses an IFrame to embed our page, so achieving 100% RWD is difficult.<br />
You can only adjust the window width using <code class="language-plaintext highlighter-rouge">.setWidth()</code>.</p>
  </li>
</ul>

<h4 id="google-apps-script-authorization-warning">Google Apps Script Authorization Warning</h4>

<p><strong>First time use</strong>, clicking “Debug” or “Run” may trigger the following authorization warning:</p>

<p><img src="/assets/4cb4437818f2/1*9q8KZGHER9vdtnbKVVQB9g.webp" alt="" loading="lazy" decoding="async" width="301" height="196" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMDEiIGhlaWdodD0iMTk2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*9q8KZGHER9vdtnbKVVQB9g.png" /></p>

<p><img src="/assets/4cb4437818f2/1*iWrkqMf8vkEGkiwkI1amIw.webp" alt="" loading="lazy" decoding="async" width="521" height="503" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MjEiIGhlaWdodD0iNTAzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*iWrkqMf8vkEGkiwkI1amIw.jpeg" /></p>

<p>Select the account you want to use. If you see “This app isn’t verified by Google,” click “Advanced” -&gt; “Go to XXX (unsafe),” then choose “Allow”:</p>

<p><img src="/assets/4cb4437818f2/1*ucaqLxh-TOgJIaGyqqFd3A.webp" alt="" loading="lazy" decoding="async" width="504" height="595" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDQiIGhlaWdodD0iNTk1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*ucaqLxh-TOgJIaGyqqFd3A.png" /></p>

<p><img src="/assets/4cb4437818f2/1*l-tma_YICU24goKvZvl7Ww.webp" alt="" loading="lazy" decoding="async" width="653" height="468" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTMiIGhlaWdodD0iNDY4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*l-tma_YICU24goKvZvl7Ww.png" /></p>

<p><img src="/assets/4cb4437818f2/1*JAs_3__Qt2XeDcQiKEUNhg.webp" alt="" loading="lazy" decoding="async" width="504" height="789" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MDQiIGhlaWdodD0iNzg5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*JAs_3__Qt2XeDcQiKEUNhg.png" /></p>

<p>If the GAS script permissions change (e.g., adding access to Google Sheets, etc.), re-authorization is required; otherwise, after authorizing once, it will not prompt again.</p>

<blockquote>
  <p><em>If you encounter: Access blocked for “XXX” due to incomplete Google verification process, <a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/">please refer to my latest article on GCP settings.</a></em></p>
</blockquote>

<h4 id="step-3-connect-to-github-api-to-get-pr-branch-list">Step 3. Connect to GitHub API to Get PR Branch List</h4>

<p>We add a new file <code class="language-plaintext highlighter-rouge">Github.gs</code> to store GitHub API related logic.</p>

<p><code class="language-plaintext highlighter-rouge">Github.gs:</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// SECRET</span>
<span class="kd">const</span> <span class="nx">githubPersonalAccessToken</span> <span class="o">=</span> <span class="dl">""</span>
<span class="c1">// Create a PAT using your Github account or a shared Github account for your organization</span>
<span class="c1">// https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens</span>

<span class="c1">// Method 1: Access via Restful API</span>
<span class="kd">function</span> <span class="nf">githubAPI</span><span class="p">(</span><span class="nx">method</span><span class="p">,</span> <span class="nx">path</span><span class="p">,</span> <span class="nx">payload</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://api.github.com</span><span class="dl">"</span><span class="o">+</span><span class="nx">path</span><span class="p">;</span>  
    <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
      <span class="na">method</span><span class="p">:</span> <span class="nx">method</span><span class="p">,</span>
      <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
        <span class="dl">"</span><span class="s2">Accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/vnd.github+json</span><span class="dl">"</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">Authorization</span><span class="dl">"</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">githubPersonalAccessToken</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="dl">"</span><span class="s2">X-GitHub-Api-Version</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">2022-11-28</span><span class="dl">"</span>
      <span class="p">}</span>
    <span class="p">};</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">method</span><span class="p">.</span><span class="nf">toLowerCase</span><span class="p">().</span><span class="nf">trim</span><span class="p">()</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">options</span><span class="p">.</span><span class="nx">payload</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Method 2: Access via GraphQL</span>
<span class="c1">// Some more detailed queries are only available via the Github GraphQL API</span>
<span class="c1">// https://docs.github.com/en/graphql</span>
<span class="kd">function</span> <span class="nf">githubGraphQL</span><span class="p">(</span><span class="nx">query</span><span class="p">,</span> <span class="nx">variables</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://api.github.com/graphql</span><span class="dl">"</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">query</span><span class="p">:</span> <span class="nx">query</span><span class="p">,</span>
    <span class="na">variables</span><span class="p">:</span> <span class="nx">variables</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">contentType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">Accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/vnd.github+json</span><span class="dl">"</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">Authorization</span><span class="dl">"</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">githubPersonalAccessToken</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">X-GitHub-Api-Version</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">2022-11-28</span><span class="dl">"</span>
    <span class="p">},</span>
    <span class="na">payload</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">return</span> <span class="nx">data</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// GraphQL Example:</span>
<span class="c1">// const query = `</span>
<span class="c1">//   query($owner: String!, $repo: String!) {</span>
<span class="c1">//     repository(owner: $owner, name: $repo) {</span>
<span class="c1">//       pullRequests(states: OPEN, first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {</span>
<span class="c1">//         nodes {</span>
<span class="c1">//           title</span>
<span class="c1">//           url</span>
<span class="c1">//           number</span>
<span class="c1">//           createdAt</span>
<span class="c1">//           author {</span>
<span class="c1">//             login</span>
<span class="c1">//           }</span>
<span class="c1">//           headRefName</span>
<span class="c1">//           baseRefName</span>
<span class="c1">//           body</span>
<span class="c1">//         }</span>
<span class="c1">//         pageInfo {</span>
<span class="c1">//           hasNextPage</span>
<span class="c1">//           endCursor</span>
<span class="c1">//         }</span>
<span class="c1">//       }</span>
<span class="c1">//     }</span>
<span class="c1">//   }</span>
<span class="c1">// `;</span>
<span class="c1">// const variables = {</span>
<span class="c1">//   owner: "swiftlang",</span>
<span class="c1">//   repo: "swift"</span>
<span class="c1">// };</span>
<span class="c1">// const response = githubGraphQL(query, variables);</span>
</code></pre></div></div>

<p>GitHub API has two access methods: the traditional Restful and the more flexible GraphQL; this article uses Restful as an example.</p>

<p><code class="language-plaintext highlighter-rouge">Code.gs:</code></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">function</span> <span class="nf">doGet</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Corresponds to the Form.html file on the left</span>
  <span class="k">const</span> <span class="n">htmlTemplate</span> <span class="p">=</span> <span class="nc">HtmlService</span><span class="p">.</span><span class="nf">createTemplateFromFile</span><span class="p">(</span><span class="err">'</span><span class="nc">Form</span><span class="err">'</span><span class="p">);</span>
  
  <span class="k">const</span> <span class="n">email</span> <span class="p">=</span> <span class="nc">Session</span><span class="p">.</span><span class="nf">getActiveUser</span><span class="p">().</span><span class="nf">getEmail</span><span class="p">();</span>
  <span class="c1">// Get user email, effective only when execution identity is set to Access web app as user</span>

  <span class="k">const</span> <span class="n">title</span> <span class="p">=</span> <span class="s">"App CD Packaging Request Form"</span><span class="p">;</span>
  
  <span class="k">const</span> <span class="n">pullRequests</span> <span class="p">=</span> <span class="nf">githubAPI</span><span class="p">(</span><span class="s">"get"</span><span class="p">,</span> <span class="s">"/repos/swiftlang/swift/pulls"</span><span class="p">);</span>
  <span class="c1">// Example using https://github.com/swiftlang/swift/pulls</span>
  
  <span class="k">const</span> <span class="n">buildNumber</span> <span class="p">=</span> <span class="nf">genBuildNumber</span><span class="p">();</span>

  <span class="n">htmlTemplate</span><span class="p">.</span><span class="n">email</span> <span class="p">=</span> <span class="n">email</span><span class="p">;</span>
  <span class="n">htmlTemplate</span><span class="p">.</span><span class="n">title</span> <span class="p">=</span> <span class="n">title</span><span class="p">;</span>
  <span class="n">htmlTemplate</span><span class="p">.</span><span class="n">pullRequests</span> <span class="p">=</span> <span class="n">pullRequests</span><span class="p">;</span>
  <span class="n">htmlTemplate</span><span class="p">.</span><span class="n">buildNumber</span> <span class="p">=</span> <span class="n">buildNumber</span><span class="p">;</span>

  <span class="k">const</span> <span class="n">html</span> <span class="p">=</span> <span class="n">htmlTemplate</span><span class="p">.</span><span class="nf">evaluate</span><span class="p">();</span>
  <span class="n">html</span><span class="p">.</span><span class="nf">setTitle</span><span class="p">(</span><span class="n">title</span><span class="p">);</span>
  <span class="c1">//html.setWidth(600) // Set page width</span>

  <span class="k">return</span> <span class="n">html</span>
<span class="p">}</span>

<span class="n">function</span> <span class="nf">processForm</span><span class="p">(</span><span class="k">object</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="k">object</span><span class="p">.</span><span class="n">buildNumber</span> <span class="p">==</span> <span class="s">""</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span><span class="s">"ok"</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span> <span class="s">"message"</span><span class="p">:</span> <span class="s">"Please enter the build number!"</span><span class="p">};</span>
  <span class="p">}</span>
  <span class="k">if</span> <span class="p">(</span><span class="k">object</span><span class="p">.</span><span class="n">branch</span> <span class="p">==</span> <span class="s">""</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span><span class="s">"ok"</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span> <span class="s">"message"</span><span class="p">:</span> <span class="s">"Please select a branch version!"</span><span class="p">};</span>
  <span class="p">}</span>

  <span class="c1">// Parameters to pass to Github Action</span>
  <span class="k">const</span> <span class="n">payload</span> <span class="p">=</span> <span class="p">{</span>
    <span class="n">ref</span><span class="p">:</span> <span class="k">object</span><span class="p">.</span><span class="n">branch</span><span class="p">,</span>
    <span class="n">inputs</span><span class="p">:</span> <span class="p">{</span>
      <span class="n">buildNumber</span><span class="p">:</span> <span class="k">object</span><span class="p">.</span><span class="n">buildNumber</span>
    <span class="p">}</span>
  <span class="p">};</span>
  
  <span class="c1">//  </span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">response</span> <span class="p">=</span> <span class="nf">githubAPI</span><span class="p">(</span><span class="s">"post"</span><span class="p">,</span> <span class="s">"/repos/zhgchgli0718/ios-project-for-github-action-ci-cd-demo/actions/workflows/CD-Job.yml/dispatches"</span><span class="p">,</span> <span class="n">payload</span><span class="p">);</span>
    <span class="c1">// Example using https://github.com/zhgchgli0718/ios-project-for-github-action-ci-cd-demo/blob/main/.github/workflows/CD-Job.yml</span>

    <span class="k">return</span> <span class="p">{</span><span class="s">"ok"</span><span class="p">:</span> <span class="k">true</span><span class="p">,</span> <span class="s">"message"</span><span class="p">:</span> <span class="nc">`Packaging</span> <span class="n">request</span> <span class="n">sent</span> <span class="n">successfully</span><span class="p">!&lt;</span><span class="n">br</span><span class="p">/&gt;</span><span class="nc">Branch</span><span class="p">:</span> <span class="p">&lt;</span><span class="n">strong</span><span class="p">&gt;</span><span class="err">$</span><span class="p">{</span><span class="k">object</span><span class="p">.</span><span class="n">branch</span><span class="p">}&lt;/</span><span class="n">strong</span><span class="p">&gt;&lt;</span><span class="n">br</span><span class="p">/&gt;</span><span class="nc">Build</span> <span class="nc">Number</span><span class="p">:</span> <span class="p">&lt;</span><span class="n">strong</span><span class="p">&gt;</span><span class="err">$</span><span class="p">{</span><span class="k">object</span><span class="p">.</span><span class="n">buildNumber</span><span class="p">}&lt;/</span><span class="n">strong</span><span class="p">&gt;</span><span class="err">`</span><span class="p">};</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span><span class="s">"ok"</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span> <span class="s">"message"</span><span class="p">:</span> <span class="s">"An error occurred: "</span><span class="p">+</span><span class="n">error</span><span class="p">.</span><span class="n">message</span><span class="p">};</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">processForm</code> method handles the form submission content, and you can also add more processing logic.</p>

<h4 id="gas-x-github-api-x-github-action">GAS x Github API x Github Action</h4>

<p>Here is some additional information related to the corresponding GitHub Action.</p>

<p><code class="language-plaintext highlighter-rouge">CD-Job.yml:</code></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># CD Packaging Job</span>

<span class="na">name</span><span class="pi">:</span> <span class="s">CD-Job</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">buildNumber</span><span class="pi">:</span> <span class="c1"># Corresponds to GAS payload.inputs.xxx</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Version</span><span class="nv"> </span><span class="s">number'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># ...More</span>
      <span class="c1"># Input types can be referenced from the official docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs</span>
      
<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">some-job</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Print Inputs</span>
        <span class="na">run</span><span class="pi">:</span> <span class="s">\\|</span>
          <span class="s">echo "Release Build Number</span><span class="err">:</span> <span class="s">${{ github.event.inputs.buildNumber }}"</span>    
</code></pre></div></div>

<h4 id="step-4-redeploy-google-apps-script-web-app-form">Step 4. Redeploy Google Apps Script Web App Form</h4>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<p>In GAS, click “Deploy” at the top right -&gt; select “Edit” at the top right -&gt; choose “Create new version” under Versions</p>

<p><img src="/assets/4cb4437818f2/1*ZMAB_m6HmsSsGSqqfZiRpA.webp" alt="" loading="lazy" decoding="async" width="208" height="238" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDgiIGhlaWdodD0iMjM4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*ZMAB_m6HmsSsGSqqfZiRpA.png" /></p>

<p><img src="/assets/4cb4437818f2/1*rUWlfzASAaeXcXUh4LavZw.webp" alt="" loading="lazy" decoding="async" width="776" height="619" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzYiIGhlaWdodD0iNjE5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*rUWlfzASAaeXcXUh4LavZw.png" /></p>

<p><img src="/assets/4cb4437818f2/1*HAr5TZtpnQeG-Ril0Rjf-g.webp" alt="" loading="lazy" decoding="async" width="782" height="612" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODIiIGhlaWdodD0iNjEyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*HAr5TZtpnQeG-Ril0Rjf-g.png" /></p>

<p>Click “Deploy” -&gt; Done.</p>

<p><img src="/assets/4cb4437818f2/1*z6lw7R8ivUseSzR4IZgPdg.webp" alt="" loading="lazy" decoding="async" width="777" height="611" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzciIGhlaWdodD0iNjExIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*z6lw7R8ivUseSzR4IZgPdg.png" /></p>

<p><img src="/assets/4cb4437818f2/1*eTdgKGQ1lRM7sCRXQgZ-DA.webp" alt="" loading="lazy" decoding="async" width="770" height="610" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzAiIGhlaWdodD0iNjEwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*eTdgKGQ1lRM7sCRXQgZ-DA.png" /></p>

<p><strong>Go back to the webpage and refresh to see the updated results:</strong></p>

<p><img src="/assets/4cb4437818f2/1*iI49OJC1uTyMEgzGTBowxQ.webp" alt="" loading="lazy" decoding="async" width="657" height="583" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTciIGhlaWdodD0iNTgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*iI49OJC1uTyMEgzGTBowxQ.png" /></p>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️Please note that any changes to the GAS code require redeployment to take effect.⚠️</em></strong></p>
</blockquote>

<h3 id="done-">Done! 🎉🎉🎉</h3>

<p><img src="/assets/4cb4437818f2/1*pzW-Yki-4HbE2nYXC4q-Aw.webp" alt="" loading="lazy" decoding="async" width="653" height="672" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTMiIGhlaWdodD0iNjcyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*pzW-Yki-4HbE2nYXC4q-Aw.png" /></p>

<p><img src="/assets/4cb4437818f2/1*9aaNeemezNPRlSgbLFrseA.webp" alt="Demo Web App Form" loading="lazy" decoding="async" width="648" height="628" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDgiIGhlaWdodD0iNjI4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*9aaNeemezNPRlSgbLFrseA.png" /></p>

<p><a href="https://script.google.com/macros/s/AKfycbw8SuK7lLLMdY86y3jxMJyzXqa5tdxJryRnteOnNi-lK--j6CmKYXj7UuU58DiS0NSVvA/exec" target="_blank">Demo Web App Form</a></p>

<p>You can now share this link within your organization, allowing your teammates to directly use this web GUI to perform CI/CD tasks.</p>

<h4 id="extension-1-query-slack-user-id-with-user-email--sendupdate-progress-notifications">Extension (1)— Query Slack User ID with User Email &amp; Send/Update Progress Notifications</h4>

<p>As mentioned earlier, we want to notify users promptly about the CI/CD execution status. We can use the email provided by the user to look up their Slack User ID.</p>

<p><code class="language-plaintext highlighter-rouge">Slack.gs:</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">slackBotToken</span> <span class="o">=</span> <span class="dl">""</span>
<span class="c1">// https://medium.com/zrealm-robotic-process-automation/slack-chatgpt-integration-bd94cc88f9c9</span>

<span class="kd">function</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="nx">path</span><span class="p">,</span> <span class="nx">content</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">post</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">contentType</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer </span><span class="p">${</span><span class="nx">slackBotToken</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="c1">// Use the bot token for authorization</span>
      <span class="dl">'</span><span class="s1">X-Slack-No-Retry</span><span class="dl">'</span><span class="p">:</span> <span class="mi">1</span>
    <span class="p">},</span>
    <span class="na">payload</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">content</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="k">try</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://slack.com/api/</span><span class="dl">"</span><span class="o">+</span><span class="nx">path</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">responseData</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">responseData</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">return</span> <span class="nx">responseData</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="s2">`Slack: </span><span class="p">${</span><span class="nx">responseData</span><span class="p">.</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">throw</span> <span class="nx">error</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Lookup Slack UID by email</span>
<span class="kd">function</span> <span class="nf">getSlackUserId</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="s2">`users.lookupByEmail?email=</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">email</span><span class="p">)}</span><span class="s2">`</span><span class="p">)?.</span><span class="nx">user</span><span class="p">?.</span><span class="nx">id</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Send message to target Slack UID (channelID)</span>
<span class="kd">function</span> <span class="nf">sendSlackMessage</span><span class="p">(</span><span class="nx">channelId</span><span class="p">,</span> <span class="nx">ts</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>  <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">channel</span><span class="p">:</span> <span class="nx">channelId</span>
  <span class="p">};</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">ts</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">thread_ts</span> <span class="o">=</span> <span class="nx">ts</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">text</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">blocks</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat.postMessage</span><span class="dl">"</span><span class="p">,</span> <span class="nx">content</span><span class="p">);</span>
<span class="p">}</span>

<span class="c1">// Update sent message content</span>
<span class="kd">function</span> <span class="nf">updateSlackMessage</span><span class="p">(</span><span class="nx">channelId</span><span class="p">,</span> <span class="nx">ts</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>  <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">content</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">channel</span><span class="p">:</span> <span class="nx">channelId</span>
  <span class="p">};</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">ts</span> <span class="o">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">ts</span> <span class="o">=</span> <span class="nx">ts</span><span class="p">;</span>
  <span class="p">}</span>
  
  <span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">value</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">text</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nx">content</span><span class="p">.</span><span class="nx">blocks</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nf">slackRequest</span><span class="p">(</span><span class="dl">"</span><span class="s2">chat.update</span><span class="dl">"</span><span class="p">,</span> <span class="nx">content</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For using the Slack API, please refer to the <a href="https://api.slack.com/methods/chat.postMessage" target="_blank">official documentation</a>.</p>

<p><strong>GitHub Action YAML can use this Action to continuously update messages and send Slack notifications:</strong></p>

<p><a href="https://github.com/slackapi/slack-github-action" target="_blank"><img src="https://opengraph.githubassets.com/4b7679d54edd2c5fbafdaa5a79df23035f15b7a3eda256a43b710aab59a6164e/slackapi/slack-github-action" alt="" /></a></p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ...</span>
<span class="na">on</span><span class="pi">:</span>
  <span class="na">workflow_dispatch</span><span class="pi">:</span>
    <span class="na">inputs</span><span class="pi">:</span>
      <span class="na">buildNumber</span><span class="pi">:</span> <span class="c1"># Corresponds to GAS payload.inputs.xxx</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Version</span><span class="nv"> </span><span class="s">number'</span>
        <span class="na">required</span><span class="pi">:</span> <span class="kc">false</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="c1"># ...More</span>
      <span class="na">SLACK_USER_ID</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Slack</span><span class="nv"> </span><span class="s">User</span><span class="nv"> </span><span class="s">Id</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">receiving</span><span class="nv"> </span><span class="s">action</span><span class="nv"> </span><span class="s">notification'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="na">SLACK_CHANNEL_ID</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Slack</span><span class="nv"> </span><span class="s">Channel</span><span class="nv"> </span><span class="s">Id</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">receiving</span><span class="nv"> </span><span class="s">action</span><span class="nv"> </span><span class="s">notification'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      <span class="na">SLACK_THREAD_TS</span><span class="pi">:</span>
        <span class="na">description</span><span class="pi">:</span> <span class="s1">'</span><span class="s">Slack</span><span class="nv"> </span><span class="s">message</span><span class="nv"> </span><span class="s">ts'</span>
        <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
      
<span class="na">jobs</span><span class="pi">:</span>
  <span class="c1"># some jobs...</span>

  <span class="na">if-deploy-failed-message</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">if</span><span class="pi">:</span> <span class="s">failure()</span>
      <span class="s">- name</span><span class="err">:</span> <span class="s">update slack message</span>
        <span class="s">uses</span><span class="err">:</span> <span class="s">slackapi/slack-github-action@v2.0.0</span>
        <span class="s">with</span><span class="err">:</span>
            <span class="na">method</span><span class="pi">:</span> <span class="s">chat.update</span>
            <span class="na">token</span><span class="pi">:</span> <span class="s">${{ secrets.SLACK_BOT_TOKEN }}</span>
            <span class="na">payload</span><span class="pi">:</span> <span class="s">\\|</span>
              <span class="s">channel</span><span class="err">:</span> <span class="s">${{ github.event.inputs.SLACK_CHANNEL_ID }}</span>
              <span class="s">ts</span><span class="err">:</span> <span class="s">${{ github.event.inputs.SLACK_THREAD_TS }}</span>
              <span class="s">text</span><span class="err">:</span> <span class="s2">"</span><span class="s">❌</span><span class="nv"> </span><span class="s">Build</span><span class="nv"> </span><span class="s">task</span><span class="nv"> </span><span class="s">failed,</span><span class="nv"> </span><span class="s">please</span><span class="nv"> </span><span class="s">check</span><span class="nv"> </span><span class="s">the</span><span class="nv"> </span><span class="s">execution</span><span class="nv"> </span><span class="s">status</span><span class="nv"> </span><span class="s">or</span><span class="nv"> </span><span class="s">try</span><span class="nv"> </span><span class="s">again</span><span class="nv"> </span><span class="s">later.</span><span class="se">\n</span><span class="s">&lt;${{</span><span class="nv"> </span><span class="s">github.server_url</span><span class="nv"> </span><span class="s">}}/${{</span><span class="nv"> </span><span class="s">github.repository</span><span class="nv"> </span><span class="s">}}/actions/runs/${{</span><span class="nv"> </span><span class="s">github.run_id</span><span class="nv"> </span><span class="s">}}</span><span class="se">\\</span><span class="s">|Click</span><span class="nv"> </span><span class="s">here</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">view</span><span class="nv"> </span><span class="s">execution</span><span class="nv"> </span><span class="s">status&gt;</span><span class="nv"> </span><span class="s">cc'ed</span><span class="nv"> </span><span class="s">&lt;@${{</span><span class="nv"> </span><span class="s">github.event.inputs.SLACK_USER_ID</span><span class="nv"> </span><span class="s">}}&gt;"</span>
</code></pre></div></div>

<p><strong>Effect:</strong></p>

<p><img src="/assets/4cb4437818f2/1*-w5jqjkx6p2alpzlLcz_Nw.webp" alt="" loading="lazy" decoding="async" width="428" height="559" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MjgiIGhlaWdodD0iNTU5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/4cb4437818f2/1*-w5jqjkx6p2alpzlLcz_Nw.png" /></p>

<blockquote>
  <p><strong><em>For Slack App integration details, please refer to my previous article: <a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">Slack &amp; ChatGPT Integration</a> .</em></strong></p>
</blockquote>

<h4 id="extension-2--query-jira-tickets">Extension (2) — Query Jira Tickets</h4>

<p><code class="language-plaintext highlighter-rouge">Jira.gs:</code></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">jiraPersonalAccessToken</span> <span class="o">=</span> <span class="dl">""</span>
<span class="c1">// https://confluence.atlassian.com/enterprise/using-personal-access-tokens-1026032365.html</span>

<span class="kd">function</span> <span class="nf">getJiraTickets</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="s2">`https://xxx.atlassian.net/rest/api/3/search`</span><span class="p">;</span>

  <span class="c1">// JQL query</span>
  <span class="kd">const</span> <span class="nx">jql</span> <span class="o">=</span> <span class="s2">`project = XXX`</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">queryParams</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">jql</span><span class="p">:</span> <span class="nx">jql</span><span class="p">,</span>
    <span class="na">maxResults</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span> <span class="c1">// Adjust as needed</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="na">Authorization</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Basic </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">jiraPersonalAccessToken</span><span class="p">,</span>
      <span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="na">muteHttpExceptions</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">queryString</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">keys</span><span class="p">(</span><span class="nx">queryParams</span><span class="p">).</span><span class="nf">map</span><span class="p">(</span><span class="nx">key</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">key</span><span class="p">)}</span><span class="s2">=</span><span class="p">${</span><span class="nf">encodeURIComponent</span><span class="p">(</span><span class="nx">queryParams</span><span class="p">[</span><span class="nx">key</span><span class="p">])}</span><span class="s2">`</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="dl">"</span><span class="s2">&amp;</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">?</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">queryString</span> <span class="o">+</span> <span class="dl">"</span><span class="s2">&amp;fields=</span><span class="dl">"</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="c1">// could specify only return some fields</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getResponseCode</span><span class="p">()</span> <span class="o">===</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">issues</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">()).</span><span class="nx">issues</span><span class="p">;</span>
    <span class="k">return</span> <span class="nx">issues</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Error: </span><span class="p">${</span><span class="nx">response</span><span class="p">.</span><span class="nf">getResponseCode</span><span class="p">()}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
    <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Failed to fetch Jira issues.</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span> 
</code></pre></div></div>

<p>For other Jira API uses, please refer to the <a href="https://developer.atlassian.com/cloud/jira/platform/rest/v3/" target="_blank">official documentation</a>.</p>

<h4 id="extension-3--query-asana-tasks">Extension (3) — Query Asana Tasks</h4>

<p><code class="language-plaintext highlighter-rouge">Asana.gs:</code></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">asanaPersonalAccessToken</span> <span class="p">=</span> <span class="s">""</span>
<span class="c1">// https://developers.asana.com/docs/personal-access-token</span>

<span class="n">function</span> <span class="nf">asanaAPI</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="n">method</span> <span class="p">=</span> <span class="s">"GET"</span><span class="p">,</span> <span class="n">data</span> <span class="p">=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">var</span> <span class="py">options</span> <span class="p">=</span> <span class="p">{</span>
      <span class="s">"method"</span> <span class="p">:</span> <span class="n">method</span><span class="p">,</span>
      <span class="s">"contentType"</span> <span class="p">:</span> <span class="s">"application/json"</span><span class="p">,</span>
      <span class="s">"headers"</span><span class="p">:</span> <span class="p">{</span>
          <span class="s">"Authorization"</span><span class="p">:</span>  <span class="s">"Bearer "</span><span class="p">+</span><span class="n">asanaPersonalAccessToken</span>
      <span class="p">}</span>
    <span class="p">};</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">options</span><span class="p">[</span><span class="s">"payload"</span><span class="p">]</span> <span class="p">=</span> <span class="nc">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span><span class="s">"data"</span><span class="p">:</span><span class="n">data</span><span class="p">});</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">url</span> <span class="p">=</span> <span class="s">"https://app.asana.com/api/1.0"</span><span class="p">+</span><span class="n">endpoint</span><span class="p">;</span>
    <span class="k">const</span> <span class="n">res</span> <span class="p">=</span> <span class="nc">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">options</span><span class="p">);</span>
    <span class="k">const</span> <span class="n">data</span> <span class="p">=</span> <span class="nc">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
    <span class="k">return</span> <span class="n">data</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Find tasks in project</span>
<span class="c1">// asanaAPI("/projects/PROJECT_ID/tasks");</span>
</code></pre></div></div>

<p>For other Asana API uses, please refer to the <a href="https://developers.asana.com/reference/gettasksforproject" target="_blank">official documentation</a>.</p>

<h3 id="summary">Summary</h3>

<p>What automation, work, and development process optimization always lack is not technology, but ideas; as long as we have ideas, we can find the right technology to realize them. Let’s encourage each other!</p>

<h3 id="202507-update-1">2025/07 Update:</h3>

<p>This feature has been integrated into an actual packaging tool. You can refer to the latest article case: “<a href="/posts/zrealm-dev/ci-cd-guide-integrate-google-apps-script-web-app-with-github-actions-for-free-packaging-platform-4273e57e7148/"><strong>CI/CD Practical Guide (Part 4): Using Google Apps Script Web App to Connect GitHub Actions and Build a Free and Easy Packaging Tool Platform</strong></a>”</p>]]></content>
  </entry><entry>
    <title type="html">Swift Native Type Extensions｜Elegant Namespace Implementation for Cleaner Code</title>
    <link href="https://en.zhgchg.li/posts/zrealm-dev/swift-native-type-extensions-elegant-namespace-implementation-for-cleaner-code-a8925ad9ed01/" rel="alternate" type="text/html" title="Swift Native Type Extensions｜Elegant Namespace Implementation for Cleaner Code" />
    <published>2025-01-01T22:02:32+08:00</published>
    <updated>2025-01-01T22:02:32+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-dev/a8925ad9ed01</id><summary type="html">Discover how to create elegant native type extensions in Swift by encapsulating methods with namespace functionality, improving code organization and maintainability for iOS developers.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Dev." /><category term="ios-app-development" /><category term="swift" /><category term="wrapper" /><category term="app-modularization" /><category term="ios" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/a8925ad9ed01/1*kJFHiAuTZ8TP-4aTa_daqA.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-dev/swift-native-type-extensions-elegant-namespace-implementation-for-cleaner-code-a8925ad9ed01/"><![CDATA[<h3 id="swift-an-elegant-native-type-extension-method">[Swift] An Elegant <strong>Native Type Extension Method</strong></h3>

<p>Encapsulating Extension Methods to Provide Namespace Functionality</p>

<p><img src="/assets/a8925ad9ed01/1*kJFHiAuTZ8TP-4aTa_daqA.webp" alt="&lt;https://www.swift.org/&gt;" loading="lazy" decoding="async" width="1352" height="484" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzUyIiBoZWlnaHQ9IjQ4NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/a8925ad9ed01/1*kJFHiAuTZ8TP-4aTa_daqA.png" /></p>

<p><a href="https://www.swift.org/" target="_blank">https://www.swift.org/</a></p>

<blockquote>
  <p><em>The actual source of this approach is unclear; it was learned from a skilled colleague’s code.</em></p>
</blockquote>

<h3 id="native-type-extensions">Native Type Extensions</h3>

<p>In daily iOS/Swift development, we often need to extend native APIs and write our own helpers.</p>

<p><strong>Below is an example of extending UIColor to convert it into a HEX Color String:</strong></p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">UIColor</span> <span class="p">{</span>
    <span class="c1">/// Convert a UIColor to a hex string representation.</span>
    <span class="c1">/// - Returns: A hex string (e.g., "#RRGGBB" or "#RRGGBBAA").</span>
    <span class="kd">func</span> <span class="nf">toHexString</span><span class="p">(</span><span class="nv">includeAlpha</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">var</span> <span class="nv">red</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">green</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">blue</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">alpha</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>

        <span class="k">guard</span> <span class="k">self</span><span class="o">.</span><span class="nf">getRed</span><span class="p">(</span><span class="o">&amp;</span><span class="n">red</span><span class="p">,</span> <span class="nv">green</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">green</span><span class="p">,</span> <span class="nv">blue</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">blue</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">alpha</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kc">nil</span> <span class="c1">// Color could not be represented in RGB space</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="n">includeAlpha</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"#%02X%02X%02X%02X"</span><span class="p">,</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">red</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">green</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">blue</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">alpha</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"#%02X%02X%02X"</span><span class="p">,</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">red</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">green</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">blue</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After directly extending UIColor, the access method is as follows:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">color</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">blue</span>
<span class="n">color</span><span class="o">.</span><span class="nf">toHexString</span><span class="p">()</span> <span class="c1">// #0000ff</span>
</code></pre></div></div>

<h4 id="problem">Problem</h4>

<p>When we have more and more custom extensions, the access interface can become messy, for example:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">color</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">blue</span>
<span class="n">color</span><span class="o">.</span><span class="nf">getRed</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">color</span><span class="o">.</span><span class="nf">getWhite</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">color</span><span class="o">.</span><span class="nf">getHue</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">color</span><span class="o">.</span><span class="nf">getCMYK</span><span class="p">()</span> <span class="c1">// Custom extended method</span>
<span class="n">color</span><span class="o">.</span><span class="nf">toHexString</span><span class="p">()</span> <span class="c1">// Custom extended method</span>
<span class="n">color</span><span class="o">.</span><span class="nf">withAlphaComponent</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">color</span><span class="o">.</span><span class="nf">setFill</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="n">color</span><span class="o">.</span><span class="nf">setToBlue</span><span class="p">()</span> <span class="c1">// Custom extended method</span>

<span class="c1">// A Module</span>
<span class="kd">public</span> <span class="kd">extension</span> <span class="kt">UIColor</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">getCMYK</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ...</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// B Module</span>
<span class="c1">// Invalid redeclaration of 'getCMYK()'</span>
<span class="kd">public</span> <span class="kd">extension</span> <span class="kt">UIColor</span> <span class="p">{</span>
  <span class="kd">func</span> <span class="nf">getCMYK</span><span class="p">()</span> <span class="p">{</span>
    <span class="c1">// ...</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Our custom extension methods get mixed together with the native methods, making them hard to distinguish. Also, as the project grows larger and more packages are used, extension name conflicts may occur. For example, if two packages both extend UIColor with a method called <code class="language-plaintext highlighter-rouge">getCMYK()</code>, it will cause issues.</p>

<h3 id="custom-extension-namespace-container">Custom Extension Namespace Container</h3>

<p>We can use Swift Protocols, Computed Properties, and Generics to encapsulate extension methods, giving them namespace functionality.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Declare a generic container ExtensionContainer&lt;Base&gt;:</span>
<span class="kd">public</span> <span class="kd">struct</span> <span class="kt">ExtensionContainer</span><span class="o">&lt;</span><span class="kt">Base</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="k">let</span> <span class="nv">base</span><span class="p">:</span> <span class="kt">Base</span>
    <span class="kd">public</span> <span class="nf">init</span><span class="p">(</span><span class="n">_</span> <span class="nv">base</span><span class="p">:</span> <span class="kt">Base</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">base</span> <span class="o">=</span> <span class="n">base</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// Define protocol for AnyObject, Class (Reference) Type implementations:</span>
<span class="c1">// For example, NSXXX classes in Foundation</span>
<span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">ExtensionCompatibleObject</span><span class="p">:</span> <span class="kt">AnyObject</span> <span class="p">{}</span>
<span class="c1">// Define protocol for Struct (Value) Type implementations:</span>
<span class="kd">public</span> <span class="kd">protocol</span> <span class="kt">ExtensionCompatible</span> <span class="p">{}</span>

<span class="c1">// Custom Namespace Computed Property:</span>
<span class="kd">public</span> <span class="kd">extension</span> <span class="kt">ExtensionCompatibleObject</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">zhg</span><span class="p">:</span> <span class="kt">ExtensionContainer</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kt">ExtensionContainer</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">public</span> <span class="kd">extension</span> <span class="kt">ExtensionCompatible</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">zhg</span><span class="p">:</span> <span class="kt">ExtensionContainer</span><span class="o">&lt;</span><span class="k">Self</span><span class="o">&gt;</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kt">ExtensionContainer</span><span class="p">(</span><span class="k">self</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="extending-native-types">Extending Native Types</h4>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">UIColor</span><span class="p">:</span> <span class="kt">ExtensionCompatibleObject</span> <span class="p">{}</span>

<span class="kd">extension</span> <span class="kt">ExtensionContainer</span> <span class="k">where</span> <span class="kt">Base</span><span class="p">:</span> <span class="kt">UIColor</span> <span class="p">{</span>
    <span class="c1">/// Convert a UIColor to a hex string representation.</span>
    <span class="c1">/// - Returns: A hex string (e.g., "#RRGGBB" or "#RRGGBBAA").</span>
    <span class="kd">func</span> <span class="nf">toHexString</span><span class="p">(</span><span class="nv">includeAlpha</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">String</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">var</span> <span class="nv">red</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">green</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">blue</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">var</span> <span class="nv">alpha</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>

        <span class="k">guard</span> <span class="k">self</span><span class="o">.</span><span class="n">base</span><span class="o">.</span><span class="nf">getRed</span><span class="p">(</span><span class="o">&amp;</span><span class="n">red</span><span class="p">,</span> <span class="nv">green</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">green</span><span class="p">,</span> <span class="nv">blue</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">blue</span><span class="p">,</span> <span class="nv">alpha</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">alpha</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kc">nil</span> <span class="c1">// Color could not be represented in RGB space</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="n">includeAlpha</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"#%02X%02X%02X%02X"</span><span class="p">,</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">red</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">green</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">blue</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">alpha</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="k">return</span> <span class="kt">String</span><span class="p">(</span><span class="nv">format</span><span class="p">:</span> <span class="s">"#%02X%02X%02X"</span><span class="p">,</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">red</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">green</span> <span class="o">*</span> <span class="mi">255</span><span class="p">),</span>
                          <span class="kt">Int</span><span class="p">(</span><span class="n">blue</span> <span class="o">*</span> <span class="mi">255</span><span class="p">))</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="usage">Usage</h4>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">color</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">blue</span>
<span class="n">color</span><span class="o">.</span><span class="n">zhg</span><span class="o">.</span><span class="nf">toHexString</span><span class="p">()</span> <span class="c1">// #0000ff</span>
</code></pre></div></div>

<h4 id="example-2-url-queryitems-extension">Example 2. URL .queryItems Extension</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extension URL: ExtensionCompatible {}

extension ExtensionContainer where Base == URL {
    
    var queryParameters: [String: String]? {
        URLComponents(url: base, resolvingAgainstBaseURL: true)?
            .queryItems?
            .reduce(into: [String: String]()) { $0[$1.name] = $1.value }
    }
}
let url = URL(string: "https://zhgchg.li?a=b&amp;c=d")!
url.zhg.queryParameters // ["c": "d", "a": "b"]
</code></pre></div></div>

<h3 id="combining-with-builder-pattern">Combining with <a href="https://refactoring.guru/design-patterns/builder" target="_blank">Builder Pattern</a></h3>

<p>Additionally, we can combine this encapsulation method with the Builder Pattern:</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="kd">class</span> <span class="kt">URLBuilder</span> <span class="p">{</span>
    <span class="kd">private</span> <span class="k">var</span> <span class="nv">components</span><span class="p">:</span> <span class="kt">URLComponents</span>

    <span class="nf">init</span><span class="p">(</span><span class="nv">base</span><span class="p">:</span> <span class="kt">URL</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">components</span> <span class="o">=</span> <span class="kt">URLComponents</span><span class="p">(</span><span class="nv">url</span><span class="p">:</span> <span class="n">base</span><span class="p">,</span> <span class="nv">resolvingAgainstBaseURL</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span><span class="o">!</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">setQueryParameters</span><span class="p">(</span><span class="n">_</span> <span class="nv">parameters</span><span class="p">:</span> <span class="p">[</span><span class="kt">String</span><span class="p">:</span> <span class="kt">String</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kt">URLBuilder</span> <span class="p">{</span>
        <span class="n">components</span><span class="o">.</span><span class="n">queryItems</span> <span class="o">=</span> <span class="n">parameters</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="nv">$0</span><span class="o">.</span><span class="n">key</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="nv">$0</span><span class="o">.</span><span class="n">value</span><span class="p">)</span> <span class="p">}</span>
        <span class="k">return</span> <span class="k">self</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">setScheme</span><span class="p">(</span><span class="n">_</span> <span class="nv">scheme</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">URLBuilder</span> <span class="p">{</span>
        <span class="n">components</span><span class="o">.</span><span class="n">scheme</span> <span class="o">=</span> <span class="n">scheme</span>
        <span class="k">return</span> <span class="k">self</span>
    <span class="p">}</span>

    <span class="kd">func</span> <span class="nf">build</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">URL</span><span class="p">?</span> <span class="p">{</span>
        <span class="k">return</span> <span class="n">components</span><span class="o">.</span><span class="n">url</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">URL</span><span class="p">:</span> <span class="kt">ExtensionCompatible</span> <span class="p">{}</span>

<span class="kd">extension</span> <span class="kt">ExtensionContainer</span> <span class="k">where</span> <span class="kt">Base</span> <span class="o">==</span> <span class="kt">URL</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">builder</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">URLBuilder</span> <span class="p">{</span>
        <span class="k">return</span> <span class="kt">URLBuilder</span><span class="p">(</span><span class="nv">base</span><span class="p">:</span> <span class="n">base</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">let</span> <span class="nv">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">string</span><span class="p">:</span> <span class="s">"https://zhgchg.li"</span><span class="p">)</span><span class="o">!.</span><span class="n">zhg</span><span class="o">.</span><span class="nf">builder</span><span class="p">()</span><span class="o">.</span><span class="nf">setQueryParameters</span><span class="p">([</span><span class="s">"a"</span><span class="p">:</span> <span class="s">"b"</span><span class="p">,</span> <span class="s">"c"</span><span class="p">:</span> <span class="s">"d"</span><span class="p">])</span><span class="o">.</span><span class="nf">setScheme</span><span class="p">(</span><span class="s">"ssh"</span><span class="p">)</span><span class="o">.</span><span class="nf">build</span><span class="p">()</span>
<span class="c1">// ssh://zhgchg.li?a=b&amp;c=d</span>
</code></pre></div></div>]]></content>
  </entry><entry>
    <title type="html">San’in &amp;amp; Kansai 7-Day Solo Trip｜Explore Izumo, Matsue, Tottori, Himeji, Osaka, Kobe</title>
    <link href="https://en.zhgchg.li/posts/travel-journals/san-in-kansai-7-day-solo-trip-explore-izumo-matsue-tottori-himeji-osaka-kobe-aacd5f5cacd1/" rel="alternate" type="text/html" title="San&apos;in &amp; Kansai 7-Day Solo Trip｜Explore Izumo, Matsue, Tottori, Himeji, Osaka, Kobe" />
    <published>2024-12-03T00:04:56+08:00</published>
    <updated>2025-08-13T13:38:47+08:00</updated>
    <id>https://en.zhgchg.li/posts/travel-journals/aacd5f5cacd1</id><summary type="html">Solo travelers seeking a comprehensive 7-day itinerary covering San&apos;in and Kansai regions can follow a 1,000 km route from Okayama through Izumo, Matsue, Tottori, Himeji, Osaka, and Kobe, completing 100,000 steps of immersive sightseeing and efficient transport connections.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="Travel Journals" /><category term="lifestyle" /><category term="japan" /><category term="travel-writing" /><category term="shimane-prefecture" /><category term="travel" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/aacd5f5cacd1/1*ucEUq8avIv1nBoNnCMlgRw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/travel-journals/san-in-kansai-7-day-solo-trip-explore-izumo-matsue-tottori-himeji-osaka-kobe-aacd5f5cacd1/"><![CDATA[<h3 id="travelogue-2024-sanin-wide-area-shimane-izumo-matsue-tottori-himeji-osaka-kobe-7-day-solo-trip">[Travelogue] 2024 San’in Wide Area Shimane Izumo Matsue Tottori Himeji Osaka Kobe 7-Day Solo Trip</h3>

<p>Starting and ending in Okayama, first visiting San’in Shimane Prefecture (Izumo and Matsue), then Tottori, and finally returning to Himeji, Kansai, and Osaka; 7 days, 100,000 steps, covering over 1,000 kilometers by transport, exploring the San’in and Kansai regions in one trip.</p>

<p><img src="/assets/aacd5f5cacd1/1*ucEUq8avIv1nBoNnCMlgRw.webp" alt="Sunset at Lake Shinji" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ucEUq8avIv1nBoNnCMlgRw.jpeg" /></p>

<p>Sunset at Lake Shinji</p>

<h4 id="preface">Preface</h4>

<p>At first, I planned to visit Sendai in the second half of 2024. However, after checking the attractions, I wasn’t very interested in going at this time. I recalled that during the same week last year (11/13–18), I took a <strong>“<a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">Sanyo Region Hiroshima Okayama 6-Day Free Trip</a>“</strong> and had a great impression of the Chugoku region, Sanyo, and Okayama — beautiful scenery, convenient transportation, and fewer tourists. So, I decided to explore the Sanin region (Shimane, Tottori) this time. Luckily, I had some free time from work and needed a break from recent stress, which led to this 7-day, 6-night solo trip covering Sanin and Kansai areas (Himeji, Osaka).</p>

<h3 id="preparation-work">Preparation Work</h3>

<h4 id="date-111218">Date: 11/12–18</h4>

<p>This time it was another spontaneous trip. I found a free time slot in mid-October and immediately planned to depart on 11/12 and return on 11/18.</p>

<h4 id="️flight-tickets">✈️Flight Tickets</h4>

<p><img src="/assets/aacd5f5cacd1/1*XRfz2GcB_EiuvwxLYIFCTw.webp" alt="" loading="lazy" decoding="async" width="1189" height="580" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTg5IiBoZWlnaHQ9IjU4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XRfz2GcB_EiuvwxLYIFCTw.png" /></p>

<p>This year, I didn’t specifically rush to buy Tigerair’s autumn promo tickets. <strong>Last year, I got discounted tickets</strong> with better timing and lower prices ( <code class="language-plaintext highlighter-rouge">Last year: Departure (11:10), Return (15:25), including 20KG checked baggage each way + seat selection + fees: NT$ 7,012</code> ).</p>

<ul>
  <li>
    <p>Departure: 13:05 TPE Taipei Taoyuan International Airport -&gt; <strong>16:30 OKJ</strong> Okayama Momotaro Airport</p>
  </li>
  <li>
    <p>Return: <strong>17:30 OKJ</strong> Okayama Momotaro Airport -&gt; 19:35 TPE Taipei Taoyuan International Airport</p>
  </li>
  <li>
    <p>Including round-trip 20KG checked baggage + seat selection + miscellaneous fees: <code class="language-plaintext highlighter-rouge">NT$ 9,118</code></p>
  </li>
</ul>

<p>Not particularly cheap, but still acceptable. If you choose the same round-trip dates as last year, it would be even more expensive, around <code class="language-plaintext highlighter-rouge">NT$ 12,000</code>.</p>

<h4 id="tigerair-2025-new-direct-flight-routes">Tigerair 2025 New Direct Flight Routes</h4>

<p><a href="https://chugoku.letsgojp.com/archives/731900" target="_blank">Tigerair Taiwan will add a direct flight from “Taoyuan International Airport to Tottori Yonago Airport” starting Thursday, May 29, 2025, making it even more convenient to visit the San’in region!</a></p>

<h4 id="️plan">🏝️Plan</h4>

<p>I have never been to the San’in region or Himeji, so I focused on these three places.</p>

<ul>
  <li>
    <p>11/12 Arrived in Okayama in the evening, stayed one night to prepare for the trip to Shimane the next morning</p>
  </li>
  <li>
    <p>11/13 Early morning, took the JR train to Shimane. First visited Izumo Taisha and Inasa Beach, then headed to Lake Shinji in the late afternoon to watch the sunset.</p>
  </li>
  <li>
    <p>11/14 Morning: Matsue Castle and Matsue Horikawa Boat Tour; Afternoon: Adachi Museum of Art; Evening: Arrived in Tottori</p>
  </li>
  <li>
    <p>11/15 Morning at Hakuto Shrine, afternoon at Tottori Sand Dunes, evening arrival in Himeji</p>
  </li>
  <li>
    <p>11/16 Himeji Castle and shopping in Himeji City<br />
Actually visited more: Shosha-san Engyo-ji Temple</p>
  </li>
  <li>
    <p>11/17 Onaruto Bridge Walkway Uzunomichi, Naruto Whirlpools, Akashi Kaikyō Bridge<br />
Due to bad weather, we didn’t go and instead went shopping and eating in Osaka and Kobe.</p>
  </li>
  <li>
    <p>11/18 Shopping at Okayama Station and return trip</p>
  </li>
</ul>

<h4 id="-transportation---japan-jr-passkansai--sanin-area-rail-passemco-e-ticket">🚅 Transportation - <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>Japan JR PASS｜Kansai &amp; Sanin Area Rail Pass｜eMCO E-ticket</strong></a></h4>

<p>This trip also involved long-distance travel, so buying a JR Pass was a must. I initially worried there would only be passes for San’in or Sanyo, but JR Japan thoughtfully planned all possible routes. The “<a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>Kansai &amp; San’in Area Rail Pass JRPass</strong></a>” was just perfect for me!</p>

<p><img src="/assets/aacd5f5cacd1/1*H0ztJ3sX7oPQmJz7L2j87w.webp" alt="Kansai &amp; San'in Area Railway Pass JRPass Coverage" loading="lazy" decoding="async" width="1330" height="1080" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzMwIiBoZWlnaHQ9IjEwODAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*H0ztJ3sX7oPQmJz7L2j87w.png" /></p>

<p><a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank">Kansai &amp; Sanin Area Rail Pass JRPass Usage Scope</a></p>

<ul>
  <li>Price: <code class="language-plaintext highlighter-rouge">NT$ 3,779</code> Includes an extra <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>San’in-Sanyo Area Pass 3-Day Ticket (Great value when used together)</strong></a></li>
</ul>

<h4 id="internet">📱Internet</h4>

<p>This time, I also purchased the “<a href="https://www.kkday.com/zh-tw/product/137689-japan-high-speed-daily-unlimited-data-japanese-esim?cid=19365" target="_blank"><strong>Japan eSIM｜Daily High-Speed, Total Volume, Unlimited Data Plan｜65% Off</strong></a>” from KKday.</p>

<ul>
  <li>
    <p>7-Day Unlimited Data eSIM Plan</p>
  </li>
  <li>
    <p>Price: <code class="language-plaintext highlighter-rouge">NT$ 847</code></p>
  </li>
</ul>

<p>In actual use, it works very well, is stable, and I did not encounter any speed throttling or disconnection issues.</p>

<blockquote>
  <p><em>The eSIM activation method and important notes will be included in the article. Please continue reading.</em></p>
</blockquote>

<h4 id="japanese-yen">Japanese Yen</h4>

<p><img src="/assets/aacd5f5cacd1/1*ljW0bI5fjkI2Xasi4KvA5A.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ljW0bI5fjkI2Xasi4KvA5A.jpeg" /></p>

<p>Exchanged for new Japanese yen bills.</p>

<blockquote>
  <p><em>It is recommended to bring both new and old bills, as some vending machines do not accept new bills.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*_Zz6O5dhksVtJvi8TqN1yw.webp" alt="A vending machine that accepts new bills" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_Zz6O5dhksVtJvi8TqN1yw.jpeg" /></p>

<p>Vending machines that accept new bills</p>

<h4 id="-accommodation">🏠 Accommodation</h4>

<p>This time, I was lucky to book all accommodations except for one night in Matsue, where I couldn’t get a room at <a href="https://www.toyoko-inn.com/china/index2" target="_blank">Toyoko Inn</a>.</p>

<p><strong>11/12 <a href="https://www.toyoko-inn.com/china/search/detail/00232/" target="_blank">Toyoko INN Okayama Station East Exit</a> (One night)</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*Kidzn6hm372657rCN8jTgw.webp" alt="It takes 4 minutes to walk from Okayama Station" loading="lazy" decoding="async" width="620" height="289" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MjAiIGhlaWdodD0iMjg5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*Kidzn6hm372657rCN8jTgw.png" /></p>

<p>It’s a 4-minute walk from Okayama Station.</p>

<ul>
  <li>
    <p>Non-smoking twin room, occupancy: 1 person</p>
  </li>
  <li>
    <p>Price: <code class="language-plaintext highlighter-rouge">NT$ 1,616</code></p>
  </li>
</ul>

<p><strong>11/13 <a href="https://www.station.matsue-urban.co.jp/en/" target="_blank">Matsue Urban Hotel</a> (One night)</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*9QIJ7lY1W7L94dgKGm6VQQ.webp" alt="Matsue Station exit, just a 2-minute walk away" loading="lazy" decoding="async" width="413" height="287" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MTMiIGhlaWdodD0iMjg3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*9QIJ7lY1W7L94dgKGm6VQQ.png" /></p>

<p>It takes only 2 minutes to walk from Matsue Station exit.</p>

<ul>
  <li>
    <p>Double Room with Small Double Bed — Non-Smoking — Building 2<br />
Occupancy: 1 person</p>
  </li>
  <li>
    <p>Price: <code class="language-plaintext highlighter-rouge">NT$ 2,431</code></p>
  </li>
</ul>

<p><strong>11/14 <a href="https://www.toyoko-inn.com/china/search/detail/00046/" target="_blank">Toyoko INN Tottori Station South Exit</a> (One night)</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*hIqnM7MaRDWsRoRsydTqCg.webp" alt="Tottori Station exit, just a 4-minute walk" loading="lazy" decoding="async" width="587" height="373" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1ODciIGhlaWdodD0iMzczIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*hIqnM7MaRDWsRoRsydTqCg.png" /></p>

<p>It takes a 4-minute walk from Tottori Station exit.</p>

<ul>
  <li>
    <p>Non-smoking double room, occupancy: 1 person</p>
  </li>
  <li>
    <p>Price: <code class="language-plaintext highlighter-rouge">NT$ 1,595</code></p>
  </li>
</ul>

<p><strong>11/15 <a href="https://www.toyoko-inn.com/china/search/detail/00317/" target="_blank">Toyoko INN Himeji Station Shinkansen North Exit</a> (3 nights)</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*P4xut5p60ZVSDtPkpoO6Pw.webp" alt="5-minute walk from Himeji Station exit" loading="lazy" decoding="async" width="777" height="591" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzciIGhlaWdodD0iNTkxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*P4xut5p60ZVSDtPkpoO6Pw.png" /></p>

<p>It takes a 5-minute walk from Himeji Station exit.</p>

<ul>
  <li>
    <p>Non-smoking double room, occupancy: 1 person</p>
  </li>
  <li>
    <p>Price: <code class="language-plaintext highlighter-rouge">NT$ 6,055</code>, <code class="language-plaintext highlighter-rouge">NT$ 2,018</code> / night</p>
  </li>
</ul>

<p>Although I managed to book Toyoko Inn for almost all nights, some nights only had double rooms left due to late booking, so the price was higher. If I had booked a single room, the price could have been kept under NT$1,500 per night. However, based on past experience, even double rooms tend to sell out near the date, possibly because the location is less visited?</p>

<blockquote>
  <p><em>The room unboxing video will be included in the article; please continue reading.</em></p>
</blockquote>

<p>—</p>

<blockquote>
  <p><a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/"><em>Flight Tracker, iPhone Suica usage, Visit Japan pre-entry application… These were mentioned in previous articles, so I won’t repeat them here.</em></a></p>
</blockquote>

<blockquote>
  <p><em><a href="http://vjw.digital.go.jp/" target="_blank">Visit Japan</a> <strong>The only change from before is that the “Immigration Inspection” and “Customs Declaration” QR codes have been combined into a single “Immigration and Customs Declaration QR Code,” with no distinction between blue or yellow codes.</strong></em></p>
</blockquote>

<h4 id="tigerair-online-self-check-in">Tigerair Online Self Check-in</h4>

<p>You will receive a trip notification email from Tigerair 1 to 3 days before departure. <strong>Be sure! Be sure! Be sure!</strong> to complete the online “self check-in” before departure:</p>

<p><img src="/assets/aacd5f5cacd1/1*3K4biik6RnLXrqp453siiA.webp" alt="" loading="lazy" decoding="async" width="1120" height="1153" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTIwIiBoZWlnaHQ9IjExNTMiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*3K4biik6RnLXrqp453siiA.png" /></p>

<p>Clicking self check-in will redirect you to the Tigerair website to confirm passenger details and baggage safety, then the boarding pass will be generated and sent.</p>

<p><img src="/assets/aacd5f5cacd1/1*oupKW1tFwXdEwj4pEAL8hw.webp" alt="" loading="lazy" decoding="async" width="511" height="846" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTEiIGhlaWdodD0iODQ2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*oupKW1tFwXdEwj4pEAL8hw.png" /></p>

<p>Keep your boarding pass safe. With it, you can go straight to the “Self-Service Baggage Drop” at the airport to check in your luggage without waiting in the long “Regular Check-in” lines.</p>

<h4 id="lets-go-departure">Let’s go! Departure!</h4>

<h4 id="kkday-promotion-">KKday Promotion 🛒</h4>

<ul>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/153331" target="_blank">Japan JR PASS \| Tottori &amp; Matsue Area Pass \| eMCO e-ticket</a>
(Consider this if you only visit Matsue and Tottori-Matsue areas)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>Kansai &amp; San’in Area Rail Pass JRPass</strong></a>
(The best choice for arriving and departing from Okayama Airport)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/137689-japan-high-speed-daily-unlimited-data-japanese-esim?cid=19365" target="_blank"><strong>Japan eSIM Card｜Daily High-Speed, Total Data, Unlimited Data Plans</strong></a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/20315-jr-sanyo-sanin-area-pass?cid=19365" target="_blank">Japan JR PASS｜Sanyo &amp; Sanin Area Rail Pass｜eMCO e-Ticket</a>
(Explore the entire Sanyo and Sanin regions in one go)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/184144?cid=19365" target="_blank">Tottori Sand Dunes \| Japan’s Only Thrilling Sandboarding Experience</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/11463-osaka-bus-tour-tottori-sand-dunes-uratomi-coast-cruise-sand-museum-japan?cid=19365" target="_blank">【Tottori Bus One-Day Tour】Tottori Sand Dunes, Uradome Coast Cruise, Sand Museum, Pear Picking Experience｜Includes Special Lunch (Departing from Osaka)</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/262558?cid=19365" target="_blank">Tour Tottori Sand Dunes and the Famous Izumo Taisha 2-Day Bus Trip (From Osaka)</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/185589?cid=19365" target="_blank">New Year Visit! 3-Hour Stop at Izumo Taisha Shrine 【Departing from Hiroshima】</a></p>
  </li>
</ul>

<h3 id="day-1-1112-tuesday-departure">Day 1 (11/12 Tuesday) Departure</h3>

<p>The flight is at 13:05. Woke up at 8 AM and left around 9 AM, plenty of time.</p>

<p><img src="/assets/aacd5f5cacd1/1*LyhBB17WajIUYryRUrIgaA.webp" alt="2024/11 Screenshot from Online Check-in Official Website" loading="lazy" decoding="async" width="546" height="338" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NDYiIGhlaWdodD0iMzM4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*LyhBB17WajIUYryRUrIgaA.png" /></p>

<p><a href="https://www.taoyuan-airport.com/ITCI/index.html" target="_blank">2024/11 Screenshot from the Online Check-in Website</a></p>

<p>Tigerair’s <a href="https://www.taoyuan-airport.com/ITCI/index.html" target="_blank"><strong>Online Check-in Service (Check-in and drop luggage at the Airport MRT, then go straight to departure at the airport)</strong></a> is only available at <strong>Airport MRT A3 New Taipei Industrial Park Station</strong>; not available at Taipei Main Station, so I took the direct Airport MRT train straight to Terminal 1.</p>

<h4 id="-1000-arrive-at-the-airport">~= 10:00 Arrive at the airport</h4>

<p><img src="/assets/aacd5f5cacd1/1*4_ZTToEL1W6fwNWpo_GvBw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*4_ZTToEL1W6fwNWpo_GvBw.jpeg" /></p>

<p>Arrived at the airport around 10:00, but the counter opens at 10:20.</p>

<h4 id="-1020-queuing-for-self-service-baggage-check-in-to-drop-off-luggage">~= 10:20 Queuing for “Self-service Baggage Check-in” to drop off luggage</h4>

<p><img src="/assets/aacd5f5cacd1/1*4y0Kgdbk8bbcUgajmi27IQ.webp" alt="" loading="lazy" decoding="async" width="951" height="1238" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTEiIGhlaWdodD0iMTIzOCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4y0Kgdbk8bbcUgajmi27IQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5_TKj5PZ-iUMi3r3CqrmzA.webp" alt="Outbound: 9 KG" loading="lazy" decoding="async" width="611" height="776" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MTEiIGhlaWdodD0iNzc2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*5_TKj5PZ-iUMi3r3CqrmzA.png" /></p>

<p>Outbound: 9 KG</p>

<p>This is when the advantage of using online check-in on the Tigerair website comes in. On that day, the “regular check-in” counters were visibly crowded, already full by 10:20 AM, with an estimated wait time of 45 minutes to 1 hour. The “self-service baggage drop” took less than 15 minutes to complete, saving a lot of waiting time.</p>

<blockquote>
  <p><em>Completing online check-in on site, then going to the “self-service baggage drop” is much faster than queuing for “regular check-in.”</em></p>
</blockquote>

<h4 id="-1050-completed-departure-procedures">~= 10:50 Completed departure procedures</h4>

<p><img src="/assets/aacd5f5cacd1/1*aD3atJOMZWAhStzpdoaZDg.webp" alt="" loading="lazy" decoding="async" width="937" height="1167" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzciIGhlaWdodD0iMTE2NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*aD3atJOMZWAhStzpdoaZDg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OLORgiz8R6pOfG-m9P-aOw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OLORgiz8R6pOfG-m9P-aOw.jpeg" /></p>

<p>This time at Terminal 1, Gate A1. (Finally no more shuttle buses)</p>

<p><img src="/assets/aacd5f5cacd1/1*MDpHGgVgU0QRH0BGFmzLbA.webp" alt="" loading="lazy" decoding="async" width="951" height="1236" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTEiIGhlaWdodD0iMTIzNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MDpHGgVgU0QRH0BGFmzLbA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ZU4N6uWzEYhp_J2C3JE0Dg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1034" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMzQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ZU4N6uWzEYhp_J2C3JE0Dg.png" /></p>

<p>Every time I came to Terminal 1 before, I always ate at Ding Gua Gua. This time, I switched to Lao Dong Beef Noodles, which is conveniently located near Gate A1.</p>

<h4 id="-1130-waiting-at-the-airport">~= 11:30 Waiting at the airport</h4>

<p><img src="/assets/aacd5f5cacd1/1*Xaux1WjOTpXcqxeF1-UlXw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Xaux1WjOTpXcqxeF1-UlXw.jpeg" /></p>

<p>After eating, I wandered around a bit until about 11:30. The downside of gate A1 is that it’s far from the <a href="/posts/travel-journals/kyushu-9-day-independent-trip-busan-to-hakata-cruise-entry-and-scenic-tours-cb65fd5ab770/"><strong>Terminal 1 Free Lounge</strong></a>, so I skipped walking back and forth and went straight to the waiting area.</p>

<p>Without resolving two network issues first, the first is that my <a href="https://123.cht.com.tw/IVRredir/" target="_blank"><strong>Chunghwa Telecom roaming function is turned off</strong></a>, so I can’t directly buy a roaming plan if needed; therefore, I called Chunghwa Telecom to unlock the roaming function just in case.</p>

<p><img src="/assets/aacd5f5cacd1/1*dF9XuqFK3bW2X249DYtqZg.webp" alt="Need to call Chunghwa Telecom and verify personal information with customer service to unlock." loading="lazy" decoding="async" width="574" height="1206" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzQiIGhlaWdodD0iMTIwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dF9XuqFK3bW2X249DYtqZg.png" /></p>

<p>You need to call Chunghwa Telecom and confirm your personal information with customer service to unlock the account.</p>

<h4 id="pre-activate-esim-using-iphone-as-an-example">Pre-activate eSIM (Using iPhone as an example)</h4>

<p>The second internet issue was activating the eSIM at the airport. Since activating the eSIM requires an internet connection, and the WiFi at Okayama Airport might be unstable, I activated the Japan eSIM in Taiwan where the network was stable beforehand.</p>

<blockquote>
  <p><em>If you’re still worried about network issues causing problems at immigration, <strong>you can also take a screenshot of the entry QR Code</strong> or fill out a paper entry card as a backup.</em></p>
</blockquote>

<p>Previously, the eSIM bought in <a href="/posts/travel-journals/bangkok-travel-guide-5-day-independent-trip-highlights-in-thailand-b7e7c0938985/">Thailand could be added automatically by long-pressing the QR Code in the purchase confirmation email, PDF, or screenshot and tapping “Add eSIM”</a>, but this time the Japanese eSIM did not show this option:</p>

<p><img src="/assets/aacd5f5cacd1/1*su5YVQrLdjFXORHuiMZxuw.webp" alt="" loading="lazy" decoding="async" width="567" height="1013" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjciIGhlaWdodD0iMTAxMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*su5YVQrLdjFXORHuiMZxuw.png" /></p>

<p><strong>Found a method provided by a <a href="https://www.ptt.cc/bbs/iOS/M.1719797706.A.916.html" target="_blank">user</a> that successfully shows the “Add eSIM” option. (Seems like an iOS bug, broken since iOS 17.4):</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*DrYXYUfGj_BddPe8FwbM4A.webp" alt="" loading="lazy" decoding="async" width="578" height="1047" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzgiIGhlaWdodD0iMTA0NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DrYXYUfGj_BddPe8FwbM4A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Z-Ls2JPUbJAw93L7ey91gw.webp" alt="" loading="lazy" decoding="async" width="569" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjkiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Z-Ls2JPUbJAw93L7ey91gw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BwNRm45uuwJcWjnYicDIEA.webp" alt="" loading="lazy" decoding="async" width="562" height="1151" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjIiIGhlaWdodD0iMTE1MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BwNRm45uuwJcWjnYicDIEA.png" /></p>

<ol>
  <li>
    <p>Go to the eSIM voucher email or PDF, take a screenshot on your phone, and save the screenshot in your photo album.</p>
  </li>
  <li>
    <p>Open the phone camera, tap the bottom left corner to enter the photo album.</p>
  </li>
  <li>
    <p>Find the eSIM photo in the album, long press the QR code, and the “Add eSIM” option will appear.</p>
  </li>
  <li>
    <p>Click “Add eSIM” to directly load the activation information and activate the eSIM.</p>
  </li>
</ol>

<h4 id="manually-activate-esim-on-iphone">Manually Activate eSIM on iPhone</h4>

<p><strong>Go to “Settings” -&gt; “Cellular” -&gt; “Add eSIM” -&gt; “Use Cellular QR Code”:</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*TUIEbNDhczwvTehfFza_sg.webp" alt="" loading="lazy" decoding="async" width="562" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*TUIEbNDhczwvTehfFza_sg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7hYiMadhEnU5NOt58ashJw.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7hYiMadhEnU5NOt58ashJw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YUjZUpfB6BeLQKNWTMx-5A.webp" alt="" loading="lazy" decoding="async" width="581" height="1239" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1ODEiIGhlaWdodD0iMTIzOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YUjZUpfB6BeLQKNWTMx-5A.png" /></p>

<p>I printed out the eSIM QR code, so I activated it by scanning directly from there.</p>

<p>If you have nothing else, you can click “Enter Details Manually” to copy and paste the eSIM voucher information for manual activation:</p>

<p><img src="/assets/aacd5f5cacd1/1*TdmMDosao98OATMJNa0NLw.webp" alt="" loading="lazy" decoding="async" width="1200" height="1170" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjExNzAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*TdmMDosao98OATMJNa0NLw.png" /></p>

<p>Fill in the <code class="language-plaintext highlighter-rouge">SM-DP+ Address</code>, <code class="language-plaintext highlighter-rouge">Activation Code</code>, and <code class="language-plaintext highlighter-rouge">Confirmation Code</code> (none this time) as per the email, then click Next to complete activation.</p>

<p><img src="/assets/aacd5f5cacd1/1*cXjnkvYvDGnE-ByT3hcnaw.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cXjnkvYvDGnE-ByT3hcnaw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*sc6xr-nA3J_BdYMcNBNMFA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*sc6xr-nA3J_BdYMcNBNMFA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Rh7hWbEiUtLbR3VYXq9NOw.webp" alt="" loading="lazy" decoding="async" width="581" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1ODEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Rh7hWbEiUtLbR3VYXq9NOw.png" /></p>

<p>After inputting, it takes some time to activate. It may get stuck at “Activating…” for a while or show an activation failed message asking you to try again later; just wait a bit longer. Once activated successfully, “Settings” -&gt; “Cellular” will show “On,” and the signal bars will appear at the top right, displaying signal status.</p>

<ul>
  <li>The action plan tag can be set to “Travel”</li>
</ul>

<blockquote>
  <p><strong><em>We are still in Taiwan and cannot connect to the eSIM Japan network yet. Just make sure the activation is successful.</em></strong></p>
</blockquote>

<h4 id="-1220-boarding-begins-no-delays">~= 12:20 Boarding begins, no delays</h4>

<p><img src="/assets/aacd5f5cacd1/1*R1I1UY831lnjto_6jqwCFw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*R1I1UY831lnjto_6jqwCFw.jpeg" /></p>

<h4 id="-1305-departed-on-time">~= 13:05 Departed on time</h4>

<p><img src="/assets/aacd5f5cacd1/1*ovXkaMS4yzLWpm2_YBTPFw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ovXkaMS4yzLWpm2_YBTPFw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*IQ1kIFLjFgrYHUqulJMO7Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IQ1kIFLjFgrYHUqulJMO7Q.jpeg" /></p>

<p>At this time, heavy rain started in Taiwan, but there was little wind, so it did not significantly affect the takeoff.</p>

<p><img src="/assets/aacd5f5cacd1/1*Ooxdur72IKkRc4u5q52esQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Ooxdur72IKkRc4u5q52esQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ECU47qVPUd7cv6UcBy6i3Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ECU47qVPUd7cv6UcBy6i3Q.jpeg" /></p>

<p>I find Tigerair’s seat comfort to be excellent among budget airlines (compared to Peach’s seats being too small and Vietjet’s seats being uncomfortable). However, a downside of Tigerair is that <a href="https://www.threads.net/@kechi.syonen/post/DCYnEQNzU5L/%E4%BB%80%E9%BA%BC%E8%83%BD%E5%B8%B6%E4%B8%8A%E9%A3%9B%E6%A9%9F%E4%BB%80%E9%BA%BC%E4%B8%8D%E8%83%BD%E5%B8%B6%E5%BE%88%E6%B8%85%E6%A5%9A%E4%BB%99%E5%8F%B0%E6%A9%9F%E5%A0%B4" target="_blank"><strong>bringing your own food is prohibited</strong></a>, so it’s best to board just after a meal!</p>

<p>No WiFi on the plane, so just listen to music and play offline games.</p>

<p><img src="/assets/aacd5f5cacd1/1*LaNvJoYfPvU3LJchfvJHlg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*LaNvJoYfPvU3LJchfvJHlg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*B5bSSUIBkoDGo147pFsEVQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*B5bSSUIBkoDGo147pFsEVQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9Ibz55I3hZC0C7lSO-4o1A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*9Ibz55I3hZC0C7lSO-4o1A.jpeg" /></p>

<p>You can see the great weather in Japan from the window. 👍👍👍</p>

<h4 id="1630-taiwan-time-1530-on-time-landing">16:30 (Taiwan Time 15:30) On-time Landing</h4>

<p><img src="/assets/aacd5f5cacd1/1*paerDziYhfHNdjAg1QeJBg.webp" alt="" loading="lazy" decoding="async" width="532" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MzIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*paerDziYhfHNdjAg1QeJBg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*y36b_kkD7RMz1R501bPkIw.webp" alt="" loading="lazy" decoding="async" width="566" height="1267" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjYiIGhlaWdodD0iMTI2NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*y36b_kkD7RMz1R501bPkIw.png" /></p>

<p>After landing in Japan, our Japan eSIM should be able to receive a signal.</p>

<blockquote>
  <p><em>There is signal but no internet access yet. You need to go to “Settings” -&gt; “Cellular” -&gt; select <strong>Travel Japan eSIM</strong> -&gt; make sure “Enable this line” is on and <strong>turn on “Data Roaming” to have internet! ⚠️⚠️⚠️</strong></em></p>
</blockquote>

<blockquote>
  <p><em>You can also enter the <strong>main Chunghwa Telecom</strong> number. If you haven’t purchased Chunghwa Telecom’s roaming plan, please remember to <strong>turn off “Data Roaming” here, and keep “Enable this line” on to continue receiving SMS from Taiwan!</strong></em></p>
</blockquote>

<blockquote>
  <p><strong><em>Please note: Do not answer calls randomly; receiving calls from abroad to a domestic number will also incur charges! ⚠️⚠️⚠️ (Only receiving SMS is free)</em></strong></p>
</blockquote>

<h4 id="-1700-complete-immigration-and-collect-luggage">~= 17:00 Complete immigration and collect luggage</h4>

<ul>
  <li>
    <p>Okayama Momotaro Airport is a very small airport, with only one international flight at a time (yours). You go through immigration and collect your luggage quickly along with other passengers.<br />
(This flight seemed nearly full, and it took about 30 minutes to get out.)</p>
  </li>
  <li>
    <p>Unexpectedly, the airport staff all speak Chinese.</p>
  </li>
  <li>
    <p>It might be because smaller airports are stricter. Almost every solo traveler like me was asked how many days they would stay and which places they planned to visit. Some were even required to show hotel reservations.<br />
(So if you’re traveling alone, prepare these in advance.)</p>
  </li>
</ul>

<h4 id="airport-wifi">Airport WiFi</h4>

<p><img src="/assets/aacd5f5cacd1/1*evDWJrEApdI2laaMO01asw.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*evDWJrEApdI2laaMO01asw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YqPFrPIOePpu3WgHmBMXBA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YqPFrPIOePpu3WgHmBMXBA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7sCEBxL7NtIZHBeap-IW-w.webp" alt="" loading="lazy" decoding="async" width="560" height="1127" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjAiIGhlaWdodD0iMTEyNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7sCEBxL7NtIZHBeap-IW-w.png" /></p>

<blockquote>
  <p><em>After connecting to Okayama Airport’s free WiFi, you need to enter your email, then check your inbox to click the activation link to continue using it; if you only need Visit Japan, you can open a webpage within 10 minutes of connecting to display the entry QR Code.</em></p>
</blockquote>

<h4 id="-1704-took-the-bus-from-okayama-airport-to-jr-okayama-station">~= 17:04 Took the bus from Okayama Airport to JR Okayama Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*YNxPMPmNH-YBWNemZR6t-Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YNxPMPmNH-YBWNemZR6t-Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4c4diTl_B42J1RTeChslgA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4c4diTl_B42J1RTeChslgA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*k3bf2CWOwFKCWeC1eC1yTQ.webp" alt="For the latest schedule, please refer to the official website" loading="lazy" decoding="async" width="371" height="634" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNzEiIGhlaWdodD0iNjM0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*k3bf2CWOwFKCWeC1eC1yTQ.png" /></p>

<p><a href="https://www.okayama-airport.org/tw/access/bus" target="_blank">For the latest schedule, please refer to the official website</a></p>

<ul>
  <li>
    <p>The airport is small, just follow the signs to find the bus to Okayama Station. Staff will guide you, so don’t worry about getting lost—just get on the bus.</p>
  </li>
  <li>
    <p><strong>There are very few flights, so besides the scheduled bus times, extra buses are added based on flight arrivals; don’t worry about full buses, as more will be dispatched if needed.</strong><br />
(According to the <a href="https://www.okayama-airport.org/tw/access/bus" target="_blank">schedule, you’d have to wait until 19:05 for a bus</a>, but extra buses are actually available.)</p>
  </li>
  <li>
    <p><a href="https://www.ptt.cc/bbs/Japan_Travel/M.1730096575.A.AB6.html" target="_blank">Show your passport before 2024/11/30</a> to ride free; free shuttle service runs irregularly, <a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">it was not available last year</a>.</p>
  </li>
</ul>

<h4 id="-1800-arrive-at-jr-okayama-station">~= 18:00 Arrive at JR Okayama Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*8OhJp66_cz7XpEzC8I6WpQ.webp" alt="" loading="lazy" decoding="async" width="912" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8OhJp66_cz7XpEzC8I6WpQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*nxBsum1hduuDJJluFTz4DQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1010" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*nxBsum1hduuDJJluFTz4DQ.png" /></p>

<p>There was some traffic congestion during rush hour, so I arrived at Okayama Station around 18:00. <strong>(30 minutes later than expected)</strong></p>

<h4 id="jr-okayama-station-exchange-jr-pass--reserve-train-tickets-to-matsue-for-tomorrow">JR Okayama Station: Exchange JR Pass &amp; Reserve Train Tickets to Matsue for Tomorrow</h4>

<p><img src="/assets/aacd5f5cacd1/1*-2saCSONEdJw9FIYeVFZ4Q.webp" alt="" loading="lazy" decoding="async" width="953" height="1192" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTMiIGhlaWdodD0iMTE5MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-2saCSONEdJw9FIYeVFZ4Q.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KJeCftSlvW2t-a1Ef5JvEA.webp" alt="" loading="lazy" decoding="async" width="952" height="1232" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTIiIGhlaWdodD0iMTIzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KJeCftSlvW2t-a1Ef5JvEA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*t9I2iSC2-97Dv5fX6FNqWw.webp" alt="" loading="lazy" decoding="async" width="640" height="745" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDAiIGhlaWdodD0iNzQ1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*t9I2iSC2-97Dv5fX6FNqWw.png" /></p>

<ul>
  <li>
    <p>You can check the official website for <a href="https://www.westjr.co.jp/global/tc/ticket/receive/?railpass=11&amp;mco=1&amp;area=0" target="_blank">stations where you can exchange the JR Pass</a>. Not every station offers exchanges!<br />
(JR Okayama Station is available)</p>
  </li>
  <li>
    <p>At Okayama Station, find the ticket vending machines and line up at the green machine in the middle. It’s the only one that can exchange JR Pass.</p>
  </li>
</ul>

<p><strong>Redemption Process:</strong></p>

<p><strong>Have your passport and purchased JR Pass QR code ready. You can first select Traditional Chinese in the top right corner, then directly click the yellow button at the bottom left labeled “QRコードの読取り (Scan QR Code)” to redeem.</strong></p>

<blockquote>
  <p><strong><em>You can choose the start date</em></strong> <em>The reservation time for reserved seats is also counted from the start date.</em> <strong>⚠️</strong></p>
</blockquote>

<blockquote>
  <p><em>(Like I chose to start tomorrow)</em></p>
</blockquote>

<p>Follow the steps to scan the JR Pass QR Code and your passport to complete the exchange.</p>

<p><img src="/assets/aacd5f5cacd1/1*nj6t65muUKTM8rL9b9MnUQ.webp" alt="JR Pass QR Code" loading="lazy" decoding="async" width="845" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*nj6t65muUKTM8rL9b9MnUQ.png" /></p>

<p>JR Pass QR Code</p>

<p><strong>Exchange Result:</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*A-kqDIwfmGwif12qTiUU1g.webp" alt="The above image shows last year's JR Pass exchange as an example" loading="lazy" decoding="async" width="604" height="770" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDQiIGhlaWdodD0iNzcwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*A-kqDIwfmGwif12qTiUU1g.png" /></p>

<p>The above image uses last year’s JR Pass redemption as an example.</p>

<p>After redemption, you will receive three vouchers. The other two rectangular ones are instruction sheets and are not useful; <strong>the one printed with PASS is the most important. You need this for entering and exiting stations, so keep it safe!</strong></p>

<h4 id="booking-jr-reserved-seats">Booking JR Reserved Seats</h4>

<p>This is very important. According to the information provided by the <a href="https://www.westjr.co.jp/global/tc/ticket/pass/kansai_sanin/" target="_blank">JR Pass Kansai &amp; Sanin Area Pass official website</a>, the <a href="https://www.westjr.co.jp/global/tc/ticket/pass/pdf/Reserved_seats_tc.pdf" target="_blank"><strong>JR YAKUMO train to the Sanin region is all reserved seating</strong></a> <strong>with no non-reserved seats ⚠️⚠️⚠️ (starting from March 16, 2024)</strong>, so all seats must be reserved in advance.</p>

<blockquote>
  <p><em>You should also be able to book online in advance. <a href="https://medium.com/ztravel/%E9%81%8A%E8%A8%98-2024-%E4%BA%8C%E8%A8%AA%E4%B9%9D%E5%B7%9E-9-%E6%97%A5%E8%87%AA%E7%94%B1%E8%A1%8C-%E7%B6%93%E9%87%9C%E5%B1%B1-%E5%8D%9A%E5%A4%9A%E9%83%B5%E8%BC%AA%E5%85%A5%E5%A2%83-cb65fd5ab770?source=collection_home---4------1-----------------------" target="_blank">Previously, I booked the JR from Hakata to Yufuin online</a> , but you need to check it.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*jPSXmntXpBrCzRGnB0Ty5A.webp" alt="" loading="lazy" decoding="async" width="1200" height="1051" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwNTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*jPSXmntXpBrCzRGnB0Ty5A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YUzrKBjkuis18Az9sZMM5Q.webp" alt="" loading="lazy" decoding="async" width="751" height="575" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NTEiIGhlaWdodD0iNTc1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*YUzrKBjkuis18Az9sZMM5Q.png" /></p>

<blockquote>
  <p><em>The traffic information on Google Maps is incomplete. Detailed schedules and <strong>train names</strong> can be found on the <a href="https://www.westjr.co.jp/global/sc/timetable/" target="_blank">JR official website</a>. <strong>⚠️⚠️⚠️</strong></em></p>
</blockquote>

<p><strong>JR Pass Reserved Seat Booking Process:</strong></p>

<ul>
  <li>
    <p><a href="https://www.westjr.co.jp/global/tc/ticket/pass/kansai_sanin/" target="_blank"><strong>JR Pass Kansai &amp; Sanin Area Pass</strong></a> <strong>allows 6 machine reservations for reserved seats; beyond that, you must book at a staffed counter. The number and limits vary by region, so please check the official website for details.</strong></p>
  </li>
  <li>
    <p>First, check the English names of your departure point and destination <strong>JR station names</strong>, for example: OKAYAMA (岡山) -&gt; MATSUE (松江)</p>
  </li>
  <li>
    <p>Please note the JR station English names, <em>so <a href="/posts/travel-journals/kyushu-10-day-solo-travel-guide-explore-fukuoka-nagasaki-kumamoto-efficiently-d78e0b15a08a/">to go to Fukuoka, type Hakata</a></em> XD</p>
  </li>
  <li>
    <p>Step 1: Select “Traditional Chinese”</p>
  </li>
  <li>
    <p>Step 2: Directly insert a “JR Pass ticket”<br />
(If traveling with others who need seat reservations together, you will be asked to continue inserting them in Step 4.)</p>
  </li>
  <li>
    <p>Complete the reservation by following the steps shown in the video below:</p>
  </li>
</ul>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/oG8xrxEC6mk" title="JR Pass 預約指定席示範" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<blockquote>
  <p><em>The video shows the entire process of my reservation from Yasugi Station to Tottori Station as an example.</em></p>
</blockquote>

<blockquote>
  <p><em>(There was no one waiting at the ticket vending machines, so I dared to take out my phone and record the whole process)</em></p>
</blockquote>

<p><strong>Reservation Completed:</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*wdAemMFceaXQ6mZEPJfzWQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wdAemMFceaXQ6mZEPJfzWQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9E0CaLNkFZHyp6RfFbW8VQ.webp" alt="" loading="lazy" decoding="async" width="938" height="1226" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iMTIyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9E0CaLNkFZHyp6RfFbW8VQ.png" /></p>

<ul>
  <li>
    <p>Due to the schedule and tomorrow’s itinerary, I booked the early 07:05 train to Matsue to drop off my luggage at the hotel.</p>
  </li>
  <li>
    <p><strong>Actual station entry and exit still use the JR Pass (the one shown above)</strong> ⚠️⚠️⚠️, the reserved seat ticket only shows your car and seat number.</p>
  </li>
  <li>
    <p>No window seats left, can only take the aisle seat QQ</p>
  </li>
</ul>

<p><img src="/assets/aacd5f5cacd1/1*eWbl5HfHDsgs10xMbQByBA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*eWbl5HfHDsgs10xMbQByBA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*flqVSVO61pO1j1rk6sYICw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*flqVSVO61pO1j1rk6sYICw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cFmuaiXspF7Fk3FeKY_Vwg.webp" alt="" loading="lazy" decoding="async" width="952" height="1225" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTIiIGhlaWdodD0iMTIyNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cFmuaiXspF7Fk3FeKY_Vwg.png" /></p>

<p>At Okayama Station, I casually bought a fried beef cutlet bento and some snacks and drinks from a convenience store, then went to the hotel to rest. Okayama Station is under renovation, and I will return here on the last day for the trip back.</p>

<p><img src="/assets/aacd5f5cacd1/1*5OK4O6vRpvTbBHY1or45Ww.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5OK4O6vRpvTbBHY1or45Ww.jpeg" /></p>

<p>It takes five minutes to walk from Momotaro Shopping Street to the hotel.</p>

<h4 id="-1900-return-to-the-hotel-to-eat-and-rest">~= 19:00 Return to the hotel to eat and rest</h4>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/UfWKBa1FAto" title="東橫INN 岡山站東口" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/aacd5f5cacd1/1*1JJAxuSJ6NLnq4lRXZPajw.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1JJAxuSJ6NLnq4lRXZPajw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5aXGicaDQzX-9EREx0ooaw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5aXGicaDQzX-9EREx0ooaw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0XBFIrZROFSOuAr7psn7NA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0XBFIrZROFSOuAr7psn7NA.jpeg" /></p>

<p>The bento portion was a bit small, but the Okayama white peach peach wine was very tasty! Along with the nostalgic Japanese convenience store (FamilyMart) hot dog and fried chicken, tonight was very satisfying. Good night, Okayama! Fully prepared to wake up early tomorrow and start the Japan trip.</p>

<h4 id="okayama-korakuen-illumination"><a href="http://genso-teien.okayama.jp/" target="_blank">Okayama Korakuen Illumination</a></h4>

<p>The 2024 illumination at Okayama Korakuen Garden is from November 15th (Fri) to November 24th (Sun). It wasn’t on yet when I visited, but if it’s lit, it’s worth seeing as it’s very beautiful.</p>

<p><img src="/assets/aacd5f5cacd1/1*tVwuOQS4DytQGzfk3tn52g.webp" alt="Photos I took of the Sanyo lantern lighting event last year 2023" loading="lazy" decoding="async" width="1242" height="939" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjQyIiBoZWlnaHQ9IjkzOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*tVwuOQS4DytQGzfk3tn52g.png" /></p>

<p><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">Photos I took at the Sanyo Lantern Festival in 2023 last year</a></p>

<h3 id="day-2-1113-wednesday-shimane--izumo-taisha-sunset-at-lake-shinji">Day 2 (11/13 Wednesday) Shimane — Izumo Taisha, Sunset at Lake Shinji</h3>

<h4 id="0705-take-the-yakumo-やくも-no1-train-to-matsue">07:05 Take the Yakumo やくも No.1 train to Matsue</h4>

<p><img src="/assets/aacd5f5cacd1/1*-ppEnPrSVSls2IfkppB_pA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-ppEnPrSVSls2IfkppB_pA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NBhG889zmADfQDGI-7zlAQ.webp" alt="" loading="lazy" decoding="async" width="963" height="751" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjMiIGhlaWdodD0iNzUxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*NBhG889zmADfQDGI-7zlAQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*yb0D7yt38ygSmnGJyEjoEA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yb0D7yt38ygSmnGJyEjoEA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*168t2gezMC-ucwNzG3_m5g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*168t2gezMC-ucwNzG3_m5g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0DUCHWLSu7X5LG35oylmBw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0DUCHWLSu7X5LG35oylmBw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*m4JyAVEfNrUoaIt1Hk3Zeg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*m4JyAVEfNrUoaIt1Hk3Zeg.jpeg" /></p>

<p>The interior is quite comfortable, with luggage areas at the front and back for easy storage. The seats also have charging ports.</p>

<p><img src="/assets/aacd5f5cacd1/1*vg7v1Xz5RlcQ9KqnMwiZjw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vg7v1Xz5RlcQ9KqnMwiZjw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*M8ciQoiAy5GWHQoa34vo1w.webp" alt="" loading="lazy" decoding="async" width="879" height="994" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzkiIGhlaWdodD0iOTk0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*M8ciQoiAy5GWHQoa34vo1w.png" /></p>

<p>The total journey takes about 2 hours and 45 minutes. You can feel the route winding through mountains and ridges. Early in the morning, many areas are still covered in mist, giving a sense of passing through tunnels into a hidden paradise. Unfortunately, I didn’t get a window seat, so I could only enjoy the view from afar.</p>

<h4 id="0949-arrive-at-matsue">09:49 Arrive at Matsue</h4>

<p><img src="/assets/aacd5f5cacd1/1*RNcfgkoIlBNqAbZiJGXUeA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*RNcfgkoIlBNqAbZiJGXUeA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wuK_7kxYguDnQoYd5nNXtA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wuK_7kxYguDnQoYd5nNXtA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*GfeSKNvv30aqgIOUIcVAzw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GfeSKNvv30aqgIOUIcVAzw.jpeg" /></p>

<p>The weather in Matsue was perfect ☀️. After arriving at Matsue Station, I went straight to tonight’s hotel, <a href="https://www.station.matsue-urban.co.jp/en/" target="_blank"><strong>Matsue Urban Hotel</strong></a>, to drop off my luggage.</p>

<p><img src="/assets/aacd5f5cacd1/1*YCar9o50KKuyrx5blLVipg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YCar9o50KKuyrx5blLVipg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*58UjwwimiMZZS5fhCc2o7w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*58UjwwimiMZZS5fhCc2o7w.jpeg" /></p>

<p>After dropping off the luggage, return to Matsue Station around 10:00 and rebook the seat for Yakumo No. 3 departing from Matsue to Izumoshi at 10:48.</p>

<blockquote>
  <p><strong><em>I probably should have reserved a seat yesterday for Okayama to Matsue and also for Matsue to Izumo-shi</em></strong> <em>because I wanted to see the sunset in the afternoon, so I first left my luggage in Matsue. Otherwise, I would have gone straight to Izumo-shi without wasting time changing trains midway.</em></p>
</blockquote>

<blockquote>
  <p><em>You can also take the Ichibata Electric Railway Taisha Line to Izumo Taisha, which takes about the same time.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*Wvd6qczYLcEb8XywbGV_Vg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Wvd6qczYLcEb8XywbGV_Vg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*602_X70DAMAYJiEI2d024Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*602_X70DAMAYJiEI2d024Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Ji2S8d1orIkJB5Wyu3DCSw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Ji2S8d1orIkJB5Wyu3DCSw.jpeg" /></p>

<p>There was still some time, so I went to the convenience store to buy two rice balls, coffee, and fried chicken to take as lunch.</p>

<p><img src="/assets/aacd5f5cacd1/1*OkaENzl4tJ0nSMqMSznEmw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OkaENzl4tJ0nSMqMSznEmw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NNFURE5UfVdWOgkpYuAtrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*NNFURE5UfVdWOgkpYuAtrg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5-vTR2_TeMIlIiPggNON5w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5-vTR2_TeMIlIiPggNON5w.jpeg" /></p>

<p>Along the way, we passed by Lake Shinji, which is so large that I initially thought it was the sea, but it’s actually a lake.</p>

<blockquote>
  <p><em>You need to reserve a D seat by the window to directly see Lake Shinji; I reserved an A side seat facing the mountain, so no view QQ.</em></p>
</blockquote>

<h4 id="1112-arrive-at-izumo-city">11:12 Arrive at Izumo City</h4>

<p><img src="/assets/aacd5f5cacd1/1*IGzRRA0zChR7eosgVBM30g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IGzRRA0zChR7eosgVBM30g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_dWPzssuOTsPLhGlPeaxBg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_dWPzssuOTsPLhGlPeaxBg.jpeg" /></p>

<p>After arriving at Matsue Station, I ran all the way to catch the 11:25 Ichibata Electric Railway Kitamatsue Line heading to Kawato Station.</p>

<blockquote>
  <p><em>Ichibata Electric Railway does not accept transit cards for entry. You must first buy a ticket at the automatic ticket machine at the entrance (electronic payments accepted). If you’re unsure how to buy, just tell the staff you want to go to <code class="language-plaintext highlighter-rouge">Izumo Taisha</code>.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*zOWAvtPBUa4Q6p_O0UI_4A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*zOWAvtPBUa4Q6p_O0UI_4A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*hqp8E0znWzflcoF_wdXbYA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hqp8E0znWzflcoF_wdXbYA.jpeg" /></p>

<p>At Kawato Station, you need to transfer trains (follow everyone to the opposite platform) to take the Ichibata Electric Railway Taisha Line heading to Izumo Taisha-mae Station.</p>

<h4 id="arrive-at-izumo-taisha-around-12-pm">Arrive at Izumo Taisha around 12 PM</h4>

<p><img src="/assets/aacd5f5cacd1/1*DbbHj7dvLzIZ5yucQPqW2Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DbbHj7dvLzIZ5yucQPqW2Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*TNniVRe3T0Lx2h8ZEO6UoQ.webp" alt="" loading="lazy" decoding="async" width="902" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*TNniVRe3T0Lx2h8ZEO6UoQ.png" /></p>

<p>The approach to Ōkami Shrine is quiet and beautiful. If you have time, there are many small shops and delicious food to explore here.</p>

<blockquote>
  <p><em>Later, I learned that the four torii gates of Izumo are also famous. One of the large white torii gates can be found at the entrance by walking from Izumo Station’s Omotesando in the opposite direction of the shrine:</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*PsT0vPHhgZXJUJVbJzg9TA.webp" alt="Source: Shimane Tourism Official Website" loading="lazy" decoding="async" width="1268" height="823" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY4IiBoZWlnaHQ9IjgyMyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PsT0vPHhgZXJUJVbJzg9TA.png" /></p>

<p><a href="https://www.kankou-shimane.com/zh-tw/highlights/1615" target="_blank">Source: Shimane Tourism Official Website</a></p>

<blockquote>
  <p><em>For more details, please refer to the <a href="https://www.kankou-shimane.com/zh-tw/highlights/1615" target="_blank">Shimane Tourism Official Website</a></em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*l9SL74ldAxYcnEpLmIFgsA.webp" alt="" loading="lazy" decoding="async" width="1200" height="875" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*l9SL74ldAxYcnEpLmIFgsA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NmTX-LAuUYpyNxZH1iyOmA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NmTX-LAuUYpyNxZH1iyOmA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lL6Lshf934FqRMAHwuf8Sw.webp" alt="" loading="lazy" decoding="async" width="988" height="1029" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODgiIGhlaWdodD0iMTAyOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*lL6Lshf934FqRMAHwuf8Sw.png" /></p>

<p>Walk uphill to the entrance of Izumo Taisha. The visit on <code class="language-plaintext highlighter-rouge">2024/11/13</code> happened to be during the <strong>Kamiari Festival</strong> ( <code class="language-plaintext highlighter-rouge">2024 Reiwa 6 Nov 11~Nov 17</code> ), so there were many Japanese visitors coming to pray.</p>

<blockquote>
  <p><em>Every year in Japan’s lunar October (Gregorian November), eight million gods gather at Izumo for the “Divine Council” to discuss human affairs, such as fate arrangements and harmony with nature.</em></p>
</blockquote>

<blockquote>
  <p><em>Therefore, <strong>this month is called “Kannazuki” (the month without gods) in other regions of Japan, and there are usually no festivals</strong>.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Only in Izumo is this called the “Month of the Gods,” meaning that only the Izumo region has gods present.</em></strong></p>
</blockquote>

<blockquote>
  <p><em>The festival includes the “Kami Mukae Festival” to welcome the gods, the “Enmusubi Grand Festival” to pray for good relationships, and the “Kami Okuri Festival” to send off the gods.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>Izumo Taisha’s main deity: Ōkuninushi no Mikoto</em></strong></p>
</blockquote>

<p>What a coincidence! When I was planning the schedule, I didn’t intentionally choose this time. I only found out while checking the attraction information.</p>

<p><img src="/assets/aacd5f5cacd1/1*mHn3NX4zb31WmXFITQBOIQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*mHn3NX4zb31WmXFITQBOIQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*iehGyLgSPf4LgUzjhMjfzw.webp" alt="" loading="lazy" decoding="async" width="952" height="1285" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTIiIGhlaWdodD0iMTI4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*iehGyLgSPf4LgUzjhMjfzw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*IVJtJR2htbOjimqgqKvLrg.webp" alt="" loading="lazy" decoding="async" width="896" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTYiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IVJtJR2htbOjimqgqKvLrg.png" /></p>

<p>After passing through the entrance torii gate, it takes about another 5 minutes to reach the shrine.</p>

<h4 id="japans-largest-hinomaru-national-flag">Japan’s Largest “Hinomaru” National Flag</h4>

<p><img src="/assets/aacd5f5cacd1/1*WBerwVlV_I8PeYqj2KRX1w.webp" alt="" loading="lazy" decoding="async" width="959" height="1183" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTkiIGhlaWdodD0iMTE4MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*WBerwVlV_I8PeYqj2KRX1w.png" /></p>

<p>After entering the shrine, look to the left to see a tall flagpole with Japan’s largest Hinomaru flag (about 75 tatami mats, 137 square meters).</p>

<p>Walk towards this flagpole to reach the “Kaguraden,” where you can find Japan’s largest <a href="https://sanin-japan.com/zh-tw/special/territory-of-the-gods-yokozuna-and-izumo-taisha/" target="_blank">Shimenawa</a>.</p>

<p><img src="/assets/aacd5f5cacd1/1*x8zuQYtIXNILxGLkMjQbJg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1056" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*x8zuQYtIXNILxGLkMjQbJg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*3WjUHQV3lwqRTi3IegRb6w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*3WjUHQV3lwqRTi3IegRb6w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KeY6bAoW8DsPQf4BtMuhcA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KeY6bAoW8DsPQf4BtMuhcA.jpeg" /></p>

<p>Up close, it’s really huge, especially compared to the regular shimenawa ropes at other shrines—about 10 times bigger. (Length is 14 meters, weight is 5 tons, replaced every few years, and handmade)</p>

<blockquote>
  <p><em>Please remember that the largest one is at the “Kaguraden,” not at the main hall like I initially thought.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*KZnzwRAX5CdPvprYWwCzTg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KZnzwRAX5CdPvprYWwCzTg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*L3BUl_V1wCtc9hiRiiGlqw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*L3BUl_V1wCtc9hiRiiGlqw.jpeg" /></p>

<p>I walked around the area; there were quite a few people, and some spots required waiting in long lines to pray.</p>

<p><img src="/assets/aacd5f5cacd1/1*7iUkAuzNjt8d1wHbtiUWhQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7iUkAuzNjt8d1wHbtiUWhQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_DCfvI3CSAqMeC0hnZzCHA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_DCfvI3CSAqMeC0hnZzCHA.jpeg" /></p>

<blockquote>
  <p><em>The way to worship at Izumo Taisha is different from other shrines: ⚠️⚠️⚠️</em></p>
</blockquote>

<blockquote>
  <p><em>It is <a href="https://www.japan.travel/hk/spot/937/" target="_blank"><strong>to bow twice, clap four times, and bow once more at the end</strong></a>.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*uKMCGxlcrtLXp_wuz0Okqg.webp" alt="" loading="lazy" decoding="async" width="890" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uKMCGxlcrtLXp_wuz0Okqg.png" /></p>

<p>Bought a limited edition amulet for the Kamiari Festival. <strong>(Sold only during this week)</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*Zg626XNieV5uT7JLszzsXQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Zg626XNieV5uT7JLszzsXQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kGHi22HqIHPB3-vtdGXShg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kGHi22HqIHPB3-vtdGXShg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KUh8eckUM7jasbTZFl3nmw.webp" alt="" loading="lazy" decoding="async" width="815" height="1019" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MTUiIGhlaWdodD0iMTAxOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KUh8eckUM7jasbTZFl3nmw.png" /></p>

<p>After the visit, I got a stamp collection book and stamped the Izumo Taisha commemorative seal.</p>

<p><img src="/assets/aacd5f5cacd1/1*M2zO2XrTYc941f8q6n439A.webp" alt="Locations of the Five Major Shrines for the Izumo Kamiari and Tsukimari Festival." loading="lazy" decoding="async" width="1400" height="664" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjY2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*M2zO2XrTYc941f8q6n439A.png" /></p>

<p><a href="https://www.google.com/maps/d/u/0/edit?mid=1InF3hfsLjC7uW8T2J2ojcXJ_Ej3Eo0k&amp;usp=sharing" target="_blank">Locations of the Five Major Shrines for the Izumo Kamiari Festival Pilgrimage.</a></p>

<p>Due to limited time, I didn’t visit other shrines in Izumo. Without driving, it felt difficult to explore them all.</p>

<h4 id="-1245-walk-to-inasa-beach-inasa-no-hama">~= 12:45 Walk to Inasa Beach Inasa no Hama</h4>

<p><img src="/assets/aacd5f5cacd1/1*tp8izFV2pcWAkHrOxguusw.webp" alt="" loading="lazy" decoding="async" width="960" height="235" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMjM1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*tp8izFV2pcWAkHrOxguusw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*-hHa8tNAXHA8hkuI3ySAVA.webp" alt="" loading="lazy" decoding="async" width="953" height="1245" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTMiIGhlaWdodD0iMTI0NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-hHa8tNAXHA8hkuI3ySAVA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Yj37xFyP1gByfLvNdDTOtA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Yj37xFyP1gByfLvNdDTOtA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*RQmCSnIsPBVcJW1r9nSS3w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*RQmCSnIsPBVcJW1r9nSS3w.jpeg" /></p>

<p>The bus service is infrequent, so walking is the only option. It takes about 15 minutes, passing by Izumo Okuni’s grave along the way. Walking all the way leads to Inasa Beach.</p>

<blockquote>
  <p><strong><em>Inasahama Beach:</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*YRUdkflWxYERcA62-AQ2Eg.webp" alt="Source: Shimane Tourism Official Website" loading="lazy" decoding="async" width="1200" height="1015" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjEwMTUiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*YRUdkflWxYERcA62-AQ2Eg.png" /></p>

<p><a href="https://www.kankou-shimane.com/zh-tw/destinations/991" target="_blank">Source: Shimane Tourism Official Website</a></p>

<blockquote>
  <p><em>A site deeply rooted in Japanese mythology. The small island in the picture is called Bentenjima, which has a small shrine on it.</em></p>
</blockquote>

<blockquote>
  <p><a href="https://www.kankou-shimane.com/zh-tw/destinations/991" target="_blank"><em>One of Japan’s top 100 beaches, the “Sunset Sacred Site Izumo” landmark offers a beautiful view of the sun setting behind Benten Island into the sea. It is registered as a Japan Heritage site and hosts the Kami-mukae (Welcoming the Gods) ceremony, welcoming deities from all over Japan.</em></a></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*PIyVVCAMngDrgpErhjGRMw.webp" alt="" loading="lazy" decoding="async" width="958" height="1245" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NTgiIGhlaWdodD0iMTI0NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PIyVVCAMngDrgpErhjGRMw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lYRW_DUWWfI1aDJVkBtphA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*lYRW_DUWWfI1aDJVkBtphA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*tI4HMhJCNhlYlHKkFIJm9w.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*tI4HMhJCNhlYlHKkFIJm9w.jpeg" /></p>

<p>Due to limited time, I couldn’t stay until sunset and left after a brief visit.</p>

<blockquote>
  <p><strong><em>After returning to Taiwan and browsing Instagram, I found out you can get the <a href="https://www.japaholic.com/tw/article/detail/887878-%E4%BE%86%E5%B3%B6%E6%A0%B9%E7%B8%A3%E5%87%BA%E9%9B%B2%E5%A4%A7%E7%A4%BE%E6%B1%82%E5%BA%87%E8%AD%B7%E8%A6%81%E6%8C%96%E7%A0%82%EF%BC%9F%E7%A8%BB%E4%BD%90%E4%B9%8B%E6%BF%B1%E5%8F%96%E7%A5%9E%E7%A0%82%E6%B1%82%E5%BE%A1%E5%AE%88%E6%95%99%E5%AD%B8" target="_blank">Sand Amulet here</a> ：</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*9XZ8oaxmYdrNU3I68EgTnw.webp" alt="https://www.instagram.com/reel/CynStXPyZAX/" loading="lazy" decoding="async" width="696" height="620" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTYiIGhlaWdodD0iNjIwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*9XZ8oaxmYdrNU3I68EgTnw.png" /></p>

<p><a href="https://www.instagram.com/reel/CynStXPyZAX/?utm_source=ig_web_copy_link" target="_blank">https://www.instagram.com/reel/CynStXPyZAX/</a></p>

<p>Walk back to the Izumo Taisha bus stop to catch the bus back to JR Izumo Station.</p>

<p><img src="/assets/aacd5f5cacd1/1*AqsQ2S3q61tJaq7k6fgdIA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*AqsQ2S3q61tJaq7k6fgdIA.jpeg" /></p>

<p>You can see a continuous line of vehicles waiting to enter the parking lot for visiting Izumo Taisha.</p>

<p><img src="/assets/aacd5f5cacd1/1*kL0wZHJiMcqSCE3ChzTpNQ.webp" alt="" loading="lazy" decoding="async" width="759" height="415" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NTkiIGhlaWdodD0iNDE1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*kL0wZHJiMcqSCE3ChzTpNQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*8aaLagUno9PZTAYUCpX4-Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8aaLagUno9PZTAYUCpX4-Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*XrDT2aXpUPfF7NT5IEFFNA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XrDT2aXpUPfF7NT5IEFFNA.jpeg" /></p>

<p>The Izumo Taisha bus stop is located behind the restrooms, past the souvenir shop. It’s small, so at first I was worried about waiting at the wrong spot.</p>

<p><strong>Around 13:15, I had to wait for the 13:40 bus back to JR Izumo Station. Feeling very hungry, I went to the souvenir shop ahead and bought some snacks to eat by hand.</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*FYPk9dJKH166TgfMJbKinA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*FYPk9dJKH166TgfMJbKinA.jpeg" /></p>

<p>Bought a few chūnibyō stickers at the souvenir shop.</p>

<h4 id="1340-take-the-bus-back-to-jr-izumo-station"><strong>13:40 Take the bus back to JR Izumo Station</strong></h4>

<p><img src="/assets/aacd5f5cacd1/1*gvzJ1xXYnLyxNU_HAlFyNA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*gvzJ1xXYnLyxNU_HAlFyNA.jpeg" /></p>

<blockquote>
  <p><em>On the way back to Izumo, a Japanese person asked to borrow my power bank because his phone was completely dead. I took the chance to confirm by asking (and practicing the few Japanese phrases I remember), “<strong>このバスは JR 出雲市駅へ行きますか</strong>” (Does this bus go to JR Izumo Station?). He replied “<strong>はい</strong>” (yes), which is the only word I understood.</em></p>
</blockquote>

<h4 id="-1415-return-to-jr-izumoshi-station">~= 14:15 Return to JR Izumoshi Station</h4>

<p>(Rushed to see the sunset at Lake Shinji before it set. According to the information, <strong>the sun sets at 17:05 on that day</strong>.)</p>

<p><img src="/assets/aacd5f5cacd1/1*roLGAz0BE_7yrZ7010cJ1w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*roLGAz0BE_7yrZ7010cJ1w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JxPpTZphqlQpjXjOlEmRfA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JxPpTZphqlQpjXjOlEmRfA.jpeg" /></p>

<p>This time, I didn’t take the Yakumo train. I took the 14:54 local train to Yonago and followed Google Maps’ suggestion to get off at Nogi Station, then walked to the Shinjiko Sunset Viewing Spot.</p>

<p><img src="/assets/aacd5f5cacd1/1*6-c6bp49Is4qZ_IdbiMPCg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*6-c6bp49Is4qZ_IdbiMPCg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kXIIylCmu_fP_FA6IGQ3_g.webp" alt="" loading="lazy" decoding="async" width="947" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kXIIylCmu_fP_FA6IGQ3_g.png" /></p>

<blockquote>
  <p><em>If you take the left side (A) towards Matsue, you can see the lake view passing by Lake Shinji. Yes, I sat on the wrong side again, on the mountain side.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>At this small station, you need to press a button to open the train door. It’s my first time encountering this.</em></strong> <em>⚠️</em></p>
</blockquote>

<h4 id="1535-arrive-at-nogi-station">~=15:35 Arrive at Nogi Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*4CNzZ0RmgGHRVr4NJ899zA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*4CNzZ0RmgGHRVr4NJ899zA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5mXpmq_hk-UlrD_KF-x1xg.webp" alt="" loading="lazy" decoding="async" width="489" height="583" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0ODkiIGhlaWdodD0iNTgzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*5mXpmq_hk-UlrD_KF-x1xg.png" /></p>

<p>Nogi Station is very small, with no staff or ticket gates. <strong>If you have a JR Pass, just exit directly without leaving your JR Pass in the ticket box. ⚠️⚠️⚠️</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*hiBPok9dbi9XGQeAPRQ5NQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*hiBPok9dbi9XGQeAPRQ5NQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*E4Yd7PB_MJh1CyuwFJS3zw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*E4Yd7PB_MJh1CyuwFJS3zw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MAtnP-62r3MY0NSPPSy0yg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*MAtnP-62r3MY0NSPPSy0yg.jpeg" /></p>

<p>Walking from Nogi Station was a bit of a mistake; this area is quite deserted with few people around…</p>

<p>Fortunately, there was a 7–11 along the way where I bought a hot dog, fried chicken, and drinks to enjoy while waiting for the sunset.</p>

<h4 id="1600-sunset-at-lake-shinji">16:00 Sunset at Lake Shinji</h4>

<p><img src="/assets/aacd5f5cacd1/1*ptNqLKfhX0azm3b5HxfBZg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ptNqLKfhX0azm3b5HxfBZg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*krl-46lxVoh33r7TcwgpkQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*krl-46lxVoh33r7TcwgpkQ.jpeg" /></p>

<blockquote>
  <p><a href="https://www.kankou-shimane.com/zh-tw/destinations/985" target="_blank"><em>Shinji Lake, Japan’s seventh largest lake, Shinji Lake sunset, Japan’s top sunset.</em></a></p>
</blockquote>

<blockquote>
  <p><em>In the photo, the small island Yomejima Shrine has a small white torii gate.</em></p>
</blockquote>

<blockquote>
  <p><em>Previously mistyped as “穴道湖”.</em></p>
</blockquote>

<p>There is a heron.</p>

<p><img src="/assets/aacd5f5cacd1/1*ceThDvVZuO8MOR9OXn74ew.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ceThDvVZuO8MOR9OXn74ew.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*tz5i0B2jBta5vBsz-JMWHQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*tz5i0B2jBta5vBsz-JMWHQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1oDwRChVUUz-EVOv-HAWkw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1oDwRChVUUz-EVOv-HAWkw.jpeg" /></p>

<p>Going up leads to the Shimane Art Museum, which is livelier and has cafes; however, the viewpoint on the stair platform offers a better angle for the scenery.</p>

<h4 id="1645-sunset-in-the-west">16:45 Sunset in the West</h4>

<p><img src="/assets/aacd5f5cacd1/1*rmwJe1rnyjhQkJ9erx7q_w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*rmwJe1rnyjhQkJ9erx7q_w.jpeg" /></p>

<p>At first, I didn’t feel much, but as the sunset approached, I began to appreciate the charm of the first sunset. The heron earlier also blended perfectly with the scenery.</p>

<p><img src="/assets/aacd5f5cacd1/1*CdDLOmzP80ovaY94HDD3Wg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CdDLOmzP80ovaY94HDD3Wg.jpeg" /></p>

<p>After the sunset approaches the horizon, time visibly slips away with the naked eye; every minute and every second, the sun keeps descending.</p>

<p><img src="/assets/aacd5f5cacd1/1*pT7mh8XcC_Xt1_BO61SbfQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*pT7mh8XcC_Xt1_BO61SbfQ.jpeg" /></p>

<p>Quickly, by 17:01, the sun was already gone.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/K-xDmVjqG9g" title="20241113 宍道湖夕日" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<blockquote>
  <p><strong><em>Truly beautiful and healing.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*OG9M3KjHgChA0KY-MyBQPA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OG9M3KjHgChA0KY-MyBQPA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fzAcAYv8VNf8TjnrqwuqSg.webp" alt="" loading="lazy" decoding="async" width="923" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fzAcAYv8VNf8TjnrqwuqSg.png" /></p>

<p>Finally, take another look around the <a href="https://maps.app.goo.gl/LRQAMZNht9km1DFm6" target="_blank">Sunset Viewing Staircase Platform</a> and the people watching the sunset together.</p>

<p><img src="/assets/aacd5f5cacd1/1*sV6sLyOiVzxv0W_K2i8SOQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*sV6sLyOiVzxv0W_K2i8SOQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*VaZo5Kl6x-oLJOs9Id3pYg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*VaZo5Kl6x-oLJOs9Id3pYg.jpeg" /></p>

<p>After sunset, I turned around and waited for the bus to head back to Matsue.</p>

<h4 id="sunset-index">Sunset Index</h4>

<p><img src="/assets/aacd5f5cacd1/1*gWAukxe8ynZEUN6Yr3-WAg.webp" alt="Sunset index can be referenced on the Shimane tourism website" loading="lazy" decoding="async" width="1035" height="902" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDM1IiBoZWlnaHQ9IjkwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gWAukxe8ynZEUN6Yr3-WAg.png" /></p>

<p><a href="https://tw.visit-matsue.com/sunset" target="_blank">Sunset index can be referenced on the Shimane Tourism website</a></p>

<h4 id="shinjiko-lake-sightseeing-boat">Shinjiko Lake Sightseeing Boat</h4>

<p><img src="/assets/aacd5f5cacd1/1*owwW5fWHIBKb0PQU_gMqVg.webp" alt="&lt;https://hakuchougo.jp/timetable/&gt;" loading="lazy" decoding="async" width="1136" height="834" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTM2IiBoZWlnaHQ9IjgzNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*owwW5fWHIBKb0PQU_gMqVg.png" /></p>

<p><a href="https://hakuchougo.jp/timetable/" target="_blank">https://hakuchougo.jp/timetable/</a></p>

<p><a href="https://www.kankou-shimane.com/zh-tw/destinations/1086" target="_blank">There are sightseeing boats on Lake Shinji</a>, which you can take near sunset to enjoy the view from the boat. I would like to try it next time.</p>

<p><img src="/assets/aacd5f5cacd1/1*RGoFqjg8YVZwTzpINu4PCQ.webp" alt="" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*RGoFqjg8YVZwTzpINu4PCQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*nt2iPompX1qAecOKd_C7hA.webp" alt="" loading="lazy" decoding="async" width="1179" height="2556" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc5IiBoZWlnaHQ9IjI1NTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*nt2iPompX1qAecOKd_C7hA.jpeg" /></p>

<blockquote>
  <p><em>You can directly use the JR Pass included <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>San’in-Sanyo Area Pass 3-Day Ticket</strong></a> <strong>for free rides, saving 1,800 yen.</strong></em></p>
</blockquote>

<h4 id="1800-return-to-matsue-station-and-hotel">18:00 Return to Matsue Station and Hotel</h4>

<h4 id="buy-some-souvenirs-at-the-gift-shop-on-the-first-floor-of-matsue-station">Buy some souvenirs at the gift shop on the first floor of Matsue Station</h4>

<p>I thought since San’in is quite unique, I’ll buy souvenirs here.</p>

<p><img src="/assets/aacd5f5cacd1/1*gEdRChjcIYrXT_A8f6viyw.webp" alt="" loading="lazy" decoding="async" width="1144" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ0IiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gEdRChjcIYrXT_A8f6viyw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fQj5GAdIRZ8uKKofUerGSw.webp" alt="" loading="lazy" decoding="async" width="1400" height="995" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fQj5GAdIRZ8uKKofUerGSw.png" /></p>

<p>What’s special is the beef bone broth ramen, usually it’s pork bone. I bought a box to try back in Taiwan.</p>

<p><img src="/assets/aacd5f5cacd1/1*n7-mvNNxKfjHJ7eNusg_Dg.webp" alt="" loading="lazy" decoding="async" width="1200" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*n7-mvNNxKfjHJ7eNusg_Dg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*vqDE5sO1mYFkzrYP2WNsZg.webp" alt="" loading="lazy" decoding="async" width="1400" height="937" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjkzNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vqDE5sO1mYFkzrYP2WNsZg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*P175dGuAEKQkW-rvGbIIeg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*P175dGuAEKQkW-rvGbIIeg.jpeg" /></p>

<p>Bought two large boxes of cookies (good for sharing with colleagues) to distribute. The sweet ones are ordinary but serve as a souvenir from visiting Izumo Taisha. The savory seafood pancake cookies, I think, are very tasty!</p>

<h4 id="return-to-the-hotel">Return to the hotel</h4>

<p><img src="/assets/aacd5f5cacd1/1*kmBiT9EX6g-d743wStYgxQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*kmBiT9EX6g-d743wStYgxQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9NE5tv23D6k51TvDFNEypw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9NE5tv23D6k51TvDFNEypw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MAHLZoAXHY9TYQO4fO8OEw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MAHLZoAXHY9TYQO4fO8OEw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*VkEob5Q4_DKkVV14e67bQA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*VkEob5Q4_DKkVV14e67bQA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*e3EL2ltwD0nNVb2TbJzonQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*e3EL2ltwD0nNVb2TbJzonQ.jpeg" /></p>

<p>There is no elevator in this building. The booking is for Building 2, which can be reached by crossing the skybridge from the main building lobby. On the 2nd floor of Building 2, there is a lounge with a microwave, laundry room, and vending machines.</p>

<blockquote>
  <p><em>The experience at Building 2 didn’t feel very safe. The elevator has no access control, and <a href="https://www.cubic-room.jp/en/" target="_blank">the 1st and 2nd floors are capsule hotels</a>, making entry and exit more complicated. It’s also quite far from the main building lobby, so after checking in, I used my suitcase to block the door, fearing someone strange might break in.</em></p>
</blockquote>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/NLHi7arAioA" title="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p><img src="/assets/aacd5f5cacd1/1*jXAp31kbI-h2r6EC9p3GTg.webp" alt="" loading="lazy" decoding="async" width="1200" height="920" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkyMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*jXAp31kbI-h2r6EC9p3GTg.png" /></p>

<p>In the lobby, you can get free face masks, coffee, and tea bags.</p>

<h4 id="finding-food">Finding Food</h4>

<p><img src="/assets/aacd5f5cacd1/1*bXI0YIH7eaO7vZj8MaAfDA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bXI0YIH7eaO7vZj8MaAfDA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*O6V7aJeRtLcN5NWwYYtEUw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*O6V7aJeRtLcN5NWwYYtEUw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*rNzPDu9gW2s89oW0oqxWSA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*rNzPDu9gW2s89oW0oqxWSA.jpeg" /></p>

<p>After checking in, I went outside to find food. It was quite cold, dark, and quiet outside, so I quickly grabbed a bento from a restaurant near the station and bought some snacks and drinks at a convenience store before heading back to the hotel.</p>

<p><img src="/assets/aacd5f5cacd1/1*FaaPod-j1hdncbRKhnNUOQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*FaaPod-j1hdncbRKhnNUOQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*UAYwYj0cNQZJsC99V7wq8A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UAYwYj0cNQZJsC99V7wq8A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Oubxpe7aZ2GPBXI92NEMuQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Oubxpe7aZ2GPBXI92NEMuQ.jpeg" /></p>

<p>The beef tongue bento was delicious, and the peach sparkling wine bought at the convenience store was also tasty.</p>

<blockquote>
  <p><em>Good night, Matsue.</em></p>
</blockquote>

<p><strong>Also bought almost the same next-day breakfast combo every day…</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*YTezWlFUnTYd6qv1J-yjTw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YTezWlFUnTYd6qv1J-yjTw.jpeg" /></p>

<p>This pulp juice from FamilyMart is a must-drink every time I visit Japan!</p>

<h4 id="matsue-shinjiko-24-hour-live-stream">Matsue Shinjiko 24-Hour Live Stream:</h4>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/QYIjp0flsdQ" title="【LIVE】松江・宍道湖ライブカメラ" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>A YouTube channel I found by chance where you can see the weather and scenery of Lake Shinji.</p>

<h3 id="day-3-1114-thursday-matsue-horikawa-sightseeing-boat-matsue-castle-adachi-museum-of-art">Day 3 (11/14 Thursday) Matsue Horikawa Sightseeing Boat, Matsue Castle, Adachi Museum of Art</h3>

<p>In the morning, first leave the luggage at the hotel, then take the bus from Matsue Bus Station to the Matsue Castle boat tour.</p>

<p><img src="/assets/aacd5f5cacd1/1*6Urhj-Bh4CXFgweozc99fQ.webp" alt="" loading="lazy" decoding="async" width="928" height="1183" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iMTE4MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6Urhj-Bh4CXFgweozc99fQ.png" /></p>

<p>Google Maps doesn’t show the platforms; you have to check on-site. Trains at platform 2 go to Matsue Castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*xXKGKy95X00KjnN50FBzvw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*xXKGKy95X00KjnN50FBzvw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*gdwD77FXsNcNeaKz7NlXBQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gdwD77FXsNcNeaKz7NlXBQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4i5NFX226vWGW4yTEqOqIw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4i5NFX226vWGW4yTEqOqIw.jpeg" /></p>

<p>Get off in front of the Prefectural Civic Hall and walk towards Matsue Castle. You can see the “Horikawa Sightseeing Boat” dock near the parking lot.</p>

<h4 id="activate-the-jr-pass-sanin-sanyo-area-3-day-pass-free-ride-on-horikawa-sightseeing-boat">Activate the JR Pass <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>San’in-Sanyo Area 3-Day Pass</strong></a> <strong>Free ride</strong> on Horikawa sightseeing boat</h4>

<p><img src="/assets/aacd5f5cacd1/1*NKAYef5qS_1KVVW7sx2BKg.webp" alt="" loading="lazy" decoding="async" width="554" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTQiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NKAYef5qS_1KVVW7sx2BKg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ksm-qt0AJ9qWES0k9HQ8-g.webp" alt="" loading="lazy" decoding="async" width="843" height="828" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NDMiIGhlaWdodD0iODI4Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*ksm-qt0AJ9qWES0k9HQ8-g.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*dBYTcpDfIH02XoGUtviyTQ.webp" alt="" loading="lazy" decoding="async" width="583" height="1261" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1ODMiIGhlaWdodD0iMTI2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dBYTcpDfIH02XoGUtviyTQ.png" /></p>

<ol>
  <li>
    <p>Download <a href="https://apps.apple.com/tw/app/discover-another-japan/id1530307213" target="_blank">Discover Another Japan App</a></p>
  </li>
  <li>
    <p>Copy the serial number from the certificate email.</p>
  </li>
  <li>
    <p>Open the app, select “Input,” paste the code, and tap “Activate.”</p>
  </li>
</ol>

<p><img src="/assets/aacd5f5cacd1/1*bXVG4mS9rEj5I2nV8hUpqQ.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bXVG4mS9rEj5I2nV8hUpqQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6s5mwpiKNIIC3bNIw48qjw.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6s5mwpiKNIIC3bNIw48qjw.jpeg" /></p>

<p>Switch to “Use” to fill out the health questionnaire. After completing it, a QR Code camera will appear; scan the QR Code provided by the store and show them the result to complete the redemption.</p>

<p>You can also click “Target Content” to view redeemable items.</p>

<blockquote>
  <p><strong><em>The actual calculation time starts from the first time the target is redeemed</em></strong> <em>, not from when the code is activated (I entered it on 11/13).</em></p>
</blockquote>

<p><strong>Here are some popular locations for your reference:</strong></p>

<ul>
  <li>
    <p>Shinji Lake sightseeing boat, original price 1,800 yen</p>
  </li>
  <li>
    <p>Matsue Castle boat tour, original price 1,600 yen</p>
  </li>
  <li>
    <p>Adachi Museum of Art, original price 2,300 yen</p>
  </li>
  <li>
    <p>Matsue Castle, original price 680 yen</p>
  </li>
  <li>
    <p>Tottori Sand Dunes Museum, original price 800 yen</p>
  </li>
  <li>
    <p>Tsutenkaku Tower, original price 800 yen</p>
  </li>
  <li>
    <p>Okayama Korakuen, original price 410 yen</p>
  </li>
</ul>

<p>It’s really a great deal. Just adding up the above savings amounts to 8,390 yen, which is already half the price of the entire JR Pass.</p>

<h4 id="0915-take-the-matsue-sightseeing-boat">09:15 Take the Matsue Sightseeing Boat</h4>

<p><img src="/assets/aacd5f5cacd1/1*yTUoyuDBmxnZi7Z-LW5scg.webp" alt="" loading="lazy" decoding="async" width="943" height="1191" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDMiIGhlaWdodD0iMTE5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yTUoyuDBmxnZi7Z-LW5scg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*D0KiYKloofkru6tcupBTWw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*D0KiYKloofkru6tcupBTWw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0xbwCSI0IA7-pvoRwIhPVQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0xbwCSI0IA7-pvoRwIhPVQ.png" /></p>

<p>Use the <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank">3-Day Pass</a> for free rides.</p>

<ul>
  <li>
    <p>This year is warmer, so the heated kotatsu boat has not been set up yet.</p>
  </li>
  <li>
    <p>There are four bridge points where the boat deck needs to be lowered, so be careful not to pinch your hands.</p>
  </li>
  <li>
    <p>The entire trip takes about 50 minutes.</p>
  </li>
  <li>
    <p><strong>Remove Shoes Before Boarding</strong> ⚠️</p>
  </li>
  <li>
    <p><strong>Only the row on the right side facing the boatman can directly capture the view near Matsue Castle. The farther from the boatman, the closer to the bow (= the first row right after boarding)</strong> ⚠️ As shown in the picture below</p>
  </li>
</ul>

<p><img src="/assets/aacd5f5cacd1/1*NNpCqdpowVo3qcGUB2MtIA.webp" alt="" loading="lazy" decoding="async" width="963" height="1194" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjMiIGhlaWdodD0iMTE5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NNpCqdpowVo3qcGUB2MtIA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mycuhLZJdCqQCLjmdKBVuA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*mycuhLZJdCqQCLjmdKBVuA.jpeg" /></p>

<p>Boarding the ship.</p>

<p><img src="/assets/aacd5f5cacd1/1*OaajDPfbGMPtNGE45b0D7w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*OaajDPfbGMPtNGE45b0D7w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*pnqFPC8vK3sPGShIQY8vwg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pnqFPC8vK3sPGShIQY8vwg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_zTAi1v6rP1ObrmmGAyivw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_zTAi1v6rP1ObrmmGAyivw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*LqE3q-z47EBROFnfkCzONA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*LqE3q-z47EBROFnfkCzONA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7hUFpDj5m7TYkRzY_QhrOA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7hUFpDj5m7TYkRzY_QhrOA.jpeg" /></p>

<p>Maybe it’s because the maple leaf season hasn’t started yet, so there were few people. There were only four passengers on the boat, making it very spacious.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/1dP7cQcqkW8" title="松江堀川遊覽船過橋" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>There are four bridges where the boat lowers its height, so you need to bend down slightly to pass through.</p>

<p><img src="/assets/aacd5f5cacd1/1*f71WOF93FIEGnY_AFJ94OQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*f71WOF93FIEGnY_AFJ94OQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*CgP34NWi1KsKk8je60g2Aw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CgP34NWi1KsKk8je60g2Aw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mqX7jwzT8WnOhg9hwZKZPA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mqX7jwzT8WnOhg9hwZKZPA.jpeg" /></p>

<p>Cruising along the river with the breeze was very relaxing. The boatman also introduced the history of the surrounding sights along the way (although I only understood “hidari” for left and “migi” for right…).</p>

<p><img src="/assets/aacd5f5cacd1/1*vY1xPWmFrFq7m4ErBFYRAQ.webp" alt="&lt;https://youtube.com/shorts/KA9xKZV16bk&gt;" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*vY1xPWmFrFq7m4ErBFYRAQ.jpeg" /></p>

<p><a href="https://youtube.com/shorts/KA9xKZV16bk" target="_blank">https://youtube.com/shorts/KA9xKZV16bk</a></p>

<p>Near the end, close to the return point, there is a photo spot for Matsue Castle where you can capture a distant view of the castle.</p>

<p>The boat tour ended around 10:00.</p>

<h4 id="1015-matsue-castle">10:15 Matsue Castle</h4>

<p><img src="/assets/aacd5f5cacd1/1*ceaDL6BSPF0gB-VCXOw3fA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ceaDL6BSPF0gB-VCXOw3fA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KTDVdtsyThXmXZC0hlXqPQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KTDVdtsyThXmXZC0hlXqPQ.jpeg" /></p>

<p>After returning by boat and walking back, the entrance to Matsue Castle is on the other side. Walking uphill will lead you to Matsue Castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*1zbsEA6XAM1EhL6AoGwyng.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*1zbsEA6XAM1EhL6AoGwyng.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*PNjiGAlCMJvOWEgPCi6Vrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*PNjiGAlCMJvOWEgPCi6Vrg.jpeg" /></p>

<p>Passing by Matsue Shrine, we arrived at Matsue Castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*rrtm3CezY7W1ZWPIbj5p-A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*rrtm3CezY7W1ZWPIbj5p-A.jpeg" /></p>

<p>Due to limited time, I decided to skip going up and continued on to Adachi Museum of Art.</p>

<p><img src="/assets/aacd5f5cacd1/1*9JAHlSW6QmLNzAWDU0-THA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9JAHlSW6QmLNzAWDU0-THA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*XmGdiTwZJk7pWY95CCZkrQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*XmGdiTwZJk7pWY95CCZkrQ.jpeg" /></p>

<p>Return by waiting for the bus at the Kenmin Hall bus stop in the opposite direction from when you arrived.</p>

<h4 id="1030-return-to-the-hotel-to-pick-up-luggage">~=10:30 Return to the hotel to pick up luggage</h4>

<p><img src="/assets/aacd5f5cacd1/1*z5o-twtGfhsaje84wIlhlA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*z5o-twtGfhsaje84wIlhlA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cG2vaqw6VB1jXg2Eg78Jnw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cG2vaqw6VB1jXg2Eg78Jnw.jpeg" /></p>

<p>Around 10:30, return to Matsue Station and pick up luggage at the hotel.</p>

<blockquote>
  <p><strong><em>While waiting at the traffic light here, I was almost hit by a bike with brake failure. Luckily, I was standing further back and wasn’t hit. Always stay cautious when you’re out and about.</em></strong> <em>⚠️⚠️⚠️</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*J13Zlai29b-V7OR6-NSDuw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*J13Zlai29b-V7OR6-NSDuw.jpeg" /></p>

<p>After arriving at the station, also reserve a seat on Yakumo 14 reserved seat bound for Yasugi (Adachi Museum of Art).</p>

<h4 id="1103-head-to-yasugi">11:03 Head to Yasugi</h4>

<p><img src="/assets/aacd5f5cacd1/1*-eYBNFeXAXeaT3i73YPeRg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*-eYBNFeXAXeaT3i73YPeRg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5JTFZNrBFGhSKo0M54Rwrg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5JTFZNrBFGhSKo0M54Rwrg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*49TFnjK8AnLDrYCA-naQtw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*49TFnjK8AnLDrYCA-naQtw.jpeg" /></p>

<p>The train departs at 11:03, so I bought some food near the station to eat on the train.</p>

<blockquote>
  <p><strong><em>Goodbye Matsue.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*g-Sewe5VgTJrqioiNt32mw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*g-Sewe5VgTJrqioiNt32mw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*gCedzvZrVNY7_hfPkBBtEg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gCedzvZrVNY7_hfPkBBtEg.jpeg" /></p>

<p>The pancakes and the savory grilled chicken were both very delicious.</p>

<h4 id="1112-arrived-at-yasugi-station">11:12 Arrived at Yasugi Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*6VLLQilNbIvambZZ2FqlTg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*6VLLQilNbIvambZZ2FqlTg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fzfeXO1wERdPoGfoSeImew.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*fzfeXO1wERdPoGfoSeImew.jpeg" /></p>

<p>Don’t rush to exit at Yasugi Station. Find the tourist information center to store large luggage.<br />
(There are coin lockers outside the station, but they are mostly full.)</p>

<h4 id="1130-take-the-shuttle-bus-to-adachi-museum-of-art">11:30 Take the shuttle bus to Adachi Museum of Art</h4>

<p><img src="/assets/aacd5f5cacd1/1*BHs4S2OQTB2L5GC5beFFCQ.webp" alt="" loading="lazy" decoding="async" width="894" height="942" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTQiIGhlaWdodD0iOTQyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*BHs4S2OQTB2L5GC5beFFCQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Ng6cROhDXC8dFa-jX9Rr9g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Ng6cROhDXC8dFa-jX9Rr9g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ZGpQ1IE2Rs3sdSVHF4FKKw.webp" alt="Please refer to the official website for the latest timetable" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ZGpQ1IE2Rs3sdSVHF4FKKw.jpeg" /></p>

<p><a href="https://www.adachi-museum.or.jp/en/shuttle-bus" target="_blank">For the latest timetable, please refer to the official website</a></p>

<p>After placing the luggage, I took the shuttle bus to Adachi Museum of Art (free, board in order while queuing). There were not many people in the morning.</p>

<p>Walk through the rural alleys for about 20 minutes to reach the Adachi Museum of Art.</p>

<p><img src="/assets/aacd5f5cacd1/1*yWU0ZbZ-BplOe9PpE7gLKw.webp" alt="" loading="lazy" decoding="async" width="931" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yWU0ZbZ-BplOe9PpE7gLKw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*EzIv3NLDJE4hVA0BgtIQoQ.webp" alt="" loading="lazy" decoding="async" width="947" height="1217" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDciIGhlaWdodD0iMTIxNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EzIv3NLDJE4hVA0BgtIQoQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*dKhsS-vXIfeBwam-mmARTQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dKhsS-vXIfeBwam-mmARTQ.jpeg" /></p>

<p>After getting off, walk straight ahead to reach the entrance of Adachi Museum of Art.</p>

<p><img src="/assets/aacd5f5cacd1/1*pNf0-7iKNi1tn50chXc1-w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pNf0-7iKNi1tn50chXc1-w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ROq_9K0FNF31iLA7f-A5Ng.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ROq_9K0FNF31iLA7f-A5Ng.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Q_wkpK8esb5iKNWHxOf4vg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Q_wkpK8esb5iKNWHxOf4vg.jpeg" /></p>

<p>Use the <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank">3-day pass</a> for free admission.</p>

<p>The first step is to get a “shuttle bus ticket” matching your schedule at the front desk near the entrance, one ticket per person. When the time comes, return to the drop-off spot and use the ticket to take the shuttle back to Yasurai Station.</p>

<p><img src="/assets/aacd5f5cacd1/1*mxWMQacl1bNSfdRo8X9JQA.webp" alt="" loading="lazy" decoding="async" width="923" height="1185" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjMiIGhlaWdodD0iMTE4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mxWMQacl1bNSfdRo8X9JQA.png" /></p>

<p>My itinerary plans to take the JR departing from Yasugi to Tottori at 14:46. Please note <strong>there are not many trains heading to Tottori⚠️.</strong></p>

<blockquote>
  <p><strong><em>Very important! Very important! Very important! You must get this, or we can’t guarantee you will have a seat on the bus. ⚠️⚠️⚠️</em></strong></p>
</blockquote>

<blockquote>
  <p><em>I was the one who didn’t know I needed to get the ticket. I originally planned to take the 13:00 train but realized I had to get the ticket first, so I went back to get it and took the 13:35 train to Anlai instead.</em></p>
</blockquote>

<blockquote>
  <p><em>Luckily, I planned my time quite flexibly.</em></p>
</blockquote>

<h4 id="adachi-museum-of-art--japans-top-garden">Adachi Museum of Art — Japan’s Top Garden</h4>

<p><img src="/assets/aacd5f5cacd1/1*P4YjMlS0gulw8Z9Jr4a2mA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*P4YjMlS0gulw8Z9Jr4a2mA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cIY8_Z-eP8OSvK-NBQ8Rtw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cIY8_Z-eP8OSvK-NBQ8Rtw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*bBghMwu_IRFmgbT7fW3EXw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bBghMwu_IRFmgbT7fW3EXw.jpeg" /></p>

<p>You can quietly sit here and enjoy the garden view.</p>

<p><img src="/assets/aacd5f5cacd1/1*l_vmr2dgh14H2GQ3LV3t4Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*l_vmr2dgh14H2GQ3LV3t4Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*U6yP0ur5hbX5RPLBCUJhOA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*U6yP0ur5hbX5RPLBCUJhOA.jpeg" /></p>

<p>Besides the gardens, the core of Adachi Museum of Art lies in its extensive art collection, where photography is prohibited. Follow the designated path to view the artworks along with the gardens.</p>

<p><img src="/assets/aacd5f5cacd1/1*wBCdJ9HmdU55eywoII8eZQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wBCdJ9HmdU55eywoII8eZQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ftl17PV6R9Xvj6u_W2uTeg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ftl17PV6R9Xvj6u_W2uTeg.jpeg" /></p>

<p>You can see part of the courtyard view outside, which shows that a lot of time and effort have been spent maintaining this landscaping.</p>

<p><img src="/assets/aacd5f5cacd1/1*jNTMLjG2TDFOdTE4Sl72DA.webp" alt="" loading="lazy" decoding="async" width="945" height="1234" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDUiIGhlaWdodD0iMTIzNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*jNTMLjG2TDFOdTE4Sl72DA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*tcvXnD0yZU2J36q-7DFPPw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*tcvXnD0yZU2J36q-7DFPPw.jpeg" /></p>

<p>There are two cafes inside, offering not only desserts and coffee but also savory dishes. Feel free to stop by, rest, have a bite, and enjoy the view.</p>

<blockquote>
  <p><em>Since I was traveling alone, I didn’t make specific stops for meals.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*r-y6_oqYcCr7gHBkO2htAw.webp" alt="" loading="lazy" decoding="async" width="1400" height="980" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*r-y6_oqYcCr7gHBkO2htAw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*P2G-cBOftGDBhXjRe93JmA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1009" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*P2G-cBOftGDBhXjRe93JmA.png" /></p>

<p>Bought a cardboard replica of the artwork <a href="https://artsandculture.google.com/asset/mt-fuji/gQGjCiwQOJy4bg?hl=zh-TW" target="_blank">Yokoyama Taikan “Mt.Fuji”</a> seen in the collection as a souvenir at the gift shop.</p>

<h4 id="1230-strolling-around-the-shopping-arcade-outside">12:30 Strolling around the shopping arcade outside</h4>

<p>There is a small shopping street outside. Since it was still early, I wandered around here first.</p>

<p><img src="/assets/aacd5f5cacd1/1*F0w3N5TCBhJnHxDTPKvIlw.webp" alt="" loading="lazy" decoding="async" width="942" height="1182" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDIiIGhlaWdodD0iMTE4MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*F0w3N5TCBhJnHxDTPKvIlw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*vUCovPlzvYxsnyfTqahbdg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vUCovPlzvYxsnyfTqahbdg.jpeg" /></p>

<blockquote>
  <p><em>This Shimane wagyu rice burger was <strong>quite bad</strong>. The microwaved rice burger was sticky, and the wagyu in the middle I ate was still cold and not fully heated…</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*04ZXVwciPcQrHZaCi_mP_A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*04ZXVwciPcQrHZaCi_mP_A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*IIi5iKRgO4Z3X-WVGXSAlg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IIi5iKRgO4Z3X-WVGXSAlg.jpeg" /></p>

<p>As mentioned earlier, I didn’t know I needed to get a numbered ticket at first, so I just waited for the bus. It wasn’t until 1:00 PM that I realized I couldn’t board and then went to get a numbered ticket for the 1:35 PM bus.</p>

<p><img src="/assets/aacd5f5cacd1/1*_qEUfX9ZPhw4e7GoCkd1mA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_qEUfX9ZPhw4e7GoCkd1mA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*CYcgaIGY63OyZ8AVqxp6zQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CYcgaIGY63OyZ8AVqxp6zQ.jpeg" /></p>

<p>Get the full ticket and re-enter (re-entry allowed), then sit in the initial indoor viewing area to relax and watch the scenery while waiting for the bus.</p>

<h4 id="1355-return-to-yasugi-station">13:55 Return to Yasugi Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*32LdARFSPE8UZX5Ncpp__Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*32LdARFSPE8UZX5Ncpp__Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wiaNCbcWfMAr83dH_VyhVg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wiaNCbcWfMAr83dH_VyhVg.jpeg" /></p>

<p>On the way back to Anlai Station, I saw very vibrant red maple leaves.</p>

<p><img src="/assets/aacd5f5cacd1/1*hmDP79nt4683WCrugG3F6A.webp" alt="" loading="lazy" decoding="async" width="1200" height="848" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hmDP79nt4683WCrugG3F6A.png" /></p>

<p>At this time, there are actually more people visiting the Adachi Museum of Art.</p>

<p><img src="/assets/aacd5f5cacd1/1*pNI1d_ksSnexO0WKcSSLww.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pNI1d_ksSnexO0WKcSSLww.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9O3RZpisQ42q9L5HBVfK2w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9O3RZpisQ42q9L5HBVfK2w.jpeg" /></p>

<p>This station was not crowded and it was still early, so I took the opportunity to record a short video demonstrating how to reserve a seat with the JR Pass. <a href="https://www.youtube.com/shorts/oG8xrxEC6mk" target="_blank">Watch here</a>.</p>

<p><img src="/assets/aacd5f5cacd1/1*09Yg8-nz6BzwsrB4MboUmg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*09Yg8-nz6BzwsrB4MboUmg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*hgL7isSy3JI2ynErDWL-bw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hgL7isSy3JI2ynErDWL-bw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xtWvh1YCoprT8eqqP_uxoQ.webp" alt="Shimane Cat" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*xtWvh1YCoprT8eqqP_uxoQ.jpeg" /></p>

<p><a href="https://www.kankou-shimane.com/shimanekko/zh-TW/" target="_blank">Shimane Cat</a></p>

<p>Wandering around the tourist information center to pass the time.</p>

<p><img src="/assets/aacd5f5cacd1/1*90geto8qdEZ4we55uwzlmw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*90geto8qdEZ4we55uwzlmw.jpeg" /></p>

<p>The Kiyomizu Temple in Yasugi also feels like a hidden spot for autumn leaves viewing.</p>

<p><img src="/assets/aacd5f5cacd1/1*ekgTftsMdymYawBX5WrRZw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ekgTftsMdymYawBX5WrRZw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9UPTdrAVmNFfUTae3Z4jbQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9UPTdrAVmNFfUTae3Z4jbQ.jpeg" /></p>

<h4 id="1446-take-the-limited-express-train-to-tottori">14:46 Take the Limited Express Train to Tottori</h4>

<p><img src="/assets/aacd5f5cacd1/1*9xXAaj9MkAymGU2AgeaMKg.webp" alt="" loading="lazy" decoding="async" width="687" height="1177" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODciIGhlaWdodD0iMTE3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9xXAaj9MkAymGU2AgeaMKg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4QAm8AK5zXIGMVeIjRc8sg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4QAm8AK5zXIGMVeIjRc8sg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*2gCmHxDsPcbPLcngamFmpg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*2gCmHxDsPcbPLcngamFmpg.jpeg" /></p>

<p>The train model is very old and has only two cars (one non-reserved seat car and one reserved seat car). My 26-inch suitcase couldn’t fit in the overhead luggage rack (newer JR trains can, but this model’s rack is too small). I forgot that I could walk to the last row and place it behind the last seat, so I had to squeeze it into my own seat area.</p>

<p>Luckily, there was no one sitting next to me the whole way. The non-reserved cars looked more crowded, while the reserved seats were only about 60% full.</p>

<blockquote>
  <p><em>Due to the itinerary, I didn’t specifically visit GeGeGe no Kitaro Station (Sakaiminato Station) and Conan Station (Yura Station).</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*STYxvjU_ogrZ2fDlL9Y6ng.webp" alt="Source" loading="lazy" decoding="async" width="894" height="667" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTQiIGhlaWdodD0iNjY3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*STYxvjU_ogrZ2fDlL9Y6ng.png" /></p>

<p><a href="https://www.pref.tottori.lg.jp/289054.htm" target="_blank">Source</a></p>

<p><img src="/assets/aacd5f5cacd1/1*hZSjEq5-ijzcPGGte6C40g.webp" alt="Source" loading="lazy" decoding="async" width="875" height="595" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzUiIGhlaWdodD0iNTk1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*hZSjEq5-ijzcPGGte6C40g.png" /></p>

<p><a href="https://www.pref.tottori.lg.jp/289055.htm" target="_blank">Source</a></p>

<blockquote>
  <p><em>Interested friends can plan to visit more.</em></p>
</blockquote>

<h4 id="1558-arrive-at-jr-tottori-station">15:58 Arrive at JR Tottori Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*oPupIuWiEBmo4l8oYjdXEg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oPupIuWiEBmo4l8oYjdXEg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BkBOsOOK3AJtMY4Fh4nWlw.webp" alt="" loading="lazy" decoding="async" width="946" height="1195" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDYiIGhlaWdodD0iMTE5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BkBOsOOK3AJtMY4Fh4nWlw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kGq-zyfiHwjuwxWFtSTlnA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*kGq-zyfiHwjuwxWFtSTlnA.jpeg" /></p>

<p>Head to tonight’s hotel <a href="https://www.toyoko-inn.com/china/search/detail/00046/" target="_blank"><strong>Toyoko INN Tottori Station South Exit</strong></a>. There is also a Toyoko INN at the North Exit, but I couldn’t book there. The North Exit area has a livelier shopping street.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/d8wVYHG4zkA" title="東橫INN 鳥取站南口" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h4 id="1700-鳥取亂逛">17:00 鳥取亂逛</h4>

<p><img src="/assets/aacd5f5cacd1/1*vHGHHoGQxbgvDqexHR0gLQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*vHGHHoGQxbgvDqexHR0gLQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*k3EiMwp0DAViTzXwjie4pw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*k3EiMwp0DAViTzXwjie4pw.jpeg" /></p>

<p>There is still an hour before mealtime, so I went to the nearby AEON for a walk.</p>

<p><img src="/assets/aacd5f5cacd1/1*4ZSUZ9tpUCmTPjwuSXtUBg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4ZSUZ9tpUCmTPjwuSXtUBg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mOuFR0sVsVoQ1RxuPH3YlA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*mOuFR0sVsVoQ1RxuPH3YlA.jpeg" /></p>

<p>Around 18:00, I walked to the area near Tottori North Exit Shopping Street to find a place to eat. After eating randomly the past few days, I wanted to find a better restaurant today.</p>

<p><img src="/assets/aacd5f5cacd1/1*UPSjd6gbL9TCVXl8qXLPvw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*UPSjd6gbL9TCVXl8qXLPvw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_Qkjzr6Xf_PV642XoYTcgw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_Qkjzr6Xf_PV642XoYTcgw.jpeg" /></p>

<p>Found a Tottori Wagyu restaurant with good reviews on the main street — “<a href="https://maps.app.goo.gl/pSZF8bCsiRNJ4aSt6" target="_blank">Niku Ryori Nick</a>” to try Tottori Wagyu. Not sure if it was a weekday, but I was the only customer.</p>

<blockquote>
  <p><em>Tottori wagyu is not as famous as Kobe or Omi beef, but Tottori is the <strong>birthplace of Japan’s wagyu culture</strong>.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*4fDg5JJ-GGjiV8AVasNE3A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4fDg5JJ-GGjiV8AVasNE3A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*sI_cXfEuxYrVLS6SUqDArQ.webp" alt="" loading="lazy" decoding="async" width="782" height="1143" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3ODIiIGhlaWdodD0iMTE0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*sI_cXfEuxYrVLS6SUqDArQ.png" /></p>

<p>I ordered the “Tottori Wagyu Hitsumabushi,” a three-way Tottori Wagyu beef rice dish for 3,500 yen, which is much cheaper than Kobe beef.</p>

<p><img src="/assets/aacd5f5cacd1/1*2jNeW3RO6WBh83rZqe0ZNA.webp" alt="" loading="lazy" decoding="async" width="1400" height="998" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*2jNeW3RO6WBh83rZqe0ZNA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*qkqZMeWtQcobrIOS8mrBJw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*qkqZMeWtQcobrIOS8mrBJw.jpeg" /></p>

<p>Tottori wagyu is very tender, but has a milder beef flavor. The first bite, eating pure wagyu, has little taste.</p>

<p><img src="/assets/aacd5f5cacd1/1*jkud_7mVWN_F6ALBWCj14g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*jkud_7mVWN_F6ALBWCj14g.jpeg" /></p>

<p>The second and third bites, combined with the soup it comes with, make for a delicious chazuke (tea poured over rice).</p>

<h4 id="1830-return-to-the-station-and-hotel-after-dinner">18:30 Return to the station and hotel after dinner</h4>

<p><img src="/assets/aacd5f5cacd1/1*OyxvrME3X2vnxLFRhB_IwQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OyxvrME3X2vnxLFRhB_IwQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ZDMJBQeQx2jvWFpFmbhB9g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ZDMJBQeQx2jvWFpFmbhB9g.jpeg" /></p>

<p>On the way, I first bought the train ticket for the next day to Himeji.</p>

<p><img src="/assets/aacd5f5cacd1/1*2v_BIl1Ho5wcJS0WYAuDGg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*2v_BIl1Ho5wcJS0WYAuDGg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*21KCGclvNLUL2WojrkxJMA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*21KCGclvNLUL2WojrkxJMA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*VJNLFfTTyZCRFH0kgSGL5w.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*VJNLFfTTyZCRFH0kgSGL5w.jpeg" /></p>

<p>Stopped by on the way to buy late-night snacks to eat back at the hotel. Also got a Y1000 Yakult 1000. This time, I didn’t really drink it much but still slept well…</p>

<blockquote>
  <p><em>Good night, Tottori.</em></p>
</blockquote>

<h3 id="day-4-1115-friday-white-rabbit-shrine-tottori-sand-dunes-himeji">Day 4 (11/15 Friday) White Rabbit Shrine, Tottori Sand Dunes, Himeji</h3>

<p>In the morning, first visit Hakuto Shrine (for matchmaking) to pray. Return to the north exit of Tottori Station and head to the bus terminal. Walk through the bus hall to the back and wait at platform 4 for the bus.</p>

<p><img src="/assets/aacd5f5cacd1/1*XR70UqceYMl6CmDWivqTaQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XR70UqceYMl6CmDWivqTaQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mpW7ne5REM1X5yfOAa5m4A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mpW7ne5REM1X5yfOAa5m4A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JzTpCld7REqa-Rqt4UpkgA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JzTpCld7REqa-Rqt4UpkgA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KJUGKv9wIKoO7sDwEnTYIw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KJUGKv9wIKoO7sDwEnTYIw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*z_BfpEdl3Z-O_QW6OfC4SA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*z_BfpEdl3Z-O_QW6OfC4SA.jpeg" /></p>

<p>Take the Hakuto Coast Line bus No. 41 from Shikano Office to Hakuto Shrine-mae according to Google Maps.</p>

<blockquote>
  <p><strong><em>Buses in Tottori do not accept electronic payments; cash only</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Buses in Tottori do not accept electronic payments; cash only</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Buses in Tottori do not accept electronic payments; cash only</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>Please prepare exact change in advance or exchange a 1000-yen bill for smaller bills next to the driver.</em></strong> <em>⚠️⚠️⚠️</em></p>
</blockquote>

<blockquote>
  <p><em>The fare is about 610 yen.</em></p>
</blockquote>

<p>Or buy a one-day sightseeing pass at Tottori Station for better value.</p>

<p><img src="/assets/aacd5f5cacd1/1*9cUeNishMRW8xDRs7-ri_Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*9cUeNishMRW8xDRs7-ri_Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*qHL6pCuXKRfxH86xFFs45g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*qHL6pCuXKRfxH86xFFs45g.jpeg" /></p>

<p>Before getting off, insert coins according to the amount shown on the screen. Since we boarded at the last stop without a ticket, we paid the amount shown in the first section before the previous one.</p>

<h4 id="-0930-arrive-at-shirahama-shrine">~= 09:30 Arrive at Shirahama Shrine</h4>

<p><img src="/assets/aacd5f5cacd1/1*40gg6SkBkYmveH13l0nvXQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*40gg6SkBkYmveH13l0nvXQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*-PFzilV6gkDEpOvbXLbG_A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*-PFzilV6gkDEpOvbXLbG_A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*m33qZ2cJ4FwqBjkb-6IsAg.webp" alt="" loading="lazy" decoding="async" width="938" height="1194" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iMTE5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*m33qZ2cJ4FwqBjkb-6IsAg.png" /></p>

<p>After getting off, walk back towards the overpass to reach the entrance of Hakuto Shrine.</p>

<blockquote>
  <p><em>The White Rabbit Shrine originates from the legend of the “White Rabbit of Inaba.” According to the tale, Okuninushi no Mikoto (from Izumo Taisha) helped the injured white rabbit recover, received its blessing, and successfully won the love of Princess Yakami-hime.</em></p>
</blockquote>

<blockquote>
  <p><em>Shirousagi Shrine symbolizes good relationships and healing. Both shrines share the sacred meaning of praying for happiness and connections.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*69iTu4F0VaHW07l992wjMA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*69iTu4F0VaHW07l992wjMA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OFRxkKGCLjl2SPMHKOlgcA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OFRxkKGCLjl2SPMHKOlgcA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*eUPpPJUou85qulUku9cDVA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*eUPpPJUou85qulUku9cDVA.jpeg" /></p>

<p>The shrine is not far away, but along the way, there are many rabbit statues, all covered with love stones.</p>

<blockquote>
  <p><em>Izumo Taisha also has many Inaba white rabbit statues, but I forgot to take photos of them.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*RKT2AGgUHkzeHVEP7OlkgA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*RKT2AGgUHkzeHVEP7OlkgA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1awKOYwzIPQ5BHVD0OgePQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1awKOYwzIPQ5BHVD0OgePQ.jpeg" /></p>

<p>The torii gate is also piled with matchmaking stones.</p>

<p><img src="/assets/aacd5f5cacd1/1*6JmuOYncQTNDnamDE8lhoA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6JmuOYncQTNDnamDE8lhoA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KCuCyqwlHaza-Kz1r2J2Pw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KCuCyqwlHaza-Kz1r2J2Pw.jpeg" /></p>

<p>The shrine is not very large; it takes about 15–20 minutes to visit.</p>

<p><img src="/assets/aacd5f5cacd1/1*E5qACETPs0VUTwxrsXGXlg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*E5qACETPs0VUTwxrsXGXlg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*l2_nfwxbvPJW78TGZAQdng.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*l2_nfwxbvPJW78TGZAQdng.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_mqNm0k5ubWNxOFxlWaIuw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_mqNm0k5ubWNxOFxlWaIuw.jpeg" /></p>

<p>I also spent 500 yen to buy a matchmaking stone to place at the statue.</p>

<p><img src="/assets/aacd5f5cacd1/1*fvYIjtTDJYlLzG5Zi9eJJg.webp" alt="" loading="lazy" decoding="async" width="1200" height="844" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fvYIjtTDJYlLzG5Zi9eJJg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NFlQA9p4TtNZTg6oEcFi-w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*NFlQA9p4TtNZTg6oEcFi-w.jpeg" /></p>

<p><strong>The Inaba White Rabbit I Bought:</strong></p>

<ul>
  <li>
    <p>Left: Bought at a souvenir shop outside Izumo Taisha Shrine</p>
  </li>
  <li>
    <p>Right side: Bought at Shirahama Shrine (includes a poem fortune slip)</p>
  </li>
</ul>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/HbxBcKkfCnk" title="一隻兔子爲何要招惹一群鯊魚？經歷過兩次的死而復生，他竟然迎娶了自己祖先的女兒！大國主神話\\|大國主\\|因幡之白兔\\|根之囯\\|日本神話\\|蘭爸爸說故事" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>The story of the White Rabbit of Inaba and the deity Okuninushi.</p>

<h4 id="-0950-white-rabbit-shrine-tourist-information-center-white-rabbit-beach">~= 09:50 White Rabbit Shrine Tourist Information Center, White Rabbit Beach</h4>

<p><img src="/assets/aacd5f5cacd1/1*OElc39nUjmOmxt1b2l_8vQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OElc39nUjmOmxt1b2l_8vQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*y-ATn5pE43fRSQoNCQKHAA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*y-ATn5pE43fRSQoNCQKHAA.jpeg" /></p>

<p>The tourist information center really has a white rabbit, and they also sell some snacks and souvenirs. I bought a bread to fill my stomach.</p>

<p><img src="/assets/aacd5f5cacd1/1*HHmrUUqcvXrDsCAvzyOkAw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*HHmrUUqcvXrDsCAvzyOkAw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*bs7k1F-QNASY8qe7v9HFzQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bs7k1F-QNASY8qe7v9HFzQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ayrC4DXTeXmXI1axNs29CA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ayrC4DXTeXmXI1axNs29CA.jpeg" /></p>

<p>White Rabbit Beach is very desolate.</p>

<p><img src="/assets/aacd5f5cacd1/1*yGwYlF_RqRc9JJ6wMFmjug.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*yGwYlF_RqRc9JJ6wMFmjug.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*rXW71tg5gJziOeVd5X3iRw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*rXW71tg5gJziOeVd5X3iRw.jpeg" /></p>

<p>The bus only departs towards Tottori Station at 10:45. It’s better to arrive earlier and take an earlier bus to save time.</p>

<p><img src="/assets/aacd5f5cacd1/1*JfxN2JQq74dWK90Tsor_YQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JfxN2JQq74dWK90Tsor_YQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*PuL80Noe3RS2fR-rkUF8vg.webp" alt="from: 圣炜" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PuL80Noe3RS2fR-rkUF8vg.jpeg" /></p>

<p>from: Sheng Wei</p>

<blockquote>
  <p><strong><em>Remember to take a boarding ticket when you get on. It has a number, and when you get off, pay the corresponding amount in coins along with the ticket.</em></strong> <em>⚠️</em></p>
</blockquote>

<p>On the train, I met a tourist from Fujian, China; his itinerary was the opposite of mine. He went to Himeji first and then to Shimane. We exchanged recommendations—he suggested visiting Shosha-san Engyo-ji Temple before Himeji Castle, and said the photo spot shown in the picture is great for taking pictures of the castle.</p>

<blockquote>
  <p><strong><em>Thanks to him! In the end, it was definitely worth it.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*ItWdhOq9YNwojhJt9l1pgA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ItWdhOq9YNwojhJt9l1pgA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ZRMhH7FMc8ypC3jQoBKqkA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ZRMhH7FMc8ypC3jQoBKqkA.jpeg" /></p>

<p>Get off at Aioi-cho, then transfer at the opposite bus stop to the bus heading to Tottori Sand Dunes.</p>

<p><img src="/assets/aacd5f5cacd1/1*3hf1z-tdcV_FVh_wW9JSKw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3hf1z-tdcV_FVh_wW9JSKw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*UjpATzEo91FX4Ptdxtlb4g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*UjpATzEo91FX4Ptdxtlb4g.jpeg" /></p>

<p>Take this bus and get off at the Dune East Entrance to walk up.</p>

<blockquote>
  <p><em>Take ticket number 3 when boarding, then check the screen for the fare corresponding to number 3 when getting off. In this example, you need to pay 250 yen.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*b3fTmvHBaj__OiMbjr5BpA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*b3fTmvHBaj__OiMbjr5BpA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KGIqShdtrpqRYB31M253yQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KGIqShdtrpqRYB31M253yQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wiMKI8135q9TNJcrhuWiZQ.webp" alt="My chaotic visiting route." loading="lazy" decoding="async" width="1200" height="917" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkxNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wiMKI8135q9TNJcrhuWiZQ.png" /></p>

<p>My chaotic visiting route.</p>

<p>After getting off, turn back at the fork and follow the signs uphill, passing the Tottori Sand Dunes Art Museum, then continue to the entrance of the Tottori Sand Dunes.</p>

<p><img src="/assets/aacd5f5cacd1/1*FOByNsR18dk-tExeF5VPUw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*FOByNsR18dk-tExeF5VPUw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*M1l7figNu-hmgZAbR_xjaw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*M1l7figNu-hmgZAbR_xjaw.jpeg" /></p>

<blockquote>
  <p><em>Tottori Sand Dunes Prohibited Actions, <strong>especially forbidden to draw on the sand and harm nature</strong>. ⚠️</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*UCu5KdaTZWThGBgaSompbg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UCu5KdaTZWThGBgaSompbg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*87V9nbswSrqQfx3JcDcVYQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*87V9nbswSrqQfx3JcDcVYQ.jpeg" /></p>

<p>The Tottori Sand Dunes are really vast. The sky was a bit gloomy and rainy at the time, but if it were sunny, it would probably be very hot.</p>

<h4 id="1200-arrive-at-tottori-sand-dunes">~=12:00 Arrive at Tottori Sand Dunes</h4>

<p>Japan’s largest sand dune.</p>

<p><img src="/assets/aacd5f5cacd1/1*WkiGJSHcUrPZi5h7zSJGYA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*WkiGJSHcUrPZi5h7zSJGYA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lwqbSawD2wG3wO1MyAf2Bg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*lwqbSawD2wG3wO1MyAf2Bg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NEuDeBgnNxtTSaYVQCd2NQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*NEuDeBgnNxtTSaYVQCd2NQ.jpeg" /></p>

<p>People look very small. Walking all the way up to the dune ridge was a bit shaky, and the sand was loose and hard to walk on.</p>

<p><img src="/assets/aacd5f5cacd1/1*Gu98SJFPwWWKTj5PQR0zUw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Gu98SJFPwWWKTj5PQR0zUw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*S44sCcfn1C1zx3--fIIoBw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*S44sCcfn1C1zx3--fIIoBw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*w0d4u78ensGTedfoTINN4g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*w0d4u78ensGTedfoTINN4g.jpeg" /></p>

<p>From the highest point, you can overlook the entire coastline. The view of the sand dunes facing the sea is unique to this spot.</p>

<p><img src="/assets/aacd5f5cacd1/1*B-Oqyt2XYx4TVTMblCdEdw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1008" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*B-Oqyt2XYx4TVTMblCdEdw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9ZoPtPjdHz3aaFTGNDT8iw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9ZoPtPjdHz3aaFTGNDT8iw.jpeg" /></p>

<p>According to online information, camel riding is usually available, but it seems it was canceled today due to bad weather.</p>

<p><img src="/assets/aacd5f5cacd1/1*9WiZqF_alWi6M1PrHwG3sg.webp" alt="" loading="lazy" decoding="async" width="1400" height="994" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9WiZqF_alWi6M1PrHwG3sg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JxF65EE6eeACd1uRENVwNQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JxF65EE6eeACd1uRENVwNQ.jpeg" /></p>

<p>Looking back at Tottori Sand Dunes as I left, it really is huge; some sand inevitably got into my shoes. <strong>I saw someone smartly put on plastic shoe covers.</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*6cq3xUwokLToOCx38TXV9g.webp" alt="" loading="lazy" decoding="async" width="946" height="1199" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDYiIGhlaWdodD0iMTE5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6cq3xUwokLToOCx38TXV9g.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OacuYCefS-wThnPRbo49xw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OacuYCefS-wThnPRbo49xw.jpeg" /></p>

<p>You can take the cable car directly to the Tottori Sand Dunes Observatory. I decided to first go down and try the famous Tottori No.1 Sand Dune Pudding, so I didn’t take the cable car. Later, I walked up since it was on the way and not far (about 15 minutes).</p>

<p><img src="/assets/aacd5f5cacd1/1*SkKPyRhs_REhQrWDiLluNQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*SkKPyRhs_REhQrWDiLluNQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*30ZIWteqj87esh4ptH2w7A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*30ZIWteqj87esh4ptH2w7A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6LhlMpfWyszDfyJ7PTf1vQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*6LhlMpfWyszDfyJ7PTf1vQ.jpeg" /></p>

<p>Exiting from the parking lot, you can see across the street the Tottori Sand Dunes “Takahama Cafe 高濱咖啡廳,” designed by Kengo Kuma.</p>

<p><img src="/assets/aacd5f5cacd1/1*3OinDP-RDFHBQHLpY0olPw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3OinDP-RDFHBQHLpY0olPw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*iGxKRD9KvMZrGuuHEjGNcA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*iGxKRD9KvMZrGuuHEjGNcA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YA0J3gRmOPgWhacPYV0LEg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*YA0J3gRmOPgWhacPYV0LEg.jpeg" /></p>

<p>Bought one to try, the taste is really good. The pudding has a rich milk flavor and a texture similar to cheesecake.</p>

<p><img src="/assets/aacd5f5cacd1/1*cRVlgCNRJysVUNkOkCHgFg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*cRVlgCNRJysVUNkOkCHgFg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*TafJZGPlGd5gft3gYj8Q3A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*TafJZGPlGd5gft3gYj8Q3A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*sF3liqjkM6eQ6KURqwEfxA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*sF3liqjkM6eQ6KURqwEfxA.jpeg" /></p>

<p>A special feature is that they give you a small packet of sugar that looks like sand to add and eat together.</p>

<p><img src="/assets/aacd5f5cacd1/1*qOmSScKgkmuIBhtPJKcbiA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*qOmSScKgkmuIBhtPJKcbiA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*hv6d0AdzA-Ubx4XTqW8ZXQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hv6d0AdzA-Ubx4XTqW8ZXQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*30_-rtFKE2Az1z6ikKIOSg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*30_-rtFKE2Az1z6ikKIOSg.jpeg" /></p>

<p>There are also some restaurants along this road. Continuing back, there is a small path; walk up a short distance and the Sand Dunes Museum second entrance is on the right. On the left, if you go up a bit further, you’ll reach the “Tottori Sand Dunes Observatory” where the cable car arrives.</p>

<p><img src="/assets/aacd5f5cacd1/1*D9v4msXBz0mfsAWzXugtTw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*D9v4msXBz0mfsAWzXugtTw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*N9shdulrUJo5pphjg4lKMw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*N9shdulrUJo5pphjg4lKMw.jpeg" /></p>

<p>First, head left to the Tottori Sand Dunes Observatory to enjoy the view and have some food. You will arrive at the cable car boarding area first.</p>

<p><img src="/assets/aacd5f5cacd1/1*mo37aWPygCHfl-dAetnVQg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mo37aWPygCHfl-dAetnVQg.jpeg" /></p>

<p>You can first go to the vending area and use the brush nearby to clean the sand off your shoes. (The staff at the entrance will also remind you.)</p>

<p><img src="/assets/aacd5f5cacd1/1*gTZJLV-MjOhVf3kxsPHwmg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*gTZJLV-MjOhVf3kxsPHwmg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lQX5yEbrhiEqUxf6ekViVw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*lQX5yEbrhiEqUxf6ekViVw.jpeg" /></p>

<p>Finally, waited here for the bus back to Tottori.</p>

<p><img src="/assets/aacd5f5cacd1/1*Re9IYuYqgtJre9oHgws_aA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Re9IYuYqgtJre9oHgws_aA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*GLQ1-BDnEqB25ZBp2X3BAA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GLQ1-BDnEqB25ZBp2X3BAA.jpeg" /></p>

<p>The first and second floors of the observation deck sell souvenirs and have a small restaurant. On the third floor, you can go up to view the Tottori Sand Dunes (although there isn’t much to see).</p>

<p><img src="/assets/aacd5f5cacd1/1*fyEhTboh7RX-ZXqz9-FCPw.webp" alt="" loading="lazy" decoding="async" width="1200" height="868" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fyEhTboh7RX-ZXqz9-FCPw.png" /></p>

<p>There aren’t many food options inside. You need to first buy the corresponding meal ticket from the nearby vending machine, then enter, find a seat, and hand the ticket to the staff.</p>

<p>Had a casual oyster set meal.</p>

<p><img src="/assets/aacd5f5cacd1/1*bN0RPnkejfCFjKjmcGwNCw.webp" alt="" loading="lazy" decoding="async" width="1071" height="873" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDcxIiBoZWlnaHQ9Ijg3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bN0RPnkejfCFjKjmcGwNCw.png" /></p>

<p>I also bought some souvenirs from Tottori here.</p>

<h4 id="1330-dune-art-museum">13:30 Dune Art Museum</h4>

<p><img src="/assets/aacd5f5cacd1/1*30_-rtFKE2Az1z6ikKIOSg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*30_-rtFKE2Az1z6ikKIOSg.jpeg" /></p>

<p>After eating, head down from Entrance No. 2 to visit the Sand Dunes Museum. (Also free admission with the <a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>3-Day Pass</strong></a> )</p>

<p><img src="/assets/aacd5f5cacd1/1*Y_ybpatDG6ffIfa3CDMm8g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Y_ybpatDG6ffIfa3CDMm8g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OvdkGmr4yDdAyhW9XwMMaQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OvdkGmr4yDdAyhW9XwMMaQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BBkrd9MBZZdcAEwYz8gYxw.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BBkrd9MBZZdcAEwYz8gYxw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NALOYc1jrxf6O7nYgweBvg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1184" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExODQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*NALOYc1jrxf6O7nYgweBvg.png" /></p>

<p>Starting from Entrance No. 2, the tour begins with the sand sculptures outside, then goes down from the 3rd floor to the 2nd floor. Actually, the sand sculptures are only on the 2nd floor inside the building. The venue is small, and if it weren’t free, I probably wouldn’t have entered.</p>

<p><img src="/assets/aacd5f5cacd1/1*bRQIVt04vcixxfHrHh2_yg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bRQIVt04vcixxfHrHh2_yg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*W01cTTu7FAgiW_zJRAeo-g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*W01cTTu7FAgiW_zJRAeo-g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*WZLGVTQPbJoyEK5-cZG7NQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*WZLGVTQPbJoyEK5-cZG7NQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*LiBlPyu3seaWbMkGm0eBnw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*LiBlPyu3seaWbMkGm0eBnw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*SnJ5-b_QTJ19ATurPLHEpw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*SnJ5-b_QTJ19ATurPLHEpw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*rubP2uOF0-o0QdJsoBzSWg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*rubP2uOF0-o0QdJsoBzSWg.jpeg" /></p>

<p>Although the venue is small, the details of each sand sculpture are very exquisite.</p>

<p><img src="/assets/aacd5f5cacd1/1*GRvwUBqcwGumVvkL8utiJw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GRvwUBqcwGumVvkL8utiJw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lB74w9zvrdIiJBqpIIIT7g.webp" alt="" loading="lazy" decoding="async" width="686" height="881" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODYiIGhlaWdodD0iODgxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*lB74w9zvrdIiJBqpIIIT7g.png" /></p>

<p>There is also a funny QR Code for the Sand Dunes Art Museum that you can scan.</p>

<p><img src="/assets/aacd5f5cacd1/1*uH_T1z2iVu4fs6XPKwCstg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*uH_T1z2iVu4fs6XPKwCstg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*K7XJXE5cRDMV7MYzrVybIQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*K7XJXE5cRDMV7MYzrVybIQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*lZu_yUKJOB44L4wuG04uRA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*lZu_yUKJOB44L4wuG04uRA.jpeg" /></p>

<p>After walking around, I went back up to the Tottori Sand Dunes Observatory. Wandering around, I had a Tottori pear ice cream. Around 14:20, I waited at the bus stop for the 14:42 bus heading to Tottori Station.</p>

<blockquote>
  <p><em>The bus back to Tottori was almost full when it arrived at this stop, and I barely squeezed on. If you have time, it might be better to board at the previous stop.</em></p>
</blockquote>

<h4 id="202501-update">2025/01 Update</h4>

<p>After returning, I noticed that because transportation in Tottori is less convenient, there is a taxi charter service for a one-day tour. <a href="https://chugoku.letsgojp.com/archives/661034" target="_blank">For details, please refer to the tourist center or this website</a>.</p>

<h4 id="1510-return-to-tottori-station">15:10 Return to Tottori Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*zD1KRkIAiYp-wtxk48AogA.webp" alt="" loading="lazy" decoding="async" width="704" height="1176" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDQiIGhlaWdodD0iMTE3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zD1KRkIAiYp-wtxk48AogA.png" /></p>

<p>Around 15:10, I returned to Tottori Station, and it started raining at that time.</p>

<p><img src="/assets/aacd5f5cacd1/1*7Ex8903aKslRQc4IpfH6GA.webp" alt="" loading="lazy" decoding="async" width="949" height="1189" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDkiIGhlaWdodD0iMTE4OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7Ex8903aKslRQc4IpfH6GA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cfJLhRhuQjLhQqWfzVqgFA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cfJLhRhuQjLhQqWfzVqgFA.jpeg" /></p>

<p>Wandering around the shops in the station, the souvenir store here also sells Dune pudding!</p>

<p><img src="/assets/aacd5f5cacd1/1*HyBzvP9I1y2YC4KbxyLr0w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HyBzvP9I1y2YC4KbxyLr0w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*-Q6nYvGZP-PBIgMdW6Lk2A.webp" alt="" loading="lazy" decoding="async" width="637" height="432" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MzciIGhlaWdodD0iNDMyIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*-Q6nYvGZP-PBIgMdW6Lk2A.jpeg" /></p>

<p>I originally wanted to visit Tottori’s famous すなば珈琲 (Sunaba Coffee), but they were already closed after 3 PM. So I found a nana’s green tea inside the station to have some dessert and coffee to recharge (both myself and my phone).</p>

<p><img src="/assets/aacd5f5cacd1/1*a6nZx2RTbeZAsTfxOvTPKw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*a6nZx2RTbeZAsTfxOvTPKw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*UrMfxX9LNlDIR7suza8tCQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UrMfxX9LNlDIR7suza8tCQ.jpeg" /></p>

<p>The store is comfortable and not crowded.</p>

<p><img src="/assets/aacd5f5cacd1/1*5os6rpDnUSjttVgCpigSRA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5os6rpDnUSjttVgCpigSRA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4RdqbhEiL0vrRwrMKnIXLw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4RdqbhEiL0vrRwrMKnIXLw.jpeg" /></p>

<p>Warabi mochi + ice cream + hot coffee, very satisfying.</p>

<h4 id="1615-return-to-the-hotel-to-pick-up-luggage-then-go-back-to-the-station-to-wait-for-the-train">16:15 Return to the hotel to pick up luggage, then go back to the station to wait for the train</h4>

<p><img src="/assets/aacd5f5cacd1/1*UHCBHtR1kg15a4alJuXYlw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UHCBHtR1kg15a4alJuXYlw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*i5dxFrKSOdkjH9186Q9nQA.webp" alt="" loading="lazy" decoding="async" width="931" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*i5dxFrKSOdkjH9186Q9nQA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*zlhp1-kuQp679hHmnFJE-g.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*zlhp1-kuQp679hHmnFJE-g.jpeg" /></p>

<p>It’s still early, and we will take the 16:55 Limited Express Super Hakuto to Himeji.</p>

<p><img src="/assets/aacd5f5cacd1/1*8UmhqEYZA-9f5gzPNnhsiQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8UmhqEYZA-9f5gzPNnhsiQ.jpeg" /></p>

<p>I saw the Conan train while waiting for the bus.</p>

<h4 id="1655-board-the-train-to-himeji">16:55 Board the train to Himeji</h4>

<blockquote>
  <p><strong><em>Goodbye San’in.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*-Xupoj1iDwTdGF6ofS94Dw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-Xupoj1iDwTdGF6ofS94Dw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*yVwbXNEx2C4NV80hjdj3uA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*yVwbXNEx2C4NV80hjdj3uA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*j3SvEKoIHa7lEXyq5_jcxA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*j3SvEKoIHa7lEXyq5_jcxA.jpeg" /></p>

<p>Got a seat in the first row of the Green Car, very comfortable.</p>

<blockquote>
  <p><strong><em>Because the train was quite shaky, I didn’t dare to put my luggage overhead. Instead, I placed it sideways behind the last row of seats.</em></strong></p>
</blockquote>

<h4 id="1827-arrive-at-himeji-station">18:27 Arrive at Himeji Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*3jfwcI6yLbuWuWzDM4mC8A.webp" alt="" loading="lazy" decoding="async" width="708" height="1190" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDgiIGhlaWdodD0iMTE5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3jfwcI6yLbuWuWzDM4mC8A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OdglmUjNMHHemWGLuppg3g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OdglmUjNMHHemWGLuppg3g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*XbSKEhcwPLxItxqUXd6CVg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XbSKEhcwPLxItxqUXd6CVg.jpeg" /></p>

<p>I happened to catch their councilor election campaign outside, where a councilor was giving a rally speech with a large crowd.</p>

<p><img src="/assets/aacd5f5cacd1/1*9HmKh497akO8a6922Z7kLA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*9HmKh497akO8a6922Z7kLA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*uBit7SVmrOOBDLSPv9IEGw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uBit7SVmrOOBDLSPv9IEGw.jpeg" /></p>

<p>After exiting the station, head straight to the hotel <a href="https://www.toyoko-inn.com/china/search/detail/00317/" target="_blank"><strong>Toyoko INN Himeji Station Shinkansen North Exit</strong></a>. The last three nights were all spent here. The Toyoko INN near the north exit is livelier; it looks close on the map but feels a bit farther when walking, taking about 10 minutes.</p>

<p><img src="/assets/aacd5f5cacd1/1*VT_z6TemLvMX-ujCfUewiQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*VT_z6TemLvMX-ujCfUewiQ.jpeg" /></p>

<p>Assigned a room on the 14th-floor rooftop, with a great view outside the window.</p>

<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/fAnO4AoRX-Q" title="東橫INN 姬路站新幹線北口" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<iframe class="embed-video" loading="lazy" src="https://www.youtube.com/embed/xl9eOyv4A-I" title="東橫INN 姬路站新幹線北口 14 樓窗外鐵路景觀" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>You can directly look back at Himeji Station railway from the window. Trains passing by make slight track noises, but fortunately, there are no trains after nightfall.</p>

<h4 id="1900-go-out-for-a-casual-food-hunt">19:00 Go out for a casual food hunt</h4>

<p>As soon as I came out, I saw the night view of Himeji Castle and thought it was beautiful. For some reason, I decided to walk to Himeji Castle to take night photos. Being too close made it hard to frame the shots, so I didn’t take many photos and left quickly. It was cold and I was hungry the whole way—I should have just gone straight to eat.</p>

<p><img src="/assets/aacd5f5cacd1/1*dy69JBLXC40kKDIYB3adGw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*dy69JBLXC40kKDIYB3adGw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*dyjiO-KVxTv99UskMZs9Ag.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*dyjiO-KVxTv99UskMZs9Ag.jpeg" /></p>

<p>Himeji Castle is about 1 kilometer from the station, taking nearly 30 minutes to walk round trip.</p>

<p><img src="/assets/aacd5f5cacd1/1*hEbq2RVL18Yscc-y9ozE8g.webp" alt="&lt;https://himeji-kyoukasuigetsu.com/en/&gt;" loading="lazy" decoding="async" width="1400" height="1040" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*hEbq2RVL18Yscc-y9ozE8g.png" /></p>

<p><a href="https://himeji-kyoukasuigetsu.com/en/" target="_blank">https://himeji-kyoukasuigetsu.com/en/</a></p>

<p><img src="/assets/aacd5f5cacd1/1*KLFCGcyq_F3ZVnM4w_GK2w.webp" alt="&lt;https://himeji-kyoukasuigetsu.com/en/&gt;" loading="lazy" decoding="async" width="1042" height="1059" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQyIiBoZWlnaHQ9IjEwNTkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*KLFCGcyq_F3ZVnM4w_GK2w.png" /></p>

<p><a href="https://himeji-kyoukasuigetsu.com/en/" target="_blank">https://himeji-kyoukasuigetsu.com/en/</a></p>

<blockquote>
  <p><strong><em>Unfortunately, I missed the Himeji Castle winter illumination event this time. It looked beautiful and romantic. Hopefully, I can visit again someday.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>The event starts on Friday, November 22, 2024, and runs until Sunday, February 23, 2025.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*tnvYT7YGcklotJdPdfpY8w.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*tnvYT7YGcklotJdPdfpY8w.jpeg" /></p>

<p>I found that the viewing platform on the second floor of the station’s north exit is the best spot to photograph Himeji Castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*dRxfkrBM2x_gMgusOlnZ5A.webp" alt="" loading="lazy" decoding="async" width="1036" height="861" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDM2IiBoZWlnaHQ9Ijg2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dRxfkrBM2x_gMgusOlnZ5A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*zCHRn2ZG88rg_a7WnfXz1g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zCHRn2ZG88rg_a7WnfXz1g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Pqy-Rk3t-bA36QHoaNrTPg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Pqy-Rk3t-bA36QHoaNrTPg.jpeg" /></p>

<p>Before buying dinner, I took a short walk and played with capsule toys at the department store <a href="https://www.jrw-urban.co.jp/piole-himeji/tw" target="_blank">Piole Himeji</a> near Himeji Station.</p>

<h4 id="2000-buy-dinner-and-go-back">~=20:00 Buy dinner and go back</h4>

<p><img src="/assets/aacd5f5cacd1/1*8hgCoDK6cgGQJT4re9WRAA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8hgCoDK6cgGQJT4re9WRAA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*piFnI5vcIs2l8lG5gQZRdg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*piFnI5vcIs2l8lG5gQZRdg.jpeg" /></p>

<p>Walked from the second-floor skybridge to the adjacent TERASSO and found a yakiniku restaurant that was still open. Took out a yakiniku bento to eat back at the hotel.</p>

<p><img src="/assets/aacd5f5cacd1/1*PfaI0llzUMpwdqEzyz5iPg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PfaI0llzUMpwdqEzyz5iPg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*vGLx0HKICVp2XggRVuXvQQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vGLx0HKICVp2XggRVuXvQQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*V_AQ9WNRUuMFogArfhYn_g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*V_AQ9WNRUuMFogArfhYn_g.jpeg" /></p>

<p>Stopped by a convenience store and bought some food and drinks.</p>

<p><img src="/assets/aacd5f5cacd1/1*VfGo_hbbLnLFXz2wdnLF4A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*VfGo_hbbLnLFXz2wdnLF4A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*x3_zZ9A0QkjHdWDNkjuQqg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*x3_zZ9A0QkjHdWDNkjuQqg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1nvfzK3jA2eVr-tTRIAUBw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1nvfzK3jA2eVr-tTRIAUBw.jpeg" /></p>

<p>After eating and drinking, taking a shower, I went downstairs to do laundry. This time I knew Toyoko Inn requires bringing your own detergent, so I first bought washing powder with coins and put it in the washing machine along with my clothes. The dryer here is very new and high-end, and you can even select the temperature.</p>

<p><img src="/assets/aacd5f5cacd1/1*1b9IFBlF0z6jwwChXs1zPg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1b9IFBlF0z6jwwChXs1zPg.jpeg" /></p>

<blockquote>
  <p><strong><em>Good night, Himeji.</em></strong></p>
</blockquote>

<h3 id="day-5-1116-saturday-mount-shosha-engyo-ji-temple-himeji-castle-shopping-around-himeji">Day 5 (11/16 Saturday) Mount Shosha Engyo-ji Temple, Himeji Castle, Shopping Around Himeji</h3>

<p><img src="/assets/aacd5f5cacd1/1*EqvQAU91nnYsl8w7vT7CTg.webp" alt="" loading="lazy" decoding="async" width="1200" height="851" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EqvQAU91nnYsl8w7vT7CTg.png" /></p>

<p>Every Toyoko INN offers free breakfast, but since I was rushing my schedule, I usually just grabbed some convenience store bread and coffee I bought the night before and headed out.</p>

<h4 id="0845-leave-the-house">08:45 Leave the house</h4>

<p><img src="/assets/aacd5f5cacd1/1*HIa6B4yuh-MjFWMeBs97IQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*HIa6B4yuh-MjFWMeBs97IQ.jpeg" /></p>

<p>At 8:45 AM, left the hotel and walked through the deserted shopping street to the station.</p>

<h4 id="0855-take-the-bus-to-shoshazan-engyoji-temple">08:55 Take the bus to Shoshazan Engyoji Temple</h4>

<p><img src="/assets/aacd5f5cacd1/1*Z3_zOdcbly3jEyXyN8RKWA.webp" alt="" loading="lazy" decoding="async" width="918" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MTgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Z3_zOdcbly3jEyXyN8RKWA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*3KNztIIzy_Yw85kEm5ai-Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3KNztIIzy_Yw85kEm5ai-Q.jpeg" /></p>

<p>Himeji Station has several platforms. Google Maps doesn’t specify which platform or bus number to take, so you need to follow the signs on site. Take bus number 10 from platform 10, bound for Himeji Station North to Shoshazan Ropeway.</p>

<blockquote>
  <p><em>Mount Shosha Engyo-ji Temple was a filming location for the movie The Last Samurai:</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*ElCz1QpeR3XaKMbYyPvuPQ.webp" alt="&lt;https://visit-himeji.com/zh-hant/trip-ideas/featured-in-the-last-samurai-shoshazan-engyoji-temple-in-himeji/&gt;" loading="lazy" decoding="async" width="1069" height="973" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDY5IiBoZWlnaHQ9Ijk3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ElCz1QpeR3XaKMbYyPvuPQ.png" /></p>

<p><a href="https://visit-himeji.com/zh-hant/trip-ideas/featured-in-the-last-samurai-shoshazan-engyoji-temple-in-himeji/" target="_blank">https://visit-himeji.com/zh-hant/trip-ideas/featured-in-the-last-samurai-shoshazan-engyoji-temple-in-himeji/</a></p>

<h4 id="0920-arrive-at-shosha-cable-car-boarding-area">~=09:20 Arrive at Shosha Cable Car Boarding Area</h4>

<p><img src="/assets/aacd5f5cacd1/1*a7E1GJ36evNeg3HIpTajiA.webp" alt="" loading="lazy" decoding="async" width="1395" height="1049" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzk1IiBoZWlnaHQ9IjEwNDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*a7E1GJ36evNeg3HIpTajiA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*deyU47Fxzh-iOMa3Cm6RMg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*deyU47Fxzh-iOMa3Cm6RMg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*d-BdwUp1HRFSSl_Zu16cDQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*d-BdwUp1HRFSSl_Zu16cDQ.jpeg" /></p>

<p>After getting off, you will find the cable car station. After buying a round-trip ticket from the vending machine, you can line up to wait for the cable car. (Runs every 15 minutes)</p>

<p><img src="/assets/aacd5f5cacd1/1*Q96ZZxOE04V57QXJcsL2iA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Q96ZZxOE04V57QXJcsL2iA.jpeg" /></p>

<p>At Mount Shosha, from 11/15 to 11/17, there is a special night opening event until 8:00 PM, and the cable car service is extended until 8:30 PM.</p>

<h4 id="0930-take-the-cable-car-up-the-mountain">09:30 Take the cable car up the mountain</h4>

<p><img src="/assets/aacd5f5cacd1/1*-t6iFYeu1txyQ6-B9pZdCQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-t6iFYeu1txyQ6-B9pZdCQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*2MnYBWj9005tN57I3IRrXQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*2MnYBWj9005tN57I3IRrXQ.jpeg" /></p>

<h4 id="0935-arrive-at-the-entrance-of-shoshazan-engyo-ji-temple">09:35 Arrive at the entrance of Shoshazan Engyo-ji Temple</h4>

<p><img src="/assets/aacd5f5cacd1/1*4AYY1gP1Kdb589JwBHaFuw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1010" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*4AYY1gP1Kdb589JwBHaFuw.png" /></p>

<p>Tickets are required for entry. At that time, I only bought the <strong>regular mountain admission fee, which does not include the round-trip shuttle bus, so you have to walk up the mountain</strong>. ⚠️⚠️⚠️</p>

<blockquote>
  <p><em>About a 5-minute ride</em></p>
</blockquote>

<blockquote>
  <p><em>Hiking time: about 20 minutes (1 KM)</em></p>
</blockquote>

<blockquote>
  <p><strong><em>If I had to choose again, I would pay an extra 500 yen to save time and energy.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*p4QIb3vPtJivhpdoDQ6GAQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*p4QIb3vPtJivhpdoDQ6GAQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7e4WBDn1nRnJYdz5B_hYOA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7e4WBDn1nRnJYdz5B_hYOA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ewt3SGQwn6WyhRV7Am0_MA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ewt3SGQwn6WyhRV7Am0_MA.jpeg" /></p>

<p>Fortunately, it was early in the morning and I still had plenty of energy and spirit, so I walked up the gentle slope while enjoying the autumn leaves.</p>

<p><img src="/assets/aacd5f5cacd1/1*AS55vHl96nZYuwqjRa93ZA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*AS55vHl96nZYuwqjRa93ZA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*T8xwRPwgXPqr5nZ5LXUq2w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*T8xwRPwgXPqr5nZ5LXUq2w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*W-yWWLLv0UJlQ2QU10Q3Vg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*W-yWWLLv0UJlQ2QU10Q3Vg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fcCW8202ZiA2OcPgUm-gKw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*fcCW8202ZiA2OcPgUm-gKw.jpeg" /></p>

<p>About 400 meters left after passing through the Niomon Gate.</p>

<h4 id="0955-arrive-at-moni-hall">09:55 Arrive at Moni Hall</h4>

<p><img src="/assets/aacd5f5cacd1/1*ceS4fQCizXrx1avWg9Y3yw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ceS4fQCizXrx1avWg9Y3yw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*80TxXA5vgZrLaPnSGHQVlQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*80TxXA5vgZrLaPnSGHQVlQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6m7mDkJHyCYezj5AlTKznA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6m7mDkJHyCYezj5AlTKznA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fLzPG1W3E1eJ4_pEYPKepQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fLzPG1W3E1eJ4_pEYPKepQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*AxTqVqv_JomxzIlBE_HbSA.webp" alt="" loading="lazy" decoding="async" width="965" height="1232" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjUiIGhlaWdodD0iMTIzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*AxTqVqv_JomxzIlBE_HbSA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5nc-fnNgZHhIwlJhTcYKjg.webp" alt="" loading="lazy" decoding="async" width="1400" height="899" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijg5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5nc-fnNgZHhIwlJhTcYKjg.png" /></p>

<p>Mount Shosha Engyo-ji Temple covers a vast area, and I think it takes at least 2 hours to explore it all.</p>

<p><img src="/assets/aacd5f5cacd1/1*U4wMNwdurbeaNEOqUKkC9Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="529" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjUyOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*U4wMNwdurbeaNEOqUKkC9Q.png" /></p>

<p>I only visited the Mani Hall and the Great Lecture Hall before heading back down the mountain.</p>

<p><img src="/assets/aacd5f5cacd1/1*h70I3V6EU2UIea8JaVs_jw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*h70I3V6EU2UIea8JaVs_jw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cQEiVSO01SrHGYuhQb8Y6A.webp" alt="" loading="lazy" decoding="async" width="946" height="1234" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NDYiIGhlaWdodD0iMTIzNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cQEiVSO01SrHGYuhQb8Y6A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*IyDpkfR-0QmrIiNwGg0aJA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*IyDpkfR-0QmrIiNwGg0aJA.jpeg" /></p>

<p>You can take off your shoes and enter the Mani Hall to pray, and also enjoy a bird’s-eye view of the maple leaves from here.</p>

<p><img src="/assets/aacd5f5cacd1/1*BbDp9dGeoep-40MgJeDiOQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*BbDp9dGeoep-40MgJeDiOQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fImTadRZFNlut58qayxvhQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*fImTadRZFNlut58qayxvhQ.jpeg" /></p>

<p>It is very quiet and peaceful, but this year the maple leaves turned red later, with only a few scattered trees showing color.</p>

<p><img src="/assets/aacd5f5cacd1/1*mVNbxhCrjF5jWx5MXgRNWA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mVNbxhCrjF5jWx5MXgRNWA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9j_zrDZmNITjHHmA56PmCQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9j_zrDZmNITjHHmA56PmCQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mbYDfRbjjG6Z5_w8mI--EQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mbYDfRbjjG6Z5_w8mI--EQ.jpeg" /></p>

<p>Walk forward from the rear corridor towards the Main Lecture Hall.</p>

<p><img src="/assets/aacd5f5cacd1/1*hsCWaBEKnGp5QMX_YaF4Gw.webp" alt="" loading="lazy" decoding="async" width="1200" height="870" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hsCWaBEKnGp5QMX_YaF4Gw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0yUCPUF_oEy4manAS8kJ0A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0yUCPUF_oEy4manAS8kJ0A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*gjTjoLDLZC6EeAP3Gcnbdw.webp" alt="" loading="lazy" decoding="async" width="960" height="1244" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTI0NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gjTjoLDLZC6EeAP3Gcnbdw.png" /></p>

<p>The main hall features a <a href="https://x.com/kugikumo" target="_blank">Engyoji x Kengo Kuma</a> installation art, on display until 12/1.</p>

<p><img src="/assets/aacd5f5cacd1/1*4m561KwGCbbgXnrrYERkJw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4m561KwGCbbgXnrrYERkJw.jpeg" /></p>

<p>The maple leaves beside the main hall are very red.</p>

<blockquote>
  <p><em>When we arrived at the main hall, a sutra chanting was in progress. Visitors can remove their shoes and enter to pray. The chanting style of Japanese monks is different from Taiwan’s; they spread out the entire sutra, chant along, then fold it back and clap at the end.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*V_0ogPmKoPiQ9-nuUfxvlw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*V_0ogPmKoPiQ9-nuUfxvlw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NKB_Hzykps-3rC3HHrxHIg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NKB_Hzykps-3rC3HHrxHIg.jpeg" /></p>

<p>The adjacent cafeteria is also open for visits, displaying many historical artifacts.</p>

<p>All the buildings are historical monuments (built in 1282). Walking on them gives a feeling of traveling through history. The entire architecture is well preserved and maintained.</p>

<p><img src="/assets/aacd5f5cacd1/1*T4ybcdOD3Fr9qJPRvKyjvA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*T4ybcdOD3Fr9qJPRvKyjvA.jpeg" /></p>

<p>View of the main auditorium from the second floor of the cafeteria.</p>

<p><img src="/assets/aacd5f5cacd1/1*kwc7TD6kOkAMUEGCyQZyHw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kwc7TD6kOkAMUEGCyQZyHw.jpeg" /></p>

<p>After visiting the Great Lecture Hall, I returned to the Mani Hall and bought a freshly grilled red bean mochi at the shop outside to recharge. (The freshly grilled one was delicious, with a crispy skin)</p>

<p><img src="/assets/aacd5f5cacd1/1*3vuyM_hvr2vqmMigtjrJfQ.webp" alt="Have a safe trip home." loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3vuyM_hvr2vqmMigtjrJfQ.jpeg" /></p>

<p>Have a safe trip home.</p>

<p>After eating, spend another 20 minutes walking down the mountain back to the Shinosho entrance.</p>

<h4 id="1055-take-the-cable-car-down-the-mountain">~=10:55 Take the cable car down the mountain</h4>

<p><img src="/assets/aacd5f5cacd1/1*HYi218ukiOp87of9sIPW7g.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HYi218ukiOp87of9sIPW7g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MxZa66WDA3XJLESJ6bTAHA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MxZa66WDA3XJLESJ6bTAHA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*CPsPU2ed-0Dr7Xk1yyszAw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CPsPU2ed-0Dr7Xk1yyszAw.jpeg" /></p>

<h4 id="1110-return-to-himeji-city-center">11:10 Return to Himeji City Center</h4>

<p><img src="/assets/aacd5f5cacd1/1*--Rj51jGDSdIKsruPXX5OQ.webp" alt="" loading="lazy" decoding="async" width="938" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MzgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*--Rj51jGDSdIKsruPXX5OQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mLKgafi4UWM-lqsxIxnTHg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*mLKgafi4UWM-lqsxIxnTHg.jpeg" /></p>

<p>The bus stop when walking back down the mountain, around 11:05, waiting for the 11:10 bus back to Himeji city.</p>

<h4 id="-1135-arrive-at-himeji-castle">~= 11:35 Arrive at Himeji Castle</h4>

<p><img src="/assets/aacd5f5cacd1/1*rVlB-YbFKOoCB_4vUzk50w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*rVlB-YbFKOoCB_4vUzk50w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kxFG8jVc9-4cfH5uTVpbsQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kxFG8jVc9-4cfH5uTVpbsQ.jpeg" /></p>

<p>After getting off the bus, walk a bit ahead to reach the Himeji Castle boat tour. However, since the weather was gloomy and it looked like rain, I didn’t go.</p>

<p><img src="/assets/aacd5f5cacd1/1*ReisZ-PRti3sK13ozeceDA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ReisZ-PRti3sK13ozeceDA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OwoTR1-C35HhJiv-rq8o2w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*OwoTR1-C35HhJiv-rq8o2w.jpeg" /></p>

<p>Entering Himeji Castle, the bridge at the entrance is a great spot for photos!</p>

<p><img src="/assets/aacd5f5cacd1/1*KmgaWgD9UXFA3R6UGw8QoQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*KmgaWgD9UXFA3R6UGw8QoQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NISOgxwwrowWgi9jHz_qWw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NISOgxwwrowWgi9jHz_qWw.jpeg" /></p>

<p>The San-no-maru Square in front of Himeji Castle is very large, and it takes another 5 minutes on foot from here to reach the castle entrance.</p>

<p><img src="/assets/aacd5f5cacd1/1*TczWjw__6r_H15vgUwkcUQ.webp" alt="" loading="lazy" decoding="async" width="726" height="660" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MjYiIGhlaWdodD0iNjYwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*TczWjw__6r_H15vgUwkcUQ.png" /></p>

<p>I decided to first visit the Himeji Castle photo spot recommended by a passerby yesterday. The spot is opposite the castle entrance, inside the “Himeji City Zoo” on the right side.</p>

<p><img src="/assets/aacd5f5cacd1/1*NAGnOqG4plLpo1AVEtwCgg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NAGnOqG4plLpo1AVEtwCgg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*UzJF2eGGGmIhgK_a3xANAA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UzJF2eGGGmIhgK_a3xANAA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Ms3Z4aqqUSa2RWA35dGc1g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Ms3Z4aqqUSa2RWA35dGc1g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*GmIPAY57WWuEBRXl6tmcBw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GmIPAY57WWuEBRXl6tmcBw.jpeg" /></p>

<p>According to the promotion, there is a capybara on the right side, but it might not have come out due to the cold. After entering, go straight across the bridge, turn right after seeing the Ferris wheel, and there is an open space behind it which is the photo spot.</p>

<p><img src="/assets/aacd5f5cacd1/1*Rupw705abK9nx8m8x0MPdw.webp" alt="&lt;https://youtube.com/shorts/XDquPOanhpQ&gt;" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Rupw705abK9nx8m8x0MPdw.jpeg" /></p>

<p><a href="https://youtube.com/shorts/XDquPOanhpQ" target="_blank">https://youtube.com/shorts/XDquPOanhpQ</a></p>

<p>This shooting spot has a great view, but unfortunately, there are no cherry blossoms or maple leaves to complement it.</p>

<p><img src="/assets/aacd5f5cacd1/1*84kPNVhjXpUqyGMCbSgE1Q.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*84kPNVhjXpUqyGMCbSgE1Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JPyc8SBiGKtcrh9r31l3iQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JPyc8SBiGKtcrh9r31l3iQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xUkRrlHFLbKJWKjnVAw59A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*xUkRrlHFLbKJWKjnVAw59A.jpeg" /></p>

<blockquote>
  <p><em>After filming, I walked around the zoo a bit. The facilities are very old (rides require extra payment), animal spaces are cramped, <strong>many animals appeared very depressed and showed repetitive behaviors, hard to watch</strong>, so I left shortly after.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*Ua__vVJVs-uFK2f4yD6erA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Ua__vVJVs-uFK2f4yD6erA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*pNyzUtrYYyKahNitfuhxBg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pNyzUtrYYyKahNitfuhxBg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KEQvJI90xEVXc4r4GQ0wew.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KEQvJI90xEVXc4r4GQ0wew.jpeg" /></p>

<p>After taking photos of Himeji Castle, walk back to the left entrance to visit inside the castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*NDwQbWnS0PRC8k90t6MIcw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NDwQbWnS0PRC8k90t6MIcw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*h0xLPH3EGTMSljU26o5GLw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*h0xLPH3EGTMSljU26o5GLw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wVG-5blJurZ0maq8rx5IDg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*wVG-5blJurZ0maq8rx5IDg.jpeg" /></p>

<p>After buying the ticket, follow the tour route straight into the castle.</p>

<p><img src="/assets/aacd5f5cacd1/1*j-yJn4Drf_XR8nZn9aO19w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*j-yJn4Drf_XR8nZn9aO19w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6NcZZitF85yrZIt8J6kSmw.webp" alt="" loading="lazy" decoding="async" width="928" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MjgiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6NcZZitF85yrZIt8J6kSmw.png" /></p>

<p>After entering indoors, you also need to take off your shoes. A plastic bag will be given to store your shoes.</p>

<p><img src="/assets/aacd5f5cacd1/1*LD55vnyY6e_RUQpxAhenDg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*LD55vnyY6e_RUQpxAhenDg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*dRqPqCQhSezJ5wsttSJeVg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*dRqPqCQhSezJ5wsttSJeVg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*zl-BV1uAW_u3qxG-KR4wLQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zl-BV1uAW_u3qxG-KR4wLQ.jpeg" /></p>

<p>Looking back at Himeji Station from the second floor, there are six floors in total. The higher you go, the smaller and steeper the stairs become, requiring you to queue to go up.</p>

<blockquote>
  <p><em>It was nearly 12:30 PM, and I was very hungry. I also forgot to bring a power bank today, so my phone was almost out of battery. With a large crowd waiting in line, I decided not to continue climbing and turned back at the second level to exit the castle.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*yjWyIFXF4Adh-MEwmBLpsA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yjWyIFXF4Adh-MEwmBLpsA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*zcaGTdjsKLn3hEBR92zb4g.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zcaGTdjsKLn3hEBR92zb4g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Zohjzd5XZTXDuf-IkRpI8Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Zohjzd5XZTXDuf-IkRpI8Q.jpeg" /></p>

<p>After leaving the castle, walk back to Sannomaru Plaza and then exit outside.</p>

<p><img src="/assets/aacd5f5cacd1/1*Q-BpgNDNoWH4qo3mxZFA5A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Q-BpgNDNoWH4qo3mxZFA5A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JmXBE3gSYq898CG_1y-rLA.webp" alt="Kushiyaki Kobe beef" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JmXBE3gSYq898CG_1y-rLA.jpeg" /></p>

<p><a href="https://maps.app.goo.gl/3ANcmi6uR1wnDgqYA" target="_blank">Kushiyaki Kobe beef</a></p>

<p>Outside Himeji Castle, I found a shop selling Kobe beef in the shopping street and took away Kobe beef rice bowls and skewers to the hotel for rest and recharge.</p>

<h4 id="1320-return-to-the-hotel-to-rest">13:20 Return to the hotel to rest</h4>

<p>Bad weather + morning hiking + phone dead = back to the hotel to rest.</p>

<p><img src="/assets/aacd5f5cacd1/1*Sp_2bL6TiDs-FTIT1hf4cg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Sp_2bL6TiDs-FTIT1hf4cg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0BxN4BJO9GZ3jCHQr7_qpg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0BxN4BJO9GZ3jCHQr7_qpg.jpeg" /></p>

<p>Besides the portion being a bit small, the Kobe beef here tastes quite good.</p>

<h4 id="1530-shopping-near-himeji-station">15:30 Shopping near Himeji Station</h4>

<p>Rested until about 15:30 and then went out again.</p>

<p><img src="/assets/aacd5f5cacd1/1*VoC5czZ4ep3Pej07BaIl8g.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*VoC5czZ4ep3Pej07BaIl8g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*bU2RJeIL3HZ-eIE5aGQ0-A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bU2RJeIL3HZ-eIE5aGQ0-A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1yvJy0uUoWXpna40MC5ZTg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*1yvJy0uUoWXpna40MC5ZTg.jpeg" /></p>

<p>There isn’t much to shop around Himeji Station. Only the Piole mall at the station and the department store in the SANYO Electric Railway building, plus a nearby shopping street. There’s no Don Quijote or McDonald’s, only one Lawson, and just one Matsumoto Kiyoshi drugstore.</p>

<p><img src="/assets/aacd5f5cacd1/1*EtwTImtOX4SlPrRo6CKBYg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EtwTImtOX4SlPrRo6CKBYg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*H_cAfQPJP8QXU_7MPgzYyA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*H_cAfQPJP8QXU_7MPgzYyA.jpeg" /></p>

<p>On the third floor of Piole, Kiddy Land has some GiiKawa items. There aren’t many people here, so no need to queue. I brought three little ones home. Unfortunately, I couldn’t get the Usagi pajama version QQ.</p>

<p><img src="/assets/aacd5f5cacd1/1*kG_MA7MnFH9QHo2YPKbofw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kG_MA7MnFH9QHo2YPKbofw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*DZIjEJdNFvBHGIdphvJ8Kw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DZIjEJdNFvBHGIdphvJ8Kw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*AGyjVuNTXv_gO9DfdSKAIg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*AGyjVuNTXv_gO9DfdSKAIg.jpeg" /></p>

<p>Wandering around the shopping street, looking for a capsule toy machine.</p>

<p><img src="/assets/aacd5f5cacd1/1*61f9uD9NUYRW1QumaFpNLA.webp" alt="" loading="lazy" decoding="async" width="1400" height="961" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk2MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*61f9uD9NUYRW1QumaFpNLA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wxoORyKqaWXDmUt9lk9vRg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1016" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*wxoORyKqaWXDmUt9lk9vRg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YtH0na_odN_spz7_I1o6cQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YtH0na_odN_spz7_I1o6cQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*yNe-JTCDJuloVlg7xdtGwA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*yNe-JTCDJuloVlg7xdtGwA.jpeg" /></p>

<p>Although I just said there’s not much to shop for, there is at least a gacha capsule toy store and an Animate shop.</p>

<p><img src="/assets/aacd5f5cacd1/1*o5h-XaDa6kZ6kjXjPAq7Rw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*o5h-XaDa6kZ6kjXjPAq7Rw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*9u7HGC1YdSWWcS_1_iqMzw.webp" alt="" loading="lazy" decoding="async" width="695" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*9u7HGC1YdSWWcS_1_iqMzw.png" /></p>

<p>It started raining heavily near dusk, but fortunately, there were no special plans for today.</p>

<h4 id="1800-dinner">~=18:00 Dinner</h4>

<p><img src="/assets/aacd5f5cacd1/1*UJqc0DWngnbESTHo_PybYw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*UJqc0DWngnbESTHo_PybYw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*IJ8XIPnSJdaAx-6OkmCoJg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IJ8XIPnSJdaAx-6OkmCoJg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*66Zxcn9rbOdkL3GqjV_cNA.webp" alt="O-dashi, Wine, and Cuisine. motto" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*66Zxcn9rbOdkL3GqjV_cNA.jpeg" /></p>

<p><a href="https://maps.app.goo.gl/arsJs8CYEW3kAdg46" target="_blank">O-dashi, Wine, and Cuisine. motto</a></p>

<p>I randomly picked a restaurant with Kobe beef in the shopping street for dinner.</p>

<p><img src="/assets/aacd5f5cacd1/1*5Cc0K6Xd5p0FMJbxhB4gOg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5Cc0K6Xd5p0FMJbxhB4gOg.jpeg" /></p>

<p>This place features a lucky cat dessert as an appetizer, which is not too sweet and quite good.</p>

<p><img src="/assets/aacd5f5cacd1/1*1b3oeJs9c2rjOL-TaleOQg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1b3oeJs9c2rjOL-TaleOQg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6ijkG1uNzojnZlIkRc2fMA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6ijkG1uNzojnZlIkRc2fMA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*i_EvfGqTY-fQQdXlK7aBjA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*i_EvfGqTY-fQQdXlK7aBjA.jpeg" /></p>

<p>Ordered Kobe beef steak, ochazuke, and draft beer.</p>

<p>I found their Kobe beef steak quite average; it was a bit tough and lacked the distinctive Kobe beef flavor. However, the chazuke (tea poured over rice) was quite tasty.</p>

<p><img src="/assets/aacd5f5cacd1/1*Z0oLUni88-W6TPpVm2gF6g6g.jpeg" alt="" loading="lazy" decoding="async" width="1200" height="800" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" /></p>

<p><img src="/assets/aacd5f5cacd1/1*q_ek8sUX3nM5xCBTPkXa6Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*q_ek8sUX3nM5xCBTPkXa6Q.jpeg" /></p>

<p>After eating, I wandered around a bit—Himeji after the rain.</p>

<h4 id="2000-return-to-hotel-to-rest--watch-the-taiwan-japan-game">~=20:00 Return to hotel to rest &amp; watch the Taiwan-Japan game</h4>

<p><img src="/assets/aacd5f5cacd1/1*j17lqXN-p1caPqDaC6z-rA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*j17lqXN-p1caPqDaC6z-rA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*oBZH4_-Pb7difuUCdwvSBw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oBZH4_-Pb7difuUCdwvSBw.jpeg" /></p>

<p>The Japanese broadcast is completely unintelligible to me, but their coverage is very thorough. On the left, there are baseball knowledge supplements, and on the right, a detailed analysis of the pitch types.</p>

<p><img src="/assets/aacd5f5cacd1/1*4wJ-WWxbgYAeGgcf-KPtyQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4wJ-WWxbgYAeGgcf-KPtyQ.jpeg" /></p>

<p>The final match was a win for Japan.</p>

<blockquote>
  <p><em>Good night, Himeji.</em></p>
</blockquote>

<h3 id="day-6-1117-sunday-osaka-kobe">Day 6 (11/17 Sunday) Osaka, Kobe</h3>

<p>Originally planned to visit Naruto Whirlpools and Akashi Kaikyō Bridge, but due to bad weather, switched to shopping in Osaka and Kobe.</p>

<h4 id="-0845-take-the-shinkansen-non-reserved-seat-to-shin-osaka">~= 08:45 Take the Shinkansen non-reserved seat to Shin-Osaka</h4>

<p><img src="/assets/aacd5f5cacd1/1*T5blo2Kdt0KsYaItd6H5dA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*T5blo2Kdt0KsYaItd6H5dA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*RvSje4c8thVxGXJpBZwnTA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*RvSje4c8thVxGXJpBZwnTA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*gGkwpzey7E9tlg0gbbV35g.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gGkwpzey7E9tlg0gbbV35g.jpeg" /></p>

<p>Taking the Shinkansen to Shin-Osaka (25 minutes) and then transferring to the Midosuji Line to Osaka takes about the same time as taking a regular train directly to Osaka. Since there are many Shinkansen departures, I chose the more comfortable Shinkansen where I could also charge my devices.</p>

<h4 id="917-arrived-at-shin-osaka">9:17 Arrived at Shin-Osaka</h4>

<p><img src="/assets/aacd5f5cacd1/1*apUd_VYqJJ_mM_Kr_R3YnQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="765" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*apUd_VYqJJ_mM_Kr_R3YnQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6ptZt3XbRNnIK1Ph62j9eg.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6ptZt3XbRNnIK1Ph62j9eg.jpeg" /></p>

<p>After arriving at Shin-Osaka, you still need to walk to the Midosuji Line.</p>

<p>First, visit <a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">Namba Yasaka Shrine</a> in Kansai, which I missed last year.</p>

<p><img src="/assets/aacd5f5cacd1/1*vWqBuGY0nf4NQY1pw5Pb7w.webp" alt="" loading="lazy" decoding="async" width="882" height="1095" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODIiIGhlaWdodD0iMTA5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vWqBuGY0nf4NQY1pw5Pb7w.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*uCKGMuqTmPVL61gvJU_AKg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uCKGMuqTmPVL61gvJU_AKg.jpeg" /></p>

<p>After getting off at Namba Station, walk a short distance into an alley where you’ll find the shrine.</p>

<blockquote>
  <p><em>Just found out that Osaka is also testing facial recognition for station entry and exit.</em></p>
</blockquote>

<h4 id="950-arrive-at-namba-yasaka-shrine">~=9:50 Arrive at Namba Yasaka Shrine</h4>

<p><img src="/assets/aacd5f5cacd1/1*DCyi2vXA6WzEvhxJ9Qx6Uw.webp" alt="" loading="lazy" decoding="async" width="866" height="1107" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjYiIGhlaWdodD0iMTEwNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DCyi2vXA6WzEvhxJ9Qx6Uw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*qu1yc41lJWfbKvt2iiI8Og.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*qu1yc41lJWfbKvt2iiI8Og.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*eENL5QrW5I0g_Bb3Iqlc6Q.webp" alt="" loading="lazy" decoding="async" width="1200" height="873" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*eENL5QrW5I0g_Bb3Iqlc6Q.png" /></p>

<p>The shrine is small and crowded with tourists. I took a few photos and left shortly after.</p>

<p><img src="/assets/aacd5f5cacd1/1*Ms6lilqCGKSJEQY-2Ad_NQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Ms6lilqCGKSJEQY-2Ad_NQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1YnEjMoc8KV-OaV08XrMcA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1YnEjMoc8KV-OaV08XrMcA.jpeg" /></p>

<p>After leaving, continue to the next station “Daikokucho,” take one stop to “Dobutsuen-mae,” and get off to head to Tsutenkaku.</p>

<p><img src="/assets/aacd5f5cacd1/1*bxBxGhqonhPE-kNU7jl5DA.webp" alt="" loading="lazy" decoding="async" width="877" height="1071" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzciIGhlaWdodD0iMTA3MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bxBxGhqonhPE-kNU7jl5DA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_ZGMWno1bnS5VIsevjVuGw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_ZGMWno1bnS5VIsevjVuGw.jpeg" /></p>

<p>After exiting the station, go through the underground passage and shopping street, then continue straight to see Tsutenkaku Tower.</p>

<blockquote>
  <p><em>The area here feels quite unsafe. Right after exiting the station and passing through the underpass, there are many homeless people, and some are loudly arguing. It’s a bit scary.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*WOzCL0xA6hirl6Ru3iYxlg.webp" alt="" loading="lazy" decoding="async" width="859" height="1130" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NTkiIGhlaWdodD0iMTEzMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*WOzCL0xA6hirl6Ru3iYxlg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xWz0KkdvI5042L31ritmjw.webp" alt="" loading="lazy" decoding="async" width="1200" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*xWz0KkdvI5042L31ritmjw.png" /></p>

<p>The shopping street alley is narrow with food on both sides. A few shops seem to be popular spots with long queues and many people.</p>

<p><img src="/assets/aacd5f5cacd1/1*-cCMXQtTVn7ExtRxRLyOcA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-cCMXQtTVn7ExtRxRLyOcA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*89McCOa7Itm_L97WibLASw.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*89McCOa7Itm_L97WibLASw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*GiVI9kQjjJLKp5E0BYq__Q.webp" alt="" loading="lazy" decoding="async" width="875" height="1093" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzUiIGhlaWdodD0iMTA5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GiVI9kQjjJLKp5E0BYq__Q.png" /></p>

<p>Around 10:30, I arrived at the base of Tsutenkaku. There was a long line of people waiting to go up, so I gave up immediately.</p>

<p><img src="/assets/aacd5f5cacd1/1*oRVH7x_G3Ga19FD04YQCIg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oRVH7x_G3Ga19FD04YQCIg.jpeg" /></p>

<p>Walk back to the subway station to return to Osaka.</p>

<h4 id="1100-return-to-osaka-umeda">~=11:00 Return to Osaka Umeda</h4>

<p><img src="/assets/aacd5f5cacd1/1*A97lYcYLqPya_YJp3UFQxw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*A97lYcYLqPya_YJp3UFQxw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kbWzjvQnQicbyGhGx7qLAw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kbWzjvQnQicbyGhGx7qLAw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*i-1TYGeHu_AxlzRaluBkGw.webp" alt="" loading="lazy" decoding="async" width="874" height="1099" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzQiIGhlaWdodD0iMTA5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*i-1TYGeHu_AxlzRaluBkGw.png" /></p>

<p>After exiting the station, head to LINKS Department Store. The 5th floor connects directly to the Yodobashi gacha toy area.</p>

<p><img src="/assets/aacd5f5cacd1/1*LHUFlm9UQQto7rkwZ4oJ6A.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*LHUFlm9UQQto7rkwZ4oJ6A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4BeiVIybWx9ixjgSA3Kp6A.webp" alt="" loading="lazy" decoding="async" width="1400" height="977" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4BeiVIybWx9ixjgSA3Kp6A.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*RafKwlM7JvNBLvYwtTFhrA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*RafKwlM7JvNBLvYwtTFhrA.jpeg" /></p>

<p>After visiting LINKS, walk through Osaka Station and the Time Square upstairs to reach Osaka Daimaru Department Store.</p>

<p><img src="/assets/aacd5f5cacd1/1*a02vHVVx_CBehnuTqyh9jA.webp" alt="" loading="lazy" decoding="async" width="1200" height="849" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*a02vHVVx_CBehnuTqyh9jA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Xa_Xd-BwWMKuukRyd5Tz8w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Xa_Xd-BwWMKuukRyd5Tz8w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*iCYhY01vtTyAvBSsh3NWuw.webp" alt="" loading="lazy" decoding="async" width="1200" height="843" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*iCYhY01vtTyAvBSsh3NWuw.png" /></p>

<p>On the 13th floor, there is a Pokémon Center, Nintendo Osaka, CAPCOM…</p>

<p><img src="/assets/aacd5f5cacd1/1*EJivBHlcWiG1tgWQoVQWQQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EJivBHlcWiG1tgWQoVQWQQ.jpeg" /></p>

<p>Checked out some Zelda merchandise, but <a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">already bought a lot last time</a>, so I left without buying anything this time.</p>

<p><img src="/assets/aacd5f5cacd1/1*1H-S0fCwZQFmXJNLTMEISQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*1H-S0fCwZQFmXJNLTMEISQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YxL275thElHiiaMctikMGg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YxL275thElHiiaMctikMGg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*pNCyg_53IysVfZa5ndJLXg.webp" alt="" loading="lazy" decoding="async" width="1200" height="845" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg0NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pNCyg_53IysVfZa5ndJLXg.png" /></p>

<p>Passing by the Hanshin Tigers merchandise store.</p>

<p>Getting ready to eat at Shake Shack. I tried it in Bangkok last July and still crave it <a href="https://medium.com/ztravel/%E9%81%8A%E8%A8%98-2024-%E6%B3%B0%E5%9C%8B%E6%9B%BC%E8%B0%B7-bangkok-5-%E6%97%A5%E8%87%AA%E7%94%B1%E8%A1%8C-b7e7c0938985?source=collection_home---4------0-----------------------" target="_blank">link</a>. Now enjoying it again in Osaka.</p>

<h4 id="1200-shake-shack">~=12:00 Shake Shack</h4>

<p><img src="/assets/aacd5f5cacd1/1*EV2jpsFyxgjE9lVIg3isgw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EV2jpsFyxgjE9lVIg3isgw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*w3Nl5TcRSrFT-9ugnIpuug.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*w3Nl5TcRSrFT-9ugnIpuug.jpeg" /></p>

<p><a href="https://maps.app.goo.gl/LfNFo7TJK7gEZrcN6" target="_blank">Shake Shack Hanshin Umeda Store</a> is right next to the Hanshin Tigers shop.</p>

<p><img src="/assets/aacd5f5cacd1/1*GZ_3ywx9RZuH5BuTp0bseQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GZ_3ywx9RZuH5BuTp0bseQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KdW0FlYYw-Iit-2uYclbLQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*KdW0FlYYw-Iit-2uYclbLQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*chnnVelVCqU-6IsNNWHEGg.webp" alt="" loading="lazy" decoding="async" width="873" height="1136" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzMiIGhlaWdodD0iMTEzNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*chnnVelVCqU-6IsNNWHEGg.png" /></p>

<p>I ordered through the self-service kiosk this time, choosing the avocado bacon chicken burger and a milkshake. This place has a spacious interior with plenty of seats.</p>

<p><img src="/assets/aacd5f5cacd1/1*Hqk1yAuVB8DpddJFgHrPZg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Hqk1yAuVB8DpddJFgHrPZg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*-Ud0FELy464hSGqc-6i2mg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-Ud0FELy464hSGqc-6i2mg.jpeg" /></p>

<h4 id="osaka-station">Osaka Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*cBx2n43-tFXz7zHHk9YIBQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*cBx2n43-tFXz7zHHk9YIBQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*fhj864wdmrcC9LyNv0Xl6A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*fhj864wdmrcC9LyNv0Xl6A.jpeg" /></p>

<p>After eating, I wandered around near Osaka Station and decided to visit Osaka’s Kiddy Land to look for Gii Kawa.</p>

<p><img src="/assets/aacd5f5cacd1/1*lKUQzWMeXVljaUZvdyKy9Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*lKUQzWMeXVljaUZvdyKy9Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*oyvLMv7OPW7GbnylhM072g.webp" alt="" loading="lazy" decoding="async" width="1200" height="856" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oyvLMv7OPW7GbnylhM072g.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*qyNQwZ0U6ZWvoyVpBq69yA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*qyNQwZ0U6ZWvoyVpBq69yA.jpeg" /></p>

<p>Got lost in the Osaka underground mall and walked for a long time before finding Hankyu Sanbangai. Then I took the underground passage from the South Building to the North Building, and the food court was right upstairs.</p>

<p><img src="/assets/aacd5f5cacd1/1*EJF9zT0iOWMNlMddrLdCkQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EJF9zT0iOWMNlMddrLdCkQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7vqSVT1kVyDJQMFtlcOy_w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7vqSVT1kVyDJQMFtlcOy_w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BYv-IJr_v2YuYmD_-FLjoA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BYv-IJr_v2YuYmD_-FLjoA.jpeg" /></p>

<p>Jiikawa has its own separate counter on the other side.</p>

<h4 id="1300-kiddy-land-osaka-chiikawa">13:00 Kiddy Land Osaka Chiikawa</h4>

<p><img src="/assets/aacd5f5cacd1/1*Cct5wKtjkqkFmVhrZSEraw.webp" alt="" loading="lazy" decoding="async" width="1200" height="865" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Cct5wKtjkqkFmVhrZSEraw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xvhRITM1s26Q9sgxLP5ZWg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*xvhRITM1s26Q9sgxLP5ZWg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*r4v-nQd9yCftkhFxmVafyA.webp" alt="" loading="lazy" decoding="async" width="880" height="1095" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODAiIGhlaWdodD0iMTA5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*r4v-nQd9yCftkhFxmVafyA.png" /></p>

<p>There were quite a few people in line, estimated to wait at least 40 minutes.</p>

<blockquote>
  <p><strong><em>Obviously, I didn’t spend time waiting in line.</em></strong></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*uJHEegOR7Zr8Vlp-DqbYCA.webp" alt="" loading="lazy" decoding="async" width="1163" height="636" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTYzIiBoZWlnaHQ9IjYzNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uJHEegOR7Zr8Vlp-DqbYCA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*QYlEBbbuFij9v2_60PQXeg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*QYlEBbbuFij9v2_60PQXeg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*E3rZ-ET_Z8qEO6Eo3QIyeA.webp" alt="" loading="lazy" decoding="async" width="868" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjgiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*E3rZ-ET_Z8qEO6Eo3QIyeA.png" /></p>

<p>There is a <a href="/posts/travel-journals/kyushu-10-day-solo-travel-guide-explore-fukuoka-nagasaki-kumamoto-efficiently-d78e0b15a08a/">Kumamon</a> event on the first floor.</p>

<p><img src="/assets/aacd5f5cacd1/1*RW5sFcypjE_mj4CVwxKotQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="852" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg1MiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*RW5sFcypjE_mj4CVwxKotQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ipoT-8QkWmfXhInjaK3Q2w.webp" alt="" loading="lazy" decoding="async" width="1330" height="2364" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzMwIiBoZWlnaHQ9IjIzNjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*ipoT-8QkWmfXhInjaK3Q2w.jpeg" /></p>

<p>Maybe because it’s a holiday, there are more events. Outside Kiddy Land, there is also a Kanahei photo event. (Another Usagi XD)</p>

<h4 id="1320-head-to-kobe">13:20 Head to Kobe</h4>

<p><img src="/assets/aacd5f5cacd1/1*iuobD0lpLgt6UCJ8Wqk3ag.webp" alt="" loading="lazy" decoding="async" width="1400" height="1012" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMTIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*iuobD0lpLgt6UCJ8Wqk3ag.png" /></p>

<p>I got a bit lost in the underground mall before finally finding Osaka Umeda subway station, then took the Hankyu Kobe Line to Kobe Sannomiya (Hankyu).</p>

<h4 id="1350-arrive-at-kobe">13:50 Arrive at Kobe</h4>

<p>It’s still early, so I plan to visit <a href="https://maps.app.goo.gl/fspZDr7MyGMpT24r6" target="_blank">Kobe Animal Kingdom</a> to see the capybaras first.</p>

<p><img src="/assets/aacd5f5cacd1/1*L_861E8BgHXpjDqG7B-ExA.webp" alt="" loading="lazy" decoding="async" width="1200" height="816" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgxNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*L_861E8BgHXpjDqG7B-ExA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wkKdFGjiN_C6aGWTNSNxXA.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wkKdFGjiN_C6aGWTNSNxXA.jpeg" /></p>

<p>Google Map asked me to take the “New Transit Waterfront Artificial Island Line,” but I wandered around the underground mall for a long time and couldn’t find the corresponding signs. (Lost Day)</p>

<p><img src="/assets/aacd5f5cacd1/1*OAOZe3cdGLU6r_6Nv2QEww.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OAOZe3cdGLU6r_6Nv2QEww.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Rx_UFhPm-pM6Nxn2DTKx6w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Rx_UFhPm-pM6Nxn2DTKx6w.jpeg" /></p>

<p>Only after exiting the station did I realize this is the one that runs in the sky, entering from the second floor of Sannomiya Station.</p>

<p><img src="/assets/aacd5f5cacd1/1*yWTsJjuy_Xn01e9Lus8YOw.webp" alt="" loading="lazy" decoding="async" width="1400" height="585" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjU4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yWTsJjuy_Xn01e9Lus8YOw.png" /></p>

<p>The station was very crowded, and I later found out that today happened to be the <a href="https://zh-tw.kobe-marathon.net/2024/global/" target="_blank">Kobe Marathon</a>.</p>

<p>Lost Day… Because the bus arrived just then and I didn’t check carefully, I got on the wrong bus heading to the North Pier. The tragedy started from that moment.</p>

<p><img src="/assets/aacd5f5cacd1/1*sURfSLVO2IkE2QPTq3zm7g.webp" alt="" loading="lazy" decoding="async" width="1265" height="286" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY1IiBoZWlnaHQ9IjI4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*sURfSLVO2IkE2QPTq3zm7g.png" /></p>

<blockquote>
  <p><strong><em>To get to Kobe Animal Kingdom, take the train heading to Kobe Airport. The train to Kitafuto will reverse at Kitafuto and return to Kobe.</em></strong> <em>⚠️⚠️⚠️</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*Yb1jgpn5nKnHA0to0JlBvA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*Yb1jgpn5nKnHA0to0JlBvA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*z8eEqxV_DQKM1FGLRxRU7A.webp" alt="" loading="lazy" decoding="async" width="879" height="1096" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzkiIGhlaWdodD0iMTA5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*z8eEqxV_DQKM1FGLRxRU7A.png" /></p>

<p>The Kobe Marathon features road closures with runners going onto the elevated highway. This subway line, like Taipei Metro’s Wenhu Line, is an early model of a rubber-tired metro.</p>

<h4 id="1420-realized-i-was-on-the-wrong-train-got-off-at-nakafuto-station">14:20 Realized I was on the wrong train, got off at Nakafuto Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*IGsTOnGQ2wiFBthumdIybw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*IGsTOnGQ2wiFBthumdIybw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_BOkMH5_0AnTJcaZCtwUtg.webp" alt="" loading="lazy" decoding="async" width="553" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTMiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_BOkMH5_0AnTJcaZCtwUtg.jpeg" /></p>

<p>The vibe felt off here, so I got off the bus immediately. Google Maps suggested I walk back to Civic Square Station to catch the next one.</p>

<p><img src="/assets/aacd5f5cacd1/1*wCBuXOfGc6fSMVTM1MLZ_w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wCBuXOfGc6fSMVTM1MLZ_w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*g8uGboUzgjpperWLcaCe-A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*g8uGboUzgjpperWLcaCe-A.jpeg" /></p>

<p>Perhaps because it is a reclaimed port area, this place feels very desolate and the walk seems quite long.</p>

<p><img src="/assets/aacd5f5cacd1/1*ZyXQTkEYvXAbIv0TVUgCzQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="978" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ZyXQTkEYvXAbIv0TVUgCzQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*5ZyBkEGQ_NiYON5ofd9faw.webp" alt="" loading="lazy" decoding="async" width="1200" height="876" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*5ZyBkEGQ_NiYON5ofd9faw.png" /></p>

<p>Walking quickly to the front of Civic Square, I wondered why it was so lively. It turned out to be the finish line event area for the Kobe Marathon.</p>

<p><img src="/assets/aacd5f5cacd1/1*ULve9yF_dxl_KN_wjREO0g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ULve9yF_dxl_KN_wjREO0g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YGSebYSQT5maJTHBLgu2xA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YGSebYSQT5maJTHBLgu2xA.jpeg" /></p>

<blockquote>
  <p><em>Tragedy 2: Due to overcrowding, this station is closed. You need to walk further to Minami Koen Station.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*2Gq2LpscnF2cQqBST6ueyA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*2Gq2LpscnF2cQqBST6ueyA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*gW7yF6pDt4RDQIzLnd-f0A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gW7yF6pDt4RDQIzLnd-f0A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*HoUGLVRBxl2s45E40BMLGQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HoUGLVRBxl2s45E40BMLGQ.jpeg" /></p>

<p>From Minami Koen Station, it’s just one more stop, but I was too lazy to take the subway and walked straight to Kobe Animal Kingdom…</p>

<p>As mentioned before, it feels very remote and indeed it is quite far—about 2 kilometers (around 30 minutes) walking from Nakakō Wharf.</p>

<h4 id="1450-arrive-at-kobe-animal-kingdom">14:50 Arrive at Kobe Animal Kingdom</h4>

<p><img src="/assets/aacd5f5cacd1/1*HzgFgMtmQ3Sw6piVC4Mblg.webp" alt="" loading="lazy" decoding="async" width="1400" height="977" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk3NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HzgFgMtmQ3Sw6piVC4Mblg.png" /></p>

<p>Ticket price: 2,200 JPY</p>

<p><img src="/assets/aacd5f5cacd1/1*bwUP-L5-s7kjH5O2TAcf6g.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*bwUP-L5-s7kjH5O2TAcf6g.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*jqf3mJy-K5HtBDnG0lcThA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*jqf3mJy-K5HtBDnG0lcThA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*PYEx2kx73xmpbMrGZNTcqg.webp" alt="Shoebill, Pallas's Cat, Red Panda" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PYEx2kx73xmpbMrGZNTcqg.jpeg" /></p>

<p>Shoebill, Pallas’s cat, Red panda</p>

<p>Kobe Animal Kingdom is partly indoor and partly outdoor, but the layout is well planned. There are various birds and animals, most of which you can get close to. It’s great for families to spend half a day here. The animals are in much better condition compared to Himeji City Zoo, and the environment is very clean.</p>

<h4 id="capybara">Capybara</h4>

<p><img src="/assets/aacd5f5cacd1/1*DK2DEH8iPJmiTyDHldeqHQ.webp" alt="" loading="lazy" decoding="async" width="876" height="1087" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzYiIGhlaWdodD0iMTA4NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DK2DEH8iPJmiTyDHldeqHQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*CqjDL7S99NtLuamD8V3A0w.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CqjDL7S99NtLuamD8V3A0w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*XtvMffuoulVGOt0AK88BbQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XtvMffuoulVGOt0AK88BbQ.jpeg" /></p>

<p>The purpose of coming here is achieved: to touch the capybaras. They are so fat, and their fur feels rough like a broom.</p>

<p><img src="/assets/aacd5f5cacd1/1*AoazZpBcYuh-LpiZLCk6fw.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*AoazZpBcYuh-LpiZLCk6fw.jpeg" /></p>

<p>Each one looks so lazy, it must be great not having to work!</p>

<p><img src="/assets/aacd5f5cacd1/1*Dui-4l3rOZkGVjVuEAjqZw.webp" alt="" loading="lazy" decoding="async" width="569" height="764" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjkiIGhlaWdodD0iNzY0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*Dui-4l3rOZkGVjVuEAjqZw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*a0JZ_DEQph3dAI-Mlzhxvw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*a0JZ_DEQph3dAI-Mlzhxvw.jpeg" /></p>

<p>Maybe because it’s too fat, feeding food inside the area is no longer allowed, and you cannot feed it food from outside or put anything on its head.</p>

<p>In another area, there are kangaroos and sheep for close-up interactions.</p>

<p><img src="/assets/aacd5f5cacd1/1*D4K3htSuQ7vwCDGqizRLrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*D4K3htSuQ7vwCDGqizRLrg.jpeg" /></p>

<p>Remember to wash your hands before and after touching.</p>

<p><img src="/assets/aacd5f5cacd1/1*A540bEA8Z1WvYv2BZZB6XQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*A540bEA8Z1WvYv2BZZB6XQ.jpeg" /></p>

<p>There was also a seal, but the space seemed too small, so it kept swimming in circles QQ</p>

<p><img src="/assets/aacd5f5cacd1/1*V3fEMjXiFUrjBW01U3hWOw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*V3fEMjXiFUrjBW01U3hWOw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*3YjSHmLP5S50YO2yFl6aBg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*3YjSHmLP5S50YO2yFl6aBg.jpeg" /></p>

<p>Penguin feeding (requires a numbered ticket).</p>

<p><img src="/assets/aacd5f5cacd1/1*N2xebJcxnHYGn8e1rTojCw.webp" alt="" loading="lazy" decoding="async" width="1330" height="2364" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMzMwIiBoZWlnaHQ9IjIzNjQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*N2xebJcxnHYGn8e1rTojCw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*a_o37TUg0JELGpeHIlmrZQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*a_o37TUg0JELGpeHIlmrZQ.jpeg" /></p>

<p>The cute little clawed otter family kept chirping.</p>

<p><img src="/assets/aacd5f5cacd1/1*CuP3tdfDMluPWnxc-9Wq1A.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*CuP3tdfDMluPWnxc-9Wq1A.jpeg" /></p>

<p>There is a Malayan tapir here at the otter enclosure, but it’s mostly ignored QQ</p>

<p><img src="/assets/aacd5f5cacd1/1*JNkNZEh__ouaMUnwCCIdNg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*JNkNZEh__ouaMUnwCCIdNg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xniRR-ET-l2adsy9Yd30Mw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*xniRR-ET-l2adsy9Yd30Mw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*aW3oScEuZgY3pgBar7RWrA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*aW3oScEuZgY3pgBar7RWrA.jpeg" /></p>

<p>Toucan and owl, along with pheasants that appear near your feet. (Be careful not to step on them ⚠️)</p>

<p><img src="/assets/aacd5f5cacd1/1*yEYyShOUZxvFBGvis-Vrow.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yEYyShOUZxvFBGvis-Vrow.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cgKffBX23EMZtHYSuLmf9w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*cgKffBX23EMZtHYSuLmf9w.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*7Ic9m_24xoIpTB-TS_o_wg.webp" alt="King Julien and the pelican who eats colleagues, also the nocturnal owl" loading="lazy" decoding="async" width="675" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NzUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*7Ic9m_24xoIpTB-TS_o_wg.jpeg" /></p>

<p>King Julian and pelicans that eat colleagues, also night-dwelling owls</p>

<p><img src="/assets/aacd5f5cacd1/1*zUgtU9kQCKScJsprv1NTQQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="964" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk2NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zUgtU9kQCKScJsprv1NTQQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*pM5AvtCSA_REaZKTOYODgA.webp" alt="" loading="lazy" decoding="async" width="860" height="1091" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjAiIGhlaWdodD0iMTA5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*pM5AvtCSA_REaZKTOYODgA.png" /></p>

<p>Indoors, there is also an interaction area with rabbits, cats, and dogs (requires a numbered ticket), as well as dining and souvenir shops… and more.</p>

<p>Overall, the experience was quite good!</p>

<h4 id="1550-departure-preparing-to-return-to-kobe">15:50 Departure, preparing to return to Kobe</h4>

<p>The main purpose was to see the capybaras, and since time was limited, the stay was brief.</p>

<p><img src="/assets/aacd5f5cacd1/1*yfpUfEWTujRfFn1SLBi8aQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*yfpUfEWTujRfFn1SLBi8aQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*42N4yOOTADXSR3TnaE0foA.webp" alt="" loading="lazy" decoding="async" width="1400" height="983" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*42N4yOOTADXSR3TnaE0foA.png" /></p>

<h4 id="-1605-arrive-at-kobe-sannomiya-station">~= 16:05 Arrive at (Kobe) Sannomiya Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*oNUQVDNJXP_zpuS0vPWIWA.webp" alt="" loading="lazy" decoding="async" width="872" height="1098" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iMTA5OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oNUQVDNJXP_zpuS0vPWIWA.png" /></p>

<p>Due to the Kobe Marathon, there were many people exiting the station.</p>

<p><img src="/assets/aacd5f5cacd1/1*92IdBTMJ1Z1AyzHdCQVNHA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*92IdBTMJ1Z1AyzHdCQVNHA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*g-zbhehAAcO3tmvdJ6Dotw.webp" alt="" loading="lazy" decoding="async" width="858" height="1081" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NTgiIGhlaWdodD0iMTA4MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*g-zbhehAAcO3tmvdJ6Dotw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4kvY1I-JyKoMbo6Xo3Mg6A.webp" alt="" loading="lazy" decoding="async" width="877" height="1079" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzciIGhlaWdodD0iMTA3OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4kvY1I-JyKoMbo6Xo3Mg6A.png" /></p>

<p>There is still over an hour before mealtime, so I first wandered around near Kobe Sannomiya, looking for gacha capsules and Kiddy Land as usual.</p>

<p><img src="/assets/aacd5f5cacd1/1*LAu0vz0PiOzK3QC8xQbeJg.webp" alt="" loading="lazy" decoding="async" width="872" height="1076" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iMTA3NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*LAu0vz0PiOzK3QC8xQbeJg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*eDM9G1Lrm5nQLnusWKD7uA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*eDM9G1Lrm5nQLnusWKD7uA.jpeg" /></p>

<p>Kiddly Land in Kobe was not crowded, but there were very few Gikawa products. I couldn’t find the Usagi pajamas I wanted.</p>

<p><img src="/assets/aacd5f5cacd1/1*Cmt7wQ4UV4cYAaTNzxvgHA.webp" alt="" loading="lazy" decoding="async" width="878" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzgiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Cmt7wQ4UV4cYAaTNzxvgHA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*q7C947BYHvNxBDhPSGG6uQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="819" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgxOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*q7C947BYHvNxBDhPSGG6uQ.png" /></p>

<p>Center Plaza is a bit old-fashioned but has many shops to explore.</p>

<p><img src="/assets/aacd5f5cacd1/1*8vFujFuXpB65kosAzXrtkw.webp" alt="" loading="lazy" decoding="async" width="1400" height="986" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk4NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8vFujFuXpB65kosAzXrtkw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*XD7jraHi5_yQZfB7YAkv9Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="959" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk1OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XD7jraHi5_yQZfB7YAkv9Q.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*WlCIdUIpqTHUN4jwg1x_Hg.webp" alt="" loading="lazy" decoding="async" width="820" height="1122" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MjAiIGhlaWdodD0iMTEyMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*WlCIdUIpqTHUN4jwg1x_Hg.png" /></p>

<p>Go to the second floor to look for capsule toys and check the second-hand store to see if they have the Usagi I want.</p>

<p><img src="/assets/aacd5f5cacd1/1*3AtESgoEYKHf6RFMBy7a4A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*3AtESgoEYKHf6RFMBy7a4A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*DhlS3byoBALyjqUNQNZlqw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DhlS3byoBALyjqUNQNZlqw.jpeg" /></p>

<p>There is a Nobunaga Bookstore where you can buy omamori (amulets) as gifts for friends, hehe.</p>

<h4 id="1700-kobe-beef-kissho-kichi">~=17:00 <a href="https://maps.app.goo.gl/8UcmAfUDGpH8F1PX8" target="_blank">Kobe Beef Kissho Kichi</a></h4>

<p><img src="/assets/aacd5f5cacd1/1*_kTtTzMcYzgucSSbZBpOMg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_kTtTzMcYzgucSSbZBpOMg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YeGNnRYe3W2AId3Ey5Xoxg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YeGNnRYe3W2AId3Ey5Xoxg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*AF8Ium09DuuXVdb6wgirtQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*AF8Ium09DuuXVdb6wgirtQ.jpeg" /></p>

<p>There is a Kobe Beef Kicchou Kicchou on B1, and we came early for dinner around 5 PM.</p>

<p>It was still early, and there weren’t many people; the owner was very enthusiastic when he heard I was from Taiwan. This meal was also a main reason for coming to Kobe—<strong>to have a good meal of Kobe beef</strong>—since I had been eating randomly for the past few days.</p>

<p><img src="/assets/aacd5f5cacd1/1*TplkGy7shiHlWAHyLf9-KQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="824" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*TplkGy7shiHlWAHyLf9-KQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0ufGhslTqU6l0UUlsFCOCg.webp" alt="" loading="lazy" decoding="async" width="822" height="626" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MjIiIGhlaWdodD0iNjI2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*0ufGhslTqU6l0UUlsFCOCg.png" /></p>

<p>I ordered the “Kobe Beef Lean Steak 220g” for 7,700 yen (pre-tax ⚠️, tax included at checkout) with a set meal (salad, rice, soup) and draft beer.</p>

<p><img src="/assets/aacd5f5cacd1/1*794jNjGugkDnsJOPEdOJZw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*794jNjGugkDnsJOPEdOJZw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*xSB_UvOX-MwvXzzmDx-KoQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*xSB_UvOX-MwvXzzmDx-KoQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*H55tOkGS36r6WTGAqT560w.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*H55tOkGS36r6WTGAqT560w.jpeg" /></p>

<p>The lettuce is fresh and crisp, and the soup is beef broth, very tasty.</p>

<p><img src="/assets/aacd5f5cacd1/1*MJLdfXQZoH_4vUy0KUOq5w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1002" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDIiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*MJLdfXQZoH_4vUy0KUOq5w.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MUe5xLDpu4wLU2OCnrddTw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MUe5xLDpu4wLU2OCnrddTw.jpeg" /></p>

<p>The meat is tender, juicy, and full of flavor. Compared to the Kobe beef I had yesterday, this is the true taste of Kobe beef.</p>

<blockquote>
  <p><em>But if I had to choose, I still prefer teppan dishes over Western-style food.</em></p>
</blockquote>

<p>Final actual expense: <code class="language-plaintext highlighter-rouge">$2,158 TWD</code>.</p>

<h4 id="1740-finished-eating-and-left-heading-to-kobe-tower">17:40 Finished eating and left, heading to Kobe Tower</h4>

<p>After eating, walk to the old foreign settlement along the coastline near Daimaru-mae Station, take one stop, then walk to Kobe Tower.</p>

<p><img src="/assets/aacd5f5cacd1/1*ynQiJ9mgKhOxFdSUESJFnQ.webp" alt="" loading="lazy" decoding="async" width="873" height="1095" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzMiIGhlaWdodD0iMTA5NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ynQiJ9mgKhOxFdSUESJFnQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*_BKRsKmQrLdl9fLPgOrFaQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_BKRsKmQrLdl9fLPgOrFaQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*4l2DyckNmvx97IuF256JUg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4l2DyckNmvx97IuF256JUg.jpeg" /></p>

<p>Kobe is very lively (compared to Himeji), with many department stores to explore.</p>

<p>Take the train towards Shin-Nagata and get off at the next stop, Minato Motomachi.</p>

<h4 id="1805-arrive-around-kobe-tower">18:05 Arrive around Kobe Tower</h4>

<p><img src="/assets/aacd5f5cacd1/1*441FBjZsUDMXDMQxz8XCmg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*441FBjZsUDMXDMQxz8XCmg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wH_baSj-1o9q5GyFZ97-xg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*wH_baSj-1o9q5GyFZ97-xg.jpeg" /></p>

<p><a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">It was under maintenance when I came last year</a>, but it is now fully open.</p>

<p><img src="/assets/aacd5f5cacd1/1*NdyL8AiQtaLIkZfb54dFVA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NdyL8AiQtaLIkZfb54dFVA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*cD32qAyEg7AOlZJg04FM_w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*cD32qAyEg7AOlZJg04FM_w.jpeg" /></p>

<p>Kobe Port has a nice harbor town vibe. If I get the chance, I would consider staying overnight in Kobe.</p>

<p>This time, I just took a quick glance around and left.</p>

<h4 id="1830-return-to-kobe-chinatown">18:30 Return to Kobe Chinatown</h4>

<p>On the way back to Kobe Station, I first went to Chinatown to buy <a href="https://frantz.co.jp/en/" target="_blank">Kobe Frantz chocolate souvenirs</a>.</p>

<p><img src="/assets/aacd5f5cacd1/1*XjaKzeVqxVLXk_MQFlABzw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*XjaKzeVqxVLXk_MQFlABzw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*D2ge3h9VtwH0vCO8-rT6kQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*D2ge3h9VtwH0vCO8-rT6kQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*usONmjglsnKER-HS5DS2yQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*usONmjglsnKER-HS5DS2yQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Y_VKoWARSlj-ZuQHwEYbCQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Y_VKoWARSlj-ZuQHwEYbCQ.jpeg" /></p>

<h4 id="return-to-jr-motomachi-station">Return to JR Motomachi Station</h4>

<p><img src="/assets/aacd5f5cacd1/1*HFFt6e3ORaTBS_A2xfk_Mw.webp" alt="" loading="lazy" decoding="async" width="1200" height="791" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HFFt6e3ORaTBS_A2xfk_Mw.png" /></p>

<p>Please make sure to go to JR Motomachi Station, not the Hanshin Electric Railway Motomachi Station.</p>

<h4 id="1853-take-the-rapid-train-bound-for-himeji-from-aboshi-to-kobe-hyogo">18:53 Take the Rapid train bound for Himeji from Aboshi to Kobe (Hyogo)</h4>

<p><img src="/assets/aacd5f5cacd1/1*kQMXFTC_jkFXbQrLjTKawA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kQMXFTC_jkFXbQrLjTKawA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*CIaO_bZsDuNIThM4GNUXqA.webp" alt="" loading="lazy" decoding="async" width="384" height="351" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzODQiIGhlaWdodD0iMzUxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*CIaO_bZsDuNIThM4GNUXqA.png" /></p>

<h4 id="1857-transfer-on-the-same-platform-to-the-rapid-service-via-kosei-line-through-aboshi">18:57 Transfer on the same platform to the Rapid Service via Kosei Line through Aboshi</h4>

<blockquote>
  <p><strong><em>Transfer took nearly 20 minutes</em></strong>, <em>luckily I made the switch in time to reach Himeji’s only drugstore, Matsumoto Kiyoshi, before the duty-free hours ended.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*LL8pEAjsoOyIBY7Zng5EOg.webp" alt="" loading="lazy" decoding="async" width="873" height="1100" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzMiIGhlaWdodD0iMTEwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*LL8pEAjsoOyIBY7Zng5EOg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*GNDjeJjhEgixMjH69bSMuQ.webp" alt="" loading="lazy" decoding="async" width="872" height="1106" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iMTEwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*GNDjeJjhEgixMjH69bSMuQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*zEh8247pPwlJJShPWXRhNg.webp" alt="" loading="lazy" decoding="async" width="867" height="1096" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjciIGhlaWdodD0iMTA5NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*zEh8247pPwlJJShPWXRhNg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*kNw192v7znWE0utpE0cVGA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*kNw192v7znWE0utpE0cVGA.jpeg" /></p>

<p>This train model also requires pressing the door button next to it to open the door, and the carriages are separated from each other.</p>

<h4 id="1934-arrive-at-himeji">19:34 Arrive at Himeji</h4>

<p><img src="/assets/aacd5f5cacd1/1*JGEsWM6ZsBJhQ9MDEFgjyg.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JGEsWM6ZsBJhQ9MDEFgjyg.jpeg" /></p>

<h4 id="1945-arrive-at-himeji-piole-shop-for-cosmetics-at-matsumoto-kiyoshi">19:45 Arrive at Himeji Piole, shop for cosmetics at Matsumoto Kiyoshi</h4>

<p><img src="/assets/aacd5f5cacd1/1*1yNFLnhhhRjr32KRjndzZQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1yNFLnhhhRjr32KRjndzZQ.jpeg" /></p>

<blockquote>
  <p><em>Please note that although the business hours are until 21:00, the <strong>tax-free service ends at 20:30</strong>. ⚠️⚠️⚠️</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*gEwopdiZt9hWS18pwDmbog.webp" alt="Bought a super mini Cup Noodle" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gEwopdiZt9hWS18pwDmbog.jpeg" /></p>

<p>Bought a super mini Cup Noodle</p>

<p><img src="/assets/aacd5f5cacd1/1*XIL4YdSSE1aSMvOCfo_Eog.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*XIL4YdSSE1aSMvOCfo_Eog.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1UO4_sG-Z1vARToCEpeSGQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1UO4_sG-Z1vARToCEpeSGQ.jpeg" /></p>

<p>On the last night in Japan, eating and drinking late-night snacks and beer at the hotel. (These meatballs have a soft texture…)</p>

<blockquote>
  <p><strong><em>Good night, Himeji.</em></strong></p>
</blockquote>

<h3 id="day-7-1118-monday-okayama-return-trip">Day 7 (11/18 Monday) Okayama, Return Trip</h3>

<h4 id="0830-good-morning-himeji">08:30 Good morning Himeji</h4>

<p><img src="/assets/aacd5f5cacd1/1*gIylwTEdczfgkIyCgRg-PQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*gIylwTEdczfgkIyCgRg-PQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*ZfhXzMRapc-5g77Nrk7GCQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*ZfhXzMRapc-5g77Nrk7GCQ.jpeg" /></p>

<p>A final glance at the morning in Himeji.</p>

<p><img src="/assets/aacd5f5cacd1/1*KR95SMQ2Nrdf6lK0wxqEcg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*KR95SMQ2Nrdf6lK0wxqEcg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*u1yIgbMQGfGFLOJ4fsFBbA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*u1yIgbMQGfGFLOJ4fsFBbA.jpeg" /></p>

<p>Arrived at the Shinkansen station to catch the 08:52 Shinkansen bound for Okayama.</p>

<h4 id="take-the-0852-shinkansen-non-reserved-seat-to-okayama">Take the 08:52 Shinkansen non-reserved seat to Okayama</h4>

<p><img src="/assets/aacd5f5cacd1/1*WzQVvLKiQSx-5LXpw75O3w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*WzQVvLKiQSx-5LXpw75O3w.jpeg" /></p>

<h4 id="0913-arrived-at-okayama-station">09:13 Arrived at Okayama Station</h4>

<p>It takes about 21 minutes by Shinkansen to reach Okayama Station. Unfortunately, I didn’t have enough time this trip to <a href="https://medium.com/ztravel/%E9%81%8A%E8%A8%98-2023-%E5%BB%A3%E5%B3%B6%E5%B2%A1%E5%B1%B1-6-%E6%97%A5%E9%81%8A-31b9b3a63abc?source=collection_home---4------2-----------------------" target="_blank">visit Hiroshima again</a>.</p>

<p><img src="/assets/aacd5f5cacd1/1*PgFJG7RNh8w5f4pK5Dv6Eg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PgFJG7RNh8w5f4pK5Dv6Eg.jpeg" /></p>

<p>Just like last time, go directly to the west exit of Okayama Station to catch the airport bus. There are lockers here to store luggage and restrooms available (at this time, the department stores are still closed, so restrooms are limited).</p>

<h4 id="0930-mcdonalds-at-okayama-station-to-try-the-popular-pancake-burger-trending-in-taiwan-recently">09:30 McDonald’s at Okayama Station to try the popular “pancake burger” trending in Taiwan recently</h4>

<p><img src="/assets/aacd5f5cacd1/1*1O8uNrClegWyQoAJE12e5g.webp" alt="" loading="lazy" decoding="async" width="838" height="809" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MzgiIGhlaWdodD0iODA5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*1O8uNrClegWyQoAJE12e5g.png" /></p>

<p>If you can’t read Japanese, you might almost choose the wrong item. You need to select “マックグリドル” for the pancake series.</p>

<blockquote>
  <p><em>McDonald’s breakfast hours in Japan last until 10:30.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*OOoRAGeCSlq2AhtBBs3ZaA.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OOoRAGeCSlq2AhtBBs3ZaA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*tuExEIAQs1IIAede34Odqg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*tuExEIAQs1IIAede34Odqg.jpeg" /></p>

<p>After eating, around 10:00, there was no particular place I wanted to visit.</p>

<p><img src="/assets/aacd5f5cacd1/1*17imSbehBjflWDXLWBedlQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*17imSbehBjflWDXLWBedlQ.jpeg" /></p>

<p><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">I already visited Okayama Castle and Kibitsu Shrine last time</a>, so I spent the remaining time shopping at Okayama AEON Mall.</p>

<p><img src="/assets/aacd5f5cacd1/1*j6NZ7W08WVWsntCa7qvN6A.webp" alt="Okayama Art Festival" loading="lazy" decoding="async" width="682" height="506" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2ODIiIGhlaWdodD0iNTA2Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*j6NZ7W08WVWsntCa7qvN6A.png" /></p>

<p><a href="https://forestartfest-okayama.jp/" target="_blank">Okayama Art Festival</a></p>

<p>This year, Okayama is hosting an <a href="https://forestartfest-okayama.jp/" target="_blank">arts festival</a>.</p>

<p><img src="/assets/aacd5f5cacd1/1*dhgBrvzJrFsPimXmsJb_QQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dhgBrvzJrFsPimXmsJb_QQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YEmu3f0rStKaWXCl890OSQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YEmu3f0rStKaWXCl890OSQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*HaQeBv92EgDp2qdq3EHFvQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*HaQeBv92EgDp2qdq3EHFvQ.jpeg" /></p>

<p>Okayama Station under construction and Momotaro temporarily moved beside the station.</p>

<p><img src="/assets/aacd5f5cacd1/1*_aly6JVMgGDzW4BogUg9Wg.webp" alt="" loading="lazy" decoding="async" width="878" height="1101" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzgiIGhlaWdodD0iMTEwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*_aly6JVMgGDzW4BogUg9Wg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*6bKTUuYl1MMeXhkb2JKOLg.webp" alt="" loading="lazy" decoding="async" width="882" height="1097" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODIiIGhlaWdodD0iMTA5NyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*6bKTUuYl1MMeXhkb2JKOLg.png" /></p>

<p>Cross the road from the underground mall to AEON MALL B2, where there is also a McDonald’s.</p>

<p><img src="/assets/aacd5f5cacd1/1*I7cFknifEeInNlJfK7UYSw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*I7cFknifEeInNlJfK7UYSw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*h_77ECXhHjNHSl7Lr8L7-Q.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*h_77ECXhHjNHSl7Lr8L7-Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*nvJY17geWnC69iCT0kQHrg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*nvJY17geWnC69iCT0kQHrg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BlcO-BaPAFCkd_q9L30Etw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*BlcO-BaPAFCkd_q9L30Etw.jpeg" /></p>

<p>This place is very large and great for shopping, with almost all brands available.</p>

<p><img src="/assets/aacd5f5cacd1/1*vjssm5svEoPfePGcNgElCg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vjssm5svEoPfePGcNgElCg.jpeg" /></p>

<p>There are also places to eat and buy souvenirs (B1-B2).</p>

<p><img src="/assets/aacd5f5cacd1/1*8oOH2Pe4c_SbgDzwoR21Yg.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8oOH2Pe4c_SbgDzwoR21Yg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*NapTm7rBZ4qVZ7EpNqr7xg.webp" alt="" loading="lazy" decoding="async" width="1200" height="866" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijg2NiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*NapTm7rBZ4qVZ7EpNqr7xg.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YClpxnjnyZQuS_ZNi6ZvvQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1008" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwMDgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*YClpxnjnyZQuS_ZNi6ZvvQ.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*HJ4J2jAs8mruW6BZYvyWcA.webp" alt="" loading="lazy" decoding="async" width="602" height="1306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDIiIGhlaWdodD0iMTMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HJ4J2jAs8mruW6BZYvyWcA.jpeg" /></p>

<p>Kept looking for the gacha, but couldn’t find the train button gacha I wanted, so I gave up… It’s probably no longer available.</p>

<p><img src="/assets/aacd5f5cacd1/1*HdWGWOOs9agTb9qWYaiqiQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*HdWGWOOs9agTb9qWYaiqiQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*hK5tJ8UD4aNoz9LNTYBWyA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*hK5tJ8UD4aNoz9LNTYBWyA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MQnwEbWs96IBpBEKW7IB4w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*MQnwEbWs96IBpBEKW7IB4w.jpeg" /></p>

<p>There are also a few shops selling Jiikawa.</p>

<h4 id="1100-getting-ready-for-lunch">~=11:00 Getting ready for lunch</h4>

<blockquote>
  <p><strong><em>Not very hungry, but still need to eat to prepare for the afternoon flight since Tigerair does not provide meals.</em></strong> <em>⚠️⚠️⚠️</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*s8axzlQR2pkJASqoyiPjQg.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*s8axzlQR2pkJASqoyiPjQg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*3JEFC2MwRYJbT7WR4kb2Sg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*3JEFC2MwRYJbT7WR4kb2Sg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*uxodtgWIX2tokvA8GANlBQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uxodtgWIX2tokvA8GANlBQ.jpeg" /></p>

<p>There are many dining options upstairs.</p>

<p><img src="/assets/aacd5f5cacd1/1*8OX9V42Q-S0S7W-1Ve7n6A.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*8OX9V42Q-S0S7W-1Ve7n6A.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*mIUREsw2cy1DfnAuxi9lWg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*mIUREsw2cy1DfnAuxi9lWg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*vAiLB635e_VNF1BrY7U3ZA.webp" alt="**Pork Steak Specialty Restaurant B Okayama Branch**" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vAiLB635e_VNF1BrY7U3ZA.jpeg" /></p>

<p><a href="https://tabelog.com/okayama/A3301/A330101/33018608/" target="_blank"><strong>Pork Steak Specialty Restaurant B Okayama Branch</strong></a></p>

<p>Chose a restaurant specializing in teppan pork cutlet.</p>

<p><img src="/assets/aacd5f5cacd1/1*f9-cymGYzbWqmSgVSwht8Q.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*f9-cymGYzbWqmSgVSwht8Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*-8Sx-7tZ5l-DPua6GOunTA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*-8Sx-7tZ5l-DPua6GOunTA.jpeg" /></p>

<p>I ordered a 200g teppan pork cutlet set meal and a draft beer. The total came to <code class="language-plaintext highlighter-rouge">2,178 yen</code>.</p>

<p><img src="/assets/aacd5f5cacd1/1*rT1vHotHA_BsYMp5BFadUQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*rT1vHotHA_BsYMp5BFadUQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*weW08CNKF-sFY48mz-Qw7g.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*weW08CNKF-sFY48mz-Qw7g.jpeg" /></p>

<p>Surprisingly, the pork cutlet was served medium rare, about 70% cooked. After checking reviews and descriptions, it seems this is their specialty. However, I still used the hot iron plate to cook the meat pieces that could be cooked further.</p>

<blockquote>
  <p><em>It really tastes great—very juicy, full of meaty flavor, and easy to chew; personally, I think it’s just as good as yesterday’s Kobe beef (I might just not be lucky enough).</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*4SkncSHSzMb-s7cnwrAqFQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*4SkncSHSzMb-s7cnwrAqFQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*E44bH_Sc3tK3PS-gSzr3jQ.webp" alt="Small luggage (about the size of AirPods Pro) and a doll bag" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*E44bH_Sc3tK3PS-gSzr3jQ.jpeg" /></p>

<p>Small useless suitcase (about the same size as AirPods Pro) and a kid’s backpack</p>

<p>Finally, I bought some items at Muji on the first floor and left around 12:15.</p>

<h4 id="1230-return-to-okayama-station">~=12:30 Return to Okayama Station</h4>

<p>Preparing to take the 13:00 shuttle to Okayama Airport.</p>

<p><img src="/assets/aacd5f5cacd1/1*g0faLCzFGCjUq6xndxPXCA.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*g0faLCzFGCjUq6xndxPXCA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*wp34SWl53PM47RyotD7_7A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*wp34SWl53PM47RyotD7_7A.jpeg" /></p>

<p>One last look at Okayama.</p>

<h4 id="1300-take-the-shuttle-bus-to-okayama-airport">~=13:00 Take the shuttle bus to Okayama Airport</h4>

<p><img src="/assets/aacd5f5cacd1/1*WQPyQGy5C0nZLfJ-3GIZcA.webp" alt="" loading="lazy" decoding="async" width="869" height="1101" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjkiIGhlaWdodD0iMTEwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*WQPyQGy5C0nZLfJ-3GIZcA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*YD-OAt75d1s69u6qZpWrsw.webp" alt="" loading="lazy" decoding="async" width="872" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzIiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*YD-OAt75d1s69u6qZpWrsw.png" /></p>

<p>Line up here at Platform 21 to wait for the bus. Don’t worry about missing it; as long as you queue before 13:00, you will be taken to the airport (additional shuttle buses will be added if it’s full).</p>

<p>You can directly tap your transportation card (Suica) when boarding and getting off, or you can go inside the bus shelter to pay by coin and buy a ticket.</p>

<p><img src="/assets/aacd5f5cacd1/1*wZz-iSrK6orko9_30h31gw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*wZz-iSrK6orko9_30h31gw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*2Ey3Ma7iGU0aZcvM-5-KuA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*2Ey3Ma7iGU0aZcvM-5-KuA.jpeg" /></p>

<p>The departure was moved up to 12:45 due to full capacity.</p>

<h4 id="1320-arrive-at-okayama-momotaro-airport">~=13:20 Arrive at Okayama Momotaro Airport</h4>

<p><img src="/assets/aacd5f5cacd1/1*7gnnl4V4uwpVHg9wq9sliw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*7gnnl4V4uwpVHg9wq9sliw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*Zev8bJycwEj6HruuaPqWyA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Zev8bJycwEj6HruuaPqWyA.jpeg" /></p>

<p>The time is still very early. This year, I took the 17:30 flight, while last year I took the 15:25 flight, which was just right. For the 17:30 flight, arriving now means waiting almost 4 hours.</p>

<p><img src="/assets/aacd5f5cacd1/1*EL5tuKai1e95lToFw7agNQ.webp" alt="&lt;https://www.okayama-airport.org/tw/access/bus&gt;" loading="lazy" decoding="async" width="367" height="600" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNjciIGhlaWdodD0iNjAwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*EL5tuKai1e95lToFw7agNQ.png" /></p>

<p><a href="https://www.okayama-airport.org/tw/access/bus" target="_blank">https://www.okayama-airport.org/tw/access/bus</a></p>

<blockquote>
  <p><em>But currently it’s not possible because <strong>the next bus is at 15:55, arriving at the airport at 16:25</strong>; although the airport is small and there is only one outbound flight at that time, making it relatively quick, traffic jams and other factors make it hard to guarantee arrival by 16:25. For example, when I arrived, I encountered a traffic jam that added almost 30 minutes to the trip.</em></p>
</blockquote>

<blockquote>
  <p><em>It depends on whether Okayama Airport will add more shuttle bus services later, or you can go to the <a href="https://www.facebook.com/groups/207266832755595" target="_blank"><strong>Japan Free Travel Discussion Group</strong></a> to find people to share a taxi.</em></p>
</blockquote>

<blockquote>
  <p><em>Others shared that a taxi from Okayama Station to the airport costs around 7,000 yen.</em></p>
</blockquote>

<p><img src="/assets/aacd5f5cacd1/1*BAf8ZKtaE0PZp3CSqHp5oQ.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BAf8ZKtaE0PZp3CSqHp5oQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*KulgQh4uI3M8GHBzX3_hSw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*KulgQh4uI3M8GHBzX3_hSw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*VrbZuTSIDQztMYoFgjVMOA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*VrbZuTSIDQztMYoFgjVMOA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*WloJT1lwu9sE7K1YY2Lmfg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*WloJT1lwu9sE7K1YY2Lmfg.jpeg" /></p>

<p>Wandering around near the airport, there is a mini version of Okayama Korakuen outside the airport along with popular autumn maple leaves.</p>

<p><img src="/assets/aacd5f5cacd1/1*DFOrcpULEpM1ZAaCsxVbDQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*DFOrcpULEpM1ZAaCsxVbDQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*OO1rTCEh9kw5aQMfaPk_wQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*OO1rTCEh9kw5aQMfaPk_wQ.jpeg" /></p>

<p>There is also a Momotaro statue outside the station. Additionally, the airport kindly reminds travelers to buy insurance—<strong>I almost got hit by a bicycle this time.⚠️</strong></p>

<p><img src="/assets/aacd5f5cacd1/1*oMmge-1kLe8YMumlz4I-dw.webp" alt="" loading="lazy" decoding="async" width="1200" height="900" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjkwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*oMmge-1kLe8YMumlz4I-dw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*BRsrPzntiPcckzgHSzFOaw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*BRsrPzntiPcckzgHSzFOaw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*JZnvFBhhDjBVHdeEqPDYHw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*JZnvFBhhDjBVHdeEqPDYHw.jpeg" /></p>

<p>There are quite a few seats available here in the domestic terminal to sit and rest.</p>

<p>The first floor only has a souvenir shop and nothing else. <a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">Just like last year</a>, I bought a peach mochi to eat.</p>

<p><img src="/assets/aacd5f5cacd1/1*PQ1GM0STjbTPYN0p6_S_aA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PQ1GM0STjbTPYN0p6_S_aA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*dRHe9n0Rpk7sXzbOplk9NA.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*dRHe9n0Rpk7sXzbOplk9NA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*PQ1GM0STjbTPYN0p6_S_aA.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PQ1GM0STjbTPYN0p6_S_aA.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MZVip0FtjrMIxj3t3ppxPg.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MZVip0FtjrMIxj3t3ppxPg.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1vlNWZvaNY8I47Qydyd9cw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1vlNWZvaNY8I47Qydyd9cw.jpeg" /></p>

<p>The second floor is under renovation, with only a few shops open. The observation deck that was accessible last year (<a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/">last year’s visit</a>) seems to be closed; however, there is still at least one souvenir shop and several food outlets.</p>

<h4 id="1500-start-check-in-process">~=15:00 Start check-in process</h4>

<p><img src="/assets/aacd5f5cacd1/1*lGDYdJexHQY6XkiyPZJOhw.webp" alt="" loading="lazy" decoding="async" width="882" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4ODIiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*lGDYdJexHQY6XkiyPZJOhw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*1oLSBw-IwqMJh4-RA81Apg.webp" alt="" loading="lazy" decoding="async" width="1200" height="827" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgyNyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*1oLSBw-IwqMJh4-RA81Apg.png" /></p>

<p>Momotaro Airport is very small, and the check-in and baggage drop-off process is different from other airports.</p>

<blockquote>
  <p><strong><em>This is the unified queue. Checked luggage goes through X-ray and receives a sealing sticker; carry-on luggage does not (security check before departure will scan other items). Even without checked luggage, you must line up.</em></strong> <em>When it’s your turn, tell the staff you have no checked luggage and proceed directly. If you open your suitcase after the sticker is applied, it must be rescanned and re-stickered. ⚠️⚠️⚠️</em></p>
</blockquote>

<blockquote>
  <p><em>The airport staff can speak Chinese… very impressive</em></p>
</blockquote>

<h4 id="1515-check-in-and-baggage-drop-completed">~=15:15 Check-in and baggage drop completed</h4>

<p><img src="/assets/aacd5f5cacd1/1*k2WrB1ooYpRa8zSD8A2Dlw.webp" alt="" loading="lazy" decoding="async" width="651" height="1104" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NTEiIGhlaWdodD0iMTEwNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*k2WrB1ooYpRa8zSD8A2Dlw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*MNH21H-7oPaOq-GP_uKUpA.webp" alt="" loading="lazy" decoding="async" width="879" height="1102" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzkiIGhlaWdodD0iMTEwMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*MNH21H-7oPaOq-GP_uKUpA.png" /></p>

<p>This time I didn’t buy much or any alcohol, yet it still reached 17 kg… but I hardly had any carry-on luggage; everything was packed inside.</p>

<h4 id="1620-start-security-check-and-departure-procedures">~=16:20 Start security check and departure procedures</h4>

<p><img src="/assets/aacd5f5cacd1/1*Wjgk2cV8hkiBkIjxjke6AA.webp" alt="" loading="lazy" decoding="async" width="876" height="1091" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzYiIGhlaWdodD0iMTA5MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*Wjgk2cV8hkiBkIjxjke6AA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*iezQ2KMlAUzJrKjqlHzyLw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1050" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEwNTAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*iezQ2KMlAUzJrKjqlHzyLw.jpeg" /></p>

<p>The airport is under renovation… but there are still capsule toys.</p>

<p><img src="/assets/aacd5f5cacd1/1*di7LhsC-MYyRIVwYRWVU-Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*di7LhsC-MYyRIVwYRWVU-Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*AHTIPdSzU7TJrq1hVeKACg.webp" alt="" loading="lazy" decoding="async" width="774" height="931" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NzQiIGhlaWdodD0iOTMxIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/aacd5f5cacd1/1*AHTIPdSzU7TJrq1hVeKACg.png" /></p>

<p>Although there was only one flight, the airport is small, has few staff, only one line, and strict checks, so security screening and departure took about 20–30 minutes.</p>

<p>The place for pouring water and disposing of items is just a box.</p>

<blockquote>
  <p><em>If you leave Okayama at 15:55 and take the shuttle arriving at the airport at 16:25, you can basically check in and drop off your luggage immediately since the counters are usually empty by then, and then head straight to the departure security check on time.</em></p>
</blockquote>

<blockquote>
  <p><em>We are the only flight on this route, so the plane will likely take off only after all checked-in passengers complete security. Arriving early at the airport doesn’t mean faster security if you queue late.</em></p>
</blockquote>

<h4 id="1642-begin-waiting-at-the-gate">~=16:42 Begin waiting at the gate</h4>

<p><img src="/assets/aacd5f5cacd1/1*dZLP9y156IaL2ZYxefWyAQ.webp" alt="" loading="lazy" decoding="async" width="1400" height="1867" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjE4NjciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/aacd5f5cacd1/1*dZLP9y156IaL2ZYxefWyAQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*vb73mCN99CDryoSCqdo_Dw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*vb73mCN99CDryoSCqdo_Dw.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*0-fhLcrfopTMrx9rvE0nYg.webp" alt="" loading="lazy" decoding="async" width="1400" height="965" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk2NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*0-fhLcrfopTMrx9rvE0nYg.png" /></p>

<p>The waiting lounge is small, with only one duty-free shop that sells some souvenirs and a limited selection of tobacco and alcohol.</p>

<p><img src="/assets/aacd5f5cacd1/1*SAM-saIjWrap5wL4dAxENQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*SAM-saIjWrap5wL4dAxENQ.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*QCxhYYWHW2RLvZsTgWQ0Sw.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*QCxhYYWHW2RLvZsTgWQ0Sw.jpeg" /></p>

<p>I wanted to buy a few cans of peach water to bring back to Taiwan, but they were all sold out. So I got a can of Red Tea Flower instead. After trying it, I found it very tasty and not too sweet. I regret not buying more.</p>

<h4 id="1810-departure-40-minutes-delay">~=18:10 Departure (40 minutes delay)</h4>

<p><img src="/assets/aacd5f5cacd1/1*f3Yqp6letQnKZI8spjNn-Q.webp" alt="" loading="lazy" decoding="async" width="1024" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDI0IiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*f3Yqp6letQnKZI8spjNn-Q.jpeg" /></p>

<p><img src="/assets/aacd5f5cacd1/1*PSlGa7hB3SwG6zFHnEVFLQ.webp" alt="" loading="lazy" decoding="async" width="900" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MDAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*PSlGa7hB3SwG6zFHnEVFLQ.jpeg" /></p>

<blockquote>
  <p><strong><em>Goodbye Okayama, goodbye Japan.</em></strong></p>
</blockquote>

<p>On the plane, the person sitting next to me was a YouTuber, <a href="https://www.youtube.com/@lw.chill2022" target="_blank"><strong>Chill with us</strong></a>, a very nice guy. He was going to Shikoku this time. We exchanged some sightseeing recommendations, and now I also want to visit Shikoku.</p>

<h4 id="1945-arrive-in-taiwan-10-minute-delay">19:45 Arrive in Taiwan (10-minute delay)</h4>

<p>Fortunately, it only affected 10 minutes. I quickly picked up my luggage and rushed to the bus stop.</p>

<h4 id="2020-take-the-dayou-bus-to-taipei">20:20 Take the Dayou Bus to Taipei</h4>

<p><img src="/assets/aacd5f5cacd1/1*EDhpsb6FiVOOwKoDslj3nA.webp" alt="" loading="lazy" decoding="async" width="879" height="1085" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzkiIGhlaWdodD0iMTA4NSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*EDhpsb6FiVOOwKoDslj3nA.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*63kNa-lLfoQ6uKfjEy9BVw.webp" alt="" loading="lazy" decoding="async" width="875" height="1094" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzUiIGhlaWdodD0iMTA5NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*63kNa-lLfoQ6uKfjEy9BVw.png" /></p>

<p><img src="/assets/aacd5f5cacd1/1*uRb-Shzk65f1cuiiBxd6SQ.webp" alt="" loading="lazy" decoding="async" width="768" height="1024" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NjgiIGhlaWdodD0iMTAyNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/aacd5f5cacd1/1*uRb-Shzk65f1cuiiBxd6SQ.jpeg" /></p>

<p>Depending on the location, you can take different buses home. Previously, I took the Kuo-Kuang Bus directly back to Taipei. This time, I will take the Dayou Bus 11 platform back to New Taipei.</p>

<blockquote>
  <p><em>Coincidentally, my EasyCard balance was insufficient, so I rushed to the counter to buy a ticket with cash (fortunately, I had brought Taiwanese dollars the day before), and finally caught the bus.</em></p>
</blockquote>

<h4 id="end-of-the-trip">End of the trip.</h4>

<h3 id="summary-of-experience">Summary of Experience</h3>

<p>This trip was too tight, and many places were just briefly visited. Especially in the first three days, due to long travel distances and short stays, I couldn’t fully enjoy the beautiful scenery of Izumo and Matsue in Shimane. If I have the chance, I would like to go again to see the sunset, the autumn leaves, and the Kamiari Festival.</p>

<p>If you want to learn about history before going to Izumo, you can refer to Lan Papa’s video:</p>

<ol>
  <li>
    <p><a href="https://www.youtube.com/watch?v=cnp9H--KPyY" target="_blank">How Was the Land of Japan Born? It Was Actually Born! \| Japanese Mythology \| Birth of Japan \| Creation of Heaven and Earth \| Kuniumi \| Kamiumi \| Great Eight Islands \| Izanagi \| Izanami \| Storytelling by Dad Lan</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/watch?v=W8SjLltb3U4" target="_blank">Japan’s Most Mischievous God, Who Fathered the Direct Ancestor of the Japanese Imperial Family with His Own Sister! Slayed the Eight-Headed Serpent, Married a Beautiful Wife, Truly the “God of Winning” in the Divine World! The Very First Japanese Waka Poem Came from His Mouth?! \| Japanese Mythology \| Susanoo \| Ama-no-Iwato \| Yamata no Orochi \| Inadahime \| Dad Lan Tells Stories</a></p>
  </li>
  <li>
    <p><a href="https://www.youtube.com/watch?v=HbxBcKkfCnk" target="_blank">Why did a rabbit provoke a group of sharks? After two near-death experiences, he ended up marrying the daughter of his ancestor! The Myth of Okuninushi \| Okuninushi \| White Rabbit of Inaba \| Land of Roots \| Japanese Mythology \| Storytelling by Papa Lan</a>
<strong>Includes stories about Amaterasu, Okuninushi (Izumo Shrine), and the White Rabbit of Inaba.</strong></p>
  </li>
</ol>

<h4 id="sanyo-okayama-hiroshima-area-travelogue">Sanyo Okayama Hiroshima Area Travelogue</h4>

<ul>
  <li><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/"><strong>[Travelogue] 2023 Sanyo Region Hiroshima Okayama 6-Day Free Travel</strong></a></li>
</ul>

<p>You can also refer to the Sanyo area travelogue. If you have time, you can explore both Sanyo and Sanin regions together.</p>

<h4 id="kkday-promotion--1">KKday Promotion 🛒</h4>

<ul>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/153331" target="_blank">Japan JR PASS \| Tottori &amp; Matsue Area Pass \| eMCO e-ticket</a>
(Consider this if you only visit Matsue and Tottori-Matsue areas)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/20307-jr-sanin-okayama-area-pass?cid=19365" target="_blank"><strong>Kansai &amp; San’in Area Rail Pass JRPass</strong></a>
(The best choice for arriving and departing from Okayama Airport)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/137689-japan-high-speed-daily-unlimited-data-japanese-esim?cid=19365" target="_blank"><strong>Japan eSIM Card｜Daily High-Speed, Total Data, Unlimited Data Plans</strong></a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/20315-jr-sanyo-sanin-area-pass?cid=19365" target="_blank">Japan JR PASS｜Sanyo &amp; Sanin Area Rail Pass｜eMCO e-Ticket</a>
(Explore the entire Sanyo and Sanin regions in one go)</p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/184144?cid=19365" target="_blank">Tottori Sand Dunes \| Japan’s Only Thrilling Sandboarding Experience</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/11463-osaka-bus-tour-tottori-sand-dunes-uratomi-coast-cruise-sand-museum-japan?cid=19365" target="_blank">【Tottori Bus One-Day Tour】Tottori Sand Dunes, Uradome Coast Cruise, Sand Museum, Pear Picking Experience｜Includes Special Lunch (Departing from Osaka)</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/262558?cid=19365" target="_blank">Tour Tottori Sand Dunes and the Famous Izumo Taisha 2-Day Bus Trip (From Osaka)</a></p>
  </li>
  <li>
    <p><a href="https://www.kkday.com/zh-tw/product/185589?cid=19365" target="_blank">New Year Visit! 3-Hour Stop at Izumo Taisha Shrine 【Departing from Hiroshima】</a></p>
  </li>
</ul>

<blockquote>
  <p><a href="https://www.kkday.com/zh-tw?cid=19365" target="_blank">If this article has helped you, please consider using my referral link to purchase KKday products and tours. I will receive a commission to support more travel content. Thank you.</a></p>
</blockquote>

<h3 id="more-travelogues">More Travelogues</h3>

<ul>
  <li>
    <p><a href="https://blog.zhgchg.li/%E9%81%8A%E8%A8%98-2025-%E9%9F%93%E5%9C%8B%E9%87%9C%E5%B1%B1-8-%E5%A4%A9-7-%E5%A4%9C%E8%87%AA%E7%94%B1%E8%A1%8C-8ace34a1a3d8?source=collection_home_page----96927b78a281-----0-----------------------------------" target="_blank">[Travelogue] 2025 Busan, South Korea 8 Days 7 Nights Free Travel</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/kyushu-10-day-solo-travel-guide-explore-fukuoka-nagasaki-kumamoto-efficiently-d78e0b15a08a/">[Travelogue] 2023 Kyushu 10-Day Solo Trip</a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/sanyo-region-travel-guide-6-day-hiroshima-okayama-itinerary-for-freedom-seekers-31b9b3a63abc/"><strong>[Travelogue] 2023 Sanyo Region Hiroshima Okayama 6-Day Free Travel</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/travel-journals/nagoya-one-day-quick-trip-peach-aviation-flight-experience-and-travel-tips-7b8a0563c157/">[Travelogue] 9/11 One-day Quick Trip to Nagoya</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/tokyo-5-day-travel-guide-top-tips-for-food-stay-transport-9da2c51fa4f2/">2023 Tokyo 5-Day Free Trip</a></p>
  </li>
  <li>
    <p>[Travelogue] <a href="/posts/travel-journals/kyoto-osaka-kobe-8-day-itinerary-free-travel-guide-with-food-stay-transit-tips-76d66c2e34af/">2023 Kyoto-Osaka-Kobe 8-Day Free Trip</a></p>
  </li>
</ul>]]></content>
  </entry><entry>
    <title type="html">GitHub Pages｜Create Free Custom LinkTree-Style Personal Link Pages Fast</title>
    <link href="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/github-pages-create-free-custom-linktree-style-personal-link-pages-fast-70aeddb1fd9b/" rel="alternate" type="text/html" title="GitHub Pages｜Create Free Custom LinkTree-Style Personal Link Pages Fast" />
    <published>2024-10-27T13:36:34+08:00</published>
    <updated>2024-10-27T21:57:18+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-robotic-process-automation/70aeddb1fd9b</id><summary type="html">Discover how to build a fully customizable personal link page using GitHub Pages for free, supporting custom domains and enabling quick setup to centralize your online presence effortlessly.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Robotic Process Automation" /><category term="linktree" /><category term="github-pages" /><category term="automation" /><category term="ruby" /><category term="jekyll" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/70aeddb1fd9b/1*CbiCUtVY5CV4wRXBaZBoyw.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/github-pages-create-free-custom-linktree-style-personal-link-pages-fast-70aeddb1fd9b/"><![CDATA[<h3 id="linkyee--quickly-and-freely-create-a-personal-linktree-like-page-using-github-pages">linkyee — Quickly and freely create a personal LinkTree-like page using GitHub Pages</h3>

<p>Create Your Own Link Page Quickly with GitHub Pages: 100% Free, Customizable, and Supports Custom Domains</p>

<h3 id="result">Result</h3>

<p><img src="/assets/70aeddb1fd9b/1*EtG_srpR0i0BRE1dziNDXg.webp" alt="&lt;https://link.zhgchg.li&gt;" loading="lazy" decoding="async" width="1400" height="948" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijk0OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*EtG_srpR0i0BRE1dziNDXg.png" /></p>

<p><a href="https://link.zhgchg.li" target="_blank">https://link.zhgchg.li</a></p>

<blockquote>
  <p><strong><em>I have open-sourced the project and packaged it as a Template Repo (linkyee). Friends who need it can directly Fork to quickly deploy and use it.</em></strong></p>
</blockquote>

<h3 id="linkyee--your-own-link-pages">linkyee — Your Own Link Pages</h3>

<p><a href="https://github.com/ZhgChgLi/linkyee" target="_blank"><img src="https://repository-images.githubusercontent.com/877945203/b2d2ec07-9a56-400c-b24c-db0b180f7d3e" alt="" /></a></p>

<h4 id="advantages">Advantages:</h4>

<ul>
  <li>
    <p>Direct Deployment on GitHub Pages: Stable and Free</p>
  </li>
  <li>
    <p>Full control over the HTML source files allows you to freely modify layout, styles, remove ads, and copyright notices; <em>(the default style was created using GenAI ChatGPT)</em></p>
  </li>
  <li>
    <p>Support Custom Domains</p>
  </li>
  <li>
    <p><strong>Supports dynamic variables, such as default Medium followers and GitHub repo star count variables, which can be automatically inserted and updated on the page.</strong> 🚀🚀🚀</p>
  </li>
  <li>
    <p>Fast Page Loading</p>
  </li>
  <li>
    <p>You can complete the setup and deployment in just a few simple steps according to this article.</p>
  </li>
</ul>

<h3 id="github-pages">Github Pages</h3>

<p>GitHub Pages is a free static site hosting service provided by GitHub. All GitHub Free accounts can use it directly for Public Repos, while Private Repos require a paid GitHub account upgrade.</p>

<h4 id="limitations">Limitations</h4>

<ul>
  <li>
    <p><strong>Can only host static file resources:</strong> HTML, CSS, JavaScript, font files, image files, PDFs, audio files, text files, etc.</p>
  </li>
  <li>
    <p><strong>The website (repo) size must not exceed:</strong> 1 GB. This is likely a soft limit, as my GitHub Pages Jekyll repo is already nearly 5 GB.</p>
  </li>
  <li>
    <p><strong>Longest deployment time:</strong> 10 minutes</p>
  </li>
  <li>
    <p><strong>Maximum deployments per hour:</strong> 10 times (soft limit)</p>
  </li>
  <li>
    <p><strong>Monthly Traffic Limit:</strong> 100 GB (soft limit)</p>
  </li>
  <li>
    <p>Too many requests may result in an HTTP 429 response.</p>
  </li>
</ul>

<h4 id="other-github-pages-application-articles">Other Github Pages Application Articles</h4>

<ul>
  <li>
    <p><a href="/posts/zrealm-dev/seamlessly-migrate-medium-content-to-self-hosted-sites-github-pages-with-jekyll-chirpy-a0c08d579ab1/">My Jekyll Blog deployed with Github Pages</a> ➡️ <a href="https://zhgchg.li/" target="_blank"><strong>ZhgChgLi</strong></a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/seamlessly-migrate-medium-content-to-self-hosted-sites-github-pages-with-jekyll-chirpy-a0c08d579ab1/">Painless Migration from Medium to GitHub Pages</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-dev/github-pages-custom-domain-setup-replace-github-io-with-your-own-domain-483af5d93297/"><strong>GitHub Pages Custom Domain Tutorial</strong></a></p>
  </li>
</ul>

<h3 id="getting-started--deploy-on-github-pages">Getting Started — Deploy on GitHub Pages</h3>

<h4 id="step-1-click-the-use-this-template-button-at-the-top-right-of-the-linkyee-template-repo---create-a-new-repository">Step 1. Click the “Use this template” button at the top right of the linkyee template repo -&gt; “Create a new repository”:</h4>

<p><img src="/assets/70aeddb1fd9b/1*G6pU845OnIyEdl-Os0EnwQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="993" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*G6pU845OnIyEdl-Os0EnwQ.png" /></p>

<h4 id="step-2-check-include-all-branches-enter-your-desired-github-pages-repository-name-and-click-create-repository">Step 2. Check “Include all branches,” enter your desired GitHub Pages repository name, and click “Create repository”:</h4>

<p><img src="/assets/70aeddb1fd9b/1*PN9zygdxqJmFtUz9Pq35cQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="993" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*PN9zygdxqJmFtUz9Pq35cQ.png" /></p>

<blockquote>
  <p><em>The GitHub Pages repo name will be the access URL.</em></p>
</blockquote>

<blockquote>
  <p><em>If you name the repo <code class="language-plaintext highlighter-rouge">your-username.github.io</code>, it will become the direct URL for your GitHub Pages site.</em></p>
</blockquote>

<blockquote>
  <p><em>If you already have a <code class="language-plaintext highlighter-rouge">your-username.github.io</code> repo, the GitHub Pages URL will be <code class="language-plaintext highlighter-rouge">your-username.github.io/Repo-Name</code>.</em></p>
</blockquote>

<h4 id="wait-for-the-fork-to-complete-you-may-encounter-deployment-errors-initially-due-to-permission-issues-with-the-forked-repository-next-we-will-follow-the-steps-to-adjust-this">Wait for the Fork to complete. You may encounter deployment errors initially due to permission issues with the Forked repository. Next, we will follow the steps to adjust this.</h4>

<p><img src="/assets/70aeddb1fd9b/1*EYXix1zABfKXboAxkn_-yw.webp" alt="" loading="lazy" decoding="async" width="374" height="74" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzNzQiIGhlaWdodD0iNzQiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/70aeddb1fd9b/1*EYXix1zABfKXboAxkn_-yw.png" /></p>

<h4 id="step-4-go-to-settings---actions---general-and-make-sure-to-select-the-following-options">Step 4. Go to Settings -&gt; Actions -&gt; General, and make sure to select the following options:</h4>

<p><img src="/assets/70aeddb1fd9b/1*5c4TZm0ZjolIPPalwEbJMA.webp" alt="" loading="lazy" decoding="async" width="1200" height="993" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*5c4TZm0ZjolIPPalwEbJMA.png" /></p>

<ul>
  <li>
    <p>Actions permissions: Allow all actions and reusable workflows</p>
  </li>
  <li>
    <p>Workflow permissions: Read and write permissions</p>
  </li>
</ul>

<p><strong>After making your selection, click the Save button to save the changes.</strong></p>

<h4 id="step-5-go-to-settings---pages-and-confirm-the-github-pages-branch-is-set-to-gh-pages">Step 5. Go to Settings -&gt; Pages, and confirm the GitHub Pages branch is set to “gh-pages”:</h4>

<p><img src="/assets/70aeddb1fd9b/1*2mmeneQOLEuhRqZIovSh9A.webp" alt="" loading="lazy" decoding="async" width="1400" height="1159" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExNTkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/70aeddb1fd9b/1*2mmeneQOLEuhRqZIovSh9A.png" /></p>

<blockquote>
  <p><em>The message “Your site is live at: XXXX” is your GitHub Pages public access URL.</em></p>
</blockquote>

<h4 id="step-6-go-to-settings---actions-and-wait-for-the-first-deployment-to-complete">Step 6. Go to Settings -&gt; Actions, and wait for the first deployment to complete:</h4>

<p><img src="/assets/70aeddb1fd9b/1*mFQmtcTZr-OjBhqtHwebEw.webp" alt="" loading="lazy" decoding="async" width="1400" height="1159" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExNTkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/70aeddb1fd9b/1*mFQmtcTZr-OjBhqtHwebEw.png" /></p>

<h4 id="step-7-visit-the-github-pages-url-to-confirm-if-the-fork-was-successful">Step 7. Visit the GitHub Pages URL to confirm if the Fork was successful:</h4>

<p><img src="/assets/70aeddb1fd9b/1*1vaJpnwjZtsWEjBvKcGjVw.webp" alt="" loading="lazy" decoding="async" width="1200" height="993" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*1vaJpnwjZtsWEjBvKcGjVw.png" /></p>

<blockquote>
  <p>Congratulations! Deployment successful. You can now edit the configuration file and replace it with your own information. 🎉🎉🎉</p>
</blockquote>

<h3 id="setup">Setup</h3>

<h4 id="configuration-file">Configuration File</h4>

<p>Edit the config.yml file located in the root directory.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Website Configuration  </span>

<span class="c1"># Theme name, corresponds to directory: ./theme/xxxx  </span>
<span class="na">theme</span><span class="pi">:</span> <span class="s">default</span>  

<span class="c1"># HTML language setting  </span>
<span class="na">lang</span><span class="pi">:</span> <span class="s2">"</span><span class="s">en"</span>  

<span class="c1"># Plugins (implemented in ./plugins/PLUGIN_NAME)  </span>
<span class="c1"># Use {{ vars.PLUGIN_NAME }} in the settings below  </span>

<span class="c1"># Plugin output can be used below, e.g.: {{vars.MediumFollowersCountPlugin}}  </span>
<span class="na">plugins</span><span class="pi">:</span>  
  <span class="c1"># Automatically get Medium followers count  </span>
  <span class="pi">-</span> <span class="na">MediumFollowersCountPlugin</span><span class="pi">:</span>  
      <span class="na">username</span><span class="pi">:</span> <span class="s">zhgchgli</span>  
  <span class="c1"># Automatically get GitHub repository stars count  </span>
  <span class="pi">-</span> <span class="na">GithubRepoStarsCountPlugin</span><span class="pi">:</span>  
      <span class="pi">-</span> <span class="s">ZhgChgLi/ZMarkupParser</span>  
      <span class="pi">-</span> <span class="s">ZhgChgLi/ZReviewTender</span>  
      <span class="pi">-</span> <span class="s">ZhgChgLi/ZMediumToMarkdown</span>  
      <span class="pi">-</span> <span class="s">ZhgChgLi/linkyee</span>  

<span class="c1"># Google Analytics tracking ID  </span>
<span class="na">google_analytics_id</span><span class="pi">:</span>  

<span class="c1"># HTML title  </span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Link</span><span class="nv"> </span><span class="s">Collection"</span>  

<span class="c1"># Avatar image path  </span>
<span class="na">avatar</span><span class="pi">:</span> <span class="s2">"</span><span class="s">./images/profile.jpeg"</span>  

<span class="c1"># Name block text  </span>
<span class="na">name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">@zhgchgli"</span>  

<span class="c1"># Tagline block text  </span>
<span class="na">tagline</span><span class="pi">:</span> <span class="pi">&gt;-</span>  
    <span class="s">An iOS, web, and automation developer from Taiwan who loves sharing, traveling, and writing.  </span>

<span class="c1"># Links list  </span>
<span class="c1"># icon: use Font Awesome icons (https://fontawesome.com/search?o=r&amp;m=free)  </span>
<span class="c1"># text: text shown on the link  </span>
<span class="c1"># title: link title  </span>
<span class="c1"># url: link URL  </span>
<span class="c1"># alt: alternative text (for accessibility)  </span>
<span class="c1"># target: `_blank` opens in new tab, `_self` opens in the same page  </span>
<span class="na">links</span><span class="pi">:</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-medium"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Tech</span><span class="nv"> </span><span class="s">Blog</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.MediumFollowersCountPlugin}}</span><span class="nv"> </span><span class="s">Followers)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://blog.zhgchg.li"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Tech</span><span class="nv"> </span><span class="s">Blog"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Tech</span><span class="nv"> </span><span class="s">Blog"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-medium"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Travel</span><span class="nv"> </span><span class="s">Journal</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.MediumFollowersCountPlugin}}</span><span class="nv"> </span><span class="s">Followers)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://medium.com/ztravel"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Travel</span><span class="nv"> </span><span class="s">Journal"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Travel</span><span class="nv"> </span><span class="s">Journal"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-solid</span><span class="nv"> </span><span class="s">fa-rss"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Personal</span><span class="nv"> </span><span class="s">Website"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://zhgchg.li/"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Website"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Website"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-swift"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMarkupParser</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.GithubRepoStarsCountPlugin['ZhgChgLi/ZMarkupParser']}}</span><span class="nv"> </span><span class="s">Stars)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/ZhgChgLi/ZMarkupParser"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMarkupParser</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">pure</span><span class="nv"> </span><span class="s">Swift</span><span class="nv"> </span><span class="s">library</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">converting</span><span class="nv"> </span><span class="s">HTML</span><span class="nv"> </span><span class="s">strings</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">NSAttributedString</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">custom</span><span class="nv"> </span><span class="s">styles."</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMarkupParser</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">pure</span><span class="nv"> </span><span class="s">Swift</span><span class="nv"> </span><span class="s">library</span><span class="nv"> </span><span class="s">for</span><span class="nv"> </span><span class="s">converting</span><span class="nv"> </span><span class="s">HTML</span><span class="nv"> </span><span class="s">strings</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">NSAttributedString</span><span class="nv"> </span><span class="s">with</span><span class="nv"> </span><span class="s">custom</span><span class="nv"> </span><span class="s">styles."</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-app-store-ios"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZReviewTender</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.GithubRepoStarsCountPlugin['ZhgChgLi/ZReviewTender']}}</span><span class="nv"> </span><span class="s">Stars)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/ZhgChgLi/ZReviewTender"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZReviewTender</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">tool</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">fetch</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">reviews</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">App</span><span class="nv"> </span><span class="s">Store</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">Google</span><span class="nv"> </span><span class="s">Play</span><span class="nv"> </span><span class="s">Console</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">integrate</span><span class="nv"> </span><span class="s">them</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">workflows."</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZReviewTender</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">tool</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">fetch</span><span class="nv"> </span><span class="s">app</span><span class="nv"> </span><span class="s">reviews</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">App</span><span class="nv"> </span><span class="s">Store</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">Google</span><span class="nv"> </span><span class="s">Play</span><span class="nv"> </span><span class="s">Console</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">integrate</span><span class="nv"> </span><span class="s">them</span><span class="nv"> </span><span class="s">into</span><span class="nv"> </span><span class="s">workflows."</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-markdown"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMediumToMarkdown</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.GithubRepoStarsCountPlugin['ZhgChgLi/ZMediumToMarkdown']}}</span><span class="nv"> </span><span class="s">Stars)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/ZhgChgLi/ZMediumToMarkdown"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMediumToMarkdown</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">powerful</span><span class="nv"> </span><span class="s">tool</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">easily</span><span class="nv"> </span><span class="s">download</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">convert</span><span class="nv"> </span><span class="s">Medium</span><span class="nv"> </span><span class="s">articles</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">Markdown</span><span class="nv"> </span><span class="s">format."</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZMediumToMarkdown</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">powerful</span><span class="nv"> </span><span class="s">tool</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">easily</span><span class="nv"> </span><span class="s">download</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">convert</span><span class="nv"> </span><span class="s">Medium</span><span class="nv"> </span><span class="s">articles</span><span class="nv"> </span><span class="s">to</span><span class="nv"> </span><span class="s">Markdown</span><span class="nv"> </span><span class="s">format."</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">link</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-github"</span>  
      <span class="na">text</span><span class="pi">:</span> <span class="s2">"</span><span class="s">linkyee</span><span class="nv"> </span><span class="s">&lt;span</span><span class="nv"> </span><span class="s">class='link-button-text'&gt;({{vars.GithubRepoStarsCountPlugin['ZhgChgLi/linkyee']}}</span><span class="nv"> </span><span class="s">Stars)&lt;/span&gt;"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/ZhgChgLi/linkyee"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">linkyee</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">fully</span><span class="nv"> </span><span class="s">customizable</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">open-source</span><span class="nv"> </span><span class="s">LinkTree</span><span class="nv"> </span><span class="s">alternative</span><span class="nv"> </span><span class="s">that</span><span class="nv"> </span><span class="s">can</span><span class="nv"> </span><span class="s">be</span><span class="nv"> </span><span class="s">deployed</span><span class="nv"> </span><span class="s">directly</span><span class="nv"> </span><span class="s">on</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Pages."</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">linkyee</span><span class="nv"> </span><span class="s">is</span><span class="nv"> </span><span class="s">a</span><span class="nv"> </span><span class="s">fully</span><span class="nv"> </span><span class="s">customizable</span><span class="nv"> </span><span class="s">and</span><span class="nv"> </span><span class="s">open-source</span><span class="nv"> </span><span class="s">LinkTree</span><span class="nv"> </span><span class="s">alternative</span><span class="nv"> </span><span class="s">that</span><span class="nv"> </span><span class="s">can</span><span class="nv"> </span><span class="s">be</span><span class="nv"> </span><span class="s">deployed</span><span class="nv"> </span><span class="s">directly</span><span class="nv"> </span><span class="s">on</span><span class="nv"> </span><span class="s">GitHub</span><span class="nv"> </span><span class="s">Pages."</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  

<span class="c1"># Social media links list  </span>
<span class="c1"># icon: use Font Awesome icons (https://fontawesome.com/search?o=r&amp;m=free)  </span>
<span class="c1"># title: link title  </span>
<span class="c1"># url: social media link URL  </span>
<span class="c1"># alt: alternative text (for accessibility)  </span>
<span class="c1"># target: `_blank` opens in new tab, `_self` opens in the same page  </span>
<span class="na">socials</span><span class="pi">:</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-medium"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://blog.zhgchg.li"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Medium"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Medium"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-github"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://github.com/ZhgChgLi"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">GitHub"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">GitHub"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-twitter"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://twitter.com/zhgchgli"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Twitter"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Twitter"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-linkedin"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://www.linkedin.com/in/zhgchgli/"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">LinkedIn"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">LinkedIn"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-brands</span><span class="nv"> </span><span class="s">fa-instagram"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://www.instagram.com/zhgchgli/"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Instagram"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">ZhgChgLi's</span><span class="nv"> </span><span class="s">Instagram"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  
  <span class="pi">-</span> <span class="na">social</span><span class="pi">:</span>  
      <span class="na">icon</span><span class="pi">:</span> <span class="s2">"</span><span class="s">fa-solid</span><span class="nv"> </span><span class="s">fa-envelope"</span>  
      <span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">zhgchgli@gmail.com"</span>  
      <span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Email:</span><span class="nv"> </span><span class="s">zhgchgli@gmail.com"</span>  
      <span class="na">alt</span><span class="pi">:</span> <span class="s2">"</span><span class="s">zhgchgli@gmail.com"</span>  
      <span class="na">target</span><span class="pi">:</span> <span class="s2">"</span><span class="s">_blank"</span>  

<span class="c1"># Footer text  </span>
<span class="na">footer</span><span class="pi">:</span> <span class="pi">&gt;</span>  
    <span class="s">Welcome to my website! Follow me on Medium or GitHub for the latest updates, or stay connected on Instagram and LinkedIn.  </span>

<span class="c1"># Footer copyright statement  </span>
<span class="c1"># Linkyee is a 100% free open-source project—you can freely modify the copyright statement. :)  </span>
<span class="na">copyright</span><span class="pi">:</span> <span class="pi">&gt;</span>  
  <span class="s">© 2024 &lt;a href="https://zhgchg.li" target="_blank"&gt;ZhgChgLi&lt;/a&gt;. Powered by &lt;a href="https://github.com/ZhgChgLi/linkyee" target="_blank"&gt;linkyee&lt;/a&gt;.  </span>
</code></pre></div></div>

<blockquote>
  <p><strong><em>Please note that after each file modification, you need to wait for GitHub Actions to finish the automatic build and the page build and deployment tasks.</em></strong></p>
</blockquote>

<p><img src="/assets/70aeddb1fd9b/1*56qGAyuECrqDJQMoKbPiOw.webp" alt="" loading="lazy" decoding="async" width="1200" height="999" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk5OSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*56qGAyuECrqDJQMoKbPiOw.png" /></p>

<blockquote>
  <p><strong><em>Refresh the page to apply the changes. 🚀</em></strong></p>
</blockquote>

<p><img src="/assets/70aeddb1fd9b/1*CbiCUtVY5CV4wRXBaZBoyw.webp" alt="" loading="lazy" decoding="async" width="1200" height="809" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwOSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*CbiCUtVY5CV4wRXBaZBoyw.jpeg" /></p>

<p>Success!!</p>

<h4 id="customize-styles-and-modify-the-default-theme">Customize Styles and Modify the Default Theme</h4>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">./themes/default/index.html</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">./themes/default/styles.css</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">./themes/default/scripts.js</code></p>
  </li>
</ul>

<h4 id="create-a-new-theme">Create a New Theme</h4>

<ul>
  <li>
    <p>./themes/ <code class="language-plaintext highlighter-rouge">YOUR_THEME</code></p>
  </li>
  <li>
    <p>Update to <code class="language-plaintext highlighter-rouge">theme:YOUR_THEME</code> in the config.yml file</p>
  </li>
</ul>

<p><strong>Yes, you can use GenAI tools like ChatGPT to help you create a custom link page! (The default style was also generated by ChatGPT)</strong></p>

<h4 id="automatic-redeployment">Automatic Redeployment</h4>

<p>By default, the project automatically redeploys once a day to refresh the plugin’s dynamic variable values. You can adjust the cron settings in <a href="https://github.com/ZhgChgLi/linkyee/blob/main/.github/workflows/build.yml" target="_blank">Github Action — Automatic build (.github/workflows/build.yml)</a>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">schedule</span><span class="pi">:</span>
 <span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s1">'</span><span class="s">0</span><span class="nv"> </span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*'</span> <span class="c1"># Run daily at midnight 00:00 (UTC)</span>
</code></pre></div></div>

<p>If you don’t need scheduled redeployment, you can simply delete the <code class="language-plaintext highlighter-rouge">schedule</code> block.</p>

<h3 id="custom-domain-️️️">Custom Domain ❤️❤️❤️</h3>

<p>You can set a custom GitHub Pages domain, <strong>for example mine: <a href="https://link.zhgchg.li" target="_blank">https://link.zhgchg.li</a> .</strong></p>

<p>You can refer to my previous article “<a href="/posts/zrealm-dev/github-pages-custom-domain-setup-replace-github-io-with-your-own-domain-483af5d93297/"><strong>Github Pages Custom Domain Tutorial</strong></a>” for a step-by-step guide from purchasing to binding a domain. You can also buy a domain through <a href="https://namecheap.pxf.io/P0jdZQ" target="_blank"><strong>my Namecheap referral link</strong></a> — I will receive a small commission, which helps me continue contributing to open source projects.</p>

<h3 id="buy-me-a-coffee-️️️">Buy me a coffee ❤️❤️❤️</h3>

<p><img src="/assets/70aeddb1fd9b/1*QCQqlZr6doDP-cszzpaSpw.webp" alt="&lt;https://www.paypal.com/ncp/payment/CMALMPT8UUTY2&gt;" loading="lazy" decoding="async" width="1090" height="306" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDkwIiBoZWlnaHQ9IjMwNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/70aeddb1fd9b/1*QCQqlZr6doDP-cszzpaSpw.png" /></p>

<p><a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">https://www.paypal.com/ncp/payment/CMALMPT8UUTY2</a></p>

<p>If this project helps you, please consider <a href="https://github.com/ZhgChgLi/linkyee" target="_blank">Starring the Repo or recommending it to friends</a>, <strong>or <a href="https://www.paypal.com/ncp/payment/CMALMPT8UUTY2" target="_blank">buying me a coffee. Thank you for your support!</a></strong></p>

<p>Feel free to submit an Issue or contribute fixes via Pull Request. :)</p>]]></content>
  </entry><entry>
    <title type="html">GA4 Data Alerts Automation｜3-Step Guide to Build Free Telegram Bot Notifications</title>
    <link href="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/" rel="alternate" type="text/html" title="GA4 Data Alerts Automation｜3-Step Guide to Build Free Telegram Bot Notifications" />
    <published>2024-10-20T16:19:52+08:00</published>
    <updated>2024-10-20T16:19:52+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-robotic-process-automation/1e85b8df2348</id><summary type="html">Discover how to automate GA4 data notifications using Google Apps Script in 3 simple steps. Eliminate manual monitoring with a free Telegram Bot that delivers real-time insights, boosting your data responsiveness effortlessly.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Robotic Process Automation" /><category term="automation" /><category term="google-apps-script" /><category term="telegram" /><category term="google-analytics" /><category term="rpa-solutions" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/1e85b8df2348/1*La0AKKSrGNP9EZUV-vrONQ.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/"><![CDATA[<h3 id="simple-3-steps--build-a-free-ga4-automated-data-notification-bot">Simple 3 Steps — Build a Free GA4 Automated Data Notification Bot</h3>

<p>Using Google Apps Script for RPA: Connecting GA4 + Telegram Bot Data Notification Robot by Yourself</p>

<p><img src="/assets/1e85b8df2348/1*La0AKKSrGNP9EZUV-vrONQ.webp" alt="Photo by BoliviaInteligente" loading="lazy" decoding="async" width="1200" height="750" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijc1MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/1e85b8df2348/1*La0AKKSrGNP9EZUV-vrONQ.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@boliviainteligente?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">BoliviaInteligente</a></p>

<h3 id="introduction">Introduction</h3>

<p>Since around 2020, I have been exploring how to use available tools to implement RPA. Initially, it was just to automate personal routine tasks. Later, at work, I joined larger organizations and often encountered cross-team or person-dependent tasks, as well as repetitive tasks. That’s when I realized the true benefits of RPA automation.</p>

<p>For example: a repetitive task occurs 10 times a month, each time taking 30 minutes to handle, and 60 people are involved. The team spends 3,600 hours per year on this. If 100 hours are invested to develop automation, the time saved afterward can be used for more valuable work; effectively, 3,600 hours of wasted labor plus 3,600 hours of more worthwhile output.</p>

<h4 id="for-more-details-please-refer-to-my-previous-article"><strong>For more details, please refer to my previous article:</strong></h4>

<ul>
  <li>
    <p><a href="/posts/pinkoi-engineering/high-efficiency-engineering-teams-pinkoi-tech-career-talk-insights-11f6c8568154/">2021 Pinkoi Tech Career Talk — Secrets of High-Efficiency Engineering Teams</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Automate Daily Data Reports with Google Apps Script RPA</a></p>
  </li>
</ul>

<h4 id="other-rpas-i-have-done">Other RPAs I Have Done:</h4>

<ul>
  <li>
    <p>[GMail to Slack] <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">Forward Gmail Messages to Slack Using Google Apps Script</a></p>
  </li>
  <li>
    <p>[Google Form x Google Sheet x Slack] <a href="https://medium.com/zrealm-robotic-process-automation/slack-%E6%89%93%E9%80%A0%E5%85%A8%E8%87%AA%E5%8B%95-wfh-%E5%93%A1%E5%B7%A5%E5%81%A5%E5%BA%B7%E7%8B%80%E6%B3%81%E5%9B%9E%E5%A0%B1%E7%B3%BB%E7%B5%B1-d61062833c1a?source=collection_home---6------9-----------------------" target="_blank">Create a Fully Automated WFH Employee Health Status Reporting System with Slack</a></p>
  </li>
  <li>
    <p>[Big Query x Slack ] <a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-big-query-%E6%89%93%E9%80%A0%E6%9B%B4%E5%8D%B3%E6%99%82%E4%BE%BF%E5%88%A9%E7%9A%84-crash-%E8%BF%BD%E8%B9%A4%E5%B7%A5%E5%85%B7-e77b80cc6f89?source=collection_home---6------7-----------------------" target="_blank">Crashlytics + Big Query: Building a More Real-Time and Convenient Crash Tracking Tool</a></p>
  </li>
  <li>
    <p>[Google Analytics x Slack] <a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-google-analytics-%E8%87%AA%E5%8B%95%E6%9F%A5%E8%A9%A2-app-crash-free-users-rate-793cb8f89b72?source=collection_home---6------6-----------------------" target="_blank">Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate</a></p>
  </li>
  <li>
    <p>[Github Webhook x Line Notify] <a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E4%B8%89%E6%AD%A5%E9%A9%9F%E5%85%8D%E8%B2%BB%E5%BB%BA%E7%AB%8B-github-repo-star-notifier-382218e15697?source=collection_home---6------5-----------------------" target="_blank">Create a Free Github Repo Star Notifier in 3 Steps Using Google Apps Script</a></p>
  </li>
  <li>
    <p>[Slack x OpenAI (ChatGTP)] <a href="https://medium.com/zrealm-robotic-process-automation/slack-chatgpt-integration-bd94cc88f9c9?source=collection_home---6------4-----------------------" target="_blank">Slack &amp; ChatGPT Integration</a></p>
  </li>
  <li>
    <p>[Google Analytics x Google Sheet] <a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E5%AF%A6%E7%8F%BE-google-%E6%9C%8D%E5%8B%99-rpa-%E8%87%AA%E5%8B%95%E5%8C%96-f6713ba3fee3?source=collection_home---6------3-----------------------" target="_blank">Automate Daily Data Reports with Google Apps Script RPA</a></p>
  </li>
  <li>
    <p>[iOS Shortcut x Line x Reminders] <a href="https://medium.com/zrealm-robotic-process-automation/ios-%E6%8D%B7%E5%BE%91%E8%87%AA%E5%8B%95%E5%8C%96%E6%87%89%E7%94%A8%E5%A0%B4%E6%99%AF-%E8%87%AA%E5%8B%95%E8%BD%89%E7%99%BC%E7%B0%A1%E8%A8%8A%E8%88%87%E8%87%AA%E5%8B%95%E5%BB%BA%E7%AB%8B%E6%8F%90%E9%86%92%E5%BE%85%E8%BE%A6%E4%BA%8B%E9%A0%85-309d0302877b?source=collection_home---6------2-----------------------" target="_blank">iOS Shortcut Automation Use Case — Auto Forward SMS and Auto Create Reminder Tasks</a></p>
  </li>
  <li>
    <p>[Apple Store API x Google Play Console API x Github Action] <a href="https://medium.com/zrealm-robotic-process-automation/quick-start-github-action-x-zreviewtender-%E5%85%8D%E8%B2%BB%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2%E4%BD%A0%E7%9A%84-app-%E5%95%86%E5%9F%8E%E8%A9%95%E5%83%B9%E7%9B%A3%E6%8E%A7%E6%A9%9F%E5%99%A8%E4%BA%BA-0095528cf875?source=collection_home---6------1-----------------------" target="_blank">Github Action x ZReviewTender Free and Fast Deployment of Your App Store Review Monitoring Bot</a></p>
  </li>
  <li>
    <p>[Telegram Bot] <a href="https://medium.com/zrealm-robotic-process-automation/10-%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E7%A7%BB%E8%BD%89-line-notify-%E5%88%B0-telegram-bot-%E9%80%9A%E7%9F%A5-6922e90ba90c?source=collection_home---6------0-----------------------" target="_blank">10-Minute Quick Migration from Line Notify to Telegram Bot Notifications</a></p>
  </li>
  <li>
    <p>[Medium to Jekyllrb] <a href="/posts/zrealm-dev/seamlessly-migrate-medium-content-to-self-hosted-sites-github-pages-with-jekyll-chirpy-a0c08d579ab1/">Effortlessly Migrate Medium to a Self-Hosted Website</a></p>
  </li>
</ul>

<p>From the backend data, many articles have been indexed by ChatGPT and various GenAI services, indirectly helping many non-engineers who want to try using RPA to solve problems. Therefore, I will continue to share RPA scenarios from my life and work, along with my solutions — <a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-gmail-forwarding-to-slack-channels-with-custom-filters-d414bdbdb8c9/">ZRealm Robotic Process Automation</a>.</p>

<h4 id="commercial-time">Commercial Time</h4>

<p>If you and your team need automation tools or process integration, whether it’s Slack App development, Notion, Asana, Google Sheets, Google Forms, GA data, or any other integration needs, feel free to <a href="https://zhgchg.li/contact/" target="_blank"><strong>contact me for development</strong></a>.</p>

<h3 id="this-article-google-analytics-4-x-telegram-bot">This Article: Google Analytics 4 x Telegram Bot</h3>

<p>This integration scenario follows the previous article “<a href="https://medium.com/zrealm-robotic-process-automation/10-%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E7%A7%BB%E8%BD%89-line-notify-%E5%88%B0-telegram-bot-%E9%80%9A%E7%9F%A5-6922e90ba90c?source=collection_home---6------0-----------------------" target="_blank">10-Minute Quick Migration from Line Notify to Telegram Bot Notifications</a>”. I realized that my Medium backup site “<a href="https://zhgchg.li/" target="_blank">zhgchg.li</a>” has not been monitored for GA4 website data. So, I thought of creating a notification bot to send the past 7 days’ website data daily to a specified Telegram Channel to keep me informed.</p>

<p>This article is just a brief note. For a complete automated data report, please refer to the previous article “<a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E5%AF%A6%E7%8F%BE-google-%E6%9C%8D%E5%8B%99-rpa-%E8%87%AA%E5%8B%95%E5%8C%96-f6713ba3fee3?source=collection_home---6------3-----------------------" target="_blank">Using Google Apps Script to Implement Daily Data Report RPA Automation</a>”.<br />
Also, a previous article on fetching GA4 App Crash-free rate can be found here: “<a href="https://medium.com/zrealm-robotic-process-automation/crashlytics-google-analytics-%E8%87%AA%E5%8B%95%E6%9F%A5%E8%A9%A2-app-crash-free-users-rate-793cb8f89b72?source=collection_home---6------6-----------------------" target="_blank">Crashlytics + Google Analytics Automatic Query for App Crash-Free Users Rate</a>”.</p>

<ul>
  <li>
    <p>The free limits, detailed usage, deployment, and features of Google Apps Script will not be further explained in this article. Please refer to the <a href="https://medium.com/zrealm-robotic-process-automation/%E4%BD%BF%E7%94%A8-google-apps-script-%E5%AF%A6%E7%8F%BE-google-%E6%9C%8D%E5%8B%99-rpa-%E8%87%AA%E5%8B%95%E5%8C%96-f6713ba3fee3?source=collection_home---6------3-----------------------" target="_blank">previous article</a>.</p>
  </li>
  <li>
    <p>Creating and using a Telegram Bot will not be further explained in this article. Please refer to the <a href="https://medium.com/zrealm-robotic-process-automation/10-%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E7%A7%BB%E8%BD%89-line-notify-%E5%88%B0-telegram-bot-%E9%80%9A%E7%9F%A5-6922e90ba90c?source=collection_home---6------0-----------------------" target="_blank">previous article</a>.</p>
  </li>
</ul>

<h4 id="results">Results</h4>

<p><img src="/assets/1e85b8df2348/1*J7QdgrJRNzVFQJfW5UBBFA.webp" alt="" loading="lazy" decoding="async" width="555" height="460" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NTUiIGhlaWdodD0iNDYwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*J7QdgrJRNzVFQJfW5UBBFA.png" /></p>

<p>First, the final result: Google Apps Script automatically fetches my desired Google Analytics 4 website data between 12–1 PM every day, formats it into a message, and sends it to my Telegram Channel via a Telegram Bot. I can quickly review the website data from the past 7 days.</p>

<p><strong>The data I want to monitor is:</strong></p>

<ul>
  <li>
    <p>Total page views <code class="language-plaintext highlighter-rouge">screenPageViews</code> in the last 7 days <code class="language-plaintext highlighter-rouge">7daysAgo ~ today</code></p>
  </li>
  <li>
    <p>Active Users <code class="language-plaintext highlighter-rouge">active7DayUsers</code></p>
  </li>
  <li>
    <p>New Users <code class="language-plaintext highlighter-rouge">newUsers</code></p>
  </li>
  <li>
    <p>Top 10 Page Views <code class="language-plaintext highlighter-rouge">screenPageViews</code> / <code class="language-plaintext highlighter-rouge">pageTitle</code></p>
  </li>
  <li>
    <p>New Users Initial Source Medium <code class="language-plaintext highlighter-rouge">newUsers</code> / <code class="language-plaintext highlighter-rouge">firstUserSourceMedium</code></p>
  </li>
</ul>

<blockquote>
  <p><em>You can customize and generate queries according to your own needs using <a href="https://ga-dev-tools.google/ga4/query-explorer/" target="_blank">GA Dev Tools</a>.</em></p>
</blockquote>

<h3 id="step-1-use-the-ga4-query-explorer-official-tool-to-generate-data-report-query-parameters">Step 1. Use the <a href="https://ga-dev-tools.google/ga4/query-explorer/" target="_blank">GA4 Query Explorer official tool</a> to generate data report query parameters</h3>

<p>First, we need to use the official <a href="https://ga-dev-tools.google/ga4/query-explorer/" target="_blank">GA4 Query Explorer</a> tool to generate the query parameters for the data report we need:</p>

<p><img src="/assets/1e85b8df2348/1*4b1S9nYSmO7OmGgDPllxeQ.webp" alt="" loading="lazy" decoding="async" width="1144" height="1291" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTQ0IiBoZWlnaHQ9IjEyOTEiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/1e85b8df2348/1*4b1S9nYSmO7OmGgDPllxeQ.png" /></p>

<ol>
  <li>
    <p>Select Property: Note down your <code class="language-plaintext highlighter-rouge">property ID</code><br />
<strong>The property ID will be used later when writing the Google Apps Script.</strong></p>
  </li>
  <li>
    <p>start date, end date: The date range for the report, can use <code class="language-plaintext highlighter-rouge">YYYY-MM-DD</code> or the magic variables <code class="language-plaintext highlighter-rouge">yesterday</code>, <code class="language-plaintext highlighter-rouge">today</code>, <code class="language-plaintext highlighter-rouge">NdaysAgo</code>.</p>
  </li>
  <li>
    <p>metrics: Choose the metrics you want to query</p>
  </li>
  <li>
    <p>dimensions: Select the dimensions you want to query</p>
  </li>
  <li>
    <p>metric aggregations: Data aggregation rules</p>
  </li>
</ol>

<p>Here is an example based on my scenario:</p>

<ol>
  <li>
    <p>property ID: <code class="language-plaintext highlighter-rouge">318495208</code></p>
  </li>
  <li>
    <p>start_date: <code class="language-plaintext highlighter-rouge">7daysAgo</code></p>
  </li>
  <li>
    <p>end_date: <code class="language-plaintext highlighter-rouge">yesterday</code><br />
Because GA data reports have a delay, querying data from the previous day to seven days ago is the most accurate.</p>
  </li>
  <li>
    <p>metric aggregations: <code class="language-plaintext highlighter-rouge">total</code></p>
  </li>
</ol>

<p><strong>Other filters and limits can be set according to your needs:</strong></p>

<p><img src="/assets/1e85b8df2348/1*g2psNn3gMZWs4OFRx7phWQ.webp" alt="" loading="lazy" decoding="async" width="832" height="694" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MzIiIGhlaWdodD0iNjk0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*g2psNn3gMZWs4OFRx7phWQ.png" /></p>

<p>Leave the filter empty since I don’t need it; I set the limit to 10 because I only want to know the Top 10.</p>

<h4 id="click-make-request-to-generate-the-corresponding-data-report-query-parameters-and-results"><strong>Click “MAKE REQUEST” to generate the corresponding data report query parameters and results:</strong></h4>

<p><img src="/assets/1e85b8df2348/1*Rj1kMTyZiEYGvAztEEngIQ.webp" alt="" loading="lazy" decoding="async" width="808" height="693" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MDgiIGhlaWdodD0iNjkzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*Rj1kMTyZiEYGvAztEEngIQ.png" /></p>

<h4 id="note-the-following-request-parameters-which-will-be-used-later-when-writing-the-google-apps-script"><strong>Note the following request parameters, which will be used later when writing the Google Apps Script:</strong></h4>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"dimensions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pageTitle"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"metrics"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"screenPageViews"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"dateRanges"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"startDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"7daysAgo"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"endDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yesterday"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"limit"</span><span class="p">:</span><span class="w"> </span><span class="s2">"10"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"metricAggregations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"TOTAL"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="result"><strong>Result:</strong></h4>

<p><img src="/assets/1e85b8df2348/1*ivAXeRTVB8Y7zbP-Gq2I2A.webp" alt="" loading="lazy" decoding="async" width="801" height="903" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4MDEiIGhlaWdodD0iOTAzIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*ivAXeRTVB8Y7zbP-Gq2I2A.png" /></p>

<p><img src="/assets/1e85b8df2348/1*FFThyuptIYrGsfddHcjupQ.webp" alt="" loading="lazy" decoding="async" width="1179" height="2556" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMTc5IiBoZWlnaHQ9IjI1NTYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/1e85b8df2348/1*FFThyuptIYrGsfddHcjupQ.jpeg" /></p>

<ul>
  <li>Compare with the data on GA to ensure accuracy, correctly matched ✅✅✅</li>
</ul>

<h3 id="step-2-create-google-apps-script--use-google-analytics-data-api-to-query-data">Step 2. Create Google Apps Script &amp; Use Google Analytics Data API to Query Data</h3>

<ul>
  <li>
    <p>Go to <a href="https://script.google.com/home" target="_blank">https://script.google.com/home</a></p>
  </li>
  <li>
    <p>Create a new project and name it</p>
  </li>
  <li>
    <p>Click “Services” -&gt; “+” to add a new service</p>
  </li>
  <li>
    <p>Select “Google Analytics Data API”</p>
  </li>
  <li>
    <p>Click “Add”</p>
  </li>
</ul>

<p><img src="/assets/1e85b8df2348/1*gcMDDQTXd-Gos5AdGUXIOw.webp" alt="" loading="lazy" decoding="async" width="1200" height="963" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk2MyI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/1e85b8df2348/1*gcMDDQTXd-Gos5AdGUXIOw.png" /></p>

<p><strong>Paste the Google Analytics Data API query code and combine:</strong></p>

<p>The report query parameters generated from the previous steps:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"dimensions"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pageTitle"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"metrics"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"screenPageViews"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"dateRanges"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"startDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"7daysAgo"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"endDate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yesterday"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"limit"</span><span class="p">:</span><span class="w"> </span><span class="s2">"10"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"metricAggregations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"TOTAL"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p><strong>Convert to Code:</strong></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">execute</span><span class="p">()</span> <span class="p">{</span>
  <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nf">fetchScreenPageViews</span><span class="p">(</span><span class="dl">"</span><span class="s2">318495208</span><span class="dl">"</span><span class="p">)));</span>
<span class="p">}</span>

<span class="c1">// Split into a separate function for easier reuse...</span>
<span class="c1">// Default startDate=7daysAgo, endDate=yesterday</span>
<span class="c1">// Other usages:</span>
<span class="c1">// e.g. fetchScreenPageViews("1111", "3daysAgo", "yesterday")</span>
<span class="c1">// e.g. fetchScreenPageViews("2222", "yesterday", "today")</span>
<span class="kd">function</span> <span class="nf">fetchScreenPageViews</span><span class="p">(</span><span class="nx">propertyId</span><span class="p">,</span> <span class="nx">startDate</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">7daysAgo</span><span class="dl">"</span><span class="p">,</span> <span class="nx">endDate</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">yesterday</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">screenPageViewsMetric</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newMetric</span><span class="p">();</span>
  <span class="nx">screenPageViewsMetric</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">screenPageViews</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">dateRange</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDateRange</span><span class="p">();</span>
  <span class="nx">dateRange</span><span class="p">.</span><span class="nx">startDate</span> <span class="o">=</span> <span class="nx">startDate</span><span class="p">;</span>
  <span class="nx">dateRange</span><span class="p">.</span><span class="nx">endDate</span> <span class="o">=</span> <span class="nx">endDate</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">pageTitleDimension</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDimension</span><span class="p">();</span>
  <span class="nx">pageTitleDimension</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">pageTitle</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newRunReportRequest</span><span class="p">();</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">dimensions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">pageTitleDimension</span><span class="p">];</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">metrics</span> <span class="o">=</span> <span class="p">[</span><span class="nx">screenPageViewsMetric</span><span class="p">];</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">dateRanges</span> <span class="o">=</span> <span class="nx">dateRange</span><span class="p">;</span>

  <span class="nx">request</span><span class="p">.</span><span class="nx">limit</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">metricAggregations</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TOTAL</span><span class="dl">"</span><span class="p">;</span>

  <span class="k">return</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nx">Properties</span><span class="p">.</span><span class="nf">runReport</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="dl">"</span><span class="s2">properties/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">propertyId</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Code Analysis:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Metrics can be multiple, declare them separately...</span>
<span class="kd">const</span> <span class="nx">screenPageViewsMetric</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newMetric</span><span class="p">();</span>
<span class="nx">screenPageViewsMetric</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">screenPageViews</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// For example, another metric active1DayUsers:</span>
<span class="kd">const</span> <span class="nx">active1DayUsersMetric</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newMetric</span><span class="p">();</span>
<span class="nx">active1DayUsersMetric</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">active1DayUsers</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// Declare date range</span>
<span class="kd">const</span> <span class="nx">dateRange</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDateRange</span><span class="p">();</span>
<span class="nx">dateRange</span><span class="p">.</span><span class="nx">startDate</span> <span class="o">=</span> <span class="nx">startDate</span><span class="p">;</span>
<span class="nx">dateRange</span><span class="p">.</span><span class="nx">endDate</span> <span class="o">=</span> <span class="nx">endDate</span><span class="p">;</span>

<span class="c1">// Dimensions can be multiple, declare them separately...</span>
<span class="kd">const</span> <span class="nx">pageTitleDimension</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDimension</span><span class="p">();</span>
<span class="nx">pageTitleDimension</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">pageTitle</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// For example, another dimension:</span>
<span class="kd">const</span> <span class="nx">firstUserSourceMediumDimension</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDimension</span><span class="p">();</span>
<span class="nx">firstUserSourceMediumDimension</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">firstUserSourceMedium</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">//</span>

<span class="c1">// Create Request object</span>
<span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newRunReportRequest</span><span class="p">();</span>
<span class="nx">request</span><span class="p">.</span><span class="nx">metrics</span> <span class="o">=</span> <span class="p">[</span><span class="nx">active1DayUsersMetric</span><span class="p">,</span> <span class="nx">active1DayUsersMetric</span><span class="p">];</span> <span class="c1">// Include all metrics...</span>
<span class="nx">request</span><span class="p">.</span><span class="nx">dimensions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">pageTitleDimension</span><span class="p">,</span> <span class="nx">firstUserSourceMediumDimension</span><span class="p">];</span> <span class="c1">// Include all dimensions...</span>

<span class="nx">request</span><span class="p">.</span><span class="nx">dateRanges</span> <span class="o">=</span> <span class="nx">dateRange</span><span class="p">;</span>

<span class="c1">// Only need top 10 rows</span>
<span class="nx">request</span><span class="p">.</span><span class="nx">limit</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>

<span class="c1">// Set metric aggregation logic: Total (SUM)</span>
<span class="nx">request</span><span class="p">.</span><span class="nx">metricAggregations</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TOTAL</span><span class="dl">"</span><span class="p">;</span>

<span class="c1">// Generate query result</span>
<span class="k">return</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nx">Properties</span><span class="p">.</span><span class="nf">runReport</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="dl">"</span><span class="s2">properties/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">propertyId</span><span class="p">).</span><span class="nx">rows</span><span class="p">;</span>
</code></pre></div></div>

<p><strong>The first time you run, authorization is required (if new permissions are needed due to code updates, re-authorization will be required):</strong></p>

<blockquote>
  <p><em>In fact, you are authorizing Google Apps Script to run these programs using your account identity in the future, so you need to ensure that the account you choose has access to the corresponding GA reports.</em></p>
</blockquote>

<p><img src="/assets/1e85b8df2348/1*_z-P8a4PZCozhtgssH82lA.webp" alt="" loading="lazy" decoding="async" width="579" height="477" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NzkiIGhlaWdodD0iNDc3Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*_z-P8a4PZCozhtgssH82lA.png" /></p>

<ul>
  <li>After writing the code, click “Debug” -&gt; then click “Review Permissions”</li>
</ul>

<p><img src="/assets/1e85b8df2348/1*ehox-4AjKv3Ddf1jJ_VmTQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="954" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/1e85b8df2348/1*ehox-4AjKv3Ddf1jJ_VmTQ.png" /></p>

<ul>
  <li>Select the account to execute, usually the current Google Apps Script account.</li>
</ul>

<p><img src="/assets/1e85b8df2348/1*onR-n1sI4-G9KhD_fhef3Q.webp" alt="" loading="lazy" decoding="async" width="1265" height="1006" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY1IiBoZWlnaHQ9IjEwMDYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/1e85b8df2348/1*onR-n1sI4-G9KhD_fhef3Q.png" /></p>

<ul>
  <li>Select “Advanced” to expand -&gt; Click “Go to XXX”<br />
This is an app we made for our own use, so it does not require Google verification.</li>
</ul>

<p><img src="/assets/1e85b8df2348/1*73fQnaB__qKhO7NndQ0GyQ.webp" alt="" loading="lazy" decoding="async" width="1200" height="954" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk1NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/1e85b8df2348/1*73fQnaB__qKhO7NndQ0GyQ.png" /></p>

<ul>
  <li>Click “Allow”</li>
</ul>

<p><strong>Allowing you to click “Debug” or “Run” later to execute the script:</strong></p>

<p><img src="/assets/1e85b8df2348/1*DbcQnfp8xdJCFnrDc778tw.webp" alt="" loading="lazy" decoding="async" width="1265" height="1006" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjY1IiBoZWlnaHQ9IjEwMDYiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/1e85b8df2348/1*DbcQnfp8xdJCFnrDc778tw.png" /></p>

<p>Here, we first use <code class="language-plaintext highlighter-rouge">Logger.log(JSON.stringify())</code> to get the output result:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"kind"</span><span class="p">:</span><span class="w"> </span><span class="s2">"analyticsData#runReport"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"dimensionHeaders"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pageTitle"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"rowCount"</span><span class="p">:</span><span class="w"> </span><span class="mi">71</span><span class="p">,</span><span class="w">
  </span><span class="nl">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"currencyCode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TWD"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"timeZone"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Asia/Taipei"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"rows"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"166"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"109"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Apple Watch Original Stainless Steel Milanese Band Unboxing </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"iOS ≥ 13.1 Using 'Shortcuts' Automation with Mijia Smart Home </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"101"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Medium Partner Program Finally Open to Global (Including Taiwan) Writers! </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"85"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"77"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"iOS Shortcuts Automation Use Cases — Auto Forward SMS and Auto Create Reminders </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"51"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Travelogue 9/11 Nagoya One-Day Flash Free Trip </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"42"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The Past and Present of iOS Privacy and Convenience </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"iOS Vision framework x WWDC 24 Discover Swift enhancements in the Vision framework Session </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"34"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"iOS ≥ 18 NSAttributedString attributes Range Merging Behavior Change </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"30"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The Story of Manually Building an HTML Parser </span><span class="se">\\</span><span class="s2">| ZhgChgLi"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"metricHeaders"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"TYPE_INTEGER"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"screenPageViews"</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"totals"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"dimensionValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"RESERVED_TOTAL"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"metricValues"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
          </span><span class="nl">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1229"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li><strong>Google Apps Script Successfully Requested GA Data! 🎉🎉🎉</strong></li>
</ul>

<h3 id="step-3-put-it-all-together-google-apps-script--ga4--telegram-bot">Step 3. Put It All Together! Google Apps Script + GA4 + Telegram Bot</h3>

<p>Follow the previous article “<a href="https://medium.com/zrealm-robotic-process-automation/10-%E5%88%86%E9%90%98%E5%BF%AB%E9%80%9F%E7%A7%BB%E8%BD%89-line-notify-%E5%88%B0-telegram-bot-%E9%80%9A%E7%9F%A5-6922e90ba90c?source=collection_home---6------0-----------------------" target="_blank">10-minute Quick Migration from Line Notify to Telegram Bot Notification</a>” to create your Telegram Bot and get the <code class="language-plaintext highlighter-rouge">Bot Token</code> &amp; the <code class="language-plaintext highlighter-rouge">Channel Chat ID</code> where you want to send messages.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">telegramToken</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">XXXX</span><span class="dl">"</span> <span class="c1">// Insert your Telegram Bot Token</span>
<span class="c1">//</span>

<span class="kd">function</span> <span class="nf">execute</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">screenPageViewsReport</span> <span class="o">=</span> <span class="nf">fetchScreenPageViews</span><span class="p">(</span><span class="dl">"</span><span class="s2">318495208</span><span class="dl">"</span><span class="p">);</span>
  
  <span class="c1">//</span>
  <span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nf">parseInt</span><span class="p">(</span><span class="nx">screenPageViewsReport</span><span class="p">.</span><span class="nx">totals</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">metricValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">);</span>
  <span class="kd">var</span> <span class="nx">message</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Total Views: </span><span class="dl">"</span><span class="o">+</span><span class="nx">total</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">()</span><span class="o">+</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">;</span>

  <span class="nx">screenPageViewsReport</span><span class="p">.</span><span class="nx">rows</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">element</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">pageTitle</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">dimensionValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nf">parseInt</span><span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">metricValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">);</span>

    <span class="nx">message</span> <span class="o">+=</span> <span class="dl">"</span><span class="s2">[Top </span><span class="dl">"</span><span class="o">+</span><span class="p">(</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">+</span><span class="dl">"</span><span class="s2">] </span><span class="dl">"</span><span class="o">+</span><span class="nx">pageTitle</span><span class="o">+</span><span class="dl">"</span><span class="s2">: </span><span class="dl">"</span><span class="o">+</span><span class="nx">value</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">()</span><span class="o">+</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">});</span>

  <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="o">-</span><span class="nx">xxxx</span><span class="p">);</span> <span class="c1">// Insert your Channel Chat ID</span>
<span class="p">}</span>


<span class="c1">// Send message to the specified Telegram Channel Chat ID</span>
<span class="kd">function</span> <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">chatId</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://api.telegram.org/bot</span><span class="dl">"</span><span class="o">+</span><span class="nx">telegramToken</span><span class="o">+</span><span class="dl">"</span><span class="s2">/sendMessage</span><span class="dl">"</span><span class="p">;</span>
  
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">chat_id</span><span class="dl">"</span><span class="p">:</span> <span class="nx">chatId</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">text</span><span class="dl">"</span><span class="p">:</span> <span class="nx">message</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">disable_web_page_preview</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span>
  <span class="p">}</span> 
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">method</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">contentType</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">muteHttpExceptions</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">payload</span><span class="dl">'</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>

  <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="dl">"</span><span class="s2">ok</span><span class="dl">"</span><span class="p">]</span> <span class="o">==</span> <span class="kc">undefined</span> <span class="err">\\</span><span class="o">|</span><span class="err">\\</span><span class="o">|</span> <span class="nx">data</span><span class="p">[</span><span class="dl">"</span><span class="s2">ok</span><span class="dl">"</span><span class="p">]</span> <span class="o">!=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if </span><span class="p">(</span><span class="nx">data</span><span class="p">[</span><span class="dl">"</span><span class="s2">error_code</span><span class="dl">"</span><span class="p">]</span> <span class="o">!=</span> <span class="kc">undefined</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">[</span><span class="dl">"</span><span class="s2">error_code</span><span class="dl">"</span><span class="p">]</span> <span class="o">==</span> <span class="mi">429</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">Utilities</span><span class="p">.</span><span class="nf">sleep</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
      <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">chatId</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">fetchScreenPageViews</span><span class="p">(</span><span class="nx">propertyId</span><span class="p">,</span> <span class="nx">startDate</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">7daysAgo</span><span class="dl">"</span><span class="p">,</span> <span class="nx">endDate</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">yesterday</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">screenPageViewsMetric</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newMetric</span><span class="p">();</span>
  <span class="nx">screenPageViewsMetric</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">screenPageViews</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">dateRange</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDateRange</span><span class="p">();</span>
  <span class="nx">dateRange</span><span class="p">.</span><span class="nx">startDate</span> <span class="o">=</span> <span class="nx">startDate</span><span class="p">;</span>
  <span class="nx">dateRange</span><span class="p">.</span><span class="nx">endDate</span> <span class="o">=</span> <span class="nx">endDate</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">pageTitleDimension</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newDimension</span><span class="p">();</span>
  <span class="nx">pageTitleDimension</span><span class="p">.</span><span class="nx">name</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">pageTitle</span><span class="dl">"</span><span class="p">;</span>

  <span class="kd">const</span> <span class="nx">request</span> <span class="o">=</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nf">newRunReportRequest</span><span class="p">();</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">dimensions</span> <span class="o">=</span> <span class="p">[</span><span class="nx">pageTitleDimension</span><span class="p">];</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">metrics</span> <span class="o">=</span> <span class="p">[</span><span class="nx">screenPageViewsMetric</span><span class="p">];</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">dateRanges</span> <span class="o">=</span> <span class="nx">dateRange</span><span class="p">;</span>

  <span class="nx">request</span><span class="p">.</span><span class="nx">limit</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
  <span class="nx">request</span><span class="p">.</span><span class="nx">metricAggregations</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">TOTAL</span><span class="dl">"</span><span class="p">;</span>

  <span class="k">return</span> <span class="nx">AnalyticsData</span><span class="p">.</span><span class="nx">Properties</span><span class="p">.</span><span class="nf">runReport</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="dl">"</span><span class="s2">properties/</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">propertyId</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Code Analysis:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//...</span>
  <span class="c1">// Find the total position in the report's returned JSON, parseInt converts the string to an INT number format</span>
  <span class="c1">// .toLocaleString() -&gt; formats the number, 123456 -&gt; 123,456</span>
  <span class="kd">const</span> <span class="nx">total</span> <span class="o">=</span> <span class="nf">parseInt</span><span class="p">(</span><span class="nx">screenPageViewsReport</span><span class="p">.</span><span class="nx">totals</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">metricValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">);</span>
  <span class="kd">var</span> <span class="nx">message</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Total Views: </span><span class="dl">"</span><span class="o">+</span><span class="nx">total</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">()</span><span class="o">+</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">;</span>

  <span class="c1">// Iterate through the data in the report's returned JSON to compose the message</span>
  <span class="nx">screenPageViewsReport</span><span class="p">.</span><span class="nx">rows</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">element</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">pageTitle</span> <span class="o">=</span> <span class="nx">element</span><span class="p">.</span><span class="nx">dimensionValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">;</span>
    <span class="kd">const</span> <span class="nx">value</span> <span class="o">=</span> <span class="nf">parseInt</span><span class="p">(</span><span class="nx">element</span><span class="p">.</span><span class="nx">metricValues</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nx">value</span><span class="p">);</span>

    <span class="nx">message</span> <span class="o">+=</span> <span class="dl">"</span><span class="s2">[Top </span><span class="dl">"</span><span class="o">+</span><span class="p">(</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="o">+</span><span class="dl">"</span><span class="s2">] </span><span class="dl">"</span><span class="o">+</span><span class="nx">pageTitle</span><span class="o">+</span><span class="dl">"</span><span class="s2">: </span><span class="dl">"</span><span class="o">+</span><span class="nx">value</span><span class="p">.</span><span class="nf">toLocaleString</span><span class="p">()</span><span class="o">+</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">;</span>
  <span class="p">});</span>
<span class="c1">//...</span>
</code></pre></div></div>

<h4 id="run-click-run-or-debug-above-and-make-sure-the-method-name-selected-is-execute"><strong>Run: Click “Run” or “Debug” above and make sure the method name selected is <code class="language-plaintext highlighter-rouge">execute</code>:</strong></h4>

<p><img src="/assets/1e85b8df2348/1*R7uVMGODJR2Z_GAYnyMYXA.webp" alt="" loading="lazy" decoding="async" width="512" height="109" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1MTIiIGhlaWdodD0iMTA5Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*R7uVMGODJR2Z_GAYnyMYXA.png" /></p>

<p><img src="/assets/1e85b8df2348/1*Pl2v19Ed8COUwCc1deTYFw.webp" alt="" loading="lazy" decoding="async" width="643" height="274" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2NDMiIGhlaWdodD0iMjc0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*Pl2v19Ed8COUwCc1deTYFw.png" /></p>

<ul>
  <li><strong>Success! 🎉🎉🎉</strong></li>
</ul>

<h4 id="set-up-scheduled-automatic-execution">Set Up Scheduled Automatic Execution</h4>

<p>The final step is to have the report bot run automatically on a schedule. Go to “Triggers” from the left menu:</p>

<p><img src="/assets/1e85b8df2348/1*SY1YKLwTvDtP1hQTRqj50w.webp" alt="" loading="lazy" decoding="async" width="866" height="805" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NjYiIGhlaWdodD0iODA1Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*SY1YKLwTvDtP1hQTRqj50w.png" /></p>

<ul>
  <li>Select the “Add Trigger” button at the bottom right corner</li>
</ul>

<p><img src="/assets/1e85b8df2348/1*wYOwwQBTEtns8nN_7bmvNQ.webp" alt="" loading="lazy" decoding="async" width="875" height="874" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzUiIGhlaWdodD0iODc0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/1e85b8df2348/1*wYOwwQBTEtns8nN_7bmvNQ.png" /></p>

<ol>
  <li>
    <p>Select the function to execute: choose the method name “<code class="language-plaintext highlighter-rouge">execute</code>”</p>
  </li>
  <li>
    <p>Select the deployment to run: choose “<code class="language-plaintext highlighter-rouge">Head</code>”</p>
  </li>
  <li>
    <p>Select Event Source: Choose “<code class="language-plaintext highlighter-rouge">Time-driven</code>”</p>
  </li>
  <li>
    <p>Select the time-based trigger type: choose your desired trigger frequency</p>
  </li>
  <li>
    <p>Select Time Period: Choose the time you want the daily automatic trigger to run</p>
  </li>
  <li>
    <p>If Execution Fails… Setting the Frequency of Notification Emails</p>
  </li>
  <li>
    <p>Save</p>
  </li>
</ol>

<blockquote>
  <p><strong><em>Done! It will now run automatically at the scheduled time.</em></strong> 🎉🎉🎉</p>
</blockquote>

<h3 id="extended-tasks">Extended Tasks</h3>

<p>Other data such as new users, source/medium, etc., can be obtained using the same code above. I won’t repeat it here—consider it your homework.</p>]]></content>
  </entry><entry>
    <title type="html">Line Notify to Telegram Bot Migration｜Fast 10-Minute Guide for Enhanced Notifications</title>
    <link href="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/line-notify-to-telegram-bot-migration-fast-10-minute-guide-for-enhanced-notifications-6922e90ba90c/" rel="alternate" type="text/html" title="Line Notify to Telegram Bot Migration｜Fast 10-Minute Guide for Enhanced Notifications" />
    <published>2024-10-12T21:10:46+08:00</published>
    <updated>2024-10-20T16:57:41+08:00</updated>
    <id>https://en.zhgchg.li/posts/zrealm-robotic-process-automation/6922e90ba90c</id><summary type="html">Discover how users can seamlessly transfer their Line Notify personal alerts to Telegram Bot, a free and more powerful notification service, with this step-by-step 10-minute guide for improved messaging efficiency.</summary><author>
      <name>ZhgChgLi</name>
    </author><category term="ZRealm Robotic Process Automation" /><category term="ios-app-development" /><category term="line" /><category term="telegram" /><category term="google-apps-script" /><category term="automation" /><category term="english" /><category term="ai-translation" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://en.zhgchg.li/assets/6922e90ba90c/1*r59nJAx__InU09hYMenePg.webp" /><content type="html" xml:base="https://en.zhgchg.li/posts/zrealm-robotic-process-automation/line-notify-to-telegram-bot-migration-fast-10-minute-guide-for-enhanced-notifications-6922e90ba90c/"><![CDATA[<h3 id="10-minute-quick-migration-from-line-notify-to-telegram-bot-notifications">10-Minute Quick Migration from Line Notify to Telegram Bot Notifications</h3>

<p>Step-by-step Guide to Migrating Line Notify Personal Notification Service to the Also Free and More Powerful Telegram Bot</p>

<p><img src="/assets/6922e90ba90c/1*r59nJAx__InU09hYMenePg.webp" alt="Photo by Lana Codes" loading="lazy" decoding="async" width="1200" height="801" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjgwMSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*r59nJAx__InU09hYMenePg.jpeg" /></p>

<p>Photo by <a href="https://unsplash.com/@lanacodes?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank">Lana Codes</a></p>

<h4 id="line-notify-service-termination-announcement"><a href="https://notify-bot.line.me/closing-announce" target="_blank">LINE Notify Service Termination Announcement</a></h4>

<blockquote>
  <p><em>Thank you for using LINE Notify for a long time.</em></p>
</blockquote>

<blockquote>
  <p><em>Since its launch in September 2016, LINE Notify has been dedicated to serving developers. To provide better service and focus resources on similar future products, we have decided to end this service on March 31, 2025. We sincerely thank all users who have supported and used LINE as a notification integration service over the years.</em></p>
</blockquote>

<blockquote>
  <p><em>If you still need to send notifications to users via LINE, it is recommended to switch to the more feature-rich Messaging API.</em></p>
</blockquote>

<p>Excerpted from <a href="https://notify-bot.line.me/closing-announce" target="_blank">Line Notify official website</a>, Line announced on 2024/10/08 that Line Notify will be completely shut down on 2025/04/01. To continue using Line for message notifications, only the paid <a href="https://developers.line.biz/en/services/messaging-api/" target="_blank">Message API</a> is available.</p>

<p>The advantage of Line Notify is its ease of integration, making it very convenient for personal notification bots. Some Line Bots or third-party services also use Line Notify for notifications (e.g., Louisa, your order notification feature). However, it has several drawbacks, such as limited message content, no group segmentation (all messages are sent to the same Line Notify Bot), and message length restrictions.</p>

<p>With Line Notify announcing its termination, it also gave me an opportunity to migrate to other communication and notification services:</p>

<ul>
  <li>
    <p>Slack: The free version only stores messages for 30 days. Since my notifications are mostly personal, using Slack feels like overkill. (For sending messages with Slack, you can refer to my previous article: <a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">Slack &amp; ChatGPT Integration</a> )</p>
  </li>
  <li>
    <p>Discord: My notifications are more personal, so it’s a bit overkill.</p>
  </li>
  <li>
    <p><strong>Telegram: Free and Almost Unlimited Use.</strong></p>
  </li>
</ul>

<p>For me, Telegram’s messaging service better fits my original Line Notify usage needs. I need a channel that can receive notifications, preferably with different channels for different needs. The more diverse the acceptable content and formats, the better, and it should be quick and easy to integrate. Telegram meets all these requirements and also supports interactive features with the Bot.</p>

<h4 id="results">Results</h4>

<p>First, here is the final result image (using <a href="/posts/zrealm-robotic-process-automation/google-apps-script-3-step-guide-to-build-free-github-repo-star-notifier-382218e15697/">Github Star notification and Repo Stats notification as examples</a>):</p>

<p><img src="/assets/6922e90ba90c/1*1kHJu5yZMUST-wrna6KqkA.gif" alt="" loading="lazy" decoding="async" width="380" height="550" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzODAiIGhlaWdodD0iNTUwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" /></p>

<ul>
  <li>
    <p>✅ When someone stars the repo, a webhook triggers -&gt; Google Apps Script -&gt; Telegram Bot sends a notification to the Telegram — Github Stats Group</p>
  </li>
  <li>
    <p>✅ Google Apps Script daily schedule -&gt; fetch Github Repo Stats -&gt; Telegram Bot sends notification to Telegram — Github Stats Group</p>
  </li>
  <li>
    <p>✅ Use the <code class="language-plaintext highlighter-rouge">/update</code> Telegram Bot Command to trigger fetching Github Repo Stats status -&gt; Telegram Bot sends notification to Telegram — Github Stats Group</p>
  </li>
</ul>

<h4 id="compared-to-the-original-line-notify">Compared to the original Line Notify</h4>

<p><img src="/assets/6922e90ba90c/1*mTycFPe7rPh1qc0BagUXdw.webp" alt="" loading="lazy" decoding="async" width="960" height="1404" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjAiIGhlaWdodD0iMTQwNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*mTycFPe7rPh1qc0BagUXdw.png" /></p>

<ul>
  <li>
    <p>❌ All messages cannot be categorized or grouped and are sent to Line Notify.</p>
  </li>
  <li>
    <p>❌ Cannot set special options for individual messages (such as notification sound, mute, etc.)</p>
  </li>
  <li>
    <p>❌ Unable to input message interaction</p>
  </li>
</ul>

<h4 id="table-of-contents">Table of Contents</h4>

<ul>
  <li>
    <p>Setting up Telegram Bot</p>
  </li>
  <li>
    <p>Migrate Line Notify Message Sending to Telegram Bot (Google Apps Script)</p>
  </li>
  <li>
    <p>Interacting with Telegram Bot (Command) x Using Google Apps Script</p>
  </li>
</ul>

<h3 id="12-setting-up-telegram-bot">(1/2) Setting up Telegram Bot</h3>

<p>Applying for a Telegram Bot is very simple; you don’t even need to open a webpage. Just interact with the official <a href="https://t.me/BotFather" target="_blank">BotFather bot</a>.</p>

<h4 id="step-1-apply-for-a-telegram-bot">Step 1. Apply for a Telegram Bot</h4>

<p>After installing and registering for the Telegram service, click to add <a href="https://t.me/BotFather" target="_blank">BotFather bot</a> as a friend.</p>

<p><img src="/assets/6922e90ba90c/1*-3qluwPXk-HeGRCTOd-EAA.webp" alt="" loading="lazy" decoding="async" width="691" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2OTEiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*-3qluwPXk-HeGRCTOd-EAA.png" /></p>

<p><img src="/assets/6922e90ba90c/1*QfzbJYQ2vVI2CNvvLU7r_w.webp" alt="" loading="lazy" decoding="async" width="595" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1OTUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*QfzbJYQ2vVI2CNvvLU7r_w.png" /></p>

<ol>
  <li>
    <p>Open and join the <a href="https://t.me/BotFather" target="_blank">BotFather bot</a></p>
  </li>
  <li>
    <p>After joining, send the message <code class="language-plaintext highlighter-rouge">/newbot</code> to create your bot.</p>
  </li>
  <li>
    <p>Enter your bot name.</p>
  </li>
  <li>
    <p>Enter your bot username (must be unique and end with <code class="language-plaintext highlighter-rouge">bot</code>, for example, my <code class="language-plaintext highlighter-rouge">zhgchgli_bot</code>).</p>
  </li>
  <li>
    <p>Your Bot link, click to start using (e.g. t.me/harrytest56_bot).</p>
  </li>
  <li>
    <p>Get your <code class="language-plaintext highlighter-rouge">YOUR_BOT_API_Token</code>, <strong>please keep it safe</strong> ⚠️⚠️⚠️</p>
  </li>
</ol>

<p>Click the Bot link obtained in step 4 to start using the Bot:</p>

<p><img src="/assets/6922e90ba90c/1*5q5PE3D9nwQPlYoY8SqCHQ.webp" alt="" loading="lazy" decoding="async" width="705" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDUiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*5q5PE3D9nwQPlYoY8SqCHQ.png" /></p>

<p><img src="/assets/6922e90ba90c/1*NKz9GSDlGov9q7xIlHYlCQ.webp" alt="" loading="lazy" decoding="async" width="756" height="1390" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3NTYiIGhlaWdodD0iMTM5MCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*NKz9GSDlGov9q7xIlHYlCQ.png" /></p>

<p><img src="/assets/6922e90ba90c/1*VU7EyeZL2BpVnLRHQBNOPQ.webp" alt="" loading="lazy" decoding="async" width="732" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MzIiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*VU7EyeZL2BpVnLRHQBNOPQ.png" /></p>

<p>Currently, there are no features available. You can tap the Info button at the top right to edit the name or upload a profile picture.</p>

<h4 id="step-2-create-a-telegram-notification-group--add-the-bot-account">Step 2. Create a Telegram Notification Group &amp; Add the Bot Account</h4>

<blockquote>
  <p><em>I want different types of personal notifications to be sent to different Groups, but <strong>only one My Notify Group is created here for the demo</strong>.</em></p>
</blockquote>

<blockquote>
  <p><em>You can create different groups based on your actual needs and follow the steps to add and configure the bot.</em></p>
</blockquote>

<p><img src="/assets/6922e90ba90c/1*geb_lwnJOUXiuLHvguuXjQ.webp" alt="" loading="lazy" decoding="async" width="1040" height="726" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDQwIiBoZWlnaHQ9IjcyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*geb_lwnJOUXiuLHvguuXjQ.png" /></p>

<p><img src="/assets/6922e90ba90c/1*bfXDTACy4mW3LZlq6ErKzQ.webp" alt="" loading="lazy" decoding="async" width="896" height="1526" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTYiIGhlaWdodD0iMTUyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*bfXDTACy4mW3LZlq6ErKzQ.png" /></p>

<p><img src="/assets/6922e90ba90c/1*ZmsCLqEZbNN3IS2uoZV8cw.webp" alt="" loading="lazy" decoding="async" width="896" height="1526" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4OTYiIGhlaWdodD0iMTUyNiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*ZmsCLqEZbNN3IS2uoZV8cw.png" /></p>

<ol>
  <li>
    <p>New Group</p>
  </li>
  <li>
    <p>Search for your bot account &amp; click Join</p>
  </li>
  <li>
    <p>Set Group Name and Avatar</p>
  </li>
</ol>

<h4 id="step-3-get-group-chat-id">Step 3. Get Group Chat ID</h4>

<p>Telegram Bot API does not provide an endpoint to directly get the list of Groups or Group Chat IDs. You can only obtain them by using <code class="language-plaintext highlighter-rouge">/getUpdates</code> to fetch the bot’s message list and find the Group Chat ID from there:</p>

<p><strong>Request:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">curl</span> <span class="nv">'https</span><span class="p">:</span><span class="c1">//api.telegram.org/botYOUR_BOT_API_TOKEN/getUpdates'</span>
</code></pre></div></div>

<ul>
  <li>
    <p>The Telegram API format is <code class="language-plaintext highlighter-rouge">https://api.telegram.org/bot</code> <strong>your_BOT_API_Token</strong> <code class="language-plaintext highlighter-rouge">/getUpdates</code>. The BOT API Token string must be prefixed with the <code class="language-plaintext highlighter-rouge">bot</code> string.</p>
  </li>
  <li>
    <p>Example: <code class="language-plaintext highlighter-rouge">curl 'https://api.telegram.org/bot7814194578:AAEWpPJvKn06ID7D9FjV65aDKQLkGkz8cc8/getUpdates'</code></p>
  </li>
</ul>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"result"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"update_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">706454235</span><span class="p">,</span><span class="w">
            </span><span class="nl">"my_chat_member"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"chat"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-4532420331</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Nofify"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"group"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"all_members_are_administrators"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">986128250</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"last_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Li"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="mi">1728726861</span><span class="p">,</span><span class="w">
                </span><span class="nl">"old_chat_member"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">7814194578</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry Test"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"harrytest56_bot"</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"left"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"new_chat_member"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">7814194578</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry Test"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"harrytest56_bot"</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="nl">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"member"</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"update_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">706454236</span><span class="p">,</span><span class="w">
            </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"message_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
                </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">986128250</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"last_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Li"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"chat"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-4532420331</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Nofify"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"group"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"all_members_are_administrators"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="mi">1728726861</span><span class="p">,</span><span class="w">
                </span><span class="nl">"group_chat_created"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"update_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">706454237</span><span class="p">,</span><span class="w">
            </span><span class="nl">"message"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"message_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
                </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">986128250</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"last_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Li"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"zhgchgli"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"chat"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-4532420331</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Nofify"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"group"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"all_members_are_administrators"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="mi">1728726864</span><span class="p">,</span><span class="w">
                </span><span class="nl">"new_chat_photo"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"file_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AgACAgUAAxkBAAMCZwpHUEaLZSvFFYu8GiO-8qI_jVYAApfAMRu0B1BUJP-4u2wF6scBAAMCAANhAAM2BA"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_unique_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AQADl8AxG7QHUFQAAQ"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">5922</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">160</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">160</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"file_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AgACAgUAAxkBAAMCZwpHUEaLZSvFFYu8GiO-8qI_jVYAApfAMRu0B1BUJP-4u2wF6scBAAMCAANiAAM2BA"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_unique_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AQADl8AxG7QHUFRn"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">15097</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">320</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">320</span><span class="w">
                    </span><span class="p">},</span><span class="w">
                    </span><span class="p">{</span><span class="w">
                        </span><span class="nl">"file_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AgACAgUAAxkBAAMCZwpHUEaLZSvFFYu8GiO-8qI_jVYAApfAMRu0B1BUJP-4u2wF6scBAAMCAANjAAM2BA"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_unique_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"AQADl8AxG7QHUFQB"</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">37988</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">640</span><span class="p">,</span><span class="w">
                        </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">640</span><span class="w">
                    </span><span class="p">}</span><span class="w">
                </span><span class="p">]</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You can find the nested JSON data with the corresponding Group Name + type=group in the response, where the id is the Group Chat ID:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">"chat"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
  </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-4532420331</span><span class="p">,</span><span class="w">
  </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Notify"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"group"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"all_members_are_administrators"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Group Chat Id</code> = <code class="language-plaintext highlighter-rouge">-4532420331</code></li>
</ul>

<blockquote>
  <p><strong><em>⚠️⚠️⚠️️</em></strong> <em>If your Response is empty <code class="language-plaintext highlighter-rouge">{ "ok": true, "result": [] }</code>, please try sending a message (e.g. <code class="language-plaintext highlighter-rouge">Hello</code>) in your Group and then call the API again. It should work.</em></p>
</blockquote>

<h4 id="step-4-send-messages">Step 4. Send Messages</h4>

<p>We can use <code class="language-plaintext highlighter-rouge">/sendMessage</code> to send messages to a Group.</p>

<p><strong>Request:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">curl</span> <span class="nv">'https</span><span class="p">:</span><span class="c1">//api.telegram.org/botYOUR_BOT_API_Token/sendMessage' \</span>
<span class="o">--</span><span class="n">form</span> <span class="nv">'chat_id</span><span class="o">=</span><span class="s">"Group Chat Id"</span><span class="err">'</span> <span class="err">\</span>
<span class="o">--</span><span class="n">form</span> <span class="nv">'text</span><span class="o">=</span><span class="s">"Message content"</span><span class="err">'</span>
</code></pre></div></div>

<p><strong>Example:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">curl</span> <span class="nv">'https</span><span class="p">:</span><span class="c1">//api.telegram.org/bot7814194578:AAEWpPJvKn06ID7D9FjV65aDKQLkGkz8cc8/sendMessage' \</span>
<span class="o">--</span><span class="n">form</span> <span class="nv">'chat_id</span><span class="o">=</span><span class="s">"-4532420331"</span><span class="err">'</span> <span class="err">\</span>
<span class="o">--</span><span class="n">form</span> <span class="nv">'text</span><span class="o">=</span><span class="s">"test"</span><span class="err">'</span>
</code></pre></div></div>

<ul>
  <li><a href="https://core.telegram.org/bots/api#sendmessage" target="_blank"><strong>API parameters can be found in the official documentation</strong></a></li>
</ul>

<p><strong>Response &amp; Result:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
  </span><span class="nl">"result"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"message_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
    </span><span class="nl">"from"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">7814194578</span><span class="p">,</span><span class="w">
      </span><span class="nl">"is_bot"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
      </span><span class="nl">"first_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Harry Test"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"harrytest56_bot"</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"chat"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">-4532420331</span><span class="p">,</span><span class="w">
      </span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My Nofify"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"group"</span><span class="p">,</span><span class="w">
      </span><span class="nl">"all_members_are_administrators"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"date"</span><span class="p">:</span><span class="w"> </span><span class="mi">1728727847</span><span class="p">,</span><span class="w">
    </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"test"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>Message sent successfully, received the above response</li>
</ul>

<p><img src="/assets/6922e90ba90c/1*WG4ngRKKac1cICU4NvoEJA.webp" alt="" loading="lazy" decoding="async" width="984" height="1614" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5ODQiIGhlaWdodD0iMTYxNCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*WG4ngRKKac1cICU4NvoEJA.png" /></p>

<ul>
  <li>Replying in the Telegram Group will display the content of the message just sent.</li>
</ul>

<h3 id="22-migrating-line-notify-message-sending-to-telegram-bot-google-apps-script">(2/2) Migrating Line Notify Message Sending to Telegram Bot (Google Apps Script)</h3>

<p>My personal notification bot service is implemented using Google Apps Script, so the conversion example is based on Google Apps Script (similar to JavaScript).</p>

<h4 id="original-line-notify-sending-code">Original Line Notify Sending Code:</h4>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">lineToken</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">XXXX</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nf">sendLineNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">https://notify-api.line.me/api/notify</span><span class="dl">'</span><span class="p">;</span>
  
  <span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">'</span><span class="s1">Authorization</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Bearer </span><span class="dl">'</span><span class="o">+</span><span class="nx">lineToken</span>
    <span class="p">},</span>
    <span class="na">payload</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">'</span><span class="s1">message</span><span class="dl">'</span><span class="p">:</span> <span class="nx">message</span>
    <span class="p">}</span>
  <span class="p">};</span> 
  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>You can see it is very simple, convenient, and easy to use…</p>

<h4 id="migration-to-telegram-bot-sending-code">Migration to Telegram Bot Sending Code:</h4>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">telegramToken</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">your_BOT_API_Token</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">TelegramChatId</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">GA</span><span class="p">:</span> <span class="o">-</span><span class="mi">123456</span><span class="p">,</span>
  <span class="na">GITHUB</span><span class="p">:</span> <span class="o">-</span><span class="mi">123457</span><span class="p">,</span>
  <span class="na">MEDIUM</span><span class="p">:</span> <span class="o">-</span><span class="mi">123458</span>
<span class="p">};</span>

<span class="kd">function</span> <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">chatId</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://api.telegram.org/bot</span><span class="dl">"</span><span class="o">+</span><span class="nx">telegramToken</span><span class="o">+</span><span class="dl">"</span><span class="s2">/sendMessage</span><span class="dl">"</span><span class="p">;</span>
  
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">chat_id</span><span class="dl">"</span><span class="p">:</span> <span class="nx">chatId</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">text</span><span class="dl">"</span><span class="p">:</span> <span class="nx">message</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">parse_mode</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Markdown</span><span class="dl">"</span>
  <span class="p">}</span> 
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">method</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">contentType</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">muteHttpExceptions</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">payload</span><span class="dl">'</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Information obtained from the previous Telegram Bot setup steps.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">telegramToken</code> = <code class="language-plaintext highlighter-rouge">your_BOT_API_Token</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">TelegramChatId</code> here is a custom method I defined myself because, in practice, I want different notifications sent to different groups. Therefore, I created a structure to manage target groups and their <code class="language-plaintext highlighter-rouge">Group Chat Id</code>.</p>
  </li>
</ul>

<p><code class="language-plaintext highlighter-rouge">/sendMessage</code> <a href="https://core.telegram.org/bots/api#sendmessage" target="_blank"><strong>API parameters, for more parameters and details refer to the official documentation</strong></a>, below are the parameters I commonly use:</p>

<ul>
  <li>
    <p>text: Message content (required)</p>
  </li>
  <li>
    <p>chat_id: Target Group Chat Id (required)</p>
  </li>
  <li>
    <p>parse_mode: The message content parsing mode, here I specify <code class="language-plaintext highlighter-rouge">Markdown</code></p>
  </li>
  <li>
    <p>disable_web_page_preview: Whether to disable link previews in the message content. Setting this to <code class="language-plaintext highlighter-rouge">true</code> disables previews, making the message display cleaner.</p>
  </li>
</ul>

<p><strong>Usage:</strong></p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">sendNotifyMessage</span><span class="o">(</span><span class="s2">"Hello"</span><span class="o">,</span> <span class="nt">TelegramChatId</span><span class="nc">.MEDIUM</span><span class="o">)</span> <span class="o">//</span> <span class="nt">Send</span> <span class="nt">Hello</span> <span class="nt">message</span> <span class="nt">to</span> <span class="nt">MEDIUM</span> <span class="nt">Group</span> <span class="nt">Chat</span> <span class="nt">Id</span>
<span class="nt">sendNotifyMessage</span><span class="o">(</span><span class="s2">"Hello"</span><span class="o">,</span> <span class="nt">-1234</span><span class="o">)</span> <span class="o">//</span> <span class="nt">Send</span> <span class="nt">Hello</span> <span class="nt">message</span> <span class="nt">to</span> <span class="nt">-1234</span> <span class="nt">Group</span> <span class="nt">Chat</span> <span class="nt">Id</span>
</code></pre></div></div>

<h3 id="results-1">Results</h3>

<p>Using my <a href="/posts/zrealm-robotic-process-automation/google-apps-script-3-step-guide-to-build-free-github-repo-star-notifier-382218e15697/">Github Repo Star Notifier Bot</a> as an example:</p>

<p><img src="/assets/6922e90ba90c/1*yJDcnb7n1fIJAM-AV1Qk6w.webp" alt="" loading="lazy" decoding="async" width="962" height="1374" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NjIiIGhlaWdodD0iMTM3NCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*yJDcnb7n1fIJAM-AV1Qk6w.png" /></p>

<p><img src="/assets/6922e90ba90c/1*5kBotSkNf9nN8NV-lCwZoQ.webp" alt="" loading="lazy" decoding="async" width="870" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*5kBotSkNf9nN8NV-lCwZoQ.png" /></p>

<ul>
  <li>
    <p>Verification successful! Notifications are correctly sent to the Telegram Group when someone stars my Repo! 🎉🎉🎉</p>
  </li>
  <li>
    <p>You can refer to my previous article “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-3-step-guide-to-build-free-github-repo-star-notifier-382218e15697/">Using Google Apps Script to Create a Free Github Repo Star Notifier in Three Steps</a>” for the setup process.</p>
  </li>
</ul>

<h4 id="set-custom-sounds-or-mute">Set Custom Sounds or Mute</h4>

<p><img src="/assets/6922e90ba90c/1*kQsYkefi0SD7e9t4PerdRA.webp" alt="" loading="lazy" decoding="async" width="1004" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDA0IiBoZWlnaHQ9IjEyMDAiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6922e90ba90c/1*kQsYkefi0SD7e9t4PerdRA.jpeg" /></p>

<p>Even better than Line Notify, we can set different notification sounds or mute settings for different groups.</p>

<h3 id="interacting-with-telegram-bot-command-x-using-google-apps-script">Interacting with Telegram Bot (Command) x Using Google Apps Script</h3>

<p>Besides replacing the Notify function, Telegram Bot can also easily implement interactive features with users — Telegram Bot Command.</p>

<p>Back to my use case, my bot sends notification messages to me either on a schedule or triggered by a webhook; but sometimes I also want to manually trigger the bot to get the current result immediately. Previously, Line Notify didn’t have this feature. Using Google Apps Script, the only option was to crudely set up a URL that triggers the bot whenever opened, which is not very user-friendly.</p>

<p>Telegram Bot Command allows me to directly enter command messages, making the bot immediately perform the actions I want.</p>

<blockquote>
  <p><em>This article uses <a href="https://script.google.com/home" target="_blank">Google Apps Script</a> as an example. For a detailed introduction to Google Apps Script, please refer to my previous article “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/"><strong>Automating Google Services RPA with Google Apps Script</strong></a>”.</em></p>
</blockquote>

<h4 id="step-1-implement-command-handling-logic-using-google-apps-script">Step 1. Implement Command Handling Logic Using Google Apps Script</h4>

<ul>
  <li>
    <p>Go to the Google Apps Script homepage</p>
  </li>
  <li>
    <p>Click “Create New Project” at the top left</p>
  </li>
  <li>
    <p>Click “Untitled Project” and enter the project name, e.g. <code class="language-plaintext highlighter-rouge">Telegram</code></p>
  </li>
  <li>
    <p>Paste the basic code:</p>
  </li>
</ul>

<p><img src="/assets/6922e90ba90c/1*ZeW4O7Mdgcyj0VsSLDpxeA.webp" alt="" loading="lazy" decoding="async" width="1400" height="1288" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjEyODgiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6922e90ba90c/1*ZeW4O7Mdgcyj0VsSLDpxeA.png" /></p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">telegramToken</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">your_BOT_API_Token</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">function</span> <span class="nf">doPost</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">postData</span><span class="p">.</span><span class="nx">contents</span><span class="p">);</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">content</span><span class="p">.</span><span class="nx">message</span> <span class="o">&amp;&amp;</span> <span class="nx">content</span><span class="p">.</span><span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">command</span> <span class="o">=</span> <span class="nx">content</span><span class="p">.</span><span class="nx">message</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
    <span class="kd">const</span> <span class="nx">chatId</span> <span class="o">=</span> <span class="nx">content</span><span class="p">.</span><span class="nx">message</span><span class="p">.</span><span class="nx">chat</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>

    <span class="k">if </span><span class="p">(</span><span class="nx">command</span><span class="p">.</span><span class="nf">startsWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">/update</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span> 
      <span class="c1">// Received /update command</span>
      <span class="c1">// Handle what you want to do here... then respond...</span>
      <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello.....</span><span class="se">\n</span><span class="s2">Command:</span><span class="dl">"</span><span class="o">+</span><span class="nx">command</span><span class="p">,</span> <span class="nx">chatId</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="nx">HtmlService</span><span class="p">.</span><span class="nf">createHtmlOutput</span><span class="p">(</span><span class="dl">"</span><span class="s2">OK!</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nf">sendNotifyMessage</span><span class="p">(</span><span class="nx">message</span><span class="p">,</span> <span class="nx">chatId</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">https://api.telegram.org/bot</span><span class="dl">"</span><span class="o">+</span><span class="nx">telegramToken</span><span class="o">+</span><span class="dl">"</span><span class="s2">/sendMessage</span><span class="dl">"</span><span class="p">;</span>
  
  <span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">chat_id</span><span class="dl">"</span><span class="p">:</span> <span class="nx">chatId</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">text</span><span class="dl">"</span><span class="p">:</span> <span class="nx">message</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">disable_web_page_preview</span><span class="dl">"</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">parse_mode</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Markdown</span><span class="dl">"</span>
  <span class="p">}</span> 
  <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
    <span class="dl">'</span><span class="s1">method</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">contentType</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">muteHttpExceptions</span><span class="dl">'</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
    <span class="dl">'</span><span class="s1">payload</span><span class="dl">'</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span>
  <span class="p">};</span>

  <span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">UrlFetchApp</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
  <span class="nx">Logger</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">response</span><span class="p">.</span><span class="nf">getContentText</span><span class="p">());</span>
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">telegramToken</code> = <code class="language-plaintext highlighter-rouge">your_BOT_API_Token</code></p>
  </li>
  <li>
    <p>In the above demo code, when receiving a POST request with the Command parameter equal to <code class="language-plaintext highlighter-rouge">/update</code>, it responds with <code class="language-plaintext highlighter-rouge">Hello…</code> to simulate handling the command and replying.</p>
  </li>
</ul>

<h4 id="step-2-complete-google-apps-script-web-deployment">Step 2. Complete Google Apps Script Web Deployment</h4>

<p><img src="/assets/6922e90ba90c/1*JfJfs4bYsSfsZYGVkApCSg.webp" alt="" loading="lazy" decoding="async" width="1200" height="705" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjcwNSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*JfJfs4bYsSfsZYGVkApCSg.png" /></p>

<p><img src="/assets/6922e90ba90c/1*TwSm45_Xwv4p7z4o9HJz2w.webp" alt="" loading="lazy" decoding="async" width="1400" height="1109" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExMDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6922e90ba90c/1*TwSm45_Xwv4p7z4o9HJz2w.png" /></p>

<p><img src="/assets/6922e90ba90c/1*7RLJXZ3APnEI4V9bKqp3eg.webp" alt="" loading="lazy" decoding="async" width="1200" height="941" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAwIiBoZWlnaHQ9Ijk0MSI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*7RLJXZ3APnEI4V9bKqp3eg.png" /></p>

<ul>
  <li>
    <p>Top right corner “Deploy” -&gt; “New deployment”</p>
  </li>
  <li>
    <p>Top-left corner “Settings” -&gt; “Web App”</p>
  </li>
  <li>
    <p>Who can access: Select “Everyone”</p>
  </li>
</ul>

<p><img src="/assets/6922e90ba90c/1*aRBaOOnhwBeK05l9e3Yv8g.webp" alt="" loading="lazy" decoding="async" width="568" height="380" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1NjgiIGhlaWdodD0iMzgwIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6922e90ba90c/1*aRBaOOnhwBeK05l9e3Yv8g.png" /></p>

<p><img src="/assets/6922e90ba90c/1*FtY8NL36peDbOB4JWdzhDA.webp" alt="" loading="lazy" decoding="async" width="1296" height="932" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjk2IiBoZWlnaHQ9IjkzMiI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*FtY8NL36peDbOB4JWdzhDA.png" /></p>

<p><img src="/assets/6922e90ba90c/1*e2E41TCEf5O7nSOnVzpgbw.webp" alt="" loading="lazy" decoding="async" width="1400" height="768" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9Ijc2OCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*e2E41TCEf5O7nSOnVzpgbw.png" /></p>

<ul>
  <li>
    <p>Add a new deployment task and select “Grant access”</p>
  </li>
  <li>
    <p>A pop-up window will appear; select your Google login account.</p>
  </li>
  <li>
    <p>A warning window will pop up. Select “Advanced” -&gt; “Go to <code class="language-plaintext highlighter-rouge">project name</code> (unsafe)”</p>
  </li>
  <li>
    <p>Select “Allow”</p>
  </li>
</ul>

<p><img src="/assets/6922e90ba90c/1*n0iO-XSPqifUKUkIgVQUSg.webp" alt="" loading="lazy" decoding="async" width="1400" height="1109" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNDAwIiBoZWlnaHQ9IjExMDkiPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNlZGUyY2YiLz48L3N2Zz4=" data-orig="/assets/6922e90ba90c/1*n0iO-XSPqifUKUkIgVQUSg.png" /></p>

<ul>
  <li>Web application URL: <code class="language-plaintext highlighter-rouge">Your Webhook URL</code> <strong>.</strong><br />
Copy it.<br />
e.g. <code class="language-plaintext highlighter-rouge">https://script.google.com/macros/s/AKfycbx2oFv-eB4LezdOk3P3aoEZVhx_PI6n_YnTNP7WVVQSaiRU52di5bKNThsvIZxus3Si/exec</code></li>
</ul>

<blockquote>
  <p><em>For deploying, updating, using, and debugging Google Apps Script web applications, please refer to my previous article “<a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/"><strong>Automating Google Services with Google Apps Script RPA</strong></a>”.</em></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️⚠️⚠️ Please note, if you make changes to the Google Apps Script code, you must click Deploy -&gt; Manage Deployments -&gt; Select Create New Version for the changes to take effect. For details, refer to the article above.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️⚠️⚠️Please note, if you make changes to the Google Apps Script code, you must click Deploy -&gt; Manage Deployments -&gt; Select Create New Version for the changes to take effect. For details, please refer to the article above.</em></strong></p>
</blockquote>

<blockquote>
  <p><strong><em>⚠️⚠️⚠️Please note, if you make changes to the Google Apps Script code, you must click Deploy -&gt; Manage Deployments -&gt; Select Create New Version for the changes to take effect. For details, please refer to the article above.</em></strong></p>
</blockquote>

<h4 id="step-3-register-webhook">Step 3. Register Webhook</h4>

<p>Use the Telegram API <code class="language-plaintext highlighter-rouge">/setWebhook</code> to register your Webhook URL.</p>

<p><strong>Request:</strong></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">curl</span> <span class="o">--</span><span class="n">location</span> <span class="nv">'https</span><span class="p">:</span><span class="c1">//api.telegram.org/你的_BOT_API_Token/setWebhook' \</span>
<span class="o">--</span><span class="n">form</span> <span class="nv">'url</span><span class="o">=</span><span class="s">"Your Webhook URL"</span><span class="err">'</span>
</code></pre></div></div>

<p><strong>Response:</strong></p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"result"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Webhook was set"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h4 id="test">Test</h4>

<p><img src="/assets/6922e90ba90c/1*wNhksFLSip5DC0rXqMA_0A.webp" alt="" loading="lazy" decoding="async" width="870" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*wNhksFLSip5DC0rXqMA_0A.png" /></p>

<p><img src="/assets/6922e90ba90c/1*d_B3h3C30vI61p3ALP77yw.webp" alt="" loading="lazy" decoding="async" width="870" height="1200" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI4NzAiIGhlaWdodD0iMTIwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI2VkZTJjZiIvPjwvc3ZnPg==" data-orig="/assets/6922e90ba90c/1*d_B3h3C30vI61p3ALP77yw.png" /></p>

<ul>
  <li>
    <p>We will respond based on different Chat IDs, so the bot can reply in both 1:1 chats and group chats where the bot is present.</p>
  </li>
  <li>
    <p>Success 🎉🎉🎉</p>
  </li>
</ul>

<h3 id="next">Next:</h3>

<ul>
  <li><a href="/posts/zrealm-robotic-process-automation/ga4-data-alerts-automation-3-step-guide-to-build-free-telegram-bot-notifications-1e85b8df2348/">Simple 3 Steps — Create a Free GA4 Automated Data Notification Bot</a></li>
</ul>

<h3 id="other-google-apps-script-automation-articles">Other Google Apps Script Automation Articles</h3>

<ul>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-automate-daily-data-reports-with-rpa-for-google-workspace-f6713ba3fee3/">Automate Google Services RPA with Google Apps Script</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/slack-chatgpt-integration-build-custom-openai-api-slack-app-with-google-cloud-functions-python-bd94cc88f9c9/">Slack &amp; ChatGPT Integration</a></p>
  </li>
  <li>
    <p><a href="/posts/zrealm-robotic-process-automation/google-apps-script-3-step-guide-to-build-free-github-repo-star-notifier-382218e15697/">Create a Github Repo Star Notifier for Free in Three Steps Using Google Apps Script</a></p>
  </li>
</ul>

<h4 id="note">Note</h4>

<p><img src="/assets/6922e90ba90c/1*zOFjvO5SSwbJgFL8kMff0w.webp" alt="" loading="lazy" decoding="async" width="706" height="284" lqip="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI3MDYiIGhlaWdodD0iMjg0Ij48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZWRlMmNmIi8+PC9zdmc+" data-orig="/assets/6922e90ba90c/1*zOFjvO5SSwbJgFL8kMff0w.png" /></p>

<p>This is also my 100th article on Medium (first one published in 2018/10 <a href="/posts/zrealm-life/blog-revival-how-to-claim-your-final-adsense-payment-and-restart-writing-b7a3fb3d5531/">here</a>, six years ago). I will keep persevering and working hard. I’ll share detailed insights and data once I reach 1000 followers (currently 925 as of 2024/10) or a total of 1,000,000 views (currently 984,549 as of 2024/10).</p>]]></content>
  </entry>
</feed>
