Project Structure

How a tulip project is organized and how each file is processed.

Directory layout

my-site/
  tulip.toml        # site configuration
  layouts/          # shared layouts
    main.tulip
  pages/            # .tulip templates → HTML pages
    index.tulip
    about.tulip
    blog/
      post.tulip
  components/       # reusable components
    card.tulip
    nav.tulip
  content/          # markdown files with frontmatter
    blog/
      hello.md
  static/           # copied as-is to output
    css/
    images/
  plugins/          # JS plugins
    seo/
      plugin.toml
      index.ts

tulip.toml

The configuration file at the root of every tulip project. It defines the site name, description, and feature flags:

[site]
name = "My Site"
description = "A personal blog"

[minify]
enabled = true

All settings are optional. tulip works with just an empty tulip.toml file.

layouts/

Layouts define the outer HTML structure shared across pages. A layout uses @yield('content') to mark where page content is inserted:

<!DOCTYPE html>
<html>
<head>
    <title>{{ site.name }}</title>
</head>
<body>
    @yield('content')
</body>
</html>

Pages extend a layout with @extends('layouts/main') and fill sections with @section / @endsection. You can have multiple layouts for different page types — landing pages, blog posts, documentation, etc.

pages/

Every .tulip file in pages/ becomes an HTML file. The directory structure is preserved in the output:

  • pages/index.tulipdist/index.html
  • pages/about.tulipdist/about/index.html
  • pages/blog/post.tulipdist/blog/post/index.html
Clean URLs are enabled by default. about.tulip generates about/index.html so URLs look like /about/ instead of /about.html.

components/

Reusable template fragments that accept props and an optional slot. A component file defines a piece of markup:

<div class="card">
    <h3>{{ title }}</h3>
    {{ slot }}
</div>

Use it from any page or layout with the @component directive:

@component('card', { title: "Hello" })
    <p>Card body content goes here.</p>
@endcomponent

content/

Markdown files with YAML frontmatter. Each .md file is rendered to HTML using the layout specified in its frontmatter:

---
title: Hello World
layout: layouts/main
---

# Hello World

This is rendered as HTML inside the layout.

static/

Everything in static/ is copied directly to dist/ without any processing. Use it for:

  • CSS files (when not using Tailwind)
  • Images, fonts, and favicons
  • JavaScript files
  • Any other assets that should be served as-is

plugins/

JavaScript plugins that extend the build pipeline. Each plugin lives in its own directory with a plugin.toml manifest and an index.ts (or index.js) entry point:

plugins/seo/
  plugin.toml     # name, version, description, hooks
  index.ts        # plugin logic

Plugins are executed by tulip's built-in JavaScript engine — no Node.js needed. Manage plugins with the CLI:

  • tulip plugin new <name> — scaffold a new plugin
  • tulip plugin add <github-url> — install from GitHub
  • tulip plugin remove <name> — uninstall a plugin
  • tulip plugin list — show installed plugins