# 短代码模板
> 创建自定义短代码模板：位置与命名、参数读取、返回值处理与嵌套。
- 官方英文原文：https://gohugo.io/templates/shortcode/
- 本页规范地址：https://hugozh.cn/templates/shortcode/
- 最近更新：2026-10-02
- 最后提交：912b1d3 chore(site): 添加 static/CNAME（hugozh.cn），供 GitHub Pages 等平台绑定自定义域名
- 站点：Hugo 中文文档（https://hugozh.cn/）· 社区维护的非官方中文翻译，如有出入以官方英文原文为准

---
> [!NOTE]
> 创建自定义短代码之前，请先阅读[短代码](/shortcodes/)。理解用法细节有助于设计出更好的模板。

## 简介

Hugo 为许多常见任务提供了内置短代码，但更专门的需求往往需要自己编写。常见的自定义短代码包括音频播放器、视频播放器、图片画廊、图表、地图、表格，以及各种自定义元素。

## 目录结构

短代码模板创建在 `layouts/_shortcodes` 目录中，可以放在该目录根部，也可以组织成子目录：

```tree
layouts/
└── _shortcodes/
    ├── diagrams/
    │   ├── kroki.html
    │   └── plotly.html
    ├── media/
    │   ├── audio.html
    │   ├── gallery.html
    │   └── video.html
    ├── capture.html
    ├── column.html
    ├── include.html
    └── row.html
```

在内容中调用子目录里的短代码时，写出它相对于 `_shortcodes` 目录的路径，并省略文件扩展名：

```md
```

## 查找顺序

Hugo 依据短代码名称、当前输出格式与当前语言来选择模板。下例按具体程度从高到低排列，最笼统的排在最后：

| 短代码名 | 输出格式 | 语言 | 模板路径 |
| --- | --- | --- | --- |
| foo | html | en | `layouts/_shortcodes/foo.en.html` |
| foo | html | en | `layouts/_shortcodes/foo.html.html` |
| foo | html | en | `layouts/_shortcodes/foo.html` |
| foo | html | en | `layouts/_shortcodes/foo.html.en.html` |

输出格式为 `rss` 时同理，从 `foo.en.rss.xml` 依次退到 `foo.rss.xml`、`foo.en.xml`，最后是 `foo.xml`。

## 常用方法

短代码模板中有若干方法可用，`layouts/_shortcodes` 下各方法的作用如下：

| 方法 | 作用 |
| --- | --- |
| `.Get` | 按名称或序号读取单个参数。 |
| `.GetMatch` | 按模式匹配参数名并返回值。 |
| `.Params` | 以映射或切片的形式给出全部参数。 |
| `.IsNamedParams` | 判断本次调用使用的是命名参数还是位置参数。 |
| `.Inner` | 返回成对短代码之间的内容。 |
| `.InnerDeindent` | 返回去掉公共前导缩进后的内部内容。 |
| `.Parent` | 返回父级短代码的上下文，用于嵌套。 |
| `.Name` | 返回短代码名称，常用于错误信息。 |
| `.Position` | 返回短代码在内容文件中的位置，常用于错误信息。 |
| `.Page` | 返回调用该短代码的页面对象。 |

## 示例

下面的示例由浅入深，部分为便于理解做了简化。

### 插入年份

创建一个插入当前年份的短代码：

```go-html-template {file="layouts/_shortcodes/year.html"}
{{- now.Format "2006" -}}
```

然后在内容中调用它：

```md {file="content/example.md"}
This is {{</* year */>}}, and look at how far we've come.
```

这个短代码既可以行内使用，也可以独占一行作为块使用。若可能被行内调用，请用带连字符的动作定界符去掉两侧的空白。

### 插入图片

假设 `content/example/index.md` 是一个包含若干页面资源的页面包：

```tree
content/
├── example/
│   ├── a.jpg
│   └── index.md
└── _index.md
```

创建一个短代码，把图片取为页面资源、按给定宽度缩放、转换为 WebP 格式，并加上 `alt` 属性：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{- with .Page.Resources.Get (.Get "path") }}
  {{- with .Process (printf "resize %dx wepb" ($.Get "width")) -}}
    <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $.Get "alt" }}">
  {{- end }}
{{- end -}}
```

在内容中调用：

```md {file="content/example/index.md"}
```

上面的写法用到了 `with` 语句在每次操作成功后重新绑定上下文、`Get` 方法按名称取出参数，以及 `$` 访问模板最外层的上下文。

> [!NOTE]
> 请务必彻底理解上下文的概念，新手最常犯的模板错误大多与它有关。相关说明见[简介](/templates/introduction/)。

### 加上错误处理

前面的例子虽然可用，但图片不存在时会静默失败，缺少必需参数时也不会优雅退出。下面补上错误处理：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{- with .Get "path" }}
  {{- with $r := $.Page.Resources.Get ($.Get "path") }}
    {{- with $.Get "width" }}
      {{- with $r.Process (printf "resize %dx wepb" ($.Get "width" )) }}
        {{- $alt := or ($.Get "alt") "" -}}
        <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="{{ $alt }}">
      {{- end }}
    {{- else }}
      {{- errorf "The %q shortcode requires a 'width' argument: see %s" $.Name $.Position }}
    {{- end }}
  {{- else }}
    {{- warnf "The %q shortcode was unable to find %s: see %s" $.Name ($.Get "path") $.Position }}
  {{- end }}
{{- else }}
  {{- errorf "The %q shortcode requires a 'path' argument: see %s" .Name .Position }}
{{- end -}}
```

作者没有提供 `path` 或 `width` 时，这个模板会报错并让构建优雅失败；找不到指定路径的图片时给出警告；没有提供 `alt` 时，`alt` 属性被设为空字符串。`Name` 与 `Position` 方法能为错误与警告提供有用的上下文，例如缺少 `width` 参数时会抛出：

```text
ERROR The "image" shortcode requires a 'width' argument: see "/home/user/project/content/example/index.md:7:1"
```

### 位置参数

短代码参数可以是命名参数，也可以是位置参数。前面用的是命名参数，下面看位置参数的写法。命名参数版本如下：

```md {file="content/example/index.md"}
```

改用位置参数调用：

```md {file="content/example/index.md"}
```

在模板中用从 0 开始的下标配合 `Get` 方法取值，并把它们赋给含义清晰的变量：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{ $path := .Get 0 }}
{{ $width := .Get 1 }}
{{ $alt := .Get 2 }}
```

> [!NOTE]
> 位置参数适合只有一两个参数且经常使用的短代码，因为用得多了自然记得住顺序。使用频率较低或参数超过两个时，命名参数可读性更好，也更不容易出错。

### 同时支持两种参数

可以让短代码同时接受命名参数与位置参数，但一次调用中不能混用。用 `IsNamedParams` 方法判断本次调用用的是哪一种：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{ $path := cond (.IsNamedParams) (.Get "path") (.Get 0) }}
{{ $width := cond (.IsNamedParams) (.Get "width") (.Get 1) }}
{{ $alt := cond (.IsNamedParams) (.Get "alt") (.Get 2) }}
```

这里用 `cond`（`compare.Conditional` 的别名）来实现：`IsNamedParams` 为真时按名称取值，否则按位置取值。

### 参数集合

用 `Params` 方法把参数作为一个集合取出。使用命名参数时它返回映射：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{ .Params.path }} → a.jpg
{{ .Params.width }} → 300
{{ .Params.alt }} → A white kitten
```

使用位置参数时它返回切片，需要用 `index` 按下标取值：

```go-html-template {file="layouts/_shortcodes/image.html"}
{{ index .Params 0 }} → a.jpg
{{ index .Params 1 }} → 300
{{ index .Params 2 }} → A white kitten
```

把 `Params` 与 `collections.IsSet` 函数配合使用，可以判断某个参数是否被设置过，即使它的值是假值。

### 内部内容

用 `Inner` 方法取出短代码标签之间包裹的内容。下例同时把内容与标题传给短代码，短代码生成一个 `div`，其中包含显示标题的 `h2` 与传入的内容：

```md {file="content/example.md"}
This is a **bold** word, and this is an _emphasized_ word.
```

```go-html-template {file="layouts/_shortcodes/contrived.html"}
<div class="contrived">
  <h2>{{ .Get "title" }}</h2>
  {{ .Inner | .Page.RenderString }}
</div>
```

上面的调用使用标准写法，因此需要用 `RenderString` 方法把内部内容里的 Markdown 转换成 HTML。若改用 Markdown 写法调用，这一转换就是多余的。

### 嵌套

当短代码在另一个短代码内部被调用时，`Parent` 方法提供父级短代码的上下文，从而形成一种继承模型。下面这个例子虽属刻意构造，但足以说明概念。

假设有一个 `gallery` 短代码，接受一个名为 `class` 的参数：

```go-html-template {file="layouts/_shortcodes/gallery.html"}
<div class="{{ .Get "class" }}">
  {{ .Inner }}
</div>
```

另有一个 `img` 短代码，接受一个名为 `src` 的参数；你希望它既能用在 `gallery` 内部，也能单独使用，由父级决定它的上下文：

```go-html-template {file="layouts/_shortcodes/img.html"}
{{ $src := .Get "src" }}
{{ with .Parent }}
  <img src="{{ $src }}" class="{{ .Get "class" }}-image">
{{ else }}
  <img src="{{ $src }}">
{{ end }}
```

在内容中这样调用：

```md {file="content/example.md"}
```

输出的 HTML 如下。前两个 `img` 继承了父级 `gallery` 调用中设置的 `class` 值 `content-gallery`，第三个只使用 `src`：

```html
<div class="content-gallery">
  <img src="/images/one.jpg" class="content-gallery-image">
  <img src="/images/two.jpg" class="content-gallery-image">
</div>
<img src="/images/three.jpg">
```

### 渲染顺序

调用短代码所用的写法决定它在 Markdown 渲染的哪个阶段执行：

1. 用 Markdown 写法调用的短代码在 Markdown 渲染器之前按文档顺序执行。
1. Markdown 渲染器运行。
1. 用标准写法调用的短代码在 Markdown 渲染器之后按文档顺序执行。

这意味着，文档中靠前但使用标准写法调用的短代码，仍然晚于靠后但使用 Markdown 写法调用的短代码执行。

在同一阶段内，同一嵌套层级的短代码按文档顺序自上而下执行。短代码嵌套时，Hugo 由内向外渲染：每个嵌套短代码都先于它的父级执行，父级收到的 `Inner` 内容是所有嵌套短代码渲染完成的输出。例如对于下面的调用：

```md {file="content/example.md"}
```

Hugo 的渲染顺序是 `inner-a`、`inner-b`、`outer`（把前两者的渲染结果作为 `.Inner` 接收），最后是 `standalone`。

### 关于模板的返回值

短代码模板不写返回语句，Hugo 把模板渲染出的内容当作该次调用的返回值，直接放在调用处。由于渲染 HTML 时使用的是 Go 的 `html/template` 包，模板里由数据计算出来的字符串默认会被转义，以免内容破坏页面结构或引入注入风险；模板中直接书写的标签属于静态文本，会原样输出。

因此，当一段由数据拼出的字符串需要作为 HTML 输出时，要显式把它标记为可信内容；只想把它作为纯文本显示时则保持默认的转义即可。编写模板时请把这个区别放在心上：如果转义结果中出现 `&lt;` 之类的实体，通常说明该内容被当作文本处理了。

### 其他参考

想找更多思路，可以研究 Hugo 的内置短代码，其源码是很好的范例。

## 检测短代码是否被使用

`HasShortcode` 方法可以检查某个短代码是否在页面上被调用过。例如有一个自定义的 `audio` 短代码：

```md {file="content/example.md"}
```

可以在基础模板中用 `HasShortcode` 判断该页面是否用过 `audio`，从而有条件地加载 CSS：

```go-html-template {file="layouts/baseof.html"}
<head>
  {{ if .HasShortcode "audio" }}
    <link rel="stylesheet" src="/css/audio.css">
  {{ end }}
</head>
```

