On this page
EditlyCMS — Complete Documentation

EditlyCMS

A lightweight, flat-file CMS for editing HTML templates directly in the browser. No database setup required — just drop it next to your HTML files and start editing.

Version 1.1.0  ·  PHP 7.4+  ·  SQLite3  ·  No dependencies

Introduction

EditlyCMS is a flat-file content management system that lets you edit HTML pages directly in the browser. Unlike WordPress or other database-driven CMS platforms, EditlyCMS works directly with your existing HTML files — there is no database to configure, no hosting requirements beyond PHP, and no migration needed.

It works by scanning your website for HTML files that contain specially marked elements (cms-content), then letting you click and edit those elements through a floating toolbar injected into the page.

How it works

When you open a page in edit mode, EditlyCMS:

  1. Reads the live HTML file from disk
  2. Injects a floating toolbar at the bottom of the page
  3. Makes all cms-content elements directly editable (they show a dashed blue outline)
  4. When you save, writes your changes to a draft file (page.draft.html)
  5. When you publish, copies the draft over the live file and creates a backup

What EditlyCMS is good for

  • Static HTML sites that need occasional content updates
  • Client handoff — give clients a way to edit text and images without touching code
  • Landing pages, portfolio sites, restaurant menus, property listings
  • Any site where the developer builds the HTML and the client maintains the content

What it is not

  • Not a page builder — it cannot change layout or add new sections
  • Not a multi-user system — single account only
  • Not suitable for highly dynamic sites (e-commerce, user accounts, etc.)
i

Want to see it first? Open the live demo to try Editly CMS on a real site.

What's new in 1.1.0

Version 1.1.0 is a redesign of everything you see in the CMS. How your pages are found, saved, published and backed up has not changed.

  • Light and dark mode. The login screen, dashboard and editor follow your device's setting automatically.
  • A visual dashboard. Pages appear as cards with a live preview, a Draft tag, the time of the last edit and a status dot. Sites with more than four pages get a search box.
  • A compact editor toolbar. The everyday controls sit on one bar. Strikethrough, indent, alignment, undo and redo, video, map and file upload moved into a More menu, and Discard draft and Move toolbar into a ⋯ menu. On small screens the bar wraps onto two rows instead of running off the page.
  • The editor stays out of your site's way. Its styles are scoped to its own toolbar and dialogs, and the small ID labels above editable regions no longer pick up your site's heading styles.
  • Steadier text selection. Clicking a toolbar button no longer takes the selection away from the page, so bold, links and lists apply reliably.
  • Line icons instead of emoji on the toolbar, and plainer wording in dialogs.

Upgrading from 1.0.0

Replace cms/index.php and the three files in cms/views/. Nothing else changed, so your database, drafts, backups and uploads carry over as they are.

i

Your templates need no changes. If you build image galleries, see the note on the edit-mode image wrapper.

Installation

EditlyCMS requires a web server running PHP 7.4 or higher with the SQLite3 extension enabled. It works on shared hosting (InfinityFree, cPanel, etc.), VPS servers, and locally via WAMP, XAMPP, Laragon, or similar.

Requirements

RequirementDetails
PHP7.4 or higher
ExtensionsSQLite3, mbstring (usually enabled by default)
Writable folderscms/ directory must be writable by PHP
Your HTML filesMust be in the same folder as cms/ or in subfolders

Setup steps

01

Upload the cms/ folder

Place the cms/ folder in the root of your website — the same folder that contains your HTML files.

02

Set folder permissions

Make sure the cms/ folder is writable by the web server. On Linux/cPanel set it to 755 or 777. On shared hosting this is usually done via your FTP client or File Manager.

03

Visit /cms/ in your browser

Navigate to https://yoursite.com/cms/. The CMS will initialize itself on first run — it creates the SQLite database, uploads folder, and backups folder automatically.

04

Log in with default credentials

Username: admin   Password: admin   — change these immediately after first login.

!

Security: Change the default password immediately after installation. Anyone who knows the default credentials can edit your site. On shared hosting, also consider restricting access to /cms/ via .htaccess.

Folder structure after installation

your-website/
index.html ← your site files
about.html
images/
cms/ ← EditlyCMS lives here
index.php ← router & entry point
cms.db ← auto-created SQLite database
api/
Auth.php
Pages.php
Media.php
Database.php
views/
dashboard.php
edit.php
login.php
uploads/ ← auto-created, stores uploaded media
backups/ ← auto-created, stores HTML backups

First Login

Navigate to /cms/ on your site. You'll see the login screen.

FieldDefault value
Usernameadmin
Passwordadmin
!

Change both the username and password immediately after first login. Go to the dashboard and click Settings in the top right corner.

After logging in you'll land on the Dashboard which shows all editable pages found on your site.

Dashboard user

The dashboard is your home base. It automatically scans your website and shows every HTML file that contains editable regions, as a grid of page cards.

The Editly CMS dashboard showing five pages as cards with live previews
The dashboard. The home page comes first, and the Gallery card carries a Draft tag.

Page cards

Each card shows:

  • A live preview — a small picture of the page itself. Click it to edit. Previews show the published page without running scripts, so sliders and animations stay still.
  • Page title — taken from the HTML <title> tag. If every title ends with the same site name (for example About | My Site), that shared ending is hidden so the grid stays readable. Hover the title to see the full text.
  • File path — the relative path on your server, such as index.html.
  • Edit status — when the page was last edited ("Edited 2 days ago"), with a status dot: amber for unpublished changes, green for published, grey for a page that has not been edited yet.
  • A Draft tag over the preview when unpublished changes exist. The preview itself still shows the live version.

The home page (index.html) always comes first. The other pages follow in alphabetical order.

Search

Once your site has more than four pages, a search box appears above the grid. Type part of a title or file name to filter the cards.

Page actions

ButtonWhat it does
EditOpens the page in edit mode with the floating toolbar. Clicking the preview does the same.
ViewOpens the published version of the page in a new tab
Backups (3)Shows a list of previous versions you can restore. It appears once the page has at least one backup.

Settings panel

Click Settings in the top-right header to open the settings panel. It has two tabs:

  • Password — change your login password
  • Username — change your login username (requires current password)

Light and dark mode

The login screen, dashboard and editor follow your device's light or dark setting. There is no switch to flip: change the setting on your computer or phone and the CMS follows.

Editing Content user

Click Edit on any page card, or click its preview, to open the page in edit mode. The page loads normally in the browser with a floating toolbar at the bottom of the screen. You can move it to the top.

Identifying editable regions

Editable areas have a dashed blue outline. Hover over one and the outline turns solid, and a small label shows its ID. Click inside to start typing.

A page in edit mode with dashed outlines around editable regions and the toolbar at the bottom
Edit mode. The headline is hovered, so its outline is solid and its ID is shown.
i

Only elements marked by the developer as editable will show an outline. You cannot accidentally edit navigation, footers, or other structural elements unless the developer has marked them.

The toolbar

The toolbar is a single compact row. Everyday tools sit on the bar, less frequent ones are in the More menu, and rarely used actions are in the ⋯ menu. On a narrow screen the bar wraps onto a second row so nothing runs off the edge.

ControlActionShortcut
Back arrowReturn to the dashboard. If you have unsaved changes you are asked first.—
Style ▾Change paragraph style: paragraph, headings 1–4, quote or code block—
B I UBold, italic and underline the selected textCtrl+B / I / U
Colour swatchChange text colour. Select text first, then pick a colour.—
Bulleted and numbered listTurn the current paragraph into a list—
LinkInsert a hyperlink—
ImageInsert an image by upload or URL—
More ▾Open the More menu, described below—
SaveSave changes as a draftCtrl+S
PublishSave the draft and push it to the live site. A backup of the previous version is made first.—
⋯Open the menu with Move toolbar and Discard draft, described below—

The More menu

Click More to open a small menu above (or below) the toolbar. It closes when you pick something, click elsewhere or press Esc.

The More menu open above the toolbar, showing strikethrough, indent, alignment, undo and redo buttons and three embed options
The More menu.
ItemActionShortcut
StrikethroughStrike through the selected text—
Indent / OutdentIndent or outdent a list item or paragraph—
Align left / centre / rightAlign the current paragraph—
Undo / RedoStep back or forward through your editsCtrl+Z / Y
Embed videoEmbed a YouTube or Vimeo video—
Embed mapEmbed a Google Map—
Upload fileUpload a file and insert a download link—

The ⋯ menu

The ⋯ menu starts with the name of the file you are editing, followed by two actions:

  • Move toolbar to top / bottom — switch where the toolbar sits. The CMS remembers your choice in this browser.
  • Discard draft — delete the draft and return to the live version. You are asked to confirm.

Inserting a link

  1. Select the text you want to turn into a link
  2. Click the Link button
  3. Enter the URL and optionally choose to open in a new window
  4. Click Insert link
i

If you don't select text first, you can type the link text in the "Link text" field inside the dialog. The link will be inserted at your cursor position.

Embedding a video

  1. Click inside the editable region where you want the video
  2. Open More and choose Embed video
  3. Paste a YouTube or Vimeo URL (not an embed URL — just the regular page URL)
  4. Set width and height as needed
  5. Click Embed video

Embedding a Google Map

  1. In Google Maps, open the location you want
  2. Click Share → Embed a map and copy the src="..." URL from the iframe code
  3. In EditlyCMS, open More, choose Embed map and paste that URL
  4. Set width and height, then click Embed map

Pasting content

EditlyCMS strips all formatting when you paste — only plain text is inserted. This prevents messy styles from being copied in from Word, Google Docs, or other websites. If you need formatting, apply it manually using the toolbar after pasting.

Drag and drop images

You can drag an image file from your computer directly onto any editable region. EditlyCMS will upload it automatically and insert it at the cursor position.

Images & Media user

There are two types of image regions in EditlyCMS — inline images (images inside text content) and gallery/hero images (standalone image containers set up by the developer).

Inserting inline images

  1. Click inside a text region to place your cursor
  2. Click the Image button in the toolbar
  3. Either upload a file from your device or paste an image URL
  4. Add alt text (important for accessibility and SEO)
  5. Click Insert

Editing gallery / hero images

For image-only regions (like a photo gallery or hero background), hover over the image. You'll see two controls appear:

  • Red ✕ button (top-right) — removes the image
  • Blue resize handle (bottom-right) — drag to resize the image width

To replace a gallery image, delete it with the ✕ button, then click the Image button to insert a new one.

Uploaded files

All uploaded images and files are stored in cms/uploads/. They are accessible at /cms/uploads/filename.jpg. Files are never automatically deleted — manage them directly via FTP if you need to clean up.

!

Uploaded images are stored in the cms/uploads/ folder. If you delete the CMS folder, all uploaded media will be lost. Keep regular backups of this folder.

Draft & Publish user

EditlyCMS uses a draft → publish workflow. Changes you make are never pushed live immediately — they're saved as a draft first, letting you preview and review before going live.

The workflow

01

Make edits

Click and type in any highlighted editable region. An amber dot appears in the toolbar when there are unsaved changes.

02

Save as draft

Press Ctrl+S or click Save. Changes are written to a hidden draft file. The live site is untouched. You can close the browser and come back — the draft is preserved.

03

Preview (optional)

Go back to the dashboard and click View to see the live page. Then click Edit again — the editor loads the draft if one exists.

04

Publish

Click Publish in the toolbar. EditlyCMS saves the draft, backs up the current live file, then copies the draft to the live file. The page is now updated on your site.

Discarding a draft

If you want to throw away all unsaved changes and revert to the live version, open the ⋯ menu in the toolbar and choose Discard draft. You'll be asked to confirm. This action is permanent — the draft is deleted and cannot be recovered.

i

The Discard draft option only removes the draft — it does not affect the live site. To undo changes already published to the live site, use the Backups feature.

Unsaved changes warning

If you try to close the browser tab or navigate away with unsaved changes, EditlyCMS will warn you. It also shows a prompt if you click the back arrow with unsaved changes.

Backups user

Every time you publish a page, EditlyCMS automatically creates a backup of the previous live version. Backups are stored as timestamped HTML files in cms/backups/.

Viewing backups

On the dashboard, click Backups (n) on any page card. The number is how many backups the page has, and the button appears once there is at least one. You'll see a list of all saved versions with timestamps.

Restoring a backup

  1. Click Backups (n) on the page card
  2. Find the version you want to restore
  3. Click Restore — a confirmation dialog appears
  4. Confirm — the backup becomes the new live version (and the current live gets backed up first)
i

Restoring a backup automatically backs up the current live version first, so you can always undo a restoration.

Deleting backups

Old backups can be deleted from the backup list to save disk space. Click the Delete button next to any backup, or Delete all to clear every backup of that page at once. This is permanent and cannot be undone.

Template Basics developer

EditlyCMS finds editable pages by scanning all HTML files for the string cms-content. Any file that contains it will appear in the dashboard. Any element with the class cms-content and a unique id will be editable.

The two requirements

To make an element editable, it needs exactly two things:

  1. The class cms-content
  2. A unique id attribute
HTML
<!-- Correct — has both class and unique id -->
<h1 class="hero-title cms-content" id="hero-title">
  Welcome to Our Site
</h1>

<p class="cms-content" id="hero-subtitle">
  We make great things happen.
</p>

<!-- Wrong — missing id -->
<p class="cms-content">This won't be editable</p>

<!-- Wrong — missing class -->
<p id="hero-text">This won't be editable either</p>

Which HTML elements can be editable

ElementWorks?Notes
<p>YesBest choice for paragraphs
<h1>–<h6>YesBest for headings
<span>YesGood for inline text inside other elements
<div>YesGood for image containers (see Image Regions)
<a>YesEditable link text; href must be changed via the link modal
<li>YesIndividual list items
<img>LimitedVisible in dashboard but save/publish won't update it — use a div wrapper instead (see Image Regions)
<input>NoForm inputs cannot be made editable

Nesting editable elements

Avoid nesting cms-content elements inside each other. EditlyCMS saves the full innerHTML of the outermost editable element, which would overwrite any separately-saved child elements.

HTML
<!-- Avoid — nested cms-content will cause conflicts -->
<div class="cms-content" id="section-text">
  <p class="cms-content" id="para1">Text here</p>
</div>

<!-- Better — flat siblings -->
<div class="text-block">
  <p class="cms-content" id="para1">Text here</p>
  <p class="cms-content" id="para2">More text here</p>
</div>

cms-content Rules developer

IDs must be unique across the entire page

This is the most common source of bugs. The PHP save engine uses the id attribute to find the right element in the HTML file. If two elements share an ID, the engine finds the wrong one and either skips saving or saves to the wrong place.

!

Critical: Never use the same ID on a cms-content element and a section anchor. For example, if you have <section id="gallery">, do not also use id="gallery" on a cms-content element. Use id="gallery1" or id="gallery-img-1" instead.

HTML — Common mistake
<!-- BROKEN — two elements with id="gallery" -->
<section id="gallery">
  ...
  <div class="cms-content" id="gallery">  <!-- same id! -->
    <img src="photo.jpg" />
  </div>
</section>

<!-- FIXED — unique IDs -->
<section id="gallery">
  ...
  <div class="cms-content" id="gallery1">
    <img src="photo.jpg" />
  </div>
</section>

Recommended ID naming convention

Use descriptive, section-prefixed IDs so they're easy to identify in the dashboard:

HTML
<!-- Hero section -->
<p class="hero-eyebrow cms-content" id="hero-eyebrow">...</p>
<h1 class="hero-title cms-content" id="hero-title">...</h1>
<p class="hero-subtitle cms-content" id="hero-sub">...</p>

<!-- About section -->
<h2 class="cms-content" id="about-title">...</h2>
<p class="cms-content" id="about-p1">...</p>
<p class="cms-content" id="about-p2">...</p>

<!-- Amenities -->
<div class="cms-content" id="am1-title">...</div>
<div class="cms-content" id="am1-desc">...</div>

Special characters in HTML source

EditlyCMS uses PHP's DOMDocument to save changes. This is generally reliable but one known behavior: when EditlyCMS saves a file, non-ASCII characters in HTML comments (like ═, ★, →) used to get converted to numeric HTML entities like &#9552;. This has been fixed since v1.0.0 — the save engine decodes these back to UTF-8 characters automatically.

Image Regions developer

Making standalone images editable (gallery photos, hero images, etc.) requires a specific pattern. A plain <img class="cms-content"> will appear in the dashboard but won't save correctly because:

  1. The PHP regex that reads existing content requires a closing tag — <img> is self-closing and has none
  2. The CMS save engine uses innerHTML — a plain <img> has no innerHTML
  3. The image resize/delete buttons in the editor are injected into a parent element, not onto the img itself

The correct pattern

Wrap each image in a <div> with cms-content. The div is the editable container; the img lives inside it.

HTML
<!-- Correct — div wrapper with cms-content, img inside -->
<div class="img-cell cms-content" id="hero-photo">
  <img src="images/hero.jpg" alt="Hero image" />
</div>

<!-- Wrong — img directly as cms-content -->
<img class="cms-content" id="hero-photo" src="images/hero.jpg" />

Gallery grid pattern

When using CSS Grid for galleries, the wrapper div needs height:100% to fill its grid cell, and the image fills the div:

CSS
.gallery-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  grid-template-rows: 280px 280px;
  gap: 8px;
  /* Allow CMS edit buttons to overflow the grid boundary */
  overflow: visible;
  padding: 32px;
  margin: -32px;
}

.gallery-grid .img-cell {
  display: block;
  height: 100%;        /* fills the grid row height */
  overflow: hidden;    /* clips the image to the cell */
}

.gallery-grid .img-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
HTML
<div class="gallery-grid">
  <div class="img-cell span2 cms-content" id="gallery1">
    <img src="images/gallery1.jpg" alt="Main photo" />
  </div>
  <div class="img-cell cms-content" id="gallery2">
    <img src="images/gallery2.jpg" alt="Photo 2" />
  </div>
  <div class="img-cell cms-content" id="gallery3">
    <img src="images/gallery3.jpg" alt="Photo 3" />
  </div>
</div>
i

The padding: 32px; margin: -32px trick on the grid gives the CMS edit buttons (which are position:absolute inside each cell) room to appear outside the grid cell boundaries without affecting the visual layout.

The edit-mode wrapper

While you edit, the CMS wraps every image in a small <span class="cms-iw"> so it can show the delete and resize buttons. The wrapper is inline-block, so an image styled with width:100%; height:100% can shrink or collapse in edit mode even though the live page looks fine. Tell the wrapper to fill its cell:

CSS
.img-cell .cms-iw {
  display: block;
  width: 100%;
  height: 100%;
}

The wrapper exists only while editing. It is removed again when you save, so it never reaches your published HTML.

Common Gotchas developer

Files not appearing in the dashboard

EditlyCMS only lists files that contain the string cms-content. If a file doesn't appear:

  • Confirm at least one element has class="cms-content"
  • Confirm the file is .html, .htm, or .php
  • Confirm the file is not inside the cms/ subfolder
  • Confirm the file is not a .draft.html file

Edits save but don't appear on the live site

You've saved a draft but haven't published. Click Publish in the toolbar. A Draft tag on the page card in the dashboard means unpublished changes exist.

Saves appear to work but nothing changes

Almost always caused by a duplicate ID. Check your HTML — if a cms-content element shares an ID with any other element on the page (especially section anchors like <section id="about">), the save engine finds the wrong element and skips it. Rename the cms-content element's ID to something unique.

Gallery images are visible but edit/delete buttons don't appear

The edit buttons require the image to be a child of a data-cms-editable element. If cms-content is directly on the <img> tag, the buttons won't appear. Use the div wrapper pattern described in Image Regions.

Images are invisible in the dashboard

The dashboard uses a regex to extract content previews — this regex requires a closing tag. Self-closing <img> elements are invisible to it. Wrap images in a <div class="cms-content">.

CSS Grid images collapsing to zero height

When an image wrapper div is inside a CSS Grid and the img is position:absolute, the div collapses to zero height because it has no content in normal flow. Fix: add height:100% to the wrapper div so it fills its grid row.

Images look wrong only while editing

In edit mode the CMS wraps each image in a <span class="cms-iw">, which is inline-block by default. An image with width:100%; height:100% can shrink or collapse inside it even though the live page looks fine. Make the wrapper fill its cell, as described in Image Regions.

Images work locally but not on server

Check case sensitivity. Linux servers are case-sensitive — if your folder is named Images/ but your HTML references images/, the image won't load. Rename folders to lowercase. Also check for spaces in filenames — Windows sometimes adds trailing spaces during rename operations.

Comments in HTML source getting converted to hex codes

This is caused by PHP's DOMDocument encoding non-ASCII characters as numeric HTML entities when serializing. EditlyCMS handles this automatically — no action needed.

File Structure developer

cms/
index.php ← router: handles all actions, login, edit mode, dashboard
cms.db ← SQLite database (auto-created)
api/
Auth.php ← login, logout, change password/username
Pages.php ← list, save, publish, discard, backup, restore
Media.php ← image upload, file upload, delete media
Database.php ← SQLite wrapper with schema initialization
views/
login.php ← login page HTML+JS
dashboard.php ← dashboard HTML+JS
edit.php ← editor toolbar+JS injected into site pages
uploads/ ← all uploaded media files
backups/ ← timestamped HTML backup files

Draft files

When you save a draft of index.html, a file called index.draft.html is created in the same directory. Draft files are automatically excluded from the dashboard page list. They are deleted when you publish or discard.

Backup files

Backup files are stored in cms/backups/ with names like index_20250304_143022.html. The naming format is {safe-path}_{YYYYMMDD}_{HHmmss}.html.

Database schema

The SQLite database has two tables:

SQL
-- Tracks each discovered page
CREATE TABLE pages (
  id             INTEGER PRIMARY KEY AUTOINCREMENT,
  path           TEXT UNIQUE NOT NULL,   -- relative path e.g. "index.html"
  title          TEXT,                   -- extracted from <title> tag
  has_draft      INTEGER DEFAULT 0,
  last_edited    DATETIME,
  last_published DATETIME
);

-- Tracks backup files for each page
CREATE TABLE backups (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  page_path   TEXT NOT NULL,
  backup_file TEXT NOT NULL,             -- filename in cms/backups/
  created_at  DATETIME DEFAULT CURRENT_TIMESTAMP
);

PHP API Reference developer

All API endpoints are routed through cms/index.php via the ?action= query parameter. All responses are JSON.

Authentication endpoints

ActionMethodParametersDescription
loginPOSTusername, passwordStart a session
logoutPOST—Destroy session
change_passwordPOSTcurrent, newChange password (auth required)
change_usernamePOSTpassword, usernameChange username (auth required)
check_authGET—Returns {"authenticated": true/false}

Page endpoints

ActionMethodBodyDescription
pagesGET—List all CMS pages with metadata and backups
get_pageGET?path=Get current contents of a page
save_draftPOST{path, contents}Save changes to a draft file
publishPOST{path}Publish draft to live (creates backup first)
discardPOST{path}Delete draft, revert to live
restore_backupPOST{path, backup}Restore a backup as the live version
delete_backupPOST{path, backup}Permanently delete a backup file

Media endpoints

ActionMethodParametersDescription
upload_imagePOSTfile (multipart)Upload an image, returns {success, url}
upload_filePOSTfile (multipart)Upload any file, returns {success, url}
delete_mediaPOST{path}Delete an uploaded file from disk

The save_draft payload

The contents object is a map of element IDs to their new innerHTML:

JSON
{
  "path": "index.html",
  "contents": {
    "hero-title": "New headline text",
    "hero-sub": "Updated subheading here",
    "gallery1": "<img src=\"/cms/uploads/photo.jpg\" alt=\"New photo\" />"
  }
}

Key function: injectContents()

This is the core save logic in Pages.php. It reads the live HTML file, uses PHP's DOMDocument to find each element by ID, replaces its children with the new content, and serializes back to HTML. The key behaviors:

  • Finds elements using XPath: //*[@id="element-id"]
  • Verifies the found element has cms-content class before modifying
  • Strips contenteditable and data-cms-editable attributes before saving
  • Strips the injected <base href> tag
  • Decodes numeric HTML entities back to UTF-8 (fixes the ═ comment issue)

Change Password user

01

Open Settings

From the dashboard, click Settings in the top-right header.

02

Go to the Password tab

Click the Password tab inside the settings panel.

03

Enter current and new password

Fill in your current password and your desired new password, then click Update password.

Change Username user

01

Open Settings

From the dashboard, click Settings in the top-right header.

02

Go to the Username tab

Click the Username tab inside the settings panel.

03

Enter your password and new username

Your current password is required to change the username. Enter the new username and click Update username.