---
title: "Change a response body"
language: "en"
documentation_version: "3.x"
channel: "development"
release_context: null
source_revision: "10297d1e28653321d6996609fc53718d3b7c8278"
working_tree_clean: true
source_path: "docs/guides/body-filter.md"
source_sha256: "bd8b728e72a1ea8ac3c9b1c5b293e4741a1b9c80d9095a88774cb971bf18564e"
source_at_revision: true
source_url: "https://github.com/matsumotory/ngx_mruby/blob/10297d1e28653321d6996609fc53718d3b7c8278/docs/guides/body-filter.md"
html_url: "https://ngx.mruby.org/guides/body-filter/"
markdown_url: "https://ngx.mruby.org/markdown/guides/body-filter.md"
last_updated: "2026-10-29"
---

# Change a response body

A body filter runs while nginx sends a response, whichever handler or
upstream produced it. With
[`mruby_output_body_filter`](/reference/directives/#mruby_output_body_filter-mruby_output_body_filter_code-mruby_output_header_filter-and-mruby_output_header_filter_code),
a Ruby script reads the body through `Nginx::Filter#body` and replaces it
with `Nginx::Filter#body=`. A header filter
(`mruby_output_header_filter`) runs before it and can change the response
headers.

## The sample

The location sends a text file from disk. The header filter sets the
content type to `text/html`, and the body filter escapes `&`, `<` and `>`
and wraps the text in an HTML page.

```nginx title="test/conf/conf.d/76-guide-body-filter.conf"
server {
    listen 18167;

    # A text file from disk, sent as an HTML page.
    location = /notes {
        alias html/guide_notes.txt;
        mruby_output_header_filter_code 'Nginx::Request.new.content_type = "text/html"';
        mruby_output_body_filter build/nginx/html/guide_body_filter.rb;
    }
}
```

```ruby title="test/html/guide_body_filter.rb"
f = Nginx::Filter.new
text = f.body.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
f.body = "<!doctype html>\n<title>notes</title>\n<pre>\n#{text}</pre>\n"
```

```http title="test/html/guide_body_filter.request: the request that the case sends"
GET /notes HTTP/1.1
Host: 127.0.0.1:18167
Connection: close
```

```http title="test/html/guide_body_filter.response: the response that the case compares"
HTTP/1.1 200 OK
Content-Type: text/html

<!doctype html>
<title>notes</title>
<pre>
Notes from disk.
The filter escapes &lt;b&gt; and &amp;.
</pre>
```

<p class="caption sample-caption">On every CI run, the case <code>test/t/cases/guides.rb</code> sends this request and compares the status line, the headers shown on this page and the whole body. The five files last changed on <span class="nowrap">2026-10-29</span> in commit <code class="nowrap">32219e53</code>.</p>

## What to keep in mind

- The body filter reads a response of known length, such as a file from
  disk, as one buffer. nginx passes a response without `Content-Length`
  on without the filter reading it
  ([Nginx::Filter](/reference/ruby/#nginxfilter-class)).
- ngx_mruby sends the length of the new body in `Content-Length`.
- The use case "Output filter converting Markdown to HTML" of
  [Use cases](/guides/use-cases/) converts Markdown in the same place,
  with the gem mruby-discount.
