---
title: Löve
url: 'https://wiki.teletypegames.org/platforms/love'
markdown: 'https://wiki.teletypegames.org/platforms/love.md'
lang: en
date: '2026-03-03'
description: 'LÖVE (Love2D) is a lightweight framework for 2D games in Lua. Good for prototypes and small to medium games. Website: https://love2d.org Makefile A LÖVE game ships as a .love archive. The web build is that same archive bundled with love.js. Variables Variable Description Default PROJECT Nam…'
taxonomy:
  tag:
    - howto
---

# Löve

LÖVE (Love2D) is a lightweight framework for 2D games in Lua. Good for prototypes and small to medium games.

**Website:** https://love2d.org

---

# Makefile

A LÖVE game ships as a `.love` archive. The web build is that same archive bundled with `love.js`.

## Variables

| Variable | Description | Default |
|---|---|---|
| `PROJECT` | Name used in output filenames | `love2ddemo` |
| `BIN_DIR` | Build-time checks | `bin` |
| `DIST_DIR` | Packaged artifacts | `dist` |
| `WEB_DIR` | Web files | `dist/web` |
| `LOVE_NAME` | Generated .love file | `love2ddemo.love` |
| `VERSION_FILE` | Version file for CI | `.version` |

## Targets

```sh
make build                   # luac syntax check on every .lua file
make love                    # pack into dist/love2ddemo.love (skips .git, Makefile, dist/)
make web                     # download love.js, assemble dist/web/, patch index.html + player.js
make export VERSION=1.0.0     # both, zipped as love2ddemo-1.0.0.love.zip and -1.0.0.html.zip
make watch                   # rebuild on .lua change (needs fswatch)
make clean                   # remove bin/ and dist/
```

## VS Code

Copy `example-tasks.json` to `.vscode/tasks.json` as is. Tasks: **Run Löve** (default, `Cmd+Shift+B`), **Build & Run Löve**, **Build .love package**, **Make build**.

## Generated artifacts

| File | Description |
|---|---|
| `dist/love2ddemo.love` | LÖVE package |
| `love2ddemo-<version>.love.zip` | Zipped .love |
| `love2ddemo-<version>.html.zip` | Web build |
| `love2ddemo-<version>.metadata.json` | Metadata for distribution |
| `love2ddemo-<version>-win-x64.zip` | Fused Windows build (exe + dlls) |
| `love2ddemo-<version>-mac-universal.zip` | Fused universal `.app` |
| `love2ddemo-<version>-linux-x64.zip` | Fused AppImage |

## Requirements

Local only: `make`, `luac`, `zip`, `unzip`, `curl`, `sed`, and `fswatch` for watch mode. CI images bring their own tools.

---

# Pipeline

The pipeline is not in the game repo. Woodpecker asks the update server, and WarpEngine serves the whole config from `/build/config` (template `lib/warp_engine/ci/woodpecker/platforms/love/pipeline.yaml.erb`).

The repo needs three things:

```yaml
# .woodpecker.yaml
platform: love
name: love2ddemo   # only when the software name differs from the repo name
```

- `metadata.json`
- the `application_token` Woodpecker secret: a WarpEngine ApplicationToken with `update` + `upload` scopes

Builder images come from the update server's `ci_platforms` config. Preview the served pipeline:

```sh
curl "https://teletypegames.org/build/config?platform=love"
```

## Steps

| Step | What it does |
|---|---|
| 1. version | the engine's shared step (since WarpEngine 1.7): the version from `metadata.json`; on a tag a version tag has to match it; outside the repo's default branch and on a `dev-<branch>` tag `dev-<version>-<branch>`, with no version `dev-<commit>`; write `.version` |
| 2. build | in the `love-builder` image: pack the `.love`, build the `love.js` web bundle, patch the player and HTML with name + version, zip both |
| 3. binaries | fuse the `.love` into the official LÖVE release artifacts |
| 4. upload | **on the tag only:** copy `metadata.json` to `love2ddemo-<version>.metadata.json`, then `POST /build/upload` per file with the `X-Update-Secret` header |
| 5. publish | **on the tag only:** `POST /build/publish` with `name`, `platform`, `version` |
| 6. tag | the engine's shared step (since WarpEngine 1.9), **after a push only**: a new version in the source gets its `v<version>` tag, whose pipeline runs the steps marked *on the tag only*; a version tagged already releases nothing; a dev build moves the branch's `dev-<branch>` tag onto the commit, so the newest dev build is published. See *Versions and releases* on the [WarpEngine](https://wiki.teletypegames.org/engines/warp-engine) page |

Raising the version is the release: a push builds and tags, and the tag's pipeline uploads and publishes (WarpEngine 1.9). A push that leaves the version as it is only builds.

Fusing, per platform:

- **win-x64** — `love.exe` + `.love` concatenated, dlls included
- **mac-universal** — the `.love` inside the official universal `love.app`, `Info.plist` patched to the project name
- **linux-x64** — fused AppImage, rebuilt with `mksquashfs`. The AppImage runtime is glibc-linked, so it is never executed on the alpine builder; the squashfs offset comes from `readelf`.

The LÖVE dist files come from the image cache at `/opt/love-dist/`, with a curl fallback to GitHub.

```sh
curl -X POST -H "X-Update-Secret: <SECRET>" \
  "https://teletypegames.org/build/publish?name=love2ddemo&platform=love&version=1.0.0"
```

## Result

A web bundle and three fused desktop builds, all versioned, uploaded and published. The `.love` package is built into them; since WarpEngine 1.7 it is no longer uploaded on its own (no asset kind took it). The public game index updates itself.

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Platforms](https://wiki.teletypegames.org/platforms.md)
- Previous: [Godot](https://wiki.teletypegames.org/platforms/godot.md)
- Next: [Phaser](https://wiki.teletypegames.org/platforms/phaser.md)
