A PHP-based static site generator that processes content files through an event-driven pipeline to produce deployment-ready static websites.
- Quick Start Guide - Get running in 5 minutes
- Configuration Guide - All configuration options
- Feature Development - Create custom features
- Bootstrap & Initialization - How bootstrap works
- Technical Documentation - Architecture details
- Design Documentation - Design decisions
Install StaticForge with a single command using Composer:
composer create-project eicc/staticforge my-site
cd my-siteThat's it! Your site is ready to use. StaticForge automatically:
- Creates a
.envconfiguration file - Sets up the
output/directory - Includes starter content and 4 pre-built templates
If you want to contribute to StaticForge development:
git clone https://github.com/calevans/staticforge.git my-site
cd my-site
composer install
cp .env.example .envYour StaticForge installation comes ready to use! Here's how to get started:
-
Edit your site configuration: Open
.envand customize your site name, tagline, and other settings. -
Generate your site:
php bin/console.php render:site
-
View your site: Open
output/index.htmlin your browser. -
Add more content: Create
.mdor.htmlfiles in thecontent/directory and regenerate.
StaticForge includes 4 pre-built templates. Switch between them by editing the TEMPLATE variable in .env:
- sample - Clean, minimal design (default)
- staticforce - Professional documentation theme
- terminal - Retro terminal-inspired design
- vaulttech - Fallout-inspired post-apocalyptic theme
You can delete unused templates from the templates/ directory to reduce clutter.
Create content/about.md:
---
title: About Us
menu: 2
---
# About Us
This is our about page!Then regenerate your site:
php bin/console.php render:siteGenerate and upload your site in two commands:
# Generate static files
php bin/console.php render:site
# Upload to your server via SFTP
php bin/console.php site:uploadSee Additional Commands for SFTP configuration details.
php bin/console.php render:siteOptions:
--clean- Remove output directory before generation
php bin/console.php site:uploadSee Additional Commands for SFTP configuration.
content/- Put your content files here (.html, .md, .pdf)templates/- Put your Twig template files hereoutput/- Generated static site appears here.env- Configuration file (copy from.env.example)
Content files support front matter in INI format within HTML comments:
<!-- INI
title: Page Title
template: index
date: 2025-10-27
category: blog
tags: php, static-site, generator
-->
<h1>Your content here</h1>
<p>This content will be processed and rendered using the specified template.</p>template: index→ Usestemplates/sample/index.html.twigtemplate: landing→ Usestemplates/sample/landing.html.twig- No template specified → Defaults to
templates/sample/base.html.twig
The template directory (sample) is configured via the TEMPLATE setting in .env.
Templates use Twig templating engine. Available variables:
{{ content }}- The processed content{{ title }}- Page title from front matter- Any custom variables from front matter
Each theme directory in templates/ should contain:
base.html.twig- Base template with site layoutindex.html.twig- Home page templatemenu1.html.twig- Primary navigation menumenu2.html.twig- Secondary/footer navigation menupartials/- Reusable template componentsplaceholder.jpg- (Optional) Default thumbnail for category index pages
The placeholder.jpg file is used by the CategoryIndex feature when generating category index pages.
When is it used?
When a page in a category doesn't have a hero image (first <img> tag in the content), the CategoryIndex feature will use placeholder.jpg as the thumbnail.
Specifications:
- Recommended size: 300x200 pixels (matches the thumbnail size)
- Format: JPEG
- Location:
templates/{theme_name}/placeholder.jpg
Image Handling Behavior:
-
If a category page has a hero image:
- Local images are resized to 300x200 and cached in
public/images/ - External images are downloaded, resized to 300x200, and cached in
public/images/cache/
- Local images are resized to 300x200 and cached in
-
If no hero image exists:
- CategoryIndex looks for
templates/{theme_name}/placeholder.jpg - If found, uses it as the thumbnail
- If not found, generates a gray 300x200 placeholder automatically
- CategoryIndex looks for
Customization:
Create a custom placeholder.jpg for your theme to match your site's aesthetic:
- Brand colors with your logo
- A "no image" icon in your theme style
- A terminal-style placeholder for terminal theme
The placeholder will be visible in category index pages whenever content doesn't include an image.
StaticForge includes a powerful menu builder that generates semantic HTML menus from your content files.
Add a menu: entry to your content's frontmatter:
Markdown files:
---
title: Home
menu: 1
---HTML files:
<!-- INI
title: Home
menu: 1
-->Simple menu items:
menu: 1 # First item in menu 1
menu: 2 # First item in menu 2Dropdown menus:
menu: 2.0 # Dropdown title (not clickable)
menu: 2.1 # First item in dropdown
menu: 2.2 # Second item in dropdownThree-level menus:
menu: 1.2.0 # Nested dropdown title
menu: 1.2.1 # First item in nested dropdown
menu: 1.2.2 # Second item in nested dropdownNote: Only 3 levels are supported.
Simple menu:
<ul class="menu menu-1">
<li class="menu-1">
<a href="/home.html">Home</a>
</li>
</ul>Dropdown menu:
<ul class="menu menu-2">
<li class="dropdown menu-2-0">
<span class="dropdown-title">Services</span>
<ul class="dropdown-menu menu-2-submenu">
<li class="menu-2-1">
<a href="/web-dev.html">Web Development</a>
</li>
<li class="menu-2-2">
<a href="/mobile.html">Mobile Apps</a>
</li>
</ul>
</li>
</ul>Top-level classes:
menu- Base class for all menusmenu-{N}- Specific menu number (e.g.,menu-1,menu-2)
Dropdown classes:
dropdown- Dropdown containermenu-{N}-{P}- Dropdown at position P in menu Ndropdown-title- Non-clickable dropdown labeldropdown-menu- Container for dropdown itemsmenu-{N}-submenu- Submenu within menu N
Item classes:
menu-{N}- Simple menu item in menu Nmenu-{N}-{P}- Item at position P within menu N
In PHP:
$features = $container->getVariable('features');
$menuHtml = $features['MenuBuilder']['html'][1]; // Get menu 1In Twig (future):
{{ features.MenuBuilder.html.1|raw }}The included templates (terminal, sample, vaulttech) already have full dropdown support. If you're creating a custom template, here's a minimal example:
/* Base menu styles - works in nav, footer, sidebar, anywhere */
.menu {
list-style: none;
padding: 0;
margin: 0;
display: flex;
gap: 20px;
}
/* Dropdown container */
.menu .dropdown {
position: relative;
}
/* Dropdown trigger (non-clickable title) */
.menu .dropdown-title {
cursor: pointer;
padding: 10px 15px;
display: inline-block;
}
/* Dropdown panel (hidden by default) */
.menu .dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: #fff;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
min-width: 200px;
z-index: 1000;
}
/* Show dropdown on hover */
.menu .dropdown:hover .dropdown-menu {
display: block;
}
/* Dropdown menu items */
.menu .dropdown-menu li {
display: block;
}
.menu .dropdown-menu a {
display: block;
padding: 10px 15px;
}Key points:
- Scope all dropdown styles to
.menuso they work anywhere - Use
.menu .dropdown:hover .dropdown-menufor hover functionality - Set
position: relativeon.dropdownandposition: absoluteon.dropdown-menu - Add
z-index: 1000to ensure dropdowns appear above other content
# Start development environment
lando start
# Run tests
lando phpunit
# Check code style
lando phpcs src/
# Fix code style
lando phpcbf
# Run CLI commands
lando php bin/console.php list- PHP 8.4+
- Twig templating engine
- Composer for dependency management
StaticForge uses an event-driven architecture with features that can be enabled/disabled:
- Core Application: Manages the processing pipeline
- Event Manager: Handles inter-feature communication
- Feature Manager: Loads and manages features
- File Discovery: Finds and categorizes content files
- HTML Renderer: Processes HTML content with Twig templates
See LICENSE file for details.
This section describes the available CLI commands for StaticForge.
Generate the complete static site from all content files.
Usage:
php bin/console.php render:site [options]Options:
-c, --clean- Clean output directory before generation-t, --template=TEMPLATE- Override the template theme (e.g., sample, terminal, vaulttech)-v, --verbose- Enable verbose output with detailed progress and statistics
Examples:
Basic site generation:
php bin/console.php render:siteClean build with verbose output:
php bin/console.php render:site --clean -vUse different template:
php bin/console.php render:site --template=vaulttechVerbose Output Includes:
- Configuration settings (content dir, output dir, template, etc.)
- Event pipeline steps
- Files processed count
- Active features list
- Generation time and performance metrics
All commands support these Symfony Console options:
-h, --help- Display help for the command-q, --quiet- Do not output any message-V, --version- Display application version--ansi|--no-ansi- Force (or disable) ANSI output-n, --no-interaction- Do not ask any interactive question-v|vv|vvv, --verbose- Increase verbosity (normal, verbose, debug)
Full site generation with clean output:
php bin/console.php render:site --clean -vStaticForge includes built-in SFTP upload for easy deployment:
# 1. Generate your site
php bin/console.php site:render --clean
# 2. Upload to production server
php bin/console.php site:uploadConfigure SFTP in your .env file:
SFTP_HOST="example.com"
SFTP_USERNAME="your-username"
SFTP_PASSWORD="your-password" # OR use SSH key
SFTP_REMOTE_PATH="/var/www/html"For detailed SFTP configuration and usage, see docs/ADDITIONAL_COMMANDS.md.
Use verbose mode to troubleshoot issues:
php bin/console.php render:site -vvv # Debug level verbosityrender:siteprocesses all files and runs all features (menus, tags, categories)- Verbose mode adds minimal overhead
- Average processing time shown in verbose output helps identify bottlenecks
0- Success1- Failure (check error messages and logs)
- Enable verbose for debugging:
-vshows what's happening - Clean builds: Use
--cleanwhen changing templates or structure - Template switching: Test different themes without modifying
.env
You can integrate these commands into your build pipeline:
package.json scripts:
{
"scripts": {
"build": "php bin/console.php render:site --clean",
"dev": "php bin/console.php render:site -v",
"deploy": "php bin/console.php site:upload"
}
}Makefile:
.PHONY: build dev deploy
build:
php bin/console.php render:site --clean
dev:
php bin/console.php render:site -v
deploy:
php bin/console.php render:site --clean && php bin/console.php site:uploadShell script:
#!/bin/bash
# deploy.sh
php bin/console.php site:render --clean
php bin/console.php site:uploadAlternative with rsync:
#!/bin/bash
# deploy-rsync.sh
php bin/console.php site:render --clean
if [ $? -eq 0 ]; then
rsync -avz public/ user@server:/var/www/html/
fi