diff --git a/docs/blog/rendering-images/index.qmd b/docs/blog/rendering-images/index.qmd
new file mode 100644
index 000000000..4392e3038
--- /dev/null
+++ b/docs/blog/rendering-images/index.qmd
@@ -0,0 +1,365 @@
+---
+title: "Rendering images anywhere in Great Tables"
+html-table-processing: none
+author: Jerry Wu
+date: 2024-12-13
+freeze: true
+jupyter: python3
+format:
+ html:
+ code-summary: "Show the Code"
+---
+
+Rendering images in Great Tables is straightforward with `GT.fmt_image` and `vals.fmt_image()`.
+In this post, we'll explore three key topics:
+
+* Four examples demonstrating how to render images within the body using `GT.fmt_image()`.
+* How to render images anywhere using `vals.fmt_image()` and `html()`.
+* How to manually render images anywhere using `html()`.
+
+## Rendering Images in the Body
+[GT.fmt_image()](https://posit-dev.github.io/great-tables/reference/GT.fmt_image.html#great_tables.GT.fmt_image)
+is the go-to tool for rendering images within the body of a table. Below, we'll present four examples
+corresponding to the cases outlined in the documentation:
+
+* **Case 1**: Local file paths.
+* **Case 2**: Full HTTP/HTTPS URLs.
+* **Case 3**: Image names with the `path=` argument.
+* **Case 4**: Image names using both the `path=` and `file_pattern=` arguments.
+
+::: {.callout-tip collapse="false"}
+
+## Finding the Right Case for Your Needs
+
+* **Case 1** and **Case 2** work best for data sourced directly from a database.
+* **Case 3** is ideal for users dealing with image names relative to a base directory or URL (e.g., `/path/to/images`).
+* **Case 4** is tailored for users working with patterned image names (e.g., `metro_{}.svg`).
+:::
+
+### Preparations
+For this demonstration, we'll use the first five rows of the built-in [metro](https://posit-dev.github.io/great-tables/reference/data.metro.html#great_tables.data.metro) dataset, specifically the `name` and `lines` columns.
+
+To ensure a smooth walkthrough, we’ll manipulate the `data` (a Python dictionary) directly. However,
+in real-world applications, such operations are more likely performed at the DataFrame level to leverage
+the benefits of vectorized operations.
+```{python}
+# | code-fold: true
+import pandas as pd
+from great_tables import GT, vals, html
+from importlib_resources import files
+
+pd.set_option('display.max_colwidth', 150)
+
+data = {
+ "name": [
+ "Argentine",
+ "Bastille",
+ "Bérault",
+ "Champs-Élysées—Clemenceau",
+ "Charles de Gaulle—Étoile",
+ ],
+ "lines": ["1", "1, 5, 8", "1", "1, 13", "1, 2, 6"],
+}
+
+print("""\
+data = {
+ "name": [
+ "Argentine",
+ "Bastille",
+ "Bérault",
+ "Champs-Élysées—Clemenceau",
+ "Charles de Gaulle—Étoile",
+ ],
+ "lines": ["1", "1, 5, 8", "1", "1, 13", "1, 2, 6"],
+}\
+""")
+```
+
+Attentive readers may have noticed that the values for the key `lines` are lists of strings, each
+containing one or more numbers separated by commas. `GT.fmt_image()` is specifically designed to
+handle such cases, allowing users to render multiple images in a single row.
+
+### Case 1: Local File Paths
+**Case 1** demonstrates how to simulate a column containing strings representing local file paths. We'll
+use images stored in the `data/metro_images` directory of Great Tables:
+```{python}
+img_local_paths = files("great_tables") / "data/metro_images" # <1>
+```
+1. These image files follow a patterned naming convention, such as `metro_1.svg`, `metro_2.svg`, and so on.
+
+Below is a `Pandas` DataFrame called `metro_mini1`, where the `case1` column contains local file
+paths that we want to render as images.
+```{python}
+# | code-fold: true
+metro_mini1 = pd.DataFrame(
+ {
+ **data,
+ "case1": [
+ ", ".join(
+ str((img_local_paths / f"metro_{item}").with_suffix(".svg"))
+ for item in row.split(", ")
+ )
+ for row in data["lines"]
+ ],
+ }
+)
+metro_mini1
+```
+
+::: {.callout-tip collapse="false"}
+## Use the `pathlib` Module to Construct Paths
+
+Local file paths can vary depending on the operating system, which makes it easy to accidentally
+construct invalid paths. A good practice to mitigate this is to use Python's built-in
+[pathlib](https://docs.python.org/3/library/pathlib.html) module to construct paths first and then
+convert them to strings. In this example, `img_local_paths` is actually an instance of `pathlib.Path`.
+```{python}
+# | eval: false
+from pathlib import Path
+
+isinstance(img_local_paths, Path) # True
+```
+
+:::
+
+The `case1` column is quite lengthy due to the inclusion of `img_local_paths`. In **Case 3**, we'll
+share a useful trick to avoid repeating the directory name each time—stay tuned!
+
+For now, let's use `GT.fmt_image()` to render images by passing `"case1"` as the first argument:
+```{python}
+GT(metro_mini1).fmt_image("case1").cols_align(align="right", columns="case1")
+```
+
+### Case 2: Full HTTP/HTTPS URLs
+**Case 2** demonstrates how to simulate a column containing strings representing HTTP/HTTPS URLs. We'll
+use the same images as in **Case 1**, but this time, retrieve them from the Great Tables GitHub repository:
+```{python}
+img_url_paths = "https://raw.githubusercontent.com/posit-dev/great-tables/refs/heads/main/great_tables/data/metro_images"
+```
+
+Below is a `Pandas` DataFrame called `metro_mini2`, where the `case2` column contains
+full HTTP/HTTPS URLs that we aim to render as images.
+```{python}
+# | code-fold: true
+metro_mini2 = pd.DataFrame(
+ {
+ **data,
+ "case2": [
+ ", ".join(f"{img_url_paths}/metro_{item}.svg" for item in row.split(", "))
+ for row in data["lines"]
+ ],
+ }
+)
+metro_mini2
+```
+
+The lengthy `case2` column issue can also be addressed using the trick shared in **Case 3**.
+
+Similarly, we can use `GT.fmt_image()` to render images by passing `"case2"` as the first argument:
+```{python}
+GT(metro_mini2).fmt_image("case2").cols_align(align="right", columns="case2")
+```
+
+
+### Case 3: Image Names with the `path=` Argument
+**Case 3** demonstrates how to use the `path=` argument to specify images relative to a base directory
+or URL. This approach eliminates much of the repetition in file names, offering a solution to the
+issues in **Case 1** and **Case 2**.
+
+Below is a `Pandas` DataFrame called `metro_mini3`, where the `case3` column contains file names that
+we aim to render as images.
+```{python}
+# | code-fold: true
+metro_mini3 = pd.DataFrame(
+ {
+ **data,
+ "case3": [
+ ", ".join(f"metro_{item}.svg" for item in row.split(", ")) for row in data["lines"]
+ ],
+ }
+)
+metro_mini3
+```
+
+Now we can use `GT.fmt_image()` to render the images by passing `"case3"` as the first argument and
+specifying either `img_local_paths` or `img_url_paths` as the `path=` argument:
+```{python}
+# equivalent to `Case 1`
+(
+ GT(metro_mini3)
+ .fmt_image("case3", path=img_local_paths)
+ .cols_align(align="right", columns="case3")
+)
+
+# equivalent to `Case 2`
+(
+ GT(metro_mini3)
+ .fmt_image("case3", path=img_url_paths)
+ .cols_align(align="right", columns="case3")
+)
+```
+
+After exploring **Case 1** and **Case 2**, you’ll likely appreciate the functionality of the `path=`
+argument. However, manually constructing file names can still be a bit tedious. If your file names
+follow a consistent pattern, the `file_pattern=` argument can simplify the process. Let’s see how
+this works in **Case 4** below.
+
+### Case 4: Image Names Using Both the `path=` and `file_pattern=` Arguments
+**Case 4** demonstrates how to use `path=` and `file_pattern=` to specify images with names following
+a common pattern. For example, you could use `file_pattern="metro_{}.svg"` to reference images like
+`metro_1.svg`, `metro_2.svg`, and so on.
+
+Below is a `Pandas` DataFrame called `metro_mini4`, where the `case4` column contains a copy of
+`data["lines"]`, which we aim to render as images.
+```{python}
+# | code-fold: true
+metro_mini4 = pd.DataFrame({**data, "case4": data["lines"]})
+metro_mini4
+```
+
+First, define a string pattern to illustrate the file naming convention, using `{}` to indicate the
+variable portion:
+```{python}
+file_pattern = "metro_{}.svg"
+```
+
+Next, pass `"case4"` as the first argument, along with `img_local_paths` or `img_url_paths` as the
+`path=` argument, and `file_pattern` as the `file_pattern=` argument. This allows `GT.fmt_image()`
+to render the images:
+```{python}
+# equivalent to `Case 1`
+(
+ GT(metro_mini4)
+ .fmt_image("case4", path=img_local_paths, file_pattern=file_pattern)
+ .cols_align(align="right", columns="case4")
+)
+
+# equivalent to `Case 2`
+(
+ GT(metro_mini4)
+ .fmt_image("case4", path=img_url_paths, file_pattern=file_pattern)
+ .cols_align(align="right", columns="case4")
+)
+```
+
+
+::: {.callout-warning collapse="true"}
+## Using `file_pattern=` Independently
+
+The `file_pattern=` argument is typically used in conjunction with the `path=` argument, but this
+is not a strict rule. If your local file paths or HTTP/HTTPS URLs follow a pattern, you can use
+`file_pattern=` alone without `path=`. This allows you to include the shared portion of the file
+paths or URLs directly in `file_pattern`, as shown below:
+```{python}
+file_pattern = str(img_local_paths / "metro_{}.svg")
+(
+ GT(metro_mini4)
+ .fmt_image("case4", file_pattern=file_pattern)
+ .cols_align(align="right", columns="case4")
+)
+```
+
+:::
+
+**Case 4** is undoubtedly one of the most powerful features of Great Tables. While mastering it may
+take some practice, we hope this example helps you render images effortlessly and effectively.
+
+## Rendering Images Anywhere
+While `GT.fmt_image()` is primarily designed for rendering images in the table body, what if you
+need to display images in other locations, such as the header? In such cases, you can turn to the versatile
+[vals.fmt_image()](https://posit-dev.github.io/great-tables/reference/vals.fmt_image.html#great_tables.vals.fmt_image).
+
+`vals.fmt_image()` is a hidden gem in Great Tables. Its usage is similar to `GT.fmt_image()`, but
+instead of working directly with DataFrame columns, it lets you pass a string or a list of strings
+as the first argument, returning a list of strings, each representing an image. You can then wrap
+these strings with [html()](https://posit-dev.github.io/great-tables/reference/html.html#great_tables.html),
+allowing Great Tables to render the images anywhere in the table.
+
+### Preparations
+We will create a `Pandas` DataFrame named `metro_mini` using the `data` dictionary. This will be used
+for demonstration in the following examples:
+```{python}
+# | code-fold: true
+metro_mini = pd.DataFrame(data)
+metro_mini
+```
+
+### Single Image
+This example shows how to render a valid URL as an image in the title of the table header:
+```{python}
+logo_url = "https://posit-dev.github.io/great-tables/assets/GT_logo.svg"
+
+_gt_logo, *_ = vals.fmt_image(logo_url, height=100) # <1>
+gt_logo = html(_gt_logo)
+
+(
+ GT(metro_mini)
+ .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
+ .tab_header(title=gt_logo)
+ .cols_align(align="right", columns="lines")
+ .opt_stylize(style=4, color="gray")
+)
+```
+1. `vals.fmt_image()` returns a list of strings. Here, we use tuple unpacking to extract the first
+item from the list.
+
+### Multiple Images
+This example demonstrates how to render two valid URLs as images in the title and subtitle of the
+table header:
+```{python}
+logo_urls = [
+ "https://posit-dev.github.io/great-tables/assets/GT_logo.svg",
+ "https://raw.githubusercontent.com/rstudio/gt/master/images/dataset_metro.svg",
+]
+
+_gt_logo, _metro_logo = vals.fmt_image(logo_urls, height=100) # <1>
+gt_logo, metro_logo = html(_gt_logo), html(_metro_logo)
+
+(
+ GT(metro_mini)
+ .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
+ .tab_header(title=gt_logo, subtitle=metro_logo)
+ .cols_align(align="right", columns="lines")
+ .opt_stylize(style=4, color="gray")
+)
+```
+1. Note that if you need to render images with different `height` or `width`, you might need to make
+two separate calls to `vals.fmt_image()`.
+
+## Manually Rendering Images Anywhere
+Remember, you can always use `html()` to manually construct your desired output. For example, the
+previous table can be created without relying on `vals.fmt_image()` like this:
+```{python}
+# | eval: false
+(
+ GT(metro_mini)
+ .fmt_image("lines", path=img_url_paths, file_pattern="metro_{}.svg")
+ .tab_header(
+ title=html(f''),
+ subtitle=html(f''),
+ )
+ .cols_align(align="right", columns="lines")
+ .opt_stylize(style=4, color="gray")
+)
+```
+
+Alternatively, you can manually encode the image using Python's built-in
+[base64](https://docs.python.org/3/library/base64.html) module, specify the appropriate MIME type
+and HTML attributes, and then wrap it in `html()` to display the table.
+
+## Final Words
+In this post, we focused on the most common use cases for rendering images in Great Tables, deliberately
+avoiding excessive DataFrame operations. Including such details could have overwhelmed the post with
+examples of string manipulations and the complexities of working with various DataFrame libraries.
+
+We hope you found this guide helpful and enjoyed the structured approach. Until next time, happy
+table creation with Great Tables!
+
+::: {.callout-note}
+## Appendix: Related PRs
+
+If you're interested in the recent enhancements we've made to image rendering, be sure to check out
+[#444](https://github.com/posit-dev/great-tables/pull/444),
+[#451](https://github.com/posit-dev/great-tables/pull/451) and
+[#520](https://github.com/posit-dev/great-tables/pull/520) for all the details.
+:::
diff --git a/great_tables/_formats.py b/great_tables/_formats.py
index 5c4bf0832..91157e985 100644
--- a/great_tables/_formats.py
+++ b/great_tables/_formats.py
@@ -3688,7 +3688,8 @@ def fmt_image(
In the output of images within a body cell, `sep=` provides the separator between each
image.
path
- An optional path to local image files (this is combined with all filenames).
+ An optional path to local image files or an HTTP/HTTPS URL.
+ This is combined with the filenames to form the complete image paths.
file_pattern
The pattern to use for mapping input values in the body cells to the names of the graphics
files. The string supplied should use `"{}"` in the pattern to map filename fragments to