Skip to content
Grav 2.0 is officially stable. Read the announcement →
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.0.12 · updated 5 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.0.12 Latest 6 days ago
    • Added per-language fallbacks for unsupported browser languages during HTTP_ACCEPT_LANGUAGE negotiation, allowing them to resolve to supported languages without exposing additional language routes.
    • The read_file() Twig function can now read .css files by default, so inline stylesheets can be embedded in a template without adjusting the security config (#4215).
    • Documented that the site-wide media object resolves lazily, so its inherited collection query methods need media_directory() to filter or sort site media (#4210).
    • Browser language codes are now matched case-insensitively during HTTP_ACCEPT_LANGUAGE fallback negotiation.
v2.0.11 2 weeks ago
    • You can now filter, sort, and group a page's media by the values in their .meta.yaml metafiles directly in Twig, with new filterBy, where, findBy, sortBy, groupBy, and withMeta methods on page.media. Fixes getgrav/grav#4200.
    • [security] A page editor can no longer read arbitrary files from the server by pointing an image watermark at a traversal path such as carrier.png?watermark=../secret.png; an editor-supplied watermark path is now confined to the site's media, while operator-configured watermarks and stream URIs are unaffected (GHSA-w3f4-8pj2-599w).
    • [security] A page-edit account can no longer reach file-disclosure or secret-read functions by naming an arbitrary Class::method as a dynamic field's data provider; qualified providers are now limited to a known-safe allowlist, closing a bypass of the guard added in 2.0.7 and 2.0.9 (GHSA-7pgq-cr25-xvc8, GHSA-cxv3-5jj3-cpgr).
    • A page is no longer blanked when viewed just because a trusted plugin or shortcode on it outputs markup the content security scan flags, such as an embed, form, or icon; the check that guards against dangerous editor content now runs once when the page is saved rather than every time it is rendered (GHSA-2c4f-86xc-cr74).
    • [security] Page content that uses Twig to assemble disallowed markup at render time, such as building an event handler or a <script> tag from separate pieces, is now refused when you save the page instead of being allowed through to visitors.
    • The raw Twig filter is no longer allowed inside editor-authored page content, so page content can no longer output unescaped dynamic values past the content security check; trusted theme templates are unaffected.
v2.0.10 2 weeks ago
    • A partial system.pages.process override in user/config/system.yaml (for example setting only twig: false) no longer silently turns off Markdown and leaves every page rendering its raw source. Because pages.process is a single field in the blueprint, a partial override replaced the whole map and dropped the default markdown: true; core now re-applies that default, so an affected site recovers on update with no change to its config. An explicit markdown: false is still honored.
v2.0.9 2 weeks ago
    • [security] A callable directive hidden in a Flex directory's blueprint field can no longer run commands on the server; dynamic field data on Flex types (Flex Pages, Flex Users, Flex Objects) now refuses dangerous functions, closing a path that bypassed the same guard added for page forms in 2.0.7 (GHSA-c4wf-2xxc-68qm).
    • [security] The scheduler webhook trigger now fails closed when no token is configured, so an enabled webhook left without a token can no longer run scheduled jobs for anonymous callers (GHSA-xwv3-2mv2-w33x).
    • Images and links whose filename contains spaces now render when the path is wrapped in angle brackets, for example ![](<My image.jpg>). Fixes getgrav/grav#4197.
v2.0.8 3 weeks ago
    • An email or www. URL used as the visible text of a Markdown link is no longer turned into a second, nested link when GFM autolinks are enabled. Fixes getgrav/grav#4191.
    • md5() can once again be called as a Twig function, not just as the |md5 filter, so themes and plugins that generate an id or cache-busting hash with md5(...) keep working instead of failing with an "Unknown function" error. Fixes getgrav/grav-theme-quark2#12.
    • Replacing an image in place, such as swapping the image in a Flex object field, now shows the new image on the site instead of the previously cached version (updated getgrav/image to v4.1.3, which includes the source file's modification time and size in the derivative cache key). Fixes getgrav/grav#4195.
v2.0.7 3 weeks ago
    • [security] A page editor can no longer run commands on the server by hiding a callable directive in a form field's settings; dynamic field data now refuses dangerous functions and cannot be tricked into reaching one through a helper (GHSA-fj2p-qj2f-74v5).
    • A page's translatedLanguages() now localizes ancestor slugs too, so a nested translation whose parent folder has a localized slug: produces the fully translated cross-language link instead of leaving parent segments in the current language. Fixes getgrav/grav#4186.
    • Pointing the log stream at environment:// (for example log: environment://logs) no longer crashes the site or bin/grav clear with a "stream must either be a resource or a string" error when the per-environment folder does not exist; logging now falls back to the default logs/ folder instead. Fixes getgrav/grav#4172.
    • The media:// stream now checks the per-environment user/env/<host>/media/ folder before the shared user/media/, so site media stored per environment resolves to the correct URL in the admin and in page content instead of a broken user/media/ link. Fixes getgrav/grav#4188.
    • Large file downloads such as site backups are now streamed to the browser in chunks instead of being loaded into memory all at once, so a download bigger than PHP's memory limit no longer fails with a blank server error. Fixes getgrav/grav-plugin-api#12.
    • Backups initialization no longer runs twice when something that bypasses the normal request middleware (such as the API plugin) also attaches the backup scheduler listener, so the listener is registered only once.
    • Pages accessed with URL parameters such as pagination or taxonomy filters no longer recompile every Twig template on each request, restoring full template caching on exactly the pages that get the most traffic.
    • The per-file compiled cache for YAML and markdown files now loads through its intended opcache fast path, and a source file that has been deleted no longer serves stale cached data.
    • A modular page that outputs trusted theme or plugin markup, such as a form with a reCAPTCHA field, is no longer wrongly blanked by the content security scan, which now checks the editor's own content instead of the finished template output. Fixes getgrav/grav-plugin-form#636.
    • Chaining media actions on page media under the content security scan, such as {{ page.media['x.jpg'].lightbox(1024,768).cropResize(176,176).html() }}, now works instead of being blocked, and the scan's list of allowed media methods stays in step with Grav's documented media actions automatically.
    • Updating a plugin or theme whose required dependency is held back by a newer Grav or PHP requirement now explains the real fix. Instead of reporting that the needed version is "higher than the latest release" and suggesting a cache refresh, the updater names the newer dependency release and the Grav (or PHP) version it needs, so you know to update Grav first. Relates to getgrav/grav-plugin-admin2#93.
    • Backup profiles now always appear in the scheduler where each can be switched on or off with the Enabled/Disabled toggle, instead of a profile staying hidden until its schedule was turned on; the profile's schedule setting seeds the default state and an explicit toggle takes precedence.
    • Frontend requests are noticeably faster across the board: the scheduler, backups machinery, error page renderer and logger now initialize only when actually used instead of on every page view, cutting over 50 PHP files from a typical request.
    • The filesystem scan that checks pages for changes now reuses its result for a couple of seconds (configurable with cache.check.interval), so busy sites no longer stat every page file on every single request; content edits still show up right away in normal editing workflows and admin saves remain instant.
    • Configuration, blueprint and language file lists honor the same freshness window instead of checking every tracked file's timestamp three times per request, and theme blueprints no longer load at all on normal frontend page views.
    • Class autoloading is faster: source installs get an optimized class map, APCu is suggested so the existing autoloader cache setting can engage, and plugin autoloaders no longer sit in front of the core one where every core class lookup had to pass through them first.
    • Rendering a page with cached content no longer loads its whole media collection up front, the pages index no longer stores pre-computed metadata for every page, and relative markdown links resolve their target page directly instead of building the full page list per link.
    • Assorted hot-path trims: string helpers use fast native functions for the common case, asset rendering skips per-asset integrity work when the feature is off, the site root URL is computed once per request instead of per asset, and debugger timers cost nothing when the debugger is disabled.
    • New experimental opt-in page index (pages.lazy_index: true): pages, routes, children lists, sort orders and the taxonomy map load on demand from a per-page index instead of one large cache blob that has to be fully unserialized on every request, making per-request cost flat as sites grow: a 2,000 page test site renders as fast as a 2 page one and uses a quarter of the memory; SQLite powers the index when available with a pure PHP fallback, and the default behavior is completely unchanged until the flag is enabled.
    • Page collection filters (visible, routable, published, module) now use menu flags recorded in the page index, so building a navigation menu that filters a folder to its visible pages no longer loads every hidden sibling first. On a 500 post blog under the Quark theme this cut the pages built for a page view from all 507 to 7 and roughly halved memory; it helps every site, most of all large ones with the experimental page index enabled.
    • Sorting a page collection by date, title, or another common field now reads that value from the page index instead of loading every page in the collection just to read one field, and on single language sites the automatic translated filter that every collection applies no longer loads any pages at all. With the experimental page index enabled, a blog post showing a related posts grid dropped from loading every post on the site to only the handful it displays.
    • The setting that scans page content for XSS moved to security.content.xss_scan_output, since it applies to all page content rather than only Twig in content; the previous security.twig_content.xss_scan_output location keeps working and is moved to the new one automatically on upgrade.
v2.0.12 Latest 6 days ago
    • Added per-language fallbacks for unsupported browser languages during HTTP_ACCEPT_LANGUAGE negotiation, allowing them to resolve to supported languages without exposing additional language routes.
    • The read_file() Twig function can now read .css files by default, so inline stylesheets can be embedded in a template without adjusting the security config (#4215).
    • Documented that the site-wide media object resolves lazily, so its inherited collection query methods need media_directory() to filter or sort site media (#4210).
    • Browser language codes are now matched case-insensitively during HTTP_ACCEPT_LANGUAGE fallback negotiation.
v2.0.11 2 weeks ago
    • You can now filter, sort, and group a page's media by the values in their .meta.yaml metafiles directly in Twig, with new filterBy, where, findBy, sortBy, groupBy, and withMeta methods on page.media. Fixes getgrav/grav#4200.
    • [security] A page editor can no longer read arbitrary files from the server by pointing an image watermark at a traversal path such as carrier.png?watermark=../secret.png; an editor-supplied watermark path is now confined to the site's media, while operator-configured watermarks and stream URIs are unaffected (GHSA-w3f4-8pj2-599w).
    • [security] A page-edit account can no longer reach file-disclosure or secret-read functions by naming an arbitrary Class::method as a dynamic field's data provider; qualified providers are now limited to a known-safe allowlist, closing a bypass of the guard added in 2.0.7 and 2.0.9 (GHSA-7pgq-cr25-xvc8, GHSA-cxv3-5jj3-cpgr).
    • A page is no longer blanked when viewed just because a trusted plugin or shortcode on it outputs markup the content security scan flags, such as an embed, form, or icon; the check that guards against dangerous editor content now runs once when the page is saved rather than every time it is rendered (GHSA-2c4f-86xc-cr74).
    • [security] Page content that uses Twig to assemble disallowed markup at render time, such as building an event handler or a <script> tag from separate pieces, is now refused when you save the page instead of being allowed through to visitors.
    • The raw Twig filter is no longer allowed inside editor-authored page content, so page content can no longer output unescaped dynamic values past the content security check; trusted theme templates are unaffected.
v2.0.10 2 weeks ago
    • A partial system.pages.process override in user/config/system.yaml (for example setting only twig: false) no longer silently turns off Markdown and leaves every page rendering its raw source. Because pages.process is a single field in the blueprint, a partial override replaced the whole map and dropped the default markdown: true; core now re-applies that default, so an affected site recovers on update with no change to its config. An explicit markdown: false is still honored.
v2.0.9 2 weeks ago
    • [security] A callable directive hidden in a Flex directory's blueprint field can no longer run commands on the server; dynamic field data on Flex types (Flex Pages, Flex Users, Flex Objects) now refuses dangerous functions, closing a path that bypassed the same guard added for page forms in 2.0.7 (GHSA-c4wf-2xxc-68qm).
    • [security] The scheduler webhook trigger now fails closed when no token is configured, so an enabled webhook left without a token can no longer run scheduled jobs for anonymous callers (GHSA-xwv3-2mv2-w33x).
    • Images and links whose filename contains spaces now render when the path is wrapped in angle brackets, for example ![](<My image.jpg>). Fixes getgrav/grav#4197.
v2.0.8 3 weeks ago
    • An email or www. URL used as the visible text of a Markdown link is no longer turned into a second, nested link when GFM autolinks are enabled. Fixes getgrav/grav#4191.
    • md5() can once again be called as a Twig function, not just as the |md5 filter, so themes and plugins that generate an id or cache-busting hash with md5(...) keep working instead of failing with an "Unknown function" error. Fixes getgrav/grav-theme-quark2#12.
    • Replacing an image in place, such as swapping the image in a Flex object field, now shows the new image on the site instead of the previously cached version (updated getgrav/image to v4.1.3, which includes the source file's modification time and size in the derivative cache key). Fixes getgrav/grav#4195.
v2.0.7 3 weeks ago
    • [security] A page editor can no longer run commands on the server by hiding a callable directive in a form field's settings; dynamic field data now refuses dangerous functions and cannot be tricked into reaching one through a helper (GHSA-fj2p-qj2f-74v5).
    • A page's translatedLanguages() now localizes ancestor slugs too, so a nested translation whose parent folder has a localized slug: produces the fully translated cross-language link instead of leaving parent segments in the current language. Fixes getgrav/grav#4186.
    • Pointing the log stream at environment:// (for example log: environment://logs) no longer crashes the site or bin/grav clear with a "stream must either be a resource or a string" error when the per-environment folder does not exist; logging now falls back to the default logs/ folder instead. Fixes getgrav/grav#4172.
    • The media:// stream now checks the per-environment user/env/<host>/media/ folder before the shared user/media/, so site media stored per environment resolves to the correct URL in the admin and in page content instead of a broken user/media/ link. Fixes getgrav/grav#4188.
    • Large file downloads such as site backups are now streamed to the browser in chunks instead of being loaded into memory all at once, so a download bigger than PHP's memory limit no longer fails with a blank server error. Fixes getgrav/grav-plugin-api#12.
    • Backups initialization no longer runs twice when something that bypasses the normal request middleware (such as the API plugin) also attaches the backup scheduler listener, so the listener is registered only once.
    • Pages accessed with URL parameters such as pagination or taxonomy filters no longer recompile every Twig template on each request, restoring full template caching on exactly the pages that get the most traffic.
    • The per-file compiled cache for YAML and markdown files now loads through its intended opcache fast path, and a source file that has been deleted no longer serves stale cached data.
    • A modular page that outputs trusted theme or plugin markup, such as a form with a reCAPTCHA field, is no longer wrongly blanked by the content security scan, which now checks the editor's own content instead of the finished template output. Fixes getgrav/grav-plugin-form#636.
    • Chaining media actions on page media under the content security scan, such as {{ page.media['x.jpg'].lightbox(1024,768).cropResize(176,176).html() }}, now works instead of being blocked, and the scan's list of allowed media methods stays in step with Grav's documented media actions automatically.
    • Updating a plugin or theme whose required dependency is held back by a newer Grav or PHP requirement now explains the real fix. Instead of reporting that the needed version is "higher than the latest release" and suggesting a cache refresh, the updater names the newer dependency release and the Grav (or PHP) version it needs, so you know to update Grav first. Relates to getgrav/grav-plugin-admin2#93.
    • Backup profiles now always appear in the scheduler where each can be switched on or off with the Enabled/Disabled toggle, instead of a profile staying hidden until its schedule was turned on; the profile's schedule setting seeds the default state and an explicit toggle takes precedence.
    • Frontend requests are noticeably faster across the board: the scheduler, backups machinery, error page renderer and logger now initialize only when actually used instead of on every page view, cutting over 50 PHP files from a typical request.
    • The filesystem scan that checks pages for changes now reuses its result for a couple of seconds (configurable with cache.check.interval), so busy sites no longer stat every page file on every single request; content edits still show up right away in normal editing workflows and admin saves remain instant.
    • Configuration, blueprint and language file lists honor the same freshness window instead of checking every tracked file's timestamp three times per request, and theme blueprints no longer load at all on normal frontend page views.
    • Class autoloading is faster: source installs get an optimized class map, APCu is suggested so the existing autoloader cache setting can engage, and plugin autoloaders no longer sit in front of the core one where every core class lookup had to pass through them first.
    • Rendering a page with cached content no longer loads its whole media collection up front, the pages index no longer stores pre-computed metadata for every page, and relative markdown links resolve their target page directly instead of building the full page list per link.
    • Assorted hot-path trims: string helpers use fast native functions for the common case, asset rendering skips per-asset integrity work when the feature is off, the site root URL is computed once per request instead of per asset, and debugger timers cost nothing when the debugger is disabled.
    • New experimental opt-in page index (pages.lazy_index: true): pages, routes, children lists, sort orders and the taxonomy map load on demand from a per-page index instead of one large cache blob that has to be fully unserialized on every request, making per-request cost flat as sites grow: a 2,000 page test site renders as fast as a 2 page one and uses a quarter of the memory; SQLite powers the index when available with a pure PHP fallback, and the default behavior is completely unchanged until the flag is enabled.
    • Page collection filters (visible, routable, published, module) now use menu flags recorded in the page index, so building a navigation menu that filters a folder to its visible pages no longer loads every hidden sibling first. On a 500 post blog under the Quark theme this cut the pages built for a page view from all 507 to 7 and roughly halved memory; it helps every site, most of all large ones with the experimental page index enabled.
    • Sorting a page collection by date, title, or another common field now reads that value from the page index instead of loading every page in the collection just to read one field, and on single language sites the automatic translated filter that every collection applies no longer loads any pages at all. With the experimental page index enabled, a blog post showing a related posts grid dropped from loading every post on the site to only the handful it displays.
    • The setting that scans page content for XSS moved to security.content.xss_scan_output, since it applies to all page content rather than only Twig in content; the previous security.twig_content.xss_scan_output location keeps working and is moved to the new one automatically on upgrade.