Patchlog
Patchlog
Back to blog
tutorial

How to Add a Changelog to Any Web App (In Under 10 Minutes)

A practical guide to adding a changelog widget to your web app, from scratch or using a hosted tool. No framework assumptions.

· 5 min read

Every web app should have a changelog. Not because it's required for launch, but because users who see regular updates churn less. They know the product is alive.

Here's how to add one, whether you want to build it yourself or use a hosted tool.


Option 1: The hosted widget (5 minutes)

If you want an embeddable, in-app widget, the thing that shows a little dot notification when there's a new update, you'll want a hosted tool. Building one from scratch that works reliably across different browsers, handles notification state, and looks good is more work than it sounds.

Patchlog offers this on its free plan. Here's how to add it:

Step 1: Create a project

Sign up at patchlog.io and create a project. Give it a name and slug (e.g., my-app).

Step 2: Get the embed snippet

From your project settings, copy the embed script. It looks like this:

<script
  src="https://patchlog.io/widget.js"
  data-project="YOUR_PROJECT_ID"
  defer
></script>

One thing worth being precise about, because it fails silently if you get it wrong: data-project takes the project ID, not the slug you chose in step 1. The slug is what appears in your hosted changelog URL; the ID is what the widget looks up. Copy the snippet from your project's widget settings page rather than typing it by hand and the right value is already filled in.

Step 3: Add it to your app

Paste it just before the closing </body> tag in your HTML. In React or Vue, you can add it to your main layout component or use useEffect/onMounted to inject it:

React:

import { useEffect } from 'react';

export function Layout({ children }) {
    useEffect(() => {
        const script = document.createElement('script');
        script.src = 'https://patchlog.io/widget.js';
        script.setAttribute('data-project', 'YOUR_PROJECT_ID');
        script.defer = true;
        document.body.appendChild(script);
        return () => document.body.removeChild(script);
    }, []);

    return <div>{children}</div>;
}

Vue 3:

<script setup>
import { onMounted, onUnmounted } from 'vue';

let script;
onMounted(() => {
    script = document.createElement('script');
    script.src = 'https://patchlog.io/widget.js';
    script.setAttribute('data-project', 'YOUR_PROJECT_ID');
    script.defer = true;
    document.body.appendChild(script);
});
onUnmounted(() => script?.remove());
</script>

Laravel Blade / plain HTML:

<script
    src="https://patchlog.io/widget.js"
    data-project="YOUR_PROJECT_ID"
    defer
></script>

Step 4: Post your first entry

Back in Patchlog, hit "New entry," write something short describing what you shipped, pick a category (Feature, Fix, Improvement), and publish.

The widget will show a notification dot the next time any user loads your app.

That's it.


Option 2: A dedicated /changelog page (no widget, 10 minutes)

If you don't want a widget, and would rather users found the changelog via search or you just want something simple, a static page is fine.

Using Markdown files (no database required)

Create a changelog/ directory in your project. Each entry is a Markdown file:

## 2025-03-08: Bulk order actions

You can now select multiple orders and mark them shipped in one click.
Previously you had to do this one at a time, which was terrible. Sorry.

**Also fixed:**
- Orders with special characters in the customer name no longer break the export
- Fixed a timezone bug where orders created near midnight showed the wrong date

Then serve them in your app. In Laravel:

Route::get('/changelog', function () {
    $files = collect(glob(resource_path('changelog/*.md')))
        ->sortByDesc(fn($f) => $f)
        ->map(fn($f) => Str::markdown(file_get_contents($f)));

    return view('changelog', ['entries' => $files]);
});

In Next.js:

import fs from 'fs';
import path from 'path';
import { marked } from 'marked';

export async function getStaticProps() {
    const dir = path.join(process.cwd(), 'changelog');
    const files = fs.readdirSync(dir).sort().reverse();
    const entries = files.map(file => {
        const raw = fs.readFileSync(path.join(dir, file), 'utf-8');
        return marked(raw);
    });

    return { props: { entries } };
}

The trade-offs

Building your own:

Hosted tool:


What to write in your first entry

Keep it short. A changelog entry is not a blog post.

Good format:

## Feature: Dashboard redesign

The main dashboard has been redesigned to load faster and show
the metrics you actually care about first.

Click "Dashboard" to see the new layout. Your old layout
settings are preserved.

What to avoid:


The 10-minute challenge

Here's the plan:

  1. Minute 1-2: Sign up for a changelog tool (or create your Markdown directory)
  2. Minute 3-5: Get the embed snippet and add it to your app layout
  3. Minute 6-8: Write your first changelog entry about the last thing you shipped
  4. Minute 9-10: Deploy

If you've been putting it off, this is the post to stop doing that. Your users are wondering if your product is still being maintained. Tell them it is.


Start your free Patchlog changelog →

Keep your customers in the loop

Patchlog gives your app an in-app changelog widget and a hosted updates page in minutes. One script tag, no SDK.

Start for free →

Free plan available. No credit card required.