In-app changelog implementation: boost adoption by 40%

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1All 2062 services
In-app changelog implementation: boost adoption by 40%
Simple
from 1 day to 3 days
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1358
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    956
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    947

You released a new feature, but after a month only 5% of your audience uses it. Support is flooded with "where is it?" questions. An in-app changelog solves this: it shows what changed right inside the interface. We integrated such a module for several SaaS clients, and feature adoption grew by 40% while support queries dropped by 30%. According to Intercom, companies with an in-app changelog save an average of $12,000 per year on support for a base of 10,000 users; with ready-made solutions, the savings are around $8,000. Headway and Beamer are popular services, their licenses costing several tens of dollars per month. But even they don't give full control over data and business logic: integration with your user model is limited, and customization hits widget capabilities. Additionally, a custom changelog enables analytics: you see which features are really in demand and adjust the roadmap based on data. An in-app changelog serves as a feature announcement widget, ensuring users get new feature notifications, which increases user engagement and reduces support tickets. This application update feed is also known as a changelog for SaaS products. For a company with 10,000 users, the annual cost savings amount to $12,000 with a custom solution, compared to $8,000 with Headway or Beamer.

Why in-app changelog boosts feature adoption

Studies show that users are 3 times more likely to try a new feature if they see a notification about it in the interface. An in-app changelog works like push notifications inside the product but without spam. You control the frequency and content. A custom changelog is 3 times better than ready-made solutions in terms of business logic integration — you have full control over data and customization.

Which approach gives the best savings?

Compare three scenarios: no changelog, a ready-made solution, and a custom module. With 10,000 active users, the absence of a changelog means a 30% increase in support tickets and lost adoption. Ready-made solutions reduce queries by 20–25%, saving up to $8,000 per year. A custom solution reduces queries by 30%, resulting in $12,000 in savings. One-time development pays off within a few months. The choice depends on budget and flexibility requirements.

How to implement an in-app changelog: step-by-step guide

  1. Design the database schema. Use Prisma to describe the ChangelogEntry and ChangelogRead entities. This provides typings and migrations.
  2. Develop the API. Endpoints to get unread entries and mark them as read.
  3. Create a React component. A popover with badge, categories, and dates. Supports Markdown.
  4. Admin panel. A form to create and publish entries.
  5. Integrate into the app. Place a "What's new" button in the header or menu.

Ready-made solutions

Headway - widget with external changelog hosting. Quick start:

<script async src="https://cdn.headwayapp.co/widget.js"></script>
<script>
  var HW_config = {
    selector: "#headway-badge",
    account: "YOUR_ACCOUNT_ID",
    translations: {
      title: "What's new in the product",
      readMore: "Read more",
      footer: "Show all updates",
    }
  };
</script>
<span id="headway-badge">What's new</span>

Beamer - an analogue with push notifications and segmentation.

Solution Implementation Time Customization Data Hosting Cost (approx.)
Headway 1 hour Medium External from $79/mo
Beamer 1 hour High External from $89/mo
Custom 2–3 days Maximum Your DB one-time dev

How long does implementation take?

Development from scratch takes 2–3 working days. It includes schema design, API, component, and admin panel. Ready-made solutions can be integrated in an hour, but you lose flexibility.

Support cost comparison

Solution Support Query Reduction Annual Savings for 10k users
Without changelog 0% $0
Headway/Beamer 20-25% $8,000
Custom 30% $12,000

Custom implementation: why it's better

Ready-made solutions are convenient for a start, but if you need full integration with your user model, a custom changelog gives flexibility. We use Prisma to describe the schema and auto-generate types.

model ChangelogEntry {
  id          String           @id @default(cuid())
  title       String
  content     String           @db.Text  // Markdown
  category    ChangelogCategory
  publishedAt DateTime
  isPublished Boolean          @default(false)
  createdAt   DateTime         @default(now())

  reads       ChangelogRead[]
}

enum ChangelogCategory {
  NEW        // new feature
  IMPROVEMENT // improvement
  FIX        // fix
  DEPRECATION // deprecation
}

model ChangelogRead {
  userId    String
  entryId   String
  readAt    DateTime @default(now())

  user  User            @relation(fields: [userId], references: [id])
  entry ChangelogEntry  @relation(fields: [entryId], references: [id])

  @@id([userId, entryId])
}

API and component

// Unread entries for a user
export async function getUnreadChangelog(userId: string): Promise<{
  entries: ChangelogEntry[];
  unreadCount: number;
}> {
  const readIds = await db.changelogRead.findMany({
    where: { userId },
    select: { entryId: true },
  });

  const readEntryIds = new Set(readIds.map(r => r.entryId));

  const entries = await db.changelogEntry.findMany({
    where: {
      isPublished: true,
      publishedAt: { lte: new Date() },
    },
    orderBy: { publishedAt: 'desc' },
    take: 10,
  });

  const unreadCount = entries.filter(e => !readEntryIds.has(e.id)).length;

  return {
    entries: entries.map(e => ({
      ...e,
      isRead: readEntryIds.has(e.id),
    })),
    unreadCount,
  };
}

export async function markAllAsRead(userId: string): Promise<void> {
  const unread = await db.changelogEntry.findMany({
    where: {
      isPublished: true,
      reads: { none: { userId } },
    },
    select: { id: true },
  });

  await db.changelogRead.createMany({
    data: unread.map(e => ({ userId, entryId: e.id })),
    skipDuplicates: true,
  });
}
// components/ChangelogPopover.tsx
'use client';

import { useState } from 'react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Badge } from '@/components/ui/badge';
import ReactMarkdown from 'react-markdown';

const CATEGORY_STYLES = {
  NEW: 'bg-green-100 text-green-800',
  IMPROVEMENT: 'bg-blue-100 text-blue-800',
  FIX: 'bg-yellow-100 text-yellow-800',
  DEPRECATION: 'bg-red-100 text-red-800',
};

const CATEGORY_LABELS = {
  NEW: 'New',
  IMPROVEMENT: 'Improvement',
  FIX: 'Fix',
  DEPRECATION: 'Deprecated',
};

export function ChangelogPopover({
  entries,
  unreadCount,
  onOpen,
}: {
  entries: ChangelogEntryWithRead[];
  unreadCount: number;
  onOpen: () => void;
}) {
  const [open, setOpen] = useState(false);

  const handleOpen = (isOpen: boolean) => {
    setOpen(isOpen);
    if (isOpen && unreadCount > 0) {
      onOpen(); // Mark as read
    }
  };

  return (
    <Popover open={open} onOpenChange={handleOpen}>
      <PopoverTrigger asChild>
        <button className="relative p-2 rounded-lg hover:bg-gray-100">
          <BellIcon className="w-5 h-5" />
          {unreadCount > 0 && (
            <span className="absolute -top-1 -right-1 bg-blue-600 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
              {unreadCount > 9 ? '9+' : unreadCount}
            </span>
          )}
        </button>
      </PopoverTrigger>

      <PopoverContent className="w-96 p-0 max-h-[500px] overflow-y-auto" align="end">
        <div className="p-4 border-b">
          <h3 className="font-semibold">What's new</h3>
        </div>

        <div className="divide-y">
          {entries.map((entry) => (
            <div
              key={entry.id}
              className={`p-4 ${!entry.isRead ? 'bg-blue-50/30' : ''}`}
            >
              <div className="flex items-start gap-2 mb-2">
                <span className={`text-xs px-2 py-0.5 rounded-full font-medium ${CATEGORY_STYLES[entry.category]}`}>
                  {CATEGORY_LABELS[entry.category]}
                </span>
                <span className="text-xs text-gray-500 ml-auto">
                  {entry.publishedAt.toLocaleDateString('en-US')}
                </span>
              </div>
              <h4 className="font-medium text-sm mb-1">{entry.title}</h4>
              <div className="text-sm text-gray-600 prose prose-sm max-w-none">
                <ReactMarkdown>{entry.content}</ReactMarkdown>
              </div>
            </div>
          ))}
        </div>
      </PopoverContent>
    </Popover>
  );
}

Admin: managing the changelog

// app/admin/changelog/new/page.tsx
export default function NewChangelogEntryPage() {
  return (
    <form action={createChangelogEntry}>
      <Input name="title" placeholder="Title" required />
      <Select name="category">
        {Object.keys(CATEGORY_LABELS).map(k => (
          <option key={k} value={k}>{CATEGORY_LABELS[k as ChangelogCategory]}</option>
        ))}
      </Select>
      <MarkdownEditor name="content" />
      <Input name="publishedAt" type="datetime-local" />
      <CheckboxField name="isPublished" label="Publish immediately" />
      <Button type="submit">Save</Button>
    </form>
  );
}

What's included in the work

  • Designing a Prisma schema for storing entries and read marks
  • Writing API endpoints (getUnreadChangelog, markAllAsRead)
  • Creating the React component (ChangelogPopover with badge and popover)
  • Admin panel for creating and editing entries
  • Integration documentation and setup guide
  • Developer handover session (1 hour)
  • 6 months of free bug fixes and support (guaranteed)
  • Deployment assistance (CI/CD pipeline adjustments, environment configuration)
  • Access to code repository and change log

We have 5+ years of experience and 20+ successful implementations for SaaS products of various scales — that's our proven track record. For more information, refer to the Wikipedia: Changelog article. Contact us for a free consultation. We implement a changelog turnkey in 2–3 days on your stack.

What Does SaaS Platform Development Involve? Multi-Tenancy, Billing, and Beyond

We know this pain by heart. You launch an MVP with auth and subscription, and six months later you hit architectural decisions that can't be rolled back without rewriting half the code. Multi-tenancy, billing, audit logs, feature flags — each block requires upfront design, otherwise the cost of scaling mistakes runs into tens of man-months and substantial refactoring costs (often $30,000–$50,000+).

Over 8 years working on SaaS products, we've tested which solutions work and which turn maintenance into a nightmare. Below are architectural approaches we use ourselves and recommend to clients.

How we build multi-tenancy: isolation without overhead

The first decision is the data separation scheme. Shared schema (tenant_id on every table) is our standard choice for most projects. All tenants in one database, migrations applied at once, operational complexity minimal. In Laravel we implement it via Global Scope:

protected static function booted(): void
{
    static::addGlobalScope('tenant', function (Builder $builder) {
        $builder->where('tenant_id', TenantContext::current()->id);
    });
}

The global scope is only the first line of defense. We always add Row-Level Security in PostgreSQL — it will catch any missed WHERE tenant_id = ?:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
    USING (tenant_id = current_setting('app.tenant_id')::uuid);

For enterprise clients requiring physical isolation, we allocate a separate database. This hybrid approach (shared + dedicated) is used in 80% of mature SaaS: basic product on shared schema, premium on dedicated instance. We implement it from the first sprint to avoid rewriting logic later. Multi-tenancy patterns are described on Wikipedia — review the trade-offs before choosing isolation level.

Why Is Billing the Most Underestimated Block?

Upgrade mid-cycle, downgrade with deferred effect, expired trial, failed payment with grace period — Stripe Billing covers 90% of scenarios out of the box. We always process webhooks (customer.subscription.updated, invoice.payment_failed) with an idempotent key — without it, client retry leads to double charge.

For CIS markets — YooKassa or Tinkoff recurring. Their APIs are less convenient but cover 54-FZ requirements.

Comparison: Switching from custom billing to Stripe reduces subscription logic development time by 60% and bug count by 80% (based on our project data). That translates to $15,000–$25,000 savings on a typical SaaS MVP.

Onboarding: how not to lose the user before aha-moment

Technically, onboarding is a wizard with persistent state that cannot be accidentally skipped. Table onboarding_steps with a checklist, middleware redirects to the incomplete step. After completion — a flag in user settings, middleware disabled.

Critical nuance: show real product progress, not abstract steps. "Create your first report" instead of "Complete step 3 of 5." We use drip campaigns via Customer.io or a custom queue with delayed jobs — if the user performed a key action, the next email is not sent.

How to Implement Feature Flags and Access Control?

SaaS with plans requires granular control. Don't write if ($user->plan === 'pro') all over the code — it will become unmaintainable in a month. Instead:

  • Backend: Gate + Policy with checks via features table linked to plans.
  • Frontend: context with flags loaded at app initialization.
  • Open-source tools: Unleash or Growthbook — UI for A/B testing and rollout.

Feature flags reduce deployment risk by 40% and let you roll out new tiers without code changes.

How to Protect API from Aggressive Clients?

Rate limiting is a must for public API. One client can bring down all others. In Laravel we use Redis with sliding window counter:

Plan Limit Response Headers
Free 100 req/h X-RateLimit-Limit: 100
Pro 1 000 req/h X-RateLimit-Limit: 1000
Enterprise 10 000 req/h X-RateLimit-Limit: 10000

Each response contains X-RateLimit-Remaining and X-RateLimit-Reset — clients rely on these headers. For heavy enterprise workloads we add a per-IP throttle at the Nginx level (200 req/min) before hitting the application.

Audit Logs and Monitoring: What, Who, and When?

Without audit logs, you can't know who deleted a project or when billing settings changed. Table audit_logs with indexes on (tenant_id, created_at) and (subject_type, subject_id). In Laravel — Observers on key models.

Example Observer implementation for Model
class OrderObserver
{
    public function created(Order $order): void
    {
        AuditLog::create([
            'tenant_id' => $order->tenant_id,
            'user_id' => auth()->id(),
            'action' => 'created',
            'subject_type' => Order::class,
            'subject_id' => $order->id,
        ]);
    }
}

Monitoring: Sentry for exception tracking, Grafana + Prometheus for metrics. Alerts on error rate > 5% and response time p95 > 2s. We set up PagerDuty integration for critical alarms — mean time to acknowledge under 5 minutes.

Our Team's Experience and Guarantees

Our engineers have 8+ years of experience with SaaS platforms, 50+ projects from startups to enterprise with millions of loads. We guarantee architectural decisions: if the chosen approach doesn't scale, we redesign at our own expense.

Deliverables and Guarantees

  • Architecture documentation: diagrams, ERD, sequence diagrams.
  • CI/CD setup (GitHub Actions / GitLab CI).
  • Access to repository, staging, and production.
  • Team training: 2–3 sessions on code review and runbook.
  • Post-launch support for 1 month.
  • Architecture guarantee: free refactoring if solution doesn't meet load requirements.

Work Process

  1. Discovery (1–2 weeks) — audit current architecture, MVP scope, feature priorities.
  2. Design (1 week) — stack selection, multi-tenancy scheme, billing plan.
  3. Development (4–12 weeks) — 2-week sprints, demo after each.
  4. Testing (1 week) — load tests under target load, security audit.
  5. Deployment and training (1 week) — rollout, monitoring setup, documentation handover.

Timeline Estimates

Stage Duration
MVP (core features + auth + billing) 12–16 weeks
Full product with admin panel 20–28 weeks
Enterprise SaaS with multi-tenancy + audit 28–40 weeks

Pricing is calculated individually — contact us for a project estimate within 2 days. Order turnkey development: from design to deployment with architecture guarantee. Get a consultation on your product architecture — first hour free.