Skip to content
● Meet Grav Guard: keep every Grav site you run secure and up to date, all from one place. Free for up to 3 sites. Read more →
Downloads

Everything Grav

Download Grav

Grav Core is the base package with core functionality and a few essential starting pages. Grav Core + Admin also includes the Administration Panel plugin. Both are easy to get started with — check out our Basic Tutorial and Guide to the Administration Panel.

STABLE · v2.2.28 · updated 2 days ago

Latest stable release

Production-ready. The version we recommend for every new site and every upgrade of an existing one.

Get Started

1

Quick installation

  1. Download either the Grav Core or Grav Core + Admin2 plugin installation package.
  2. Extract the zip file into your webroot.
  3. Point your browser at your local webserver: http://yoursite.com
2

How to install the Admin2 plugin

If you have not already installed the admin plugin, you can do so easily with GPM:

$

This will install the admin plugin plus its dependencies (api & login). After this is complete, point your browser to your Grav installation and you will be prompted to create a new admin user.

Changelog

v2.2.28 Latest 2 days ago
    • Browser::isHuman() no longer counts link-preview fetchers (such as facebookexternalhit, which loads a page each time a shared link or ad is shown), AI fetchers (ChatGPT-User, ClaudeBot, PerplexityBot and others), uptime and monitoring services, and headless or auditing tools like HeadlessChrome and Lighthouse as people, so page statistics stop counting requests nobody made. Facebook's and Instagram's in-app browsers are real visitors and stay human
v2.2.27 4 days ago
    • Showing the same image twice with different derivatives() ranges, for example in two modules on one page, no longer stretches the second set of responsive images, and the second set can now go up to the full width of the original #4389
    • Cropping or resizing an image after derivatives(), such as derivatives(400, 800, 400).cropZoom(300, 300), scales its responsive images down with it instead of making them larger than the image itself
    • Links and images to media files with #, ? or % in their names work again, written percent-encoded (Invoice%20%2312.pdf) or in angle brackets. Media URLs now come out percent-encoded too, so a space is %20 #4388
v2.2.26 5 days ago
    • When PHP can't start sessions (for example, session.save_path points at a folder that no longer exists after a server move), Grav now writes the reason to grav.log instead of showing "Session corruption detected" on every page to every visitor
    • A session holding an unusable user object is now actually replaced with a fresh one, instead of crashing every page with an error until the visitor clears their cookies
    • The debug bar no longer reports Twig deprecations on every page, one for Grav's sandbox source policy and one for how page content Twig is rendered #4387
v2.2.25 5 days ago
    • A scheduled job that its plugin switches off with when() (such as YetiSearch Pro's embed job while semantic search is off) is no longer run and recorded as a failure each time it comes due. The scheduler leaves it out of the run and out of the health check, and Job::passesWhen() tells other tools whether a job's condition lets it run
    • [security] A media link's attributes now go through the same name check as attribute(), so page content can no longer put an event handler on the link. Thanks @R0X4R
v2.2.24 6 days ago
    • Running a plugin's test suite against a Grav checkout no longer leaves a user/config/security-private.php in the plugin's folder, where a git add -A could commit it into the plugin's release. Grav now creates the key file only in a real Grav install, and code that loads Grav's classes from anywhere else gets a key that lasts for that process
v2.2.23 7 days ago
    • When a cache file can't be deleted, the cache purge job and bin/grav clear now name the file and the reason (such as "Permission denied") instead of "Unknown error", and carry on with the rest of the cache instead of stopping at the first one (#4384)
    • [security] The svg_image() Twig function now escapes the classes it adds to the SVG. Thanks @manus-pi
    • [security] The svg_image() Twig function only inlines real .svg files, so a theme that takes the image path from a page header can no longer be pointed at other files on the server
v2.2.28 Latest 2 days ago
    • Browser::isHuman() no longer counts link-preview fetchers (such as facebookexternalhit, which loads a page each time a shared link or ad is shown), AI fetchers (ChatGPT-User, ClaudeBot, PerplexityBot and others), uptime and monitoring services, and headless or auditing tools like HeadlessChrome and Lighthouse as people, so page statistics stop counting requests nobody made. Facebook's and Instagram's in-app browsers are real visitors and stay human
v2.2.27 4 days ago
    • Showing the same image twice with different derivatives() ranges, for example in two modules on one page, no longer stretches the second set of responsive images, and the second set can now go up to the full width of the original #4389
    • Cropping or resizing an image after derivatives(), such as derivatives(400, 800, 400).cropZoom(300, 300), scales its responsive images down with it instead of making them larger than the image itself
    • Links and images to media files with #, ? or % in their names work again, written percent-encoded (Invoice%20%2312.pdf) or in angle brackets. Media URLs now come out percent-encoded too, so a space is %20 #4388
v2.2.26 5 days ago
    • When PHP can't start sessions (for example, session.save_path points at a folder that no longer exists after a server move), Grav now writes the reason to grav.log instead of showing "Session corruption detected" on every page to every visitor
    • A session holding an unusable user object is now actually replaced with a fresh one, instead of crashing every page with an error until the visitor clears their cookies
    • The debug bar no longer reports Twig deprecations on every page, one for Grav's sandbox source policy and one for how page content Twig is rendered #4387
v2.2.25 5 days ago
    • A scheduled job that its plugin switches off with when() (such as YetiSearch Pro's embed job while semantic search is off) is no longer run and recorded as a failure each time it comes due. The scheduler leaves it out of the run and out of the health check, and Job::passesWhen() tells other tools whether a job's condition lets it run
    • [security] A media link's attributes now go through the same name check as attribute(), so page content can no longer put an event handler on the link. Thanks @R0X4R
v2.2.24 6 days ago
    • Running a plugin's test suite against a Grav checkout no longer leaves a user/config/security-private.php in the plugin's folder, where a git add -A could commit it into the plugin's release. Grav now creates the key file only in a real Grav install, and code that loads Grav's classes from anywhere else gets a key that lasts for that process
v2.2.23 7 days ago
    • When a cache file can't be deleted, the cache purge job and bin/grav clear now name the file and the reason (such as "Permission denied") instead of "Unknown error", and carry on with the rest of the cache instead of stopping at the first one (#4384)
    • [security] The svg_image() Twig function now escapes the classes it adds to the SVG. Thanks @manus-pi
    • [security] The svg_image() Twig function only inlines real .svg files, so a theme that takes the image path from a page header can no longer be pointed at other files on the server