Skip to content

A plan can include a trial. While a workspace (called a tenant in the API) is trialing, its subscription status is "trial" and endsAt holds the trial end date, both live on the bridge surface (see How billing works).

The drop-in components already understand trials: <bridge-subscription-status /> shows the trialing status and <bridge-billing-notice /> surfaces an end-of-trial prompt as the date approaches. You can also read it yourself. Note that endsAt is optional on the snapshot, so guard it before formatting:

import { Component } from '@angular/core';
import { DatePipe } from '@angular/common';
import { BridgeService } from '@nebulr-group/bridge-angular';

@Component({
  selector: 'app-trial-line',
  standalone: true,
  imports: [DatePipe],
  template: `
    @if (subscription()?.status === 'trial') {
      @if (subscription()?.endsAt; as endsAt) {
        <p>Trial ends {{ endsAt | date }}</p>
      } @else {
        <p>Trial active</p>
      }
    }
  `,
})
export class TrialLineComponent {
  protected readonly subscription = this.bridge.tenant.subscription;

  constructor(private bridge: BridgeService) {}
}

For side effects (emails, analytics, nudges), handle the trial events on the unified dispatcher; see React to billing changes:

import { inject } from '@angular/core';
import { BridgeService } from '@nebulr-group/bridge-angular';

const bridge = inject(BridgeService);

bridge.events.handle({
  'subscription.trial_started':      (m) => analytics.track('trial_started', m),
  'subscription.trial_ending_soon':  (m) => sendReminder(m),
  'subscription.trial_converted':    (m) => analytics.track('trial_converted', m),
  'subscription.trial_expired':      (m) => analytics.track('trial_expired', m),
});

When a trial expires without converting, the workspace becomes billing-locked: the server sets gateEngaged: true on the subscription snapshot, the same lock mechanism described in How billing works. Gate your app with Require a plan so the workspace can pick a paid plan and get back in.