Introducing a Simple CMS for Hype with JSON Schema

Hey Hype Users,

I've crafted a simple CMS that uses JSON Schema to create dynamic forms, with the output saved into a data.json file. The demo is live but with saving disabled to prevent any misuse and ensure the demo data remains clean.

Demo SimpleCMS
Password: test
JSON: is the result

Thinking ahead, I might externalize the JSON schema into its own file and introduce an option to write JSON directly into a selected HTML file to bypass the Fetch step.

I’d appreciate your thoughts on this... Feedback is welcome!

Cheers and happy holidays

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);

// Define a password for simplicity. This should be securely hashed in a real-world scenario.
define('PASSWORD', 'test');

// Check for logout action
if (isset($_GET['action']) && $_GET['action'] === 'logout') {

// Check if the user is logged in
$loggedIn = isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;

// Start of the form processing code
    // Check for login attempt
    if (!$loggedIn && isset($_POST['password'])) {
        if ($_POST['password'] === PASSWORD) {
            $_SESSION['logged_in'] = true;
            // Redirect to avoid resubmission on refresh
            header('Location: ' . $_SERVER['PHP_SELF']);
        } else {
            $error = "Incorrect password.";

     // Check for data submission when logged in
     if ($loggedIn && isset($_POST['jsonData'])) {
         // The form should submit a JSON string under the name 'jsonData'
         file_put_contents('data.json', $_POST['jsonData']);
         // Redirect back to the same page to avoid form resubmission issues
         header('Location: ' . $_SERVER['PHP_SELF']);


// Load data if available and user is logged in
$dataJson = $loggedIn && file_exists('data.json') ? json_decode(file_get_contents('data.json'), true) : null;

<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
    body { font-family: Arial, sans-serif; }
    .form-container { max-width: 750px; margin: auto; }
    #jsonForm h4 { margin: 40px 0px 0px 0px; }
    #jsonForm > .je-object__container > .je-object__title { margin-left: 15px; }
    .sticky-header { position: sticky; top: 0; background-color: white; padding: 10px 0px; border-bottom: 1px solid #ccc; z-index: 1000; }
    .button-container { display: grid; grid-template-columns: 1fr 1fr; }
    .left-btn { justify-self: start; }
    .right-btn { justify-self: end; }
    .submit-btn, .logout-btn { margin: 0 10px; }
<link rel="stylesheet" href="">
<link rel="stylesheet" href="">
<link rel="stylesheet" href="">
<script src=""></script>
<?php if (!$loggedIn): ?>
    <div class="form-container">
        <form id="loginForm" action="" method="post" class="form-horizontal">
        <div class="container grid-lg" style="margin-top: 50px;">
    <div class="columns">
        <!-- Centering the form on the grid -->
        <div class="column col-4 col-mx-auto">
            <form id="loginForm" action="" method="post">
                <div class="form-group">
                    <label for="password" class="form-label">Password:</label>
                    <input type="password" id="password" name="password" class="form-input" >
                <div class="form-group">
                    <!-- Using 'col-mx-auto' to center the button -->
                    <button type="submit" class="btn btn-primary btn-block">Login</button>

<?php else: ?>
    <div class="form-container">
    <form id="jsonForm" method="post" action="">
        <input type="hidden" name="jsonData" value="" />
        <!-- Sticky header container for the buttons -->
        <div class="sticky-header">
            <div class="button-container">
                <button type="submit" class="btn btn-sm submit-btn left-btn">Save</button>
                <button type="button" onclick="logout();" class="btn btn-sm logout-btn right-btn">Logout</button>
        document.addEventListener('DOMContentLoaded', function() {
            // Define the JSON Schema
            const schema = {
                "title": "Custom Data for Hype Reactive Content",
                "type": "object",
                "required": ["name", "age", "date", "favorite_color", "gender", "location", "pets", "file"],
                "properties": {
                    "name": { "type": "string", "description": "First and Last name", "minLength": 4, "default": "Jeremy Dorn" },
                    "age": { "type": "integer", "default": 25, "minimum": 18, "maximum": 99 },
                    "favorite_color": { "type": "string", "format": "color", "title": "favorite color", "default": "#ffa500" },
                    "gender": { "type": "string", "enum": ["male", "female"], "default": "male" },
                    "date": { "type": "string", "format": "date" },
                    "location": {
                        "type": "object",
                        "properties": {
                            "city": { "type": "string" },
                            "state": { "type": "string" },
                            "citystate": { "type": "string", "default": "San Francisco, CA" }
                        "required": ["city", "state"]
                    "file": {
                        "type": "string",
                        "title": "file",
                        "media": {
                            "binaryEncoding": "base64",
                            "type": "img/png"
                        "options": {
                            "grid_columns": 6,
                            "multiple": true,
                    "description": {
                        "type": "string",
                        "title": "Description",
                        "options": {
                            "grid_columns": 6

                    "pets": {
                        "type": "array",
                        "format": "table",
                        "items": {
                            "type": "object",
                            "properties": {
                                "type": { "type": "string" },
                                "name": { "type": "string" }
                            "required": ["type", "name"]


            // Initialize the editor with the schema
            const editor = new JSONEditor(document.getElementById('jsonForm'), {
                schema: schema,
                disable_edit_json: true,
                disable_properties: true,
                disable_collapse: true,
                display_required_only: false,
                theme: 'spectre',
                iconlib: 'spectre',
                startval: <?php echo $dataJson ? json_encode($dataJson) : '{}'; ?>

            document.getElementById('jsonForm').addEventListener('submit', function(event) {
                // JSON Editor instance
                const value = editor.getValue();
                const hiddenInput = document.querySelector('input[name="jsonData"]');
                hiddenInput.value = JSON.stringify(value);

            window.logout = function() {
                window.location = '?action=logout';

            // Add the saved data to the form if logged in
            <?php if ($dataJson): ?>
                editor.setValue(<?php echo json_encode($dataJson); ?>);
            <?php endif; ?>
<?php endif; ?>


It took a sec to see how this was related, but I think I get that it is a basic UI/front end such that the data can be used in a Hype document with the Hype Reactive Content extension. To that end, this is cool :slight_smile:.

My main feedback is the save button doesn't seem to work only in Safari when I have my adblocker (AdGuard) turned on?! I don't even get much information on what/why it is blocking it, just:

Safari can’t open the page “” because the server unexpectedly dropped the connection. This sometimes occurs when the server is busy. Wait for a few minutes, and then try again.

As I said … I disabled the save function on the demo.

Yes, or Hype Data Magic… or just anything that used custom data.

1 Like

People never read the docs :slight_smile:. Oddly the behavior is different with the adblocker.