Skip to the content.

Table of contents

Cozy Banners Doctype

io.cozy.banners

The io.cozy.banners doctype holds the platform messages to display to the user, one document per banner: storage nearly full, subscription or trial state, an action required on the account, a maintenance announcement.

Documents are written by the stack when the state that drives them changes, so the applications never evaluate anything: they read the documents, sort them by priority, drop the ones outside their validity window or already dismissed, and render what is left. The text is stored already localized, so a client displays it as it is.

The only field a client writes is dismissedAt. Because the documents live in the user’s own database, a banner dismissed in one application is dismissed in all of them.

A banner describes the instance owner’s own state, so clients do not read or render one in a context where the reader is not the owner, such as a share by link or a preview.

Fields

title, cta, secondaryCta, dismissedAt and endsAt are the only fields that may be missing, and for them a null value and an absent key mean the same thing. A client checks for both, including before reading cta.label or cta.url. Every other field is always present.

Dates

startsAt, endsAt, dismissedAt and source.at are ISO 8601 timestamps with an explicit UTC offset and at least second precision, as in 2026-07-22T09:14:02Z. The date only form that the generic model also allows is not used here, since these values are compared against a clock rather than displayed. The validity window is evaluated against the client’s clock.

A client null checks a date before building one from it, because new Date(null) is the Unix epoch rather than “no date”, which would mark every open ended banner expired and every active banner dismissed.

Lifecycle

Versioning

The shape of the document is versioned with cozyMetadata.doctypeVersion, like every other doctype in the generic model. It is a string, as it is for every doctype the stack writes.

Adding a field, a category, a severity or a surface keeps the version: a client renders a value it does not know rather than dropping the banner, so a new one needs no client release. Renaming or removing a field bumps it.

Contract vectors

fixtures/io.cozy.banners.json holds the cases every client is expected to reproduce: ordering and its tie-break, the validity window bounds, dismissal, the fallbacks for an unknown severity or surface, the call to action scheme check, a secondary call to action, and the version filter. Each case gives input documents and the expected list a client displays, so two implementations can be checked against the same data rather than against each other.

Example

{
  "_id": "e21dce8058b9013d800a18c04daba326",
  "_rev": "2-a1b2c3",
  "cozyMetadata": {
    "createdAt": "2026-07-22T09:14:02Z",
    "createdByApp": "stack",
    "doctypeVersion": "1",
    "metadataVersion": 1,
    "updatedAt": "2026-07-22T09:14:02Z"
  },
  "bannerId": "quota.exceeded:2026-07-22T09:14:02Z",
  "title": null,
  "category": "quota",
  "severity": "error",
  "surface": "banner",
  "text": "You have reached your storage limit.",
  "lang": "en",
  "cta": {
    "label": "Upgrade",
    "url": "https://jdoe-settings.example.org/#/subscription"
  },
  "dismissible": false,
  "dismissedAt": null,
  "priority": 100,
  "startsAt": "2026-07-22T00:00:00Z",
  "endsAt": null,
  "source": {
    "trigger": "usage.threshold.crossed",
    "at": "2026-07-22T09:14:02Z"
  }
}

Metadata

See the cozyMetadata documentation for the attributes common to every doctype. The ones a banner always carries are: