w6w docs

RFC: App

Status: Final Author: Segev Shmueli Date: 2026-04-15 (revised 2026-06-01)

Summary

An App is the unit of integration on the platform. This RFC defines the App manifest — a declarative, format-agnostic description that any publisher (first-party or third-party) can author to register their service. The manifest is the source of truth for an app’s identity, presentation, and provenance.

Motivation

Integration platforms today each invent their own manifest shape — Slack apps, Zapier apps, n8n nodes, Shopify apps, VS Code extensions, Chrome extensions — all describe roughly the same thing in incompatible ways. We want a single, open specification that:

  • Lets a publisher describe an app once and have it understood by any compliant host.
  • Is portable across serialization formats (JSON, YAML, XML, TOML). The manifest is a logical model; the file format is a detail.
  • Borrows the best parts of established ecosystems (npm, Chrome extensions, Apple/Google app stores, Slack manifest, JetBrains plugin descriptors).
  • Covers metadata only. Behavior (Actions, Triggers, Webhooks, Auth) will be defined in later RFCs and wired into this manifest once their shapes are settled.

Goals

  • Describe an app’s identity, presentation, and provenance completely in a single manifest.
  • Globally unique identity via reverse-DNS IDs (com.example.app-name).
  • Serialization-agnostic — the same logical manifest round-trips losslessly through JSON, YAML, or XML.
  • Forward-compatible via an explicit manifestVersion.
  • Localization-ready from day one.
  • Third-party friendly — nothing here requires platform insider knowledge.

Non-Goals

  • Defining Actions, Triggers, Webhooks, or Auth schemas (separate RFCs).
  • Specifying a runtime execution model.
  • Mandating hosting, distribution, or signing mechanics (may be layered on later).

Concept

An App represents any service the platform can integrate with — Slack, Stripe, Postgres, an internal CRM, a hardware device. The manifest makes an app:

  • Discoverable — categories, keywords, description, icon, and screenshots enable marketplace search and browse.
  • Trustworthy — publisher, license, privacy policy, and support channels give operators what they need to adopt it.

Every App has a stable, globally unique id in reverse-DNS form. Every other field is a property of that identity at a given version.

Shape

Below is a JSON rendering. The same logical structure maps 1:1 to YAML, XML, or TOML.

{
  "manifestVersion": "1",
  "id": "com.acme.slack",
  "name": "slack",
  "displayName": "Slack",
  "version": "1.4.2",
  "appVersion": "2024-10-22",

  "classification": {
    "maturity": "beta",
    "visibility": "public",
    "successor": "com.acme.slack-next"
  },

  "assetsRoot": "./assets",

  "description": "Send messages and react to events in Slack workspaces.",
  "longDescription": "./README.md",

  "categories": ["communication", "productivity"],
  "keywords": ["chat", "messaging", "team"],

  "appearance": {
    "icon": {
      "svg": "./assets/icon.svg",
      "sizes": {
        "16x16":   "./assets/icon-16.png",
        "128x128": "./assets/icon-128.png",
        "512x512": "./assets/icon-512.png"
      }
    },
    "brandColor": "#4A154B",
    "darkMode": {
      "icon": { "svg": "./assets/icon-dark.svg" },
      "brandColor": "#611F69"
    }
  },
  "screenshots": [
    { "url": "./assets/shot-1.png" },
    { "url": "./assets/shot-2.png" }
  ],

  "author": {
    "name":  "Acme Integrations",
    "email": "support@acme.example",
    "url":   "https://acme.example"
  },
  "publisher": "acme",

  "homepage":      "https://acme.example/apps/slack",
  "repository":    "https://github.com/acme/slack-app",
  "documentation": "https://docs.acme.example/slack",
  "support":       "https://acme.example/support",
  "bugs":          "https://github.com/acme/slack-app/issues",

  "license":        "MIT",
  "privacyPolicy":  "https://acme.example/privacy",
  "termsOfService": "https://acme.example/terms",

  "defaultLocale": "en",
  "localizations": {
    "es": { "displayName": "Slack", "description": "Envía mensajes..." }
  },

  "engines": { "platform": ">=2.0.0" }
}

Field reference

FieldTypeRequiredDescription
manifestVersionstring✅Schema version this manifest targets ("1", "2", …). Enables forward compatibility.
idstring✅Globally unique identifier in reverse-DNS form (com.example.app-name). Immutable across versions.
namestring✅Machine-friendly short name. Lowercase, kebab-case, ASCII. Used in URLs and CLIs.
displayNamestring✅Human-facing name. Free-form; localizable.
versionstring✅Semantic version (MAJOR.MINOR.PATCH) of this manifest release. Bumped on any change to the manifest itself.
appVersionstring⬜Free-form version of the underlying integrated service this manifest targets (e.g. "2024-10-22", "v2", "Slack Web API"). Informational; no SemVer constraint.
classificationobject⬜Release-status block. Defaults: { maturity: "stable", visibility: "public" }.
classification.maturityenum⬜"alpha" | "beta" | "stable" | "deprecated". Self-declared stability signal shown as a badge in the UI. Default: "stable".
classification.visibilityenum⬜"private" | "unlisted" | "public". Publisher’s distribution intent. private = workspace/team-only, unlisted = reachable by direct link but not indexed, public = listed in the marketplace. Default: "public".
classification.successorstring (app id)⬜When maturity = "deprecated", the reverse-DNS id of the app that replaces this one. Hosts SHOULD surface a “this app is deprecated; use successor” notice.
assetsRootstring (path)⬜Base directory for resolving relative paths in appearance.icon, screenshots[].url, longDescription, etc. Defaults to the directory of the manifest file. Absolute URLs are unaffected.
descriptionstring✅One-line summary, ≤ 200 chars. Shown in lists.
longDescriptionstring | path⬜Markdown prose, inline or referenced file. For marketplace detail pages.
categoriesstring[]✅1–3 entries from the controlled vocabulary. Hosts MAY accept out-of-vocabulary entries but SHOULD warn.
keywordsstring[]⬜Free-form tags for search.
appearanceobject✅Visual identity block — icon, brand color, and optional dark-mode overrides.
appearance.iconImageObject✅Icon asset.
appearance.brandColorstring⬜Hex color used for marketplace styling.
appearance.darkModeobject⬜Alternate appearance for dark UI contexts. Any field it declares (icon, brandColor) overrides the light-mode default. Omit for no dark variant.
screenshotsImageObject[]⬜Marketing screenshots.
authorobject✅{ name, email?, url? }.
publisherstring⬜Verified publisher handle assigned by the registry. Enables trust badges.
homepageURL⬜Marketing page.
repositoryURL⬜Source code.
documentationURL⬜Docs.
supportURL | email⬜Support contact.
bugsURL⬜Issue tracker.
licensestring✅SPDX identifier (MIT, Apache-2.0, UNLICENSED).
privacyPolicyURL⬜Required for apps that handle user data.
termsOfServiceURL⬜
defaultLocalestring⬜BCP-47 tag. Defaults to en.
localizationsobject⬜Map of BCP-47 tag → partial manifest overrides.
enginesobject⬜Runtime version constraints, e.g. { "platform": ">=2.0.0" }.

Serialization

The manifest defines a logical schema, not a wire format. The same manifest MUST round-trip losslessly through:

  • JSON — canonical form; used in tests, examples, and programmatic exchange.
  • YAML — for human authoring.
  • XML — for enterprise tooling.
  • TOML — optional.

A reference serializer/validator lives in core/ and validates any input against the logical schema regardless of its source format.

Reserved fields

These field names are reserved by this spec. Hosts MUST NOT repurpose them; a later RFC will define their shape.

  • signature — manifest-level signature block (provenance / publisher attestation).
  • attestations — verifiable claims about the manifest or its assets.

Resolved questions

QuestionResolution
Manifest version vs app versionSplit. version is the manifest release (SemVer); the optional appVersion is free-form metadata about the integrated service.
Deprecation lifecycleclassification.maturity: "deprecated" plus optional classification.successor (reverse-DNS id).
Asset resolutionPaths resolve relative to the manifest file by default; the optional top-level assetsRoot overrides the base directory. Absolute URLs are passed through.
Signing & provenanceDeferred to a follow-up RFC. signature and attestations are reserved as field names.

Amendment — 2026-10-07: App-level ports defaults (P-1)

This section is additive; no existing line is changed. It extends the manifest Shape with one optional field.

An App MAY declare an optional top-level ports?: Ports (Ports = { in?: PortCount; out?: PortCount }, PortCount = number | "many"; see Node Types — P-1 amendment). App ports are defaults, folded per field into every action the app serves: an action’s own ports.x wins over App.ports.x, which wins over the global default (in ⇒ 1, out ⇒ "many"), and a Step.ports.x wins over all. Concretely describe() serves each action with ports = { ...app.ports, ...action.ports } when either is declared (absent otherwise), so consumers of an app’s actions see the folded value without resolving the app themselves. This supersedes, for apps, the 2026-07-23 action.md position that cardinality belongs to the Step alone.