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

# App SDK

export const ChangelogSubscribe = () => {
  const RECAPTCHA_SITEKEY = "6LclumYsAAAAAEDZPKR5FkJS_zFx9hCaLy2Lbw0C";
  const [status, setStatus] = useState("idle");
  const [isPending, setIsPending] = useState(false);
  const [isEnabled, setIsEnabled] = useState(false);
  useEffect(() => {
    const enabled = new URLSearchParams(window.location.search).has("ff_changelog");
    setIsEnabled(enabled);
    if (enabled && !document.querySelector("script[data-recaptcha]")) {
      const script = document.createElement("script");
      script.src = `https://www.google.com/recaptcha/api.js?render=${RECAPTCHA_SITEKEY}`;
      script.dataset.recaptcha = "";
      document.head.appendChild(script);
    }
  }, []);
  const subscribe = async event => {
    event.preventDefault();
    setIsPending(true);
    const email = new FormData(event.currentTarget).get("email");
    try {
      await new Promise(resolve => window.grecaptcha.ready(resolve));
      const token = await window.grecaptcha.execute(RECAPTCHA_SITEKEY, {
        action: "email_cta"
      });
      const response = await fetch("https://attio.com/api/forms/email-cta", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Captcha-Token": token
        },
        body: JSON.stringify({
          email,
          customerEvent: "changelog_subscribed",
          type: "developer"
        })
      });
      const result = await response.json();
      setStatus(result.status === "success" ? "success" : "error");
    } catch {
      setStatus("error");
    } finally {
      setIsPending(false);
    }
  };
  if (!isEnabled) return null;
  return <div className="not-prose flex flex-col items-center text-center my-12">
      <h2 className="text-5xl font-semibold tracking-tight text-gray-900 dark:text-gray-100">
        What’s new
      </h2>
      <p className="mt-4 text-lg text-gray-700 dark:text-gray-300">
        Get updates on Attio’s developer tools in your inbox.
      </p>
      {status === "success" ? <p className="mt-6 text-gray-700 dark:text-gray-300">
          Thanks, you're subscribed to changelog updates.
        </p> : <form onSubmit={subscribe} className="mt-6 flex flex-wrap gap-3 items-start justify-center w-full">
          <label htmlFor="changelog-email" className="sr-only">
            Email
          </label>
          <div className="w-full max-w-sm">
            <input id="changelog-email" name="email" type="email" required maxLength={256} placeholder="Your email address" className="w-full rounded-xl border border-gray-300 dark:border-white/15 bg-white dark:bg-white/5 px-4 py-2.5 text-base text-gray-900 dark:text-gray-100 placeholder:text-gray-500 dark:placeholder:text-gray-400 focus:outline-none focus:border-gray-900 dark:focus:border-white" />
            {status === "error" && <p role="alert" className="mt-2 px-4 text-sm text-red-600 dark:text-red-400" style={{
    textAlign: "start"
  }}>
                Couldn't subscribe. Use a valid email and try again.
              </p>}
          </div>
          <button type="submit" disabled={isPending} aria-busy={isPending} className="w-36 rounded-xl bg-gray-900 dark:bg-white px-5 py-2.5 text-base font-medium text-white dark:text-gray-900 hover:bg-gray-700 dark:hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed">
            {isPending ? "Subscribing…" : "Subscribe"}
          </button>
        </form>}
    </div>;
};

<ChangelogSubscribe />

<Update label="v1.1.3" description="2026-10-02">
  ### Changed

  * Settings pages can now render any component from `attio/client`, such as `Stack`, `Typography`
    or `Table`, alongside the components returned by `Settings.useForm`. The regular `useForm`,
    `Button`, `EmptyState` and `LoadingState` are not supported on settings pages: linting flags
    them, `attio build` fails, and they do not render.
  * New apps use ESLint v10. The lint rules in the default app template are unchanged, plus the
    rules ESLint v10 adds to its recommended set: `no-unassigned-vars`, `no-useless-assignment` and
    `preserve-caught-error`.
</Update>

<Update label="v1.1.2" description="2026-09-22">
  ### Added

  * Added [`Image`](https://docs.attio.com/sdk/components/image), which renders an image from an
    HTTPS URL and handles its loading and failure states. Size it with `size` (`small`, `medium`,
    `large` or `fill`) and `aspectRatio`; `fit` chooses between cropping and containing the image.
</Update>

<Update label="v1.1.1" description="2026-09-21">
  ### Added

  * [`Table.Cell`](https://docs.attio.com/sdk/components/table) now accepts `Button` components. A
    cell that contains buttons must contain only buttons. A single button renders inline; two or more
    collapse into a menu on the cell, where picking an action runs that button's `onClick`.
</Update>

<Update label="v1.1.0" description="2026-09-14">
  ### Added

  * Added [pages](https://docs.attio.com/sdk/pages/overview). `Extensions.definePage` defines a full
    page of your app, default-exported from `src/app/pages/<slug>/page.tsx`. Pages are listed under
    Apps in the workspace sidebar and served at `/{workspaceSlug}/apps/{appSlug}/<slug>`.
  * Added [record tabs](https://docs.attio.com/sdk/extensions/record-tab).
    `Extensions.defineExtension({type: "record-tab", ...})` renders a React component as a tab on the
    record pages of the objects you choose.
  * Added [navigation](https://docs.attio.com/sdk/navigation/overview). `Destinations` builds a place
    inside Attio to point the user at: one of your app's pages, a record (optionally on a given tab)
    or your app's settings page. `navigate` takes the user there and `AttioLink` renders a link to it.
  * Added [`Chart`](https://docs.attio.com/sdk/guides/using-charts), with `Chart.Bar`, `Chart.Line`,
    `Chart.Pie`, `Chart.Metric`, `Chart.Funnel` and `Chart.Loading`.
  * Added the layout components [`Card`](https://docs.attio.com/sdk/components/card),
    [`Grid`](https://docs.attio.com/sdk/components/grid),
    [`Stack`](https://docs.attio.com/sdk/components/stack) and
    [`EmptyState`](https://docs.attio.com/sdk/components/empty-state).
</Update>

<Update label="v1.0.4" description="2026-09-10">
  ### Fixed

  * Fixed `Badge` and `StatusBadge` stretching to the full width of their container when placed directly inside a `Section`.
  * Fixed `Badge` rendering as plain text inside dialogs. It now renders as a badge anywhere `StatusBadge` does.

  ### Changed

  * The default app template no longer includes Biome. Newly created apps lint with ESLint
    (`eslint.config.js`, replacing `eslint.attio.config.js`) and format with Prettier.
</Update>

<Update label="v1.0.3" description="2026-09-08">
  ### Changed

  * Production builds (`attio build` and `attio version create`) now minify dependency code from
    `node_modules` and build it with `process.env.NODE_ENV` set to `"production"`, so dev-only
    branches in dependencies are dropped. This shrinks the uploaded bundle. Your own application code
    is left unminified, and `attio dev` is unchanged.
</Update>

<Update label="v1.0.2" description="2026-09-03">
  ### Added

  * Added `ConfigSchema.record(...)` for config that holds any number of keys under a single value
    schema, like a TypeScript `Record<string, TValue>`.
</Update>

<Update label="v1.0.1" description="2026-08-24">
  ### Added

  * Added linting that flags components imported from `attio/client` inside
    `Settings.defineWorkspacePage` callbacks, where components returned by `Settings.useForm` must be
    used instead.

  ### Fixed

  * Fixed a type error when binding `CollectionInput` to an optional array
    (`ConfigSchema.array(...).optional()`).
  * Fixed dev server and bundling issues on `Windows` operating system
</Update>

<Update label="v1.0.0" description="2026-08-11">
  The App SDK is stable. `attio` and `create-attio` were previously published as dated
  `0.0.1-experimental.<date>` builds. From 1.0.0 they follow
  [semantic versioning](https://semver.org/spec/v2.0.0.html), and are always released together at the
  same version.

  ### What stable means

  * Everything exported from `attio`, `attio/client` and `attio/server` will not change incompatibly
    within the 1.x line unless marked as `experimental` or `deprecated`. Breaking changes require a
    new major version.
  * Additive changes ship as minor versions and fixes ship as patches, so `^1.0.0` picks up both.
  * Individual APIs that are still taking shape are marked as experimental in their documentation and
    carry an `Experimental_` prefix. They are excluded from the guarantee above and can change or be
    removed in a minor release. Apps submitted for review
    [must not use them](https://docs.attio.com/share/code-review).

  ### What you can build

  * [Extensions](https://docs.attio.com/sdk/extensions/overview): record actions, bulk record actions,
    record widgets, object actions and call recording text actions.
  * [Workflow blocks](https://docs.attio.com/sdk/workflow-blocks/overview): triggers and steps that
    appear in Attio's workflow builder, each with its own configurator UI.
  * [Workspace settings](https://docs.attio.com/sdk/settings/overview): a settings schema and a
    settings page for your app.
  * [Forms](https://docs.attio.com/sdk/guides/building-forms) and a component library for extension UI.
  * [Queries against Attio data](https://docs.attio.com/sdk/guides/querying-attio-data), server
    functions, [inbound webhooks](https://docs.attio.com/sdk/guides/receiving-http-requests),
    [connections to external services](https://docs.attio.com/sdk/guides/authenticating-to-external-services)
    and a key-value store.

  If you have not built on Attio before, start with
  [creating an app](https://docs.attio.com/sdk/guides/creating-an-app).
</Update>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.