> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ditto.live/llms.txt
> Use this file to discover all available pages before exploring further.

# Release Notes

> Explore release notes across every Ditto product.

export function ReleaseNotesPager() {
  const currentPage = () => {
    const m = window.location.pathname.match(/\/page-(\d+)$/);
    return m ? Number(m[1]) : 1;
  };
  const base = () => window.location.pathname.replace(/\/page-\d+$/, "");
  const target = pageNum => (pageNum === 1 ? base() : base() + "/page-" + pageNum) + window.location.search;
  const [total, setTotal] = useState(null);
  useEffect(() => {
    const el = document.querySelector("[data-release-notes-filters]");
    const n = el ? Number(el.getAttribute("data-rn-total-pages")) : NaN;
    if (Number.isFinite(n) && n >= 1) setTotal(n);
  }, []);
  if (!total || total <= 1) return null;
  const cur = currentPage();
  const items = (() => {
    if (total <= 7) return Array.from({
      length: total
    }, (_, i) => i + 1);
    const nums = [...new Set([1, total, cur - 1, cur, cur + 1].filter(n => n >= 1 && n <= total))].sort((a, b) => a - b);
    const out = [];
    let prev = 0;
    for (const n of nums) {
      if (prev && n - prev > 1) out.push("…");
      out.push(n);
      prev = n;
    }
    return out;
  })();
  const propsFor = (pageNum, rel, label) => ({
    rel,
    "aria-label": label,
    href: target(pageNum),
    onMouseEnter: e => {
      e.currentTarget.href = target(pageNum);
    },
    onMouseDown: e => {
      e.currentTarget.href = target(pageNum);
    },
    onFocus: e => {
      e.currentTarget.href = target(pageNum);
    },
    onClick: e => {
      if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
      e.preventDefault();
      window.location.assign(target(pageNum));
    }
  });
  const arrow = (pageNum, rel, label, glyph, enabled) => enabled ? <a className="rnp-link rnp-arrow" {...propsFor(pageNum, rel, label)}>{glyph}</a> : <span className="rnp-link rnp-arrow rnp-disabled" aria-disabled="true" aria-label={label}>{glyph}</span>;
  return <nav id="release-notes-pager" className="rnp rn-upd" aria-label="Release notes pages">
      <style>{`
        /* Numbered pagination at the bottom of the stream: belongs to the
           main content column (.rn-upd width), visually separated from the
           final announcement by spacing + a subtle top boundary (Mintlify-
           neutral conventions). One centered, wrapping row.
           The id + scroll-margin are the anchor contract for the TOC's
           "More releases ↓" jump link (Mintlify's own --scroll-mt keeps the
           landed position below the navbar); both are additive — the pager's
           appearance and behavior are otherwise unchanged. */
        .rnp { box-sizing: border-box; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; margin-top: 48px; padding: 20px 0 8px; border-top: 1px solid rgba(23, 23, 23, 0.1); font-size: 14px; scroll-margin-top: var(--scroll-mt, 96px); }
        html.dark .rnp { border-top-color: rgba(255, 255, 255, 0.12); }
        .rnp-link { display: inline-flex; align-items: center; justify-content: center; min-width: 36px; min-height: 36px; padding: 0 10px; border-radius: 9999px; border: 1px solid rgba(23, 23, 23, 0.15); color: #374151; font-weight: 600; text-decoration: none; }
        .rnp-link:hover { border-color: #111827; color: #111827; }
        html.dark .rnp-link { border-color: rgba(255, 255, 255, 0.16); color: #d1d5db; }
        html.dark .rnp-link:hover { border-color: #e5e7eb; color: #e5e7eb; }
        .rnp-link:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        .rnp-arrow { padding: 0 12px; }
        /* Current page: filled pill — same active-state convention as the
           navbar's Release Notes pill (a.rn-nav-active in
           release-notes-filters.js). */
        .rnp-current { display: inline-flex; align-items: center; justify-content: center; min-width: 36px; min-height: 36px; padding: 0 10px; border-radius: 9999px; border: 1px solid #111827; background: #111827; color: #ffffff; font-weight: 600; }
        html.dark .rnp-current { border-color: #e5e7eb; background: #e5e7eb; color: #111827; }
        .rnp-disabled, .rnp-disabled:hover { opacity: 0.4; cursor: default; border-color: rgba(23, 23, 23, 0.15); color: #374151; }
        html.dark .rnp-disabled, html.dark .rnp-disabled:hover { border-color: rgba(255, 255, 255, 0.16); color: #d1d5db; }
        .rnp-ellipsis { padding: 0 4px; color: #6b7280; user-select: none; }
        html.dark .rnp-ellipsis { color: #9ca3af; }
      `}</style>
      {arrow(cur - 1, "prev", "Previous page", "‹", cur > 1)}
      {items.map((it, i) => it === "…" ? <span key={`e${i}`} className="rnp-ellipsis" aria-hidden="true">…</span> : it === cur ? <span key={it} className="rnp-current" aria-current="page" aria-label={`Page ${it}, current page`}>{it}</span> : <a key={it} className="rnp-link" {...propsFor(it, null, `Page ${it}`)}>{it}</a>)}
      {arrow(cur + 1, "next", "Next page", "›", cur < total)}
    </nav>;
}

export function ReleaseNotesFilters({view, page, totalPages, tocEntries, rangeStart, rangeEnd, rangeTotal}) {
  const [open, setOpen] = useState(false);
  const controlRef = useRef(null);
  const basePath = window.location.pathname.replace(/\/page-\d+$/, "");
  const WRAPPER_PREFIX = "/sdk/latest/release-notes-hub/";
  const activeId = basePath === "/release-notes" ? "all" : basePath === "/release-notes/sdk" ? "sdk-all" : basePath === "/release-notes/common" ? "common" : basePath === "/release-notes/cloud" ? "cloud" : basePath.startsWith("/release-notes/sdk/") ? basePath.slice(("/release-notes/sdk/").length) : basePath.startsWith(WRAPPER_PREFIX) ? basePath.slice(WRAPPER_PREFIX.length) : basePath === "/cloud/release-notes-hub" ? "cloud" : "all";
  const tocSlug = label => label.toLowerCase().replace(/[(),]/g, "").replace(/[.\s]/g, "-");
  const TOC_GROUP_ORDER = ["SDK", "Cloud Platform"];
  const grouped = view === "all" || view === "sdk-all" ? TOC_GROUP_ORDER.map(g => ({
    group: g,
    entries: tocEntries.filter(e => e.group === g)
  })).filter(g => g.entries.length > 0) : [{
    group: null,
    entries: tocEntries
  }];
  const tocRow = entry => <li key={entry.label}>
      <a className="rnff-toc-link" href={"#" + tocSlug(entry.label)}>
        {entry.label}
      </a>
    </li>;
  const VIEW_HREF = {
    "all": "/release-notes",
    "sdk-all": "/release-notes/sdk",
    "common": "/release-notes/common",
    "cloud": "/release-notes/cloud"
  };
  const viewHref = id => VIEW_HREF[id] || `/release-notes/sdk/${id}`;
  const PLATFORM_LINKS = [["swift", "Swift"], ["kotlin", "Kotlin"], ["flutter", "Flutter"], ["react-native", "React Native"], ["javascript-web", "JavaScript (Web)"], ["javascript-nodejs", "Node.js"], ["java", "Java"], ["c-sharp", "C#"], ["cpp", "C++"], ["rust", "Rust"], ["go", "Go"]];
  const DEST_ICONS = {
    "all": ["clipboard-list", "solid"],
    "sdk-all": ["laptop-mobile", "solid"],
    "common": ["cubes-stacked", "solid"],
    "cloud": ["cloud", "regular"]
  };
  const PLATFORM_ICONS = {
    "swift": ["apple", "brands"],
    "kotlin": ["android", "brands"],
    "flutter": ["flutter", "brands"],
    "react-native": ["react", "brands"],
    "javascript-web": ["js", "brands"],
    "javascript-nodejs": ["js", "brands"],
    "java": ["java", "brands"],
    "c-sharp": ["microsoft", "brands"],
    "cpp": ["code", "solid"],
    "rust": ["rust", "brands"],
    "go": ["golang", "brands"]
  };
  useEffect(() => {
    if (!open) return;
    const onDown = e => {
      if (controlRef.current && !controlRef.current.contains(e.target)) setOpen(false);
    };
    const onFocus = e => {
      if (controlRef.current && !controlRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener("mousedown", onDown);
    document.addEventListener("focusin", onFocus);
    return () => {
      document.removeEventListener("mousedown", onDown);
      document.removeEventListener("focusin", onFocus);
    };
  }, [open]);
  const destLink = (id, label, icon) => <a className="rnff-dest" data-active={activeId === id ? "true" : undefined} href={viewHref(id)}>
      {icon && <span className="rnff-dest-icon">
          <Icon icon={icon[0]} iconType={icon[1]} />
        </span>}
      <span>{label}</span>
    </a>;
  return <div className="rnff-stick not-prose" data-release-notes-filters="" data-rn-total-pages={totalPages}>
      {}
      <style>{`
        /* RESPONSIVE BREAKOUT SYSTEM: card 290px, gap 36px; the rail's sticky
           top tracks Mintlify's NATIVE right-rail offset (--scroll-mt =
           navbar bottom + 40, banner-aware, maintained live by Mintlify's own
           scroll-margin script; 180px fallback until that script runs).
           Below 1260px: normal single-column flow. */
        :root {
          --rn-card-w: 290px;
          --rn-gap: 36px;
          --rn-off: min(max(calc(100vw - 1204px), 0px), 350px);
        }
        .rn-upd { width: min(calc(100% - var(--rn-card-w) - var(--rn-gap) + var(--rn-off)), 100%); }
        /* COPY PAGE = STANDARD-LAYOUT POSITION (stakeholder polish): on
           rail-active desktop viewports, Mintlify's page-context menu (Copy
           page) shifts left so its right edge aligns with the releases
           column's right edge — the same relationship standard docs pages
           have (button over the article column, right rail beside it). The
           margin mirrors the .rn-upd width formula (card + gap - off), so it
           stays correct at every width with no tuning of its own; max()
           clamps it to 0 where the releases column is full-width (~1530px+,
           the same point .rn-upd's min() caps). Gated to the rail-active
           conditions (min-width 1261, min-height 641) so the collapsed
           layouts below keep the native header placement. This stylesheet
           only loads on release-notes routes, so no other page's Copy Page
           button can be affected. */
        @media (min-width: 1261px) and (min-height: 641px) {
          #page-context-menu { margin-right: max(0px, calc(var(--rn-card-w) + var(--rn-gap) - var(--rn-off))); }
        }
        /* STICKY BOUNDARY (corrected): the wrapper's box height = the rail's
           real visual height formula, compensated by an equal negative top
           margin; the sticky containment rule then measures the rail's TRUE
           extent, so the rail stops at the end of the release-notes content
           region instead of floating into Mintlify's native prev/next +
           footer area. */
        .rnff-stick { box-sizing: border-box; overflow: visible; width: var(--rn-card-w); max-width: none; margin-left: auto; position: sticky; top: var(--scroll-mt, 180px); height: max(240px, calc(100vh - var(--scroll-mt, 180px) - 24px)); margin-top: min(-240px, calc(var(--scroll-mt, 180px) + 24px - 100vh)); margin-bottom: 0; transform: translateX(var(--rn-off)); }
        .rnff-card { border: 1px solid rgba(23, 23, 23, 0.1); border-radius: 16px; padding: 16px 20px; background: #ffffff; }
        html.dark .rnff-card { border-color: rgba(255, 255, 255, 0.12); background: #171717; }
        .rnff-label { font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; letter-spacing: normal; text-transform: none; color: #171717; margin: 0 0 4px; }
        html.dark .rnff-label { color: #dfdfdf; }
        .rnff-group + .rnff-group { margin-top: 16px; }
        /* Destination rows — typography measured from the existing LEFT
           sidebar (2026-09-16): nav item = 14px / 400 / 24px / normal case /
           #3f3f3f (dark #9f9f9f), font stack Inter/system; CURRENT item =
           same weight, darker color #1e1e1e. Section labels match the
           "Key Concepts" group header (14px / 500 / normal case). Rows keep
           the sidebar shape: full-width rectangular rows + icon gutter, NOT
           chips/pills. */
        .rnff-dest { display: flex; align-items: center; gap: 8px; padding: 3px 8px; margin: 0 -8px; font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; line-height: 24px; color: #3f3f3f; text-decoration: none; border-radius: 8px; }
        .rnff-dest:hover { color: #111827; background: rgba(23, 23, 23, 0.04); }
        html.dark .rnff-dest { color: #9f9f9f; }
        html.dark .rnff-dest:hover { color: #e5e7eb; background: rgba(255, 255, 255, 0.06); }
        .rnff-dest[data-active="true"] { font-weight: 400; color: #1e1e1e; background: rgba(23, 23, 23, 0.06); }
        html.dark .rnff-dest[data-active="true"] { color: #e5e5e5; background: rgba(255, 255, 255, 0.1); }
        .rnff-dest:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        .rnff-dest-icon { display: inline-flex; width: 16px; flex: 0 0 16px; justify-content: center; color: inherit; opacity: 0.85; }
        .rnff-dest-icon svg { width: 14px; height: 14px; }
        /* Platform disclosure control — a field-shaped button that reveals
           the destination link list IN FLOW (no overlay, no overflow risk,
           no z-index games). Closed height matches the replaced select (~34px)
           so the vertical-space win from the dropdown change is retained. */
        .rnff-plat-toggle { display: flex; align-items: center; gap: 8px; width: 100%; height: 34px; padding: 0 10px; font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 400; line-height: 24px; text-align: left; color: #3f3f3f; background: #ffffff; border: 1px solid rgba(23, 23, 23, 0.15); border-radius: 8px; cursor: pointer; }
        html.dark .rnff-plat-toggle { background: #171717; color: #9f9f9f; border-color: rgba(255, 255, 255, 0.16); }
        .rnff-plat-toggle:hover { border-color: #111827; }
        html.dark .rnff-plat-toggle:hover { border-color: #e5e7eb; }
        .rnff-plat-toggle:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; }
        .rnff-plat-label { flex: 1 1 auto; }
        .rnff-plat-caret { display: inline-flex; color: #9ca3af; }
        .rnff-plat-caret svg { width: 12px; height: 12px; }
        /* OPEN MENU OVERLAYS the content below (incl. the TOC) like a normal
           dropdown/popover: absolute within .rnff-plat (position:relative),
           so opening never grows the card or pushes the TOC down. All 11
           options render at once — no max-height, no internal scroll.
           Clipping: no ancestor hides overflow (stick/rail/card are all
           overflow:visible), so absolute positioning is safe. */
        .rnff-plat { position: relative; }
        .rnff-plat-list { list-style: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 50; margin: 0; padding: 4px 0; background: #ffffff; border: 1px solid rgba(23, 23, 23, 0.12); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); }
        html.dark .rnff-plat-list { background: #171717; border-color: rgba(255, 255, 255, 0.14); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); }
        /* Dropdown option spacing (manual-review polish): rows inside the
           menu get a roomier, consistent inset — icons sit 12px from the
           menu edge with a 12px icon→label gap (the site's 4px spacing
           scale), so all 11 icons share one column and all labels share one
           text column. Row heights and menu dimensions are unchanged. */
        .rnff-plat-list .rnff-dest { padding: 3px 12px; gap: 12px; margin: 0; }
        /* Uppercase platform labels inside the dropdown only (stakeholder
           presentation request — matches the site's Aeonik/uppercase brand
           usage for navigation accents). Route ids/labels/metadata are
           unchanged; this is a visual transform of the option text. */
        .rnff-plat-label, .rnff-plat-list .rnff-dest span:last-child { text-transform: uppercase; }
        @media (max-width: 1260px) {
          .rn-upd { width: 100%; }
          .rnff-stick { transform: none; width: 100%; height: auto; position: static; margin: 0 0 24px; }
        }
        /* Extremely SHORT viewports: same static collapse as narrow screens. */
        @media (max-height: 640px) and (min-width: 1261px) {
          .rn-upd { width: 100%; }
          .rnff-stick { transform: none; width: 100%; height: auto; position: static; margin: 0 0 24px; }
          .rnff-rail { max-height: none; }
        }
        /* RIGHT-RAIL AS ONE BOUNDED REGION (unchanged): flex column capped at
           the viewport height remaining under the sticky top offset; the
           destination card never shrinks; the TOC takes leftover space and
           scrolls independently. */
        .rnff-rail { display: flex; flex-direction: column; max-height: max(240px, calc(100vh - var(--scroll-mt, 180px) - 24px)); }
        .rnff-card { flex: 0 0 auto; }
        .rnff-toc { flex: 1 1 auto; min-height: 0; margin-top: 20px; padding: 10px 12px; border: 1px solid rgba(23, 23, 23, 0.08); border-radius: 12px; overflow-y: auto; overscroll-behavior: contain; }
        html.dark .rnff-toc { border-color: rgba(255, 255, 255, 0.1); }
        /* TOC: entries match the site's NATIVE right-rail TOC convention
           (styles.css table-of-contents rule: Aeonik Fono + uppercase;
           measured native entries: 14px / 400 / 24px, header 14px / 500).
           NOTE: no backticks inside this <style> template literal — a
           backtick terminates the template and silently kills the whole
           component's export (measured the hard way). */
        .rnff-toc-label { font-family: Inter, -apple-system, system-ui, "Segoe UI", sans-serif; font-size: 14px; font-weight: 500; line-height: 24px; letter-spacing: normal; text-transform: none; color: #171717; margin: 0 0 4px; }
        html.dark .rnff-toc-label { color: #dfdfdf; }
        /* TOC header row: label left, announcement range right (same row,
           visually subordinate — 12px grey, the group-label's muted tones).
           Range values arrive as generated route props (registry-derived);
           the component never computes them. */
        .rnff-toc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
        .rnff-toc-range { font-family: 'Aeonik Fono', monospace; font-size: 12px; font-weight: 400; line-height: 24px; letter-spacing: normal; text-transform: none; color: #9ca3af; }
        html.dark .rnff-toc-range { color: #6b7280; }
        /* "More releases ↓" — TOC-footer jump link to the existing bottom
           pager (#release-notes-pager). Additive navigation only: rendered
           solely when the generated route says the view is paginated
           (totalPages > 1), so it never points at an unmounted pager. */
        .rnff-toc-more { margin-top: 8px; padding-top: 8px; border-top: 1px solid rgba(23, 23, 23, 0.08); }
        html.dark .rnff-toc-more { border-top-color: rgba(255, 255, 255, 0.1); }
        .rnff-toc-group-label { font-family: 'Aeonik Fono', monospace; font-size: 12px; font-weight: 500; letter-spacing: normal; text-transform: uppercase; color: #9ca3af; margin: 12px 0 4px; }
        html.dark .rnff-toc-group-label { color: #6b7280; }
        .rnff-toc ul { list-style: none; margin: 0; padding: 0; }
        .rnff-toc li { margin: 0; }
        .rnff-toc-link { display: block; padding: 3px 0; font-family: 'Aeonik Fono', monospace; font-size: 14px; font-weight: 400; line-height: 24px; letter-spacing: normal; text-transform: uppercase; color: #707070; text-decoration: none; }
        .rnff-toc-link:hover { color: #1e1e1e; text-decoration: underline; }
        html.dark .rnff-toc-link { color: #9f9f9f; }
        html.dark .rnff-toc-link:hover { color: #e5e5e5; }
        .rnff-toc a:focus-visible { outline: 2px solid #688ae8; outline-offset: 2px; border-radius: 4px; }
        @media (max-width: 1260px) {
          .rnff-toc { display: none; }
        }
      `}</style>
      <div className="rnff-rail">
      <div className="rnff-card">
      <div role="group" aria-label="Release Notes" className="rnff-group">
        <p className="rnff-label">Release Notes</p>
        <nav aria-label="Release notes destinations">
          {destLink("all", "All", DEST_ICONS["all"])}
          {destLink("sdk-all", "SDK - All", DEST_ICONS["sdk-all"])}
          {destLink("common", "SDK - Common", DEST_ICONS["common"])}
          {destLink("cloud", "Cloud Platform", DEST_ICONS["cloud"])}
        </nav>
      </div>

      <div role="group" aria-label="SDK Platforms" className="rnff-group">
        <p className="rnff-label">SDK Platforms</p>
        {}
        {(() => {
    const activeIsPlatform = !!PLATFORM_ICONS[activeId];
    const activeLabel = activeIsPlatform ? PLATFORM_LINKS.find(([s]) => s === activeId)?.[1] : null;
    return <div className="rnff-plat" ref={controlRef}>
              <button type="button" className="rnff-plat-toggle" aria-expanded={open ? "true" : "false"} aria-controls="rn-platform-destinations" onClick={() => setOpen(!open)} onKeyDown={e => {
      if (e.key === "ArrowDown" && !open) {
        e.preventDefault();
        setOpen(true);
      }
      if (e.key === "Escape" && open) {
        e.preventDefault();
        setOpen(false);
      }
    }}>
                {activeIsPlatform && <span className="rnff-dest-icon">
                    <Icon icon={PLATFORM_ICONS[activeId][0]} iconType={PLATFORM_ICONS[activeId][1]} />
                  </span>}
                <span className="rnff-plat-label">
                  {activeLabel || "Select a platform…"}
                </span>
                <span className="rnff-plat-caret" aria-hidden="true">
                  <Icon icon={open ? "chevron-up" : "chevron-down"} iconType="solid" />
                </span>
              </button>
              {open && <ul id="rn-platform-destinations" className="rnff-plat-list" aria-label="SDK platform destinations">
                  {PLATFORM_LINKS.map(([slug, label]) => <li key={slug}>
                      <a className="rnff-dest" data-active={activeId === slug ? "true" : undefined} href={viewHref(slug)} onKeyDown={e => {
      const links = [...controlRef.current.querySelectorAll(".rnff-plat-list a")];
      const i = links.indexOf(e.currentTarget);
      if (e.key === "ArrowDown") {
        e.preventDefault();
        (links[i + 1] || links[0]).focus();
      }
      if (e.key === "ArrowUp") {
        e.preventDefault();
        (links[i - 1] || links[links.length - 1]).focus();
      }
      if (e.key === "Escape") {
        e.preventDefault();
        setOpen(false);
        controlRef.current.querySelector(".rnff-plat-toggle").focus();
      }
    }}>
                        <span className="rnff-dest-icon">
                          <Icon icon={PLATFORM_ICONS[slug][0]} iconType={PLATFORM_ICONS[slug][1]} />
                        </span>
                        <span>{label}</span>
                      </a>
                    </li>)}
                </ul>}
            </div>;
  })()}
      </div>
      </div>
      <nav className="rnff-toc" aria-label="Release notes table of contents">
        <div className="rnff-toc-head">
          <p className="rnff-toc-label">Table of Contents</p>
          {rangeTotal > 0 && <span className="rnff-toc-range">{rangeStart}–{rangeEnd} of {rangeTotal}</span>}
        </div>
        {grouped.map(g => <div key={g.group || "flat"}>
            {g.group && <p className="rnff-toc-group-label">{g.group}</p>}
            <ul>
              {g.entries.map(entry => tocRow(entry))}
            </ul>
          </div>)}
        {totalPages > 1 && <a className="rnff-toc-link rnff-toc-more" href="#release-notes-pager">More releases ↓</a>}
      </nav>
    </div>
    </div>;
}

<ReleaseNotesFilters view="all" page={1} totalPages={5} rangeStart={1} rangeEnd={10} rangeTotal={44} tocEntries={[{"label":"v1.65.1","group":"Cloud Platform"},{"label":"5.1.0","group":"SDK"},{"label":"5.1.0 (Swift)","group":"SDK"},{"label":"5.1.0 (Kotlin)","group":"SDK"},{"label":"5.1.0 (Flutter)","group":"SDK"},{"label":"5.1.0 (React Native)","group":"SDK"},{"label":"5.1.0 (JavaScript Web)","group":"SDK"},{"label":"5.1.0 (Node.js)","group":"SDK"},{"label":"5.1.0 (Java)","group":"SDK"},{"label":"5.1.0 (C#)","group":"SDK"}]} />

<Update label="v1.65.1" description="Release Date: w/c Aug 24, 2026" tags={["Cloud Platform"]} className="rn-upd">
  **Big Peer 1.65.1 is a patch release that adds optional sharding of CDC output across multiple Kafka producers to raise produce throughput, and fixes a webhook dispatcher failure that could stop webhooks being delivered after a Kafka reconnect.**

  <Icon icon="plus" iconType="solid" horizontal /> **Added:**

  * CDC can shard its output across multiple idempotent Kafka producers, controlled by the new `PRODUCER_SHARDS` setting, raising produce throughput to downstream consumers under sustained write load. The default of `1` leaves behavior unchanged

  <Note>
    This does not change CDC's ordering contract. Changes for an individual document are still delivered sequentially, and global ordering across documents is not guaranteed either way - see [Event Ordering](/cloud/cdc#event-ordering). Contact Ditto before raising `PRODUCER_SHARDS` above `1`, as it has a deployment prerequisite.
  </Note>

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed:**

  * The webhook dispatcher no longer aborts when its Kafka consumer reconnects, so a broker restart or partition rebalance no longer leaves it restarting repeatedly instead of delivering webhooks
</Update>

<Update label="5.1.0" description="Release Date: Aug 18, 2026" tags={["SDK", "SDK-Common"]} className="rn-upd">
  \_*SDK 5.1 common capabilities — shared by all SDK platforms. Shown in the overall SDK view; platform filters show that platform's own blocks only.*

  # Ditto SDK 5.1 — Faster Local Queries. More Capable Query Engine.

  The new Ditto Edge SDK 5.1 delivers major features and optimizations across five areas. The release includes 77 platform and 112 SDK-specific improvements.

  **Key takeaway**: Existing applications get better performance and more efficient memory usage on the same hardware simply by upgrading to SDK 5.1.

  **[1. Performance: Faster Queries, Lower Memory Use](#1-performance-faster-queries-lower-memory-use)**

  * Evictions are 53.6× faster and deletes are 43.0× faster on average. This speeds up routine local data clean-up, like clearing the orders from a point-of-sale terminal.
  * A full-collection `COUNT(*)` fell from 149.03 ms to 0.89 ms, which is 167× faster on average. This speeds up badge counts and dashboard totals across an app.

  **[2. Query Engine: New Ditto Query Capabilities](#2-query-engine-new-ditto-query-capabilities)**

  * `JOIN` combines local collections in a single `SELECT`, removing the need to run separate queries and merge their results in application code.
  * `ADVISE` looks at a query and tells you the indexes to create to make it faster, all without running it or reading a document.

  **[3. Security: Expanded Certificate Revocation](#3-security-expanded-certificate-revocation)**

  * If a device is lost or stolen, it's easier to revoke the device's certificate, disconnect it from the mesh, and prevent the device from sending or receiving data.
  * Revocations travel peer to peer and reach devices even when they can't see the cloud. Connections from revoked peers are terminated and refused.

  **[4. Troubleshooting: Identify Problems and Recover Automatically](#4-troubleshooting-identify-problems-and-recover-automatically)**

  * Identifying problems is easier with better tools for analyzing deployed devices, starting with support bundles that capture the device's effective configuration.
  * Corrupted replication metadata is detected and rebuilt automatically, so field issues can be solved without a site visit.

  **[5. Transport at Scale: Multicast Beta](#5-transport-at-scale-multicast-beta)**

  * Multicast allows Ditto to scale to large networks with many edge devices. Edge devices subscribe to a shared group for data sync, which has lower connection cost at scale.
  * Opt-in beta capability in the Swift, Kotlin/KMP, Flutter, and Rust SDKs. Support will expand to additional SDKs in the future.

  **[Upgrading to 5.1 and rollback](#upgrading-to-5-1)**

  * Upgrading to Ditto SDK 5.1 changes the on-disk index format.
  * If you need to downgrade from Ditto SDK 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+.

  # 1. Performance: Faster Queries, Lower Memory Use

  Edge applications feel database performance differently from cloud applications. A slow local query doesn't just delay a response from a server. It can block a screen transition, increase battery use, create UI churn, or consume memory on a device with limited resources.

  SDK 5.1 delivers a major leap in local query performance. Applications can load data faster, complete writes sooner, and react to changes more quickly—even as their local datasets and workloads grow.

  The improvements span nearly every kind of local data operation: selects, indexed reads, inserts, updates, deletes, evictions, aggregations, and observers. In practice, this means more responsive user experiences, less time waiting for data operations, and greater capacity on the same device hardware.

  These gains come from improvements throughout the local data path. Ditto performs less decoding and allocation, finds documents more directly, executes mutations more efficiently, avoids unnecessary observer work, and reduces full database scans during subscription changes. An opt-in relaxed durability mode can further reduce disk synchronization for rebuildable sync metadata without changing the durability of application documents.

  ## Android retail benchmark

  The gains are broad rather than limited to one optimized query path. In testing on an Orion O6 Android device, SDK 5.1 was faster in 70 of 71 measured scenarios, with one result too close to call. The 72-scenario suite modeled an offline-first retail application with approximately 93,000 documents across seven synced collections.

  | DQL operation | Purpose | Geometric-mean speedup |
  | - | - | -: |
  | Evict | Clear local data without syncing the removal | 53.6× |
  | Delete | Remove documents everywhere | 43.0× |
  | Update | Change fields in existing documents | 18.5× |
  | Aggregation | Compute totals and averages | 4.2× |
  | Select | Read documents that match a query | 1.5× |
  | Indexed select | Read matches using an index | 1.4× |
  | Insert | Create new documents | 1.2× |

  These results compare median runtimes from Ditto 5.0.3 and Ditto 5.1.0. Performance varies by device, data, indexes, and query mix, so test representative workloads on your target hardware.

  ### Dramatically faster document counts

  A count is a query that answers one simple question: how many documents match? Apps use counts all the time to show how many orders are open, how many items are in stock, or how many results a search found.

  One of the largest individual improvements is full-collection document counting. Ditto 5.1.0 adds an optimized path for `COUNT(*)`, reducing the benchmark's median execution time from 149.03 ms to 0.89 ms—approximately **167× faster**. Counts with a filter also improved by approximately **4.4×**.

  | Query | Ditto 5.0.3 | Ditto 5.1.0 | Speedup |
  | - | -: | -: | -: |
  | Full-collection count | 149.03 ms | 0.89 ms | 167× |
  | Count with a condition | 60.85 ms | 13.89 ms | 4.4× |

  These improvements accelerate queries such as `SELECT COUNT(*) FROM tasks` and `SELECT COUNT(*) FROM tasks WHERE status = 'open'`, making it substantially faster to calculate totals for dashboards, backlog checks, pagination, and application status displays.

  ## Lower memory use

  Edge devices like phones, tablets, and point-of-sale terminals have a fixed amount of memory that every app shares. The less memory Ditto uses, the more room your app has for its own work, and the less likely the operating system is to slow it down.

  In a separate Android workload that grew a collection from 3,000 to 30,000 documents, Ditto 5.1.0 delivered approximately 2.1× as many observer results while using less memory than Ditto 5.0.3.

  | Memory measurement | Ditto 5.0.3 | Ditto 5.1.0 | Improvement |
  | - | -: | -: | -: |
  | Median Total PSS | 271.4 MB | 233.5 MB | 14.0% lower |
  | Median native heap | 180.5 MB | 121.4 MB | 32.7% lower |
  | Peak Total PSS | 469.5 MB | 400.6 MB | 14.7% lower |

  Total PSS estimates the process's physical RAM footprint, and peak Total PSS is the highest that footprint reached during the test. Native heap covers Ditto's Rust core—the pool of memory the program sets aside while it runs to hold its working data. Tombstone cleanup, bulk mutations, and retained disconnected sync sessions are also bounded more carefully to reduce peak memory in high-volume deployments.

  ***

  # 2. Query Engine: New Ditto Query Capabilities

  Performance is only half of the query story in this new release. DQL (Ditto Query Language) gains the two capabilities customers asked for the most: `JOIN` and composite indexes. Customers can also run `ADVISE` to identify any indexes that would speed up query performance.

  ## Join collections locally

  A join is a query that combines related data from two collections into one result. It matches records that share a value, like a task and the project it belongs to, so your application gets one combined answer instead of two separate lists.

  `SELECT` statements on edge devices can now [join multiple local collections](/dql/select#joins). This removes the need to coordinate separate queries and merge their results in application code.

  ```sql DQL theme={null}
  SELECT task._id, task.title, project.name
  FROM tasks AS task
  JOIN projects AS project ON task.projectId = project._id
  WHERE task.status = 'open'
  ```

  This makes normalized data models practical at the edge. Product catalogs, order histories, task assignments, and multi-tenant views can stay separated into logical collections without forcing every screen to coordinate multiple reads.

  Joins use data already present in the local store. They do not fetch missing data from peers, and they are not supported in sync-subscription queries. The inner collection normally requires an appropriate index; use [`ADVISE`](/dql/advise) when you need an index recommendation.

  ## Create composite indexes

  An index is a lookup structure the database maintains so it can find matching documents without reading the whole collection, much like the index at the back of a book. A composite index covers two or more fields at once, so a query that filters on one field and sorts by another can be answered in a single lookup.

  Edge devices now support [composite indexes](/dql/indexing#composite-index) over multiple fields. A single composite index can accelerate queries that repeatedly filter or sort by the same combination of fields—for example, tenant and time, status and assignee, or location and category.

  The following index is designed for queries that filter tasks by `status` and sort them by `createdAt`:

  ```sql DQL theme={null}
  CREATE INDEX IF NOT EXISTS status_created_idx
  ON tasks (status, createdAt DESC)
  ```

  It can improve queries such as:

  ```sql DQL theme={null}
  SELECT * FROM tasks
  WHERE status = 'open'
  ORDER BY createdAt DESC
  ```

  Field order matters. Put fields used in equality filters first, followed by fields used for range filters or sorting. Composite indexes can also include array and object values. If you are unsure which fields to index, run [`ADVISE`](/dql/advise) against the query to get an index recommendation.

  ## Find the right indexes with ADVISE

  [`ADVISE`](/dql/advise) turns index optimization into a guided workflow. Prefix a query with `ADVISE`, and Ditto plans how it would execute that query without actually running it or reading any documents. It inspects the query's filters, sorts, projections, and joins. Then, Ditto spots the places where the engine would have to scan the whole collection. For each one, the response explains why an index would help and provides a ready-to-run `CREATE INDEX` statement.

  For example, advise a query that filters tasks by estimated effort:

  ```sql DQL theme={null}
  ADVISE SELECT * FROM tasks WHERE estimateHours > 8
  ```

  Ditto identifies the range predicate and recommends an index on `estimateHours`:

  ```json theme={null}
  {
    "advice": {
      "statement": "select * from tasks where estimateHours > 8",
      "suggestedIndexes": [
        {
          "collection": "tasks",
          "reason": "range predicates on `estimateHours`",
          "statement": "CREATE INDEX IF NOT EXISTS adv_tasks_estimateHours ON default:`tasks` (`estimateHours` ASC)"
        }
      ]
    }
  }
  ```

  Run the suggested statement yourself, or use `ADVISE AND PROVISION` to create the recommended indexes automatically. `ADVISE` can also recommend composite and covering indexes for more complex filters, sorting, projections, and joins. It is currently available on edge devices.

  ## Return changed documents with RETURNING

  [`RETURNING`](/dql/returning) lets an `INSERT`, `UPDATE`, `DELETE`, `EVICT`, or `TOMBSTONE` statement return data from the documents it changed. A data mutation and its confirmation can become one operation. Applications can receive the affected data immediately instead of issuing a second query.

  For example, update matching documents and return their IDs and new values in one operation:

  ```sql DQL theme={null}
  UPDATE tasks
  SET status = 'done'
  WHERE projectId = 'proj-42' AND status = 'open'
  RETURNING _id, status
  ```

  `RETURNING` is especially valuable with deletes and evictions because it can return document contents before they are removed. It also supports projections, expressions, aliases, and aggregates such as `RETURNING COUNT(*) AS removed`.

  Ditto 5.1 also adds [`INSERT ... SELECT`](/dql/insert#insert-from-a-select-statement) for creating documents directly from query results.

  ## Control long-running requests

  A single expensive query on an edge device can hold resources that are needed by the rest of an application. Two new [system parameters](/dql/alter-system) can catch runaway queries before they cause performance issues.

  | System parameter | Default | Behavior |
  | - | -: | - |
  | `DQL_SLOW_REQUEST_WARN_SECONDS` | `60` | Logs request details at the threshold and repeats while the request runs. |
  | `DQL_REQUEST_TIMEOUT_SECONDS` | `0` | Cooperatively cancels requests that exceed the configured limit. |

  Set either parameter to `0` to disable it. Request history can also [filter by request type or explicit profiling requests](/dql/virtual-collections#request-history).

  ***

  # 3. Security: Expanded Certificate Revocation

  Security policies at the edge need to keep working even when devices are not continuously connected to the cloud. Security teams lose sleep over what happens when an edge device is lost or stolen.

  SDK 5.1 will help security teams sleep better. Certificate revocation information now propagates securely from Ditto Server to edge devices and from peer to peer throughout the mesh. As peers connect, they share the latest revocation information.

  Revocation enforcement is enabled by default. Peers reject new connections that present a revoked certificate and terminate matching active connections when a revocation arrives. This isolates revoked clients and prevents them from reconnecting through another peer in the mesh.

  Every hop verifies the revocation's signature against its trusted certificate authority keys, the source that issues each device's identity credentials. A compromised peer cannot forge revocations.

  ***

  # 4. Troubleshooting: Identify Problems and Recover Automatically

  A device misbehaving in the field is a hard problem to tackle in edge computing. It's expensive and inefficient to fly an engineer out to attach a debugger to a tablet that is 3,000 miles away in the back of a restaurant.

  SDK 5.1 adds new options to remotely collect edge device data and identify the root cause of an issue. Support bundles now include `config_snapshot.json`, which records the effective `DittoConfig`, transport configuration, system parameters, and SDK version at capture time. This provides essential device config information at the start of every investigation or support case.

  Other new remote diagnostics include:

  * A configurable Unix `debug_socket` lets you remotely run DQL diagnostics against an edge device.
    * You can inspect a live device directly rather than reproducing the problem in a lab.
  * Nine new network counters under `ditto.network.dsoq.*` surface Ditto Sync over QUIC (DSOQ) protocol failures in your existing production metrics, without debug logs.
  * SQLite metrics distinguish the application data store from replication metadata databases on supported Unix platforms, showing which one is driving disk activity.

  SDK 5.1 also repairs a class of problems on its own. Corrupted per-peer replication metadata is now detected, reset, and rebuilt automatically without affecting application documents. Previously, this class of corruption could require clearing the device's local store. The result is faster diagnosis, fewer escalations, and fewer cases that end with wiping the app and reinstalling.

  ***

  # 5. Transport at Scale: Multicast Beta

  Today, Ditto's default peer-to-peer model establishes a session between every pair of edge devices. The total connection count grows with the square of the mesh size, O(N²). Six devices need 15 connections, and sixty devices need 1,770. Connection maintenance eventually becomes the dominant cost in environments with a large number of edge devices like a mall, a ship, a concert venue, or an aircraft.

  Multicast is now available in SDK 5.1 as an opt-in beta. Devices join a shared multicast group rather than pairing off, which drops the connection count from O(N²) to O(N). There is one group membership per device.

  A sender would typically transmit an update once per device in the mesh, which is O(N). Multicast enables the sender to publish a single broadcast to the group, which is approximately O(1). There is one send, no matter how many devices are listening.

  The transport is built on reliable multicast (NORM, RFC 5740) combined with Ditto's data reconciliation, so peers recover missed data and catch up after joining or reconnecting.

  When multicast is configured and available, it becomes the preferred replication path. Traffic is encrypted for the group, and existing peer-to-peer transports remain active as automatic fallback for peers the group cannot reach. Documents and attachments both replicate over multicast, including repair of missing attachment data.

  <Warning>
    Multicast is a beta capability in SDK 5.1. It ships in the core SDK as an opt-in feature rather than a part of the standard build. It is available in the Swift, Kotlin/KMP, Flutter, and Rust SDKs.

    Contact Ditto support or your Ditto representative before deploying multicast in production to understand the current beta limitations.
  </Warning>

  # Upgrading to 5.1

  The upgrade to Ditto Edge SDK 5.1 is seamless. Simply bump your dependency to 5.1.0 and Ditto handles the rest. An index migration will run automatically the first time your app starts. Data sync is backward-compatible, and 5.1 peers will work with 5.0 and v4 peers while you roll out gradually.

  ## Tested rollback compatibility

  Ditto Edge SDK 5.1 has undergone extensive backward-compatibility and rollback testing to ensure production deployments can safely return to supported earlier SDK versions when needed.

  The 5.1 SDK changes the on-disk index format. If you need to downgrade from Ditto 5.1 or later, migrate through Ditto 5.0.2+. You can also migrate through Ditto 4.14.6+. These versions recognize the updated index format and automatically revert it to the format understood by earlier versions.

  During the downgrade, composite indexes are replaced with single-field indexes, one for each of the composite index's keys.

  See [index migration and downgrade behavior](/dql/indexing#migration) for details. As with any production upgrade, validate the procedure with representative application data before deployment.
</Update>

<Update label="5.1.0 (Swift)" description="Release Date: Aug 18, 2026" tags={["SDK", "Swift"]} className="rn-upd">
  ## Swift-Specific Changes

  The Swift SDK in Ditto 5.1 improves concurrency behavior and diagnostics:

  * Store observers now deliver callbacks on the queue specified by the `deliverOn` parameter.
  * The store observation handler type aliases are now `@Sendable`.
  * Exceptions thrown by application callbacks preserve their original call stack, so crash reporters attribute them to the throwing code instead of to Ditto.
  * Custom identity providers no longer stall the SDK during the first credential refresh.

  DittoSwiftTools adds `DiskUsageInspectorView`, a SwiftUI diagnostic view for inspecting live disk usage, growth trends, health thresholds, per-collection rankings, and document-size distributions.

  ### Platform changes

  * Intel (`x86_64`) Mac support and tvOS support are removed.
  * DittoSwiftTools now requires iOS 15+, Mac Catalyst 15+, or macOS 12+.
  * The bundled `DittoSwiftPresenceViewer` is removed. Use `DittoPresenceViewer` from [DittoSwiftTools](https://github.com/getditto/DittoSwiftTools) instead.
  * The `CBOR` enum and obsolete CBOR-specific store errors are deprecated.

  ## Swift Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `DittoMulticastBetaConfig` and `DittoPeerToPeer.multicastBeta` for configuring the beta reliable UDP multicast transport. The transport is disabled by default and currently supported on iOS and macOS. On iOS, changes requested while sync is active take effect after sync is stopped and successfully started again, when Ditto validates the required platform configuration. (#SDKS-4471)
  * `DittoConnectionType.multicast` enum option representing beta reliable UDP multicast connections. The transport is currently supported on iOS and macOS. (#SDKS-4471)
  * `DiskUsageInspectorView` in DittoSwiftTools: a SwiftUI diagnostic view that shows live total disk usage, health status against an adjustable threshold, growth-rate trend, and projected time to threshold. Opt-in scans add per-collection rankings and a per-collection document-size histogram. (#SUPP-284)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**:

  * DittoSwiftTools minimum platform requirements are raised to iOS 15+, Mac Catalyst 15+, and macOS 12+ (previously iOS 14+, Mac Catalyst 14+, and macOS 11+). The tvOS 15+ requirement is unchanged. (#SDKS-3141)
  * The `DittoStoreObservationHandler`, `DittoStoreObservationHandlerWithSignalNext`, and `DittoSignalNext` type aliases are now `@Sendable`. (#SDKS-3856)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * Uncaught exceptions thrown inside SDK callback closures are now re-raised with the original call stack preserved, so crash reporters attribute the crash to the throwing code instead of reporting a `SIGABRT` inside Ditto. (#SDKS-3483)
  * Custom identity providers using `DITIdentityProvider` no longer stall the SDK the first time a credential refresh is requested. The refresh request is released immediately so the peer is not blocked. (#SDKS-3623)
  * `DittoStore.registerObserver` now dispatches its callback on the queue specified by the `deliverOn` parameter, resolving a crash when used with Swift concurrency. (#SDKS-3856)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * `DittoPeerOS.tvOS`. The case is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
  * The `CBOR` enum and the `DittoError.StoreErrorReason` cases `invalidDocumentStructure(cbor:)` and `nonStringKeyInDocument(key:)`. The SDK no longer produces these; they will be removed in a future version. (#SDKS-4009)

  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**:

  * Support for Intel (`x86_64`) Macs. (#SDKS-2924)
  * The `DittoSwiftPresenceViewer` framework target and podspec. Use the `DittoPresenceViewer` product from [DittoSwiftTools](https://github.com/getditto/DittoSwiftTools) via Swift Package Manager instead. (#SDKS-3141)
  * tvOS platform support. (#SDKS-3944)
</Update>

<Update label="5.1.0 (Kotlin)" description="Release Date: Aug 18, 2026" tags={["SDK", "Kotlin"]} className="rn-upd">
  ## Kotlin-Specific Changes

  ### Data Streams Public Preview

  Android Kotlin applications can now open low-latency, bidirectional byte streams to specific reachable peers on named topics. Reliable streams deliver messages in order or fail the connection. Unreliable streams favor latency and may lose, reorder, or duplicate messages.

  Stream payloads are ephemeral: they are never stored, queried, or synchronized as documents. The preview requires explicit opt-in and NGN configuration. See [Data Streams](/sdk/latest/sync/data-streams) for setup, lifecycle, framing, reconnection, and backpressure guidance.

  ### Observability and lifecycle improvements

  * Kotlin and Kotlin Multiplatform applications can opt into OpenTelemetry spans for DQL, transaction, and attachment operations.
  * Slow Flow collectors now receive a single coalesced, up-to-date result instead of a backlog of stale observer results.
  * DQL argument maps accept additional Kotlin scalar, collection, attachment, and serializable values.
  * Memory and lifecycle fixes cover observers, diffs, attachments, query results, subscriptions, presence, and authentication handlers.

  ## Kotlin Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * Data Streams Public Preview: low-latency, bidirectional byte streams between reachable Android peers. See [Data Streams](/sdk/latest/sync/data-streams) for requirements and usage guidance.
  * `transports_wifi_aware_instant_communication_enabled` system parameter (default: `true`). Set it to `false` to disable Wi-Fi Aware Instant Communication Mode on single-radio devices that experience Wi-Fi instability from multi-channel switching. (#21321)
  * `transports_ble_warm_gatt_cache_enabled` system parameter (Android, default: `false`). When enabled before sync starts, a cleanly disconnected peripheral's GATT connection is kept warm so reconnecting to a known peer skips BLE service discovery. (#NETW-1976)
  * Opt-in OpenTelemetry tracing for DQL queries, transactions, and attachment operations, configured through the new `DittoInstrumentation.configure()` API. (#SDKS-3219)
  * `Enum`, `Char`, `UByte`, `Short`, `Byte`, and `UInt` values can now be individually converted to `DittoCborSerializable` or `DittoJsonSerializable`. (#SDKS-3823)
  * `DittoTransportConfig.PeerToPeer.MulticastBeta` and `DittoTransportConfig.PeerToPeer.multicastBeta` for configuring the beta reliable UDP multicast transport. The transport is disabled by default, with availability limited to supported platforms during the beta. On Android and iOS, changes requested while sync is active take effect after sync is stopped and successfully started again, when Ditto validates platform prerequisites. (#SDKS-4471)
  * `DittoConnectionType.Multicast` enum option representing beta reliable UDP multicast connections. Transport availability is limited to supported platforms during the beta. (#SDKS-4471)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**:

  * `DittoStore.observe()` and `DittoStore.observeWithDiff()` now coalesce writes that arrive while a slow collector is busy, delivering a single up-to-date emission rather than a backlog of stale results. (#SDKS-3836)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Memory leaks**

  * The `DittoPresence.connectionRequestHandler` setter no longer leaks the previous handler and its captured user closure on every reassignment. (#22500)
  * Memory leak in `DittoStore.fetchAttachment` and `DittoStore.newAttachment` on repeated calls for the same attachment. (#SDKS-1898)
  * Memory leak in `DittoStoreObserver` when the supplied `onClose` callback throws. (#SDKS-1898)
  * Memory leaks in `DittoStore.observe`, `DittoStore.collect`, `DittoStore.registerObserver`, and `DittoStore.fetchAttachment` when a callback races with a closing `Ditto` instance. (#SDKS-1898)
  * Memory leak on each access to `DittoConfig.default`, `DittoTransaction.info`, `DittoSync.subscriptions`, `DittoSyncSubscription.queryArgumentsCborData`, and `DittoSyncSubscription.queryArgumentsJsonString`. (#SDKS-1898)
  * Memory leak in the `DittoDiff`-receiving overloads of `DittoStore.observe`, `DittoStore.collect`, and `DittoStore.registerObserver`. (#SDKS-4018)
  * `DittoAuthenticator` now releases its `expirationHandler` binding promptly when the parent `Ditto` instance is closed. (#SPO-651)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Connectivity and transports**

  * Wi-Fi Aware TCP sync on Android no longer breaks when the shared TCP listener is bound to IPv4 or disabled. (#22612)
  * Android BLE central connections with 2M PHY enabled no longer start GATT service discovery while PHY negotiation is still in flight, which could silently leave the connection unusable on devices that are slow to negotiate PHY. (#NETW-1452)
  * Wi-Fi Aware peer-to-peer connections between Android 13+ devices no longer fail when each side independently selects a different security configuration even though a mutually supported one is available. (#NETW-900)
  * `NullPointerException` in `StableBluetoothPlatform.onCharacteristicRead` and `onCharacteristicChanged` on Android 12 and below when `BluetoothGattCharacteristic.getValue()` returns null. (#SPO-862)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Query arguments and types**

  * The DQL query `arguments` parameter is `Map<String, Any?>?` (nullable) again across the Kotlin SDK, matching v4. Pass `null` to indicate no arguments. (#SDKS-3820)
  * `Enum`, `Char`, `Short`, `Byte`, `UByte`, `UInt`, `Array<*>`, and `Set<*>` values can now be used as DQL query arguments. Previously these types caused an `IllegalArgumentException` at runtime. (#SDKS-3823)
  * APIs using `Map<String, Any?>?` arguments now correctly accept `DittoAttachment` and `DittoCborSerializable` values inside the map, matching the documented attachment-insert pattern. (#SDKS-4195)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Miscellaneous**

  * `DittoStoreObserver` race condition where the observer would never close when the event handler threw an exception, caused by the observer reference not being set before the FFI callback fired. (#21923)
  * Android apps with `isMinifyEnabled = true` no longer crash on Ditto initialization. The published Kotlin AARs now bundle the required `consumer-rules.pro` keep rules. (#SDKS-2626)
  * `Ditto.sync().stop()` no longer crashes the app with `ForegroundServiceDidNotStartInTimeException` on Android 14+ when the foreground service is enabled. (#SPO-941)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * `DittoPeerOs.Tvos`. The entry is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
</Update>

<Update label="5.1.0 (Flutter)" description="Release Date: Aug 18, 2026" tags={["SDK", "Flutter"]} className="rn-upd">
  ## Flutter-Specific Changes

  The Flutter SDK in Ditto 5.1 adds observer backpressure through `StoreObserverV2`, `registerObserverV2()`, and `registerObserverWithSignalNext()`, including support on Flutter Web. `Ditto.observeTransportConditions()` and the `DittoSyncPermissions` utility make transport configuration issues and missing Android permissions easier to diagnose.

  Repeated `Store.execute` calls now reuse a long-lived worker, improving throughput for applications that issue many small queries while keeping work off the calling isolate. Fixes to teardown, observer cancellation, presence, authentication, attachments, and memory management improve long-running application behavior.

  ### Requirements

  * Dart 3.5.4 or later
  * Flutter 3.24.5 or later
  * Apple framework targets are arm64-only; Intel (`x86_64`) support is removed.

  ## Flutter Specific Changelog

  <Icon icon="rabbit-running" iconType="solid" horizontal /> **Performance**:

  * `Store.execute` no longer incurs per-call startup overhead when dispatching many small queries, restoring throughput comparable to SDK 4.x while keeping the calling isolate unblocked during query execution. (#SDKS-3879)

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `Store.registerObserverV2()` method for registering store observers with automatic signal-next backpressure that integrates with Dart stream pause/resume. (#SDKS-2389)
  * `Store.registerObserverWithSignalNext()` method for registering store observers with manual signal-next backpressure control. (#SDKS-2389)
  * `StoreObserverV2` class with explicit signal-next backpressure control and a single-subscription `changes` stream. (#SDKS-2389)
  * Flutter Web support for `Store.registerObserverV2()` and `Store.registerObserverWithSignalNext()`. (#SDKS-2429)
  * `DittoSyncPermissions` utility class for querying required and missing Android permissions. (#SDKS-3178)
  * `Store.experimentalSkipExecuteIsolateOffload` flag (default: `false`). When set to `true`, `Store.execute` runs on the calling isolate instead of dispatching to a background isolate, improving throughput for apps that issue many small queries in sequence. (#SDKS-3878)
  * `Ditto.observeTransportConditions()` for surfacing transport health and configuration issues (missing permissions, disabled hardware, unapplied config) reported by the underlying transports. (#SDKS-439)
  * `MulticastBetaConfig` and `PeerToPeer.multicastBeta` for configuring the beta reliable UDP multicast transport. The transport is disabled by default and currently supported on Android and iOS. On Android and iOS, changes requested while sync is active take effect after sync is stopped and successfully started again, when Ditto validates platform prerequisites. (#SDKS-4471)
  * `ConnectionType.multicast` enum option representing beta reliable UDP multicast connections. The transport is currently supported on Android and iOS. (#SDKS-4471)

  <Icon icon="rotate-reverse" iconType="solid" horizontal /> **Changed**:

  * The Android Context is now set automatically in `DittoPlugin.onAttachedToEngine()`. (#SDKS-3178)
  * `Ditto.init()` is now optional; `Ditto.open()` initializes automatically. (#SDKS-3178)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Shutdown and lifecycle**

  * Crashes when force-closing apps that use a connection request handler. (#SDKS-2673)
  * Presence observer callbacks are properly cleaned up when `Ditto.close()` is called, preventing `SIGABRT` crashes during shutdown. (#SDKS-3134)
  * `Ditto.close()` now explicitly stops sync before shutdown. (#SDKS-3617)
  * Auth expiration handler callbacks are properly cleaned up when `Ditto.close()` is called, preventing `SIGABRT` crashes during shutdown. (#SDKS-3626)
  * Custom log callbacks are properly cleaned up when `Ditto.close()` is called, preventing `SIGABRT` crashes during isolate shutdown. (#SDKS-3630)
  * `Ditto.close()` no longer waits an extra \~500 ms when any presence observer was used. (#SDKS-3762)
  * Active `StoreObserver`, `StoreObserverV2`, and `SyncSubscription` instances now report being cancelled after the owning `Ditto` is closed: `isCancelled` reports `true` and `cancel()` is a no-op, as documented. (#SDKS-3916)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Presence and connection handling**

  * `Presence.connectionRequestHandler` no longer leaks memory each time a remote peer requests a connection. (#SDKS-3800)
  * When `Presence.connectionRequestHandler` throws or its returned `Future` rejects, the connection is now denied immediately instead of after a \~10-second wait. (#SDKS-3800)
  * `Presence.observe()` now delivers an immediate first callback with the current presence graph, matching the documented behavior and the other SDKs. (#SDKS-804)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Store observers and memory**

  * Memory leaks that caused the SDK's memory usage to grow over time during normal use of sync subscriptions, presence, query results, and attachments. (#SDKS-3638)
  * A `StoreObserver` registered without an `onChange` callback now defers matching queries against the store until the first listener attaches to `StoreObserver.changes`. Observers that are registered but never listened to no longer emit events or run query work. Observers registered with an `onChange` callback are unaffected. (#SDKS-3878)
  * `StoreObserver.cancel()` no longer crashes the app when called while observer callbacks are in flight, which could occur with active sync over large collections. (#SPO-1022)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed: Miscellaneous**

  * `Authenticator.login()` is now offloaded to a background isolate so a slow authentication webhook no longer blocks the UI on VM platforms. (#SDKS-1888)
  * Android apps with `isMinifyEnabled = true` no longer crash on Ditto initialization. The required Android libraries used by the Flutter plugin now correctly bundle the `consumer-rules.pro` keep rules. (#SDKS-2626)
  * Crash on Flutter Web when reading `Authenticator.developmentProvider`. (#SDKS-2699)
  * `TransportConfigBuilder.setAllPeerToPeerEnabled(false)` no longer leaves AWDL enabled on iOS/macOS or Wi-Fi Aware enabled on Android. (#SDKS-3591)
  * `ditto.sync.isActive` no longer reports `true` after `ditto.auth.logout()`. (#SDKS-649)

  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**:

  * Intel (`x86_64`) support from the Flutter SDK's Apple framework. iOS Simulator and macOS targets are now arm64-only. (#SDKS-4033)
</Update>

<Update label="5.1.0 (React Native)" description="Release Date: Aug 18, 2026" tags={["SDK", "React Native"]} className="rn-upd">
  ## React Native-Specific Changes

  The React Native SDK now initializes correctly on iOS with the new architecture in React Native 0.85+ and Expo SDK 56+. Expo configuration packages are optional peer dependencies, and `Ditto.close()` supports clean teardown and reinitialization.

  The React Native Apple framework is now arm64-only; Intel (`x86_64`) support is removed.

  ## React Native Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `"Multicast"` option to `ConnectionType`, representing beta reliable UDP multicast connections. The transport is not yet available in the React Native SDK; this option is included for cross-SDK compatibility. (#SDKS-4471)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * React Native SDK initialization no longer crashes on iOS when used under React Native 0.85+ (Expo SDK 56+) with the new architecture enabled. (#SDKS-3832)
  * `Ditto.close()` on React Native no longer throws a `TypeError`, so the SDK can be cleanly torn down and reinitialized. (#SDKS-3971)
  * The React Native SDK on iOS no longer crashes because of an unhandled `SIGPIPE` signal. (#22006)
  * Android apps with `isMinifyEnabled = true` no longer crash on Ditto initialization. The required Android libraries used by the React Native SDK now correctly bundle the `consumer-rules.pro` keep rules. (#SDKS-2626)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * The `tvOS` value of the `PeerOS` type. The value is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)

  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**:

  * Intel (`x86_64`) support from the React Native SDK's Apple framework (the `DittoReactNativeIOS` pod). iOS Simulator and macOS targets are now arm64-only. (#SDKS-4033)

  <Icon icon="circle-info" iconType="solid" horizontal /> **Other**:

  * `@expo/config-plugins` and `expo-build-properties` are now declared as optional peer dependencies of `@dittolive/ditto`, so missing Expo plugin dependencies surface as a package-install warning instead of a build-time error. (#SDKS-3832)
</Update>

<Update label="5.1.0 (JavaScript Web)" description="Release Date: Aug 18, 2026" tags={["SDK", "JavaScript Web"]} className="rn-upd">
  ## JavaScript Web-Specific Changes

  The `tvOS` value of the `PeerOS` type is deprecated. It remains available so applications can identify peers running older SDK versions on tvOS.

  JavaScript Web applications also receive the common query, sync, networking, security, and diagnostics improvements described above.

  ## JavaScript Web Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `"Multicast"` option to `ConnectionType`, representing beta reliable UDP multicast connections. The transport is not yet available in the JavaScript SDK; this option is included for cross-SDK compatibility. (#SDKS-4471)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * The `tvOS` value of the `PeerOS` type. The value remains available so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
</Update>

<Update label="5.1.0 (Node.js)" description="Release Date: Aug 18, 2026" tags={["SDK", "Node.js"]} className="rn-upd">
  ## JavaScript Node.js-Specific Changes

  Inspecting a `QueryResultItem` that contains 64-bit integers with `console.log` no longer throws a BigInt JSON serialization error on Node.js versions earlier than 16.14.0 or Electron versions earlier than 19.0.0.

  The `tvOS` value of the `PeerOS` type is deprecated. It remains available so applications can identify peers running older SDK versions on tvOS.

  ## JavaScript Node.js Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `"Multicast"` option to `ConnectionType`, representing beta reliable UDP multicast connections. The transport is not yet available in the JavaScript SDK; this option is included for cross-SDK compatibility. (#SDKS-4471)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * Inspecting a `QueryResultItem` with 64-bit integer fields via `console.log` no longer throws `TypeError: Do not know how to serialize a BigInt` on Node.js versions earlier than 16.14.0 and Electron versions earlier than 19.0.0. (#SDKS-4163)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * The `tvOS` value of the `PeerOS` type. The value remains available so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
</Update>

<Update label="5.1.0 (Java)" description="Release Date: Aug 18, 2026" tags={["SDK", "Java"]} className="rn-upd">
  ## Java-Specific Changes

  The Java SDK in Ditto 5.1 improves observer and authentication-handler cleanup:

  * `DittoStoreObserver` now closes correctly when an event handler throws.
  * Reassigning the presence connection-request handler no longer retains the previous callback.
  * Expiration handlers are released when their parent `Ditto` instance closes.

  `DittoAuthenticationCallback` and `DittoAuthenticator.setCallback()` are removed. Use `DittoAuthenticator.setExpirationHandler()` with an `ExpirationHandler` lambda instead.

  ## Java Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `DittoConnectionType.MULTICAST` enum option representing beta reliable UDP multicast connections. The transport is not yet available in the Java SDK; this option is included for cross-SDK compatibility. (#SDKS-4471)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * `DittoStoreObserver` race condition where the observer would never close when the event handler threw an exception, caused by the observer reference not being set before the FFI callback fired. (#21923)
  * `DittoPresence.setConnectionRequestHandler` no longer leaks the previous handler and its captured user closure on every reassignment. (#22500)
  * `DittoAuthenticator` now releases its `expirationHandler` binding promptly when the parent `Ditto` instance is closed. (#SPO-651)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * `DittoPeerOs.TVOS`. The constant is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)

  <Icon icon="bin-recycle" iconType="solid" horizontal /> **Removed**:

  * The `DittoAuthenticationCallback` interface and `DittoAuthenticator.setCallback()` method. Use `DittoAuthenticator.setExpirationHandler()` with an `ExpirationHandler` lambda instead. (#SDKS-2618)
</Update>

<Update label="5.1.0 (C#)" description="Release Date: Aug 18, 2026" tags={["SDK", "C#"]} className="rn-upd">
  ## C#-Specific Changes

  The .NET SDK adds a `netstandard2.0` target, enabling .NET Framework 4.8 applications to use Ditto.

  DQL arguments now serialize `DateTimeOffset`, `DateOnly`, and `TimeOnly` values as ISO 8601 strings, and convert `decimal` values predictably to double-precision numbers.

  The release also makes transaction completion under concurrent load, presence-handler disposal, and native-library resolution for `packages.config` projects more reliable.

  ## C# Specific Changelog

  <Icon icon="plus" iconType="solid" horizontal /> **Added**:

  * `netstandard2.0` target framework support in the .NET SDK, enabling .NET Framework 4.8 applications to use Ditto. (#SDKS-3000)
  * `DittoConnectionType.Multicast` enum option representing beta reliable UDP multicast connections. The transport is not yet available in the .NET SDK; this option is included for cross-SDK compatibility. (#SDKS-4471)

  <Icon icon="screwdriver-wrench" iconType="solid" horizontal /> **Fixed**:

  * A write transaction (`DittoStore.TransactionAsync`) could intermittently hang under concurrent load while the transaction was completing or the `Ditto` instance was being disposed. (#DEVX-918)
  * Android apps with `isMinifyEnabled = true` no longer crash on Ditto initialization. The required Android libraries used by the .NET MAUI SDK now correctly bundle the `consumer-rules.pro` keep rules. (#SDKS-2626)
  * `DateTimeOffset`, `DateOnly`, and `TimeOnly` values passed as DQL query arguments are now serialized to ISO 8601 strings instead of being silently dropped as empty objects. (#SDKS-3538)
  * `decimal` values passed as DQL query arguments are now serialized as double-precision floating-point numbers instead of a CBOR decimal-fraction array. To keep full precision, convert decimal values to strings before insertion. (#SDKS-3538)
  * The `Ditto.Native.iOS` and `Ditto.Native.Maccatalyst` NuGet packages no longer bundle the Sentry Cocoa framework. (#SPO-1086)
  * The `DittoPresence.ConnectionRequestHandler` callback could crash if a connection request arrived during or after disposal. (#SPO-632)
  * Projects using `packages.config` now correctly resolve the native library, preventing `DllNotFoundException`. The manual workaround of including `dittoffi.dll` is no longer needed. (#SPO-963)

  <Icon icon="triangle-exclamation" iconType="solid" horizontal /> **Deprecated**:

  * The `DittoDiff.FromCborObject()` method. It will be removed in a future major version. (#SDKS-3139)
  * `DittoPeerOS.tvOS`. The value is retained so peers running older SDK versions on tvOS can still be identified in the presence graph. (#SDKS-3944)
</Update>

<ReleaseNotesPager />


## Related topics

- [C++ Release Notes](/sdk/v4/release-notes/cpp.md)
- [C# Release Notes](/sdk/v4/release-notes/c-sharp.md)
- [Release Notes](/cloud/release-notes-hub.md)
