Documentation

Klay is a modern content management system built to be flexible, lightweight and easy to use.

  • 🔬 Klay lives in a single ~350kb php file. No complex system architecture cluttering up your code.
  • 🌍 Multilingual, of course
  • 🚀 Move quickly. Setup data types, eg. pages, blocks, whatever quickly and pull into your existing og new website in less than 5 minutes.
  • 💪🏼 Built for flexible usage: Use headlessly, build an API, integrate CMS powers into an existing system or power a small or large website.
  • 💪🏼 Power a PHP site or use as headless CMS for your JS framework site.
  • 💪🏼 Flexible schemas, setup your item types and fields
  • 💪🏼 No forced structure: Design your own item types, fields and reusable blocks.
  • 💪🏼 Block fields, customizable fields and previews
  • 🛠️ Built-in functionality for routing, controllers and database querying.
  • 🛠️ Built-in MVC complete with router, controllers filters and events
  • 🗄️ File based SQLite database, which means no need for an external database server.
  • 🗄️ Database in a SQLite3 file. No need for a separate database server.
  • 🗄️ Database query functions
  • 🖼️ Organized media library with drag-and-drop file uploads, tag-based organisation, dominant color calculation and auto-resizing
  • ⚡ Lightning quick and easy-to-use CMS panel.
  • 🔋 Batteries included: Page preview, content backups, output caching, dummy data seed generator, event webhooks and more.
  • 🙏🏼 Total freedom: Organize code structure like you want it and setup the item types, fields and blocks you want.
  • 🌱 Dummy data seeding quickly allows you to instantly populate test content
  • 🌲 Good for the environment. A lean, modern tech with less bloat makes the internet run faster and greener.

Motivation

Having worked with websites for many years, I've tried every CMS system under the sun. I've never found one that felt right. WordPress is flexible, but requires third party plugins in order to be more that a Blog system. The admin panel is slow, the database is a mess, you need plugins for stuff the system should do properly itself and the coding experience just feels outdated. Then there's something like Craft CMS, which is stronger in a lot of areas, but also is very opinionated (Twig), restricted and majorly bloated.

I never found the system I always wanted and for years thought of building one myself — until one day, after being fed up with WordPress, I did exactly that. Not long after I had several client websites running on Klay, and have been able to develop sites quicker and with less hassle and nonsense.

CMS Scope

Klay is by design a CMS system and just that. Klay doesn't force you to do your frontend in one way or another, like many other CMS's do.

There's routing and controllers helping you along, but whether you want to render your frontend with PHP, Nuxt, Svelte, React or something else is entirely up to you.

  • For a PHP rendered site you probably want to create some view files for each page and block, and some CSS and Javascript assets placed where you want them.
  • For a JS rendered site (eg. Next/Nuxt/Svelte/etc) or maybe a Flutter native app, you probably just want to keep your CMS headless, with a couple of API endpoint routes, so you can fetch content.
  • You could even use Klay to setup your own project management or CRM system, with no necessary frontend?

In short, Klay is focused on the content.

Concepts

It's all about items, fields and blocks.

For a website you might want something called 'pages'. For an app that could be 'screens'.

You can define any number of item types you want, for instance if you're building a company website, that might be 'pages', 'projects' and 'people'.

Items are by default multiple but can also be singular, for instance if you want something called 'Footer', with fields for the footer content.

Items are populated with Fields, for instance a number field for project year, a relation field, tying related projects together, and perhaps a blocks field, for showing reusable content blocks on pages.

Blocks can be thought of as stackable lego bricks for content. They are reusable and can easily be pieced together to form page content. Most any design for a website or app can be divied into reusable components, if you will, so this what Blocks in Klay represents. Any defined blocks can be selected in a block field.

  • Item schemas: These define the types of items you want in your CMS, for instance you could have something called pages for a website, screens for an app or clients and jobs for a CRM/project management system.
  • Block schemas: If youre using one or more block fields, youll want a definition of what kind of blocks the user can build with. For instance, you could create Page-hero and Text-image blocks for a website.

Requirements

All you need is a basic PHP server with SQLite and the standard GD graphics library. In other words, a totally normal, basic PHP server that you can find for cheap anywhere.

  • PHP >= 7.0
    • GD image processing lib
  • SQLite >= 3.40.0

Any server running WordPress, Craft, Laravel, CodeIgniter will run Klay smoothly.

Install

Klay is one single file, always downloadable from here: Latest version.

Put Klay.php where you want it in your codebase, include/require it and run:
PHP
$klay = new Klay($config);
..

Download via Composer and CLI is on its way. If you're interested in this let me know.

Configuration

index.php
item_schemas.php
blocks.php
routes.php
functions.php
index.php
// Minimal setup
$klay = new Klay([
'base_url' => 'https://my-website.com',
'admin_url' => 'admin',
'item_schemas'=> require 'item_schemas.php',
'blocks'=> require 'blocks.php',
'routes'=> require 'routes.php',
'logins' => [ 
    ['username'=>'myuser', 'password'=>'mypassword', 'hint'=>'The password rhymes with chestnut.'] 
],
]);
..
item_schemas.php
// Item types here
$item_schemas = [];
return $item_schemas;
..
blocks.php
// Blocks here
$blocks = [];
return $blocks;
..
routes.php
// Routes here
$routes = [];
return $routes;
..
functions.php
// Functions here
function doSomething(){}
function doSomethingElse(){}
..
index.php
// Minimal setup
$klay = new Klay([
'base_url' => 'https://my-website.com',
'admin_url' => 'admin',
'logins' => [ 
    ['username'=>'myuser', 'password'=>'mypassword', 'hint'=>'The password rhymes with chestnut.'] 
],
]);
..
PHP
$klay = new Klay([ // all parameters
'base_url' => 'https://my-website.com', // (Required) the home URL of your website/app *without* a trailing /.    
'content_url' => 'https://my-website.com/content' // Full URL where your content is available. Typically same as base_url, but with /content appended.
'content_path' => 'content', // Default is 'content', but you may change this. Remember to change content_url as well.
'view_path' => '../app/view', // Path to where you might have view files to be rendered by routes/controllers.
'admin_url' => 'admin', // This is the URL by which you access the admin panel. Will typically by 'admin' or 'cms', but can be anything you want.
'show_admin' => TRUE, // If this is set to true, the admin will be shown, regardless of URL.
'logins' => [ 
    ['username'=>'myuser', 'password'=>'mypassword', 'hint'=>'The password rhymes with chestnut.'] 
], // Array containing values for `username`, `password` and possibly a login `hint`.
]);
..
  • login_disabled (boolean): If this is set to true, there will be no login check. This is handy if you are embedding Klay on top of an existing system that already has the user authenticated. Remember to only allow authenticated users the access, however.
  • multilingual (boolean): If this is defined and set to false, multilingual support will not be enabled.
  • page_title (string): This is simply the HTML title of the admin panel.
  • admin_logo (string): If you supply a SVG string here, the logo in the admin panel will be this svg. Useful if you want to brand the admin panel.
  • brand_color (string): If you supply a color here in a web-format, eg.
    PHP
    #ff0000
    ..
    or
    PHP
    red
    ..
    , then this will be used in the admin panel. Useful for branding things.
  • item_schemas (array): Item type/schema definitions. More info on this below.
  • blocks (array): Block type/schema definitions. More info on this below.
  • routes (array): URL routes and their logic handlers. More info on this below.
  • controllers (array): Route controllers. More info on this below.
  • filters (array): Filters modifying data at certain points. More info on this below.
  • events (array): Events handlers responding to certain events. More info on this below.
  • admin_css (string): Custom CSS used in the admin panel. Mostly useful for custom field, block and rich-text previews.
  • admin_js (string): Custom JavaScript used in the admin panel. Mostly useful for custom block and field previews.
  • admin_url:
  • master_login
  • username:
  • password:
  • hint:
  • admin_logo

Project structure

Klay is built for maximum flexibility. Have your files and folders organised the way you want them.

Some people like to have their item schemas, routes, controllers, etc. placed in separate files, for instance in an app folder, residing somewhere on the server, perhaps above the public webroot. You are totally free to do so and can simply include them directly into the Klay config object.

A typical setup could look like this:
/public/index.php       -> xxxxx
/app/Klay.php           -> xxxxx
/app/routes.php         -> xxxxx
/app/functions.php      -> xxxxx
/app/item_schemas.php   -> xxxxx
/app/blocks.php         -> xxxxx
/app/functions.php      -> xxxxx
..

Want it some other way? It's completely up to you and controllable in your entry point index.php file.

Methods

PHP
$klay->get('all')
..

All, type eg. page, with/without second uid param, if single ressource is wanted

Klay.php <- The admin interface (one-file, concatenated)

  • your site should load either Klay itself or something like that /content/files/ <- Here, uploads will be placed, mostly images /content/data/ <- Here, cms database content will be placed, json files, one big or many small. And post types, fields, blocks are also saved here

Database queries

PHP
$case = $klay->get('case', $uid);
..

Get a single item by type and uid.

PHP
$case = $klay->get_by_slug('case', $slug);
..

Get a single item by type and slug.

PHP
$case = $klay->get_by_slug($type, $slug, $lang = NULL);
..
PHP
$cases = $klay->get_where('case', [ 'published'=>1 ]);
..
PHP
$cases = $klay->db_get('case', [ 'where'=>[ ['published','=',1], ['uid','!=','whi52p',] ] ]);
..
PHP
$cases = $klay->db_query("SELECT * FROM case ORDER BY created_at DESC;");
..
PHP
$case = $klay->fetch_relations($case); // will fetch related items, eg. assets, etc. Very useful.
..
PHP
$item = $klay->get_where($type, $where = []);
..
PHP
$item = $klay->get_single_where($type, $where = []);
..
PHP
$items = $klay->items_filtered($items, $where = []);
..
PHP
$item = $klay->items_get_first($items);
..
PHP
$item = $klay->items_get_last($items);
..

Assets

PHP
$klay->get_asset_format( $uid );
..

Get the format, eg. jpg, png, mp4, .pdf of an asset. Can be any format imaginably, as assets are not only restricted to images.

PHP
$klay->get_asset_src( $uid_or_asset, $max_w = NULL, $desired_format = NULL );
..
By defining max_w you can ask Klay to generate that size thumbnail, and by defining desired format as eg jpg,png,webp it will generate the image in that format.
PHP
$klay->webp_or('jpg')
..
is super useful here, allowing you to serve webp images to users on modern browsers and jpg/png to everybody else.
PHP
$klay->get_random_asset();
..
PHP
$klay->get_random_asset_uid();
..

Item schemas

  • PHP defined or defined inside editor and saved onto its database.
  • Structure is an array of objects containing:
    • PHP
      name
      ..
      (string) (required): the keyword of the type
    • active
      ..
      (Boolean): if defined and set to false, Klay will ignore the item type
    • singular
      ..
      (Boolean): If set to true, the admin panel will not allow multiple items, but instead just show the one. Useful for something like globally relevant data, for instance if you want a view showing global fields for a website.
    • title_single (string): The admin panel title
    • title_plural (string): The admin panel title for plural
    • icon (string): The icon shown in the admin panel. Google font icons are used. For a calendar icon write simply
      PHP
      calendar
      ..
      as value.
    • custom_seeder (PHP function): You might want a custom seeder function
    • multilingual (boolean): If defined and set to false, the type will live across languages
    • fields (array of objects): List of fields to be shown for the item, each containing:
      • name
      • type
      • label
      • default
      • type (string) (required): The type of field, possible options are:
        • text
        • number
        • asset
        • relation
        • select
        • checkbox
        • range
        • checkbox
        • textarea
        • richtext
        • repeater
        • blocks
        • date
        • color
        • geolocation
      • preview: Useful if you need a rendered preview inside the admin panel.
      • group
      • show_if (string): Show the field conditionally, based on the function
JSON
[ { "name": "my_item_type", "singular": 1, "icon": "calendar", "fields": [], }, ] 
..
PHP
[ [ 'name' => 'my_item_type', 'singular' => TRUE, 'icon' => 'calendar', 'fields' => [], ], ] 
..

Block schemas

  • Much like item schemas, but instead pertains to blocks digested by the block field type.
  • Structure is an array of objects, each containing:
    • name (string)
    • fields (array of objects): See above field type list.

Field types

Text
Number
Asset
Richtext
Relation
Repeater
Blocks

Routing

When initialising Klay in your PHP, supply a parameter called
PHP
routes
..
containing an array of keys and functions with
PHP
($klay)
..
params. The keys represent URL routes, and the functions are called when that URL is called.
PHP
$routes['/'] = function($klay){ ... // this is the home URL route
$routes['route3/jeff'] = 'controller2';
$routes['route3/(:any)/jack'] = 'controller2'; // (:any), (:str) or (:num) can be used to pull parameteres in
$routes['POST::/onlypost'] = function($klay){ ... // optionally specify HTTP method
$routes['GET/POST::/onlygetorpost'] = function($klay){ ... // optionally specify one of multiple HTTP methods
$routes['404'] = function($klay){ ... // 404 page route
                
..
  • You can use
    PHP
    $klay->render_view('page')
    ..
    to render a view. The view should then be called
    PHP
    page.php
    ..
    and be placed in the folder defined by the
    PHP
    view_path
    ..
    parameter supplied to Klay.
  • Fetch URL params using
    PHP
    $klay->get_route_params(1)
    ..

Controllers

When initialising Klay in your PHP, supply a parameter called
routes
..
containing an array of keys and functions.
PHP
'page_single' => function($klay){ ...
..

You can then in your routes point to a controller:

PHP
$routes['page/(:any)'] = 'page_single';
..

Which is particularly nice if you have routes you want leading the the same outcome.

Filters

When initialising Klay in your PHP, supply a parameter called
PHP
filters
..
, which should be an array containing key => function pairs. The filter function has parameters
PHP
($klay, $itemtype, $item)
..
and should return
PHP
$item
..
, which you can modify before returning it. Possible events are
PHP
save_item
..
.

Events

When initialising Klay in your PHP you can also supply a parameter called
PHP
events
..
, containing an array of keys and callback functions. Functions have params
PHP
($klay,$itemtype, $item)
..
. Possible events are
PHP
on_item_saved
..
and
PHP
on_item_deleted
..
.

Cache

PHP
$klay->cache_put($key, $data);
..
PHP
$klay->cache_get($key);
..
PHP
$klay->cache_wipe($key);
..

HTML output cache

PHP
$klay->html_cache_put($url, $html);
..
PHP
$klay->html_cache_get($url);
..
PHP
$klay->html_cache_put($url, $html);
..
PHP
$klay->html_cache_wipe($url);
..
PHP
$klay->html_cache_wipe_from_slug($slug);
..
PHP
$klay->html_cache_wipe_all();
..

Functions

PHP
$klay->render_view('page');
..
PHP
$klay->base_url
..
PHP
$klay->render_cached_view('cases', compact('cases'));
..
PHP
$klay->output(404, [ 'API'=>'Hello world!' ]); // will make Klay output JSON with a HTTP code
..
PHP
$klay->get_active_route() // will return the active route
..
PHP
$klay->get_active_controller(); // will get the active controller in action
..
PHP
$klay->get_active_controller_or($str)
..
PHP
$klay->get_active_route_or($str)
..
PHP
$klay->is_active_route($str);
..
PHP
$klay->is_active_controller($str);
..
PHP
$klay->dd($data);
..
PHP
$klay->db_save_item($type,$data); 
..
PHP
$klay->db_delete_item($itemtype, $uid)
..
PHP
$klay->get_route_path();
..
PHP
$klay->get_route_params();
..
PHP
$klay->get_route_param($slot = 1);
..
PHP
$klay->is_route_path($path);
..
PHP
$klay->is_route_param($slot,$param);
..
PHP
$klay->slugify($str);
..
PHP
$klay->rand_from_array($arr);
..
PHP
$klay->merge_obj($a, $b);
..
PHP
$klay->str_contains(string $haystack, string $needle);
..
PHP
$klay->str_before($str,$x);
..
PHP
$klay->str_after($str,$x);
..
PHP
$klay->clamp($current, $min, $max);
..
PHP
$klay->generate_alphanumeric( $length = 6 );
..
PHP
$klay->generate_dummy_item($item_schema);
..
PHP
$klay->get_item_type($item_type_name);
..
PHP
$klay->item_type_is_singular($item_type_name);
..
PHP
$klay->trigger_webhook( $url, $data = [] );
..
PHP
$klay->trigger_webhook_manual_publish();
..
PHP
$klay->webp_or($alt_format);
..

Will check if webp is supported by the users browser and return the alt format instead if not.

Admin extending

  • admin.js richtext_modifiers
  • admin.js field_previews
  • admin.js block_previews
  • admin.js block_custom_btns

Updating Klay

You can always find the latest and earlier versions of Klay in this site. And then it's a small matter of replacing your existing Klay.php file. Remember to check for breaking changes.

Download via Composer and CLI is on its way. If you're interested in this let me know.