Grav CMS for Drupal Developers
If you've never heard of it, Grav is a pretty neat little flat-file CMS. If you're a Drupal developer, words like "flat-file" and "neat" and "little" are probably foreign to you. This post is an attempt to explain what Grav is, why it's neat, and how to use it, in terms that you'll understand.
Wait, what's wrong with just using Drupal?
Why would anyone EVER think of leaving our beloved Drupal in the ditch to use something else?!
"If you want to build a small web site, I'm not sure it makes sense to use Drupal today. It's good for… ambitious sites." #driesnote
— Actually, (@eaton) April 25, 2017
That's a pretty good reason. Dries himself has said that Drupal may not be the best fit for small sites. There are simpler solutions that make the easy stuff easy and the hard stuff somewhat easier (as opposed to Drupal which makes the hard stuff easy and the easy stuff really frustratingly difficult sometimes).
First of all, where is the database?
As a Drupal developer, you live and die by the database. You've probably worked on sites that have had many hundreds of database tables. You might even remember the first time you realized that each field gets 2 database tables of its own.
The first thing you should understand about Grav is that there is no database. In place of it, there are 2 types of things:
- YAML files which hold configuration
- Markdown files which hold content
That's it. If you want to make a change to config, you change it in the relevant YAML file. If you want to update a page, you change it in the relevant Markdown file.
Oh, so it's a static site generator like Jekyll? No!
So far it may sound like a static site generator, but it's not. It's a CMS. This means that it can still do all the same types of things other CMS'es can do, that aren't available to static site generators.
For example, there's a really nice admin plugin that lets editors edit content via a UI, and upon saving, the content is instantly updated on the site (rather than the site needing to be re-built). Some static site generators have UI's, but they still require the intermediary site-generation step after making an edit.
You can also still have dynamic content listings, send emails, redirect users, integrate with web services, display user-facing forms, etc., since Grav is built with PHP and is super duper alterable via custom plugins. You'd need to handle that stuff client-side with a static site generator.
Content types in Drupal = Page Types in Grav
Let's start with the basics - the age old "content type." In Drupal, creating a content type happens in the UI.
In Grav, to create a content type, you just create a “whatever.html.twig” file in the templates/ directory of your theme. Doing that automatically tells Grav that “Whatever” should be a new Page type.
This means that when creating a page in the UI, you can choose the “Whatever” page type. Or, if you’re creating content via adding a Markdown file directly, just name the file whatever.md which tells Grav that it’s a “Whatever” type of page.
Custom fields in Drupal = Blueprints in Grav
In Drupal, creating custom fields happens in the UI.
In Grav, to create custom fields for a given page type, you’ll do it in a YAML file. Grav calls this a “Blueprint”. Just create a file in /user/blueprints/pages/PAGETYPE.yaml and throw in something like this:
<span class="token key atrule">title</span><span class="token punctuation">:</span> PAGETYPE<span class="token key atrule">'@extends'</span><span class="token punctuation">:</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> default <span class="token key atrule">context</span><span class="token punctuation">:</span> blueprints<span class="token punctuation">:</span>//pages<span class="token key atrule">form</span><span class="token punctuation">:</span> <span class="token key atrule">fields</span><span class="token punctuation">:</span> <span class="token key atrule">tabs</span><span class="token punctuation">:</span> <span class="token key atrule">fields</span><span class="token punctuation">:</span> <span class="token key atrule">content</span><span class="token punctuation">:</span> <span class="token key atrule">fields</span><span class="token punctuation">:</span> <span class="token key atrule">header.heading</span><span class="token punctuation">:</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> text <span class="token key atrule">label</span><span class="token punctuation">:</span> Heading <span class="token key atrule">header.subheading</span><span class="token punctuation">:</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> text <span class="token key atrule">label</span><span class="token punctuation">:</span> Subheading
Basically, that will add two new text fields (“Heading” and “Subheading”) to the “Content” tab of the form for that page type.
When you save that form, it’ll throw that data into a little YAML block at the top of the Markdown file that stores the content of that page. This is called Frontmatter or Headers and is actually really really cool because it means that the sky is basically the limit in terms of how to store structured data. You can store it in any way that YAML supports.
Then, in the Twig template (we’ll get to templates later), you can output the data for those custom fields using {{ header.heading }}
or {{ header.subheading }}
.
Views in Drupal = Page Collections in Grav
In Drupal, creating a content listing happens (usually) in the Views UI.
In Grav, there’s the concept of a “Collection” which allows you to loop through and list arbitrary content. Here’s an example:
<span class="token key atrule">content</span><span class="token punctuation">:</span> <span class="token key atrule">items</span><span class="token punctuation">:</span> @self.children <span class="token key atrule">order</span><span class="token punctuation">:</span> <span class="token key atrule">by</span><span class="token punctuation">:</span> date <span class="token key atrule">dir</span><span class="token punctuation">:</span> desc <span class="token key atrule">limit</span><span class="token punctuation">:</span> <span class="token number">10</span> <span class="token key atrule">pagination</span><span class="token punctuation">:</span> <span class="token boolean important">true</span>
And then in the Twig template, you’d just loop through them like so:
<span class="token tag"><span class="token ld"><span class="token punctuation">{%</span> <span class="token keyword">for</span></span> <span class="token property">p</span> <span class="token operator">in</span> <span class="token property">page</span><span class="token punctuation">.</span><span class="token property">collection</span> <span class="token rd"><span class="token punctuation">%}</span></span></span> <span class="token other"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span><span class="token punctuation">></span></span></span><span class="token tag"><span class="token ld"><span class="token punctuation">{{</span></span> <span class="token property">p</span><span class="token punctuation">.</span><span class="token property">title</span> <span class="token rd"><span class="token punctuation">}}</span></span></span><span class="token other"><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span></span> <span class="token tag"><span class="token ld"><span class="token punctuation">{{</span></span> <span class="token property">p</span><span class="token punctuation">.</span><span class="token property">summary</span> <span class="token rd"><span class="token punctuation">}}</span></span></span><span class="token tag"><span class="token ld"><span class="token punctuation">{%</span> <span class="token keyword">endfor</span></span> <span class="token rd"><span class="token punctuation">%}</span></span></span>
Collections support lots of the same filtering/sorting/pagination concepts that Views supports. Some of the more complex stuff (such as fields from relationships or exposed filters) would have to be custom built via a plugin, but this should handle most of the things you’d typically use Views for pretty well.
Another interesting note here is that there's a 3rd party plugin called View that adds some more power to this system.
Taxonomy in Drupal = Taxonomy in Grav
Yep, it’s even named the same thing for you.
In Drupal, creating a Taxonomy happens in the blah blah blah you get the idea. All of this stuff is done in the UI in Drupal.
In Grav, creating a Taxonomy just means adding it to your site.yaml config file, like so:
<span class="token key atrule">taxonomies</span><span class="token punctuation">:</span> \<span class="token punctuation">[</span>category<span class="token punctuation">,</span>tag\<span class="token punctuation">]</span>
Just add it to that array and you’ve created a new taxonomy. Then, you can reference it from any given page like this, in the YAML Frontmatter:
<span class="token key atrule">title</span><span class="token punctuation">:</span> Post title<span class="token key atrule">taxonomy</span><span class="token punctuation">:</span> <span class="token key atrule">tag</span><span class="token punctuation">:</span> \<span class="token punctuation">[</span>animal<span class="token punctuation">,</span> dog\<span class="token punctuation">]</span> <span class="token key atrule">category</span><span class="token punctuation">:</span> pets
And that’s it. Taxonomies are MUCH simpler in Grav than in Drupal. They aren’t fieldable, for example (without some customization). They’re basically just a way to group content together, so that you can create listings (“Collections”) out of them.
Configuration/CMI/Features in Drupal = YAML files in Grav
In Drupal, configuration is stored in the database. Drupal 8 provides core with the ability to sync this configuration with YAML in the filesystem, but the source of truth is the database.
This means that if you want to push some new configuration some site A to site B, you have to make the change in the UI, export it to YAML, move that YAML to the other site (via a git push or some other mechanism), and import it on the other site to make it live. People usually use Drush or Features to help with this process.
In Grav, the source of truth for configuration is the YAML itself, since there’s no database. To change configuration, just change the YAML file, and Grav will immediately recognize that. To move that change to another site, just git push/pull it and it’s live.
Install profiles/distributions in Drupal = Skeletons in Grav
This is one area where Grav really shines.
In Drupal, shipping a distribution mostly involves doing work to make sure that a site has everything it need in code and exported configuration, and installs correctly using the installer. This is a result of Drupal relying on a database, but not wanting to ship an exported copy of that database with the distribution.
In Grav, since there’s no database, a “distribution” (or a "Skeleton" in Grav-speak) is basically just a copy of the codebase. Grav has no notion of "installation" like Drupal's installer. Just copy the codebase to another web root somewhere and it’s ready to run. This means that it’s really easy to ship open source Skeletons, many of which are available here.
(It’s a tiny bit more nuanced than that since all you really need is the /user directory of the codebase which is where all the custom code is stored, but you get the idea).
Paragraphs in Drupal = Modular Pages in Grav (kind of)
If you aren't familiar, Paragraphs is a very popular Drupal module that lets you build content in arbitrary "slices", each of which can contain arbitrary fields.
Grav has the concept of Modular Pages, which is pretty similar. Basically, a Modular Page is just a collection of other pages, but those "other pages" aren't reachable on their own, and they're special types that are made specifically for being placed into Modular Pages.
For example, a Modular Page being used as the homepage may be comprised of a Slideshow page, then a Feature List page, then an Image With Caption page, etc., and none of those sub-pages are actual pages that are reachable on their own. So even though they're called "pages", it's basically the same idea as Paragraphs.
The UI for this is different since each of those sub-pages are editable on separate forms instead of all of them being in the same form like how Paragraphs does it, but you can accomplish most of the same things this way.
Drush in Drupal = CLI tools in Grav
Drush has saved the butt of many a Drupal developer. These days, Drupal Console is doing pretty well for itself too, but it’s the same basic idea. Talking to your site via the CLI is useful.
Grav has a couple built in CLI tools for many of the same purposes:
- bin/grav: performs basic site tasks such as clearing cache, making backups, installing dependencies, or creating new projects
- bin/plugin: performans commands provided by plugins (instead of Grav core), such as creating new users via the admin plugin
- bin/gpm: (“Grav Package Manager”) - performs tasks you would expect of a package manager, such as listing, downloading, and updating plugins
Other random stuff
Here’s some other stuff that didn’t really deserve its own section. Feel free to read up on the docs on these if you’re curious.
- CSS/JS aggregation in Drupal = Asset Manager (“Pipelining”) in Grav
- Image styles in Drupal = Image Actions in Grav
- Webform in Drupal = form.md files in Grav
- Caching in Drupal = Caching in Grav
- Base Themes in Drupal = Base Themes in Grav
- Per-environment settings in Drupal = Environment Configuration in Grav
- Multisite in Drupal = Multisite in Grav
Shortcomings and Downsides
There are a few things to keep in mind if you’re looking at using Grav for a project instead of Drupal.
One is that Grav doesn’t scale nearly as well. Many Drupal sites have many millions of nodes, thanks to the usage of a database. In general, I probably wouldn’t suggest using Grav once you start getting into the thousands with page count. Performance will likely start to suffer.
Drupal also really shines in creating complex content models, where there are many types of nodes/entities which reference each other or embed each other or reuse each other's fields, etc. Grav is perhaps more "page focused" than "data focused", which makes it much easier to work with for many sites, but not a great fit for some sites that need those complex relationships.
Grav also doesn’t really have the notion of an editorial workflow or moderation system. It does support published vs. unpublished, and there are things like Git Sync to auto-deploy from a staging environment (or your local site) to a production environment if you set it up to do so, but there’s no approval process along the lines of what Drupal and some modules can provide.
Obviously, Grav also isn’t going to have anywhere near the amount of 3rd party plugins (modules) that Drupal has. Things like integration with web services or commonly used libraries will have to be hooked up yourself, more often than not. That said, the API is solid and the documentation for it is legit.
That’s by no means an exhaustive list, but it's about all I’ve found so far. For your typical small to medium sized sites, Grav can be a really great solution that cuts out some of the overhead of a typical Drupal site. Recommended!