Starter Theme
The Føhn Starter Theme is a complete WordPress theme demonstrating all framework features. It's the fastest way to start a new Føhn project.
Quick Start
With DDEV (Recommended)
composer create-project studiometa/foehn-starter my-project
cd my-project
ddev startThat's it! DDEV will automatically:
- Start PHP 8.5 + MariaDB + nginx
- Create
.envfrom.env.example - Run
composer install(generatesweb/, symlinks, wp-config.php) - Install WordPress with admin/admin credentials
- Activate the starter theme
Open your site:
ddev launch # Frontend
ddev launch /wp/wp-admin # Admin (admin / admin)Without DDEV
composer create-project studiometa/foehn-starter my-project
cd my-project
cp .env.example .env
# Edit .env with your database credentials
composer installThen point your web server's document root to the web/ directory.
Project Structure
my-project/
├── theme/ # WordPress theme (versioned)
│ ├── app/
│ │ ├── Blocks/ # ACF & native blocks
│ │ ├── ContextProviders/ # Global context providers
│ │ ├── Controllers/ # Template controllers
│ │ ├── Data/ # DTOs for typed context
│ │ ├── Hooks/ # WordPress hooks
│ │ ├── ImageSizes/ # Custom image sizes
│ │ ├── Menus/ # Navigation menus
│ │ ├── Models/ # Custom post types
│ │ ├── Taxonomies/ # Custom taxonomies
│ │ └── foehn.config.php # Framework configuration
│ ├── templates/ # Twig templates
│ │ ├── blocks/ # Block templates
│ │ ├── components/ # Reusable components
│ │ ├── layouts/ # Base layouts
│ │ └── pages/ # Page templates
│ ├── functions.php # Single boot line
│ └── style.css # Theme header
│
├── web/ # Generated document root (gitignored)
│ ├── wp/ # WordPress core
│ ├── wp-content/ # Plugins, uploads
│ └── wp-config.php # Generated config
│
├── .ddev/ # DDEV configuration
├── .env # Environment variables
└── composer.json # DependenciesWhat's Included
Custom Post Types
Product (app/Models/Product.php)
#[AsPostType(
name: 'product',
singular: 'Produit',
plural: 'Produits',
public: true,
hasArchive: true,
menuIcon: 'dashicons-cart',
)]
final class Product extends TimberPost implements ConfiguresPostType
{
public function price(): ?float { /* ... */ }
public function formattedPrice(): string { /* ... */ }
public function isOnSale(): bool { /* ... */ }
}Testimonial (app/Models/Testimonial.php) — Customer reviews with ratings.
Custom Taxonomies
- ProductCategory — Hierarchical product categories
- ProductTag — Flat tags for products
Template Controllers
Controllers handle WordPress template hierarchy with dependency injection. The handle() method receives a typed TemplateContext object:
use Studiometa\Foehn\Views\TemplateContext;
#[AsTemplateController(['single', 'single-*'])]
final readonly class SingleController implements TemplateControllerInterface
{
public function __construct(
private ViewEngineInterface $view,
) {}
public function handle(TemplateContext $context): string
{
$post = $context->post; // Typed ?Post with IDE support
return $this->view->renderFirst([
"pages/single-{$post?->post_type}-{$post?->slug}",
"pages/single-{$post?->post_type}",
'pages/single',
], $context);
}
}Included controllers:
- SingleController — Single posts/pages
- ArchiveController — Archives, categories, tags, front page
- SearchController — Search results
- Error404Controller — 404 errors
Context Providers
Global context available on all templates:
use Studiometa\Foehn\Views\TemplateContext;
#[AsContextProvider('*')]
final class GlobalContextProvider implements ContextProviderInterface
{
public function provide(TemplateContext $context): TemplateContext
{
// Note: site, user, post, posts are already in TemplateContext
// Menus are auto-injected by MenuDiscovery
return $context
->with('current_year', date('Y'))
->with('is_home', is_front_page());
}
}Blocks
Hero Block (app/Blocks/HeroBlock.php) — Full-width banner demonstrating:
- ACF FieldsBuilder integration
- Built-in field fragments (
ButtonLinkBuilder) - Typed DTO context (
HeroContext) ImageDataandLinkDataDTOs
Menus & Image Sizes
- Menus: Header, Footer, Legal
- Image Sizes: Card (400×300), Hero (1920×800)
Security & Cleanup Hooks
Pre-configured in foehn.config.php:
return new FoehnConfig(
discoveryCacheStrategy: DiscoveryCacheStrategy::FULL,
hooks: [
CleanHeadTags::class, // Remove unnecessary <head> tags
DisableEmoji::class, // Remove emoji scripts/styles
DisableOembed::class, // Remove oEmbed discovery
DisableVersionDisclosure::class, // Hide WP version
DisableXmlRpc::class, // Disable XML-RPC
GenericLoginErrors::class, // Prevent username enumeration
YouTubeNoCookieHooks::class, // YouTube no-cookie embeds
],
);DDEV Commands
ddev start # Start the environment
ddev stop # Stop the environment
ddev restart # Restart after config changes
ddev launch # Open site in browser
ddev ssh # SSH into the container
ddev composer <cmd> # Run Composer commands
ddev wp <cmd> # Run WP-CLI commands
ddev describe # Show URLs and infoCustomizing the Starter
Rename the Theme
- Update
theme/style.csswith your theme name - Update
composer.jsonextra config:json"extra": { "foehn": { "theme-name": "your-theme-name" } } - Run
composer installto regenerate symlinks
Add Plugins
Add WordPress plugins via Composer using wpackagist:
ddev composer require wpackagist-plugin/advanced-custom-fields-proEnvironment Variables
The .env file controls database connection and environment:
DB_NAME=db
DB_USER=db
DB_PASSWORD=db
DB_HOST=db
WP_ENV=development
WP_DEBUG=true
WP_HOME=https://my-project.ddev.siteDeployment
For production:
- Set
WP_ENV=productionandWP_DEBUG=false - Discovery cache is already enabled (
DiscoveryCacheStrategy::FULL) - After deployment, warm the cache:bash
wp foehn discovery:warm
Next Steps
- Learn about Post Types to customize Product/Testimonial
- Add ACF Blocks for custom content blocks
- Configure Template Controllers for complex layouts
- Review Theme Conventions for best practices