---
title: Upgrading from 3.x to 4.x
permalink: /docs/upgrading/3-to-4/
---

A few things have changed in Jekyll 4.

Before we dive in, you need to have at least Ruby {{ site.data.ruby.min_version }}
installed.

Run the following in your terminal to check

```sh
ruby -v
{{ site.data.ruby.current_version_output }}
```

If you're using a supported Ruby version >= {{ site.data.ruby.min_version }}, go ahead
and fetch the latest version of Jekyll:

```sh
gem update jekyll
```

<div class="note warning">
  <h5><code>post_url</code> Tag and Baseurl</h5>
  <p>&nbsp;</p>
  <p>
    The <code>post_url</code> tag now incorporates the <code>relative_url</code> filter within itself
    and therefore automatically prepends your site's <code>baseurl</code> to the post's <code>url</code>
    value.
  </p>
  <p>
    Please ensure that you change all instances of the <code>post_url</code> usage as following:
  </p>

{% highlight diff %}
{% raw %}
- {{ site.baseurl }}/{% post_url 2018-03-20-hello-world.markdown %}
+ {% post_url 2018-03-20-hello-world.markdown %}
{% endraw %}
{% endhighlight %}
</div>

## Template rendering

We've slightly altered the way Jekyll parses and renders your various templates
to improve the overall build times. Jekyll now parses a template once, caches it
internally and then renders the parsed template multiple times as required by
your pages and documents.

The downside to this is that some of the community-authored plugins may not work
as they previously used to.

## Static files in unrendered collections

Collections other than `posts` can contain static assets along with Markdown files.
But if the collection has not been configured with metadata `output: true`, then
neither its *documents* nor its *static assets* will be output to the destination
directory.

## For plugin authors

* If your plugin depends on the following code: `site.liquid_renderer.file(path).parse(content)`,
note that the return value (`template`, an instance of *`Liquid::Template`*), from that line will
always be the **same object** for a given `path`. <br/>
The *`template`* instance is then rendered as previously, with respect to the `payload` passed to it.
You'll therefore have to ensure that *`payload`* is not memoized or cached in your plugin instance.

* If its a requirement that `template` you get from the above step *be different* at all times,
you can invoke *`Liquid::Template`* directly:

  ```diff
  - template = site.liquid_renderer.file(path).parse(content)
  + template = Liquid::Template.parse(content)
  ```

## Exclusion changes

We've enhanced our default exclusion array.
It now looks like the following:

```yaml
# default excludes
exclude:
- .sass-cache/
- .jekyll-cache/
- gemfiles/
- Gemfile
- Gemfile.lock
- node_modules/
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/
```

What's new is that this array **does not get overridden by the `exclude` array
in the user's config file anymore**. The user's exclude entries simply get
**added** to the above default array (if the entry isn't already excluded).

To forcibly "process" directories or files that have been excluded, list them
in the `include` array instead:

```yaml
# overrides your excluded items configuration and the default include array ([".htaccess"])
include:
  - .htaccess
  - node_modules/uglifier/index.js
```

The above configuration directs Jekyll to handle only
`node_modules/uglifier/index.js` while ignoring every other file in the
`node_modules` directory since that directory is "excluded" by default.

Note that the default `include` array still gets overridden by the `include`
array in your config file. So, be sure to add `.htaccess` to the list if you
need that file to be present in the generated site.

## Kramdown v2

Jekyll has dropped support for `kramdown-1.x` entirely.

From [`v2.0` onwards](https://kramdown.gettalong.org/news.html#kramdown-200-released)
kramdown requires specific extensions to be additionally installed to use
certain desired features outside of kramdown's core functionality.

Out of all the extensions listed in the report linked above, gem
`kramdown-parser-gfm` is automatically installed along with Jekyll 4.0. The
remaining extensions will have to be manually installed by the user depending on
desired functionality, by listing the extension's gem-name in their `Gemfile`.

Notes:
  * `kramdown-converter-pdf` will be ignored by Jekyll Core. To have Jekyll convert Markdown to PDF
    you'll have to depend on a plugin that subclasses `Jekyll::Converter` with the
    [required methods]({% link _docs/plugins/converters.md %}).

    For example:

    ```ruby
    module Jekyll
      External.require_with_graceful_fail "kramdown-converter-pdf"

      class Markdown2PDF < Converter
        safe true
        priority :low

        def matches(ext)
          # match only files that have an extension exactly ".markdown"
          ext =~ /^\.markdown$/
        end

        def convert(content)
          Kramdown::Document.new(content).to_pdf
        end

        def output_ext
          ".pdf"
        end
      end
    end
    ```

  * Vendors that provide a versioned Jekyll Environment Image (e.g. Docker Image, GitHub Pages, etc)
    will have to manually whitelist kramdown's extension gems in their distributions for Jekyll 4.0.

## Deprecated Configuration Options

Jekyll 4.0 has dropped support for all legacy configuration options that were deprecated over multiple
releases in the previous series.

To that end, we shall no longer output a deprecation warning when we encounter a legacy config key nor
shall we gracefully assign their values to the newer counterparts. Depending on the key, it shall either
be ignored or raise an `InvalidConfigurationError` error if the key is still valid but the associated
value is not of the valid type.
