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

# Upcoming Features

> See what we're working on at Elementum

export const EmailSubscriptionPicker = ({webhookUrl, logUrl = "https://script.google.com/macros/s/AKfycbwYKNyp9YTtV7fhwZOKwePB-0_cOz8jOD1kBLEprmvbTD5LBPn_iSuYagvaWlxbmtg/exec", heading = "Subscribe to email updates", description, buttonLabel = "Subscribe", lists = [{
  key: "ga-releases",
  label: "General Availability Release notifications",
  description: "Get an email when a new release ships — about twice a month."
}, {
  key: "upcoming-features",
  label: "Upcoming Feature updates",
  description: "Hear about new Beta features being tested — expect a few updates each week."
}]}) => {
  const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  const MAX_TEXT_LENGTH = 120;
  const logSubmissionAttempt = payload => {
    if (!logUrl) return;
    try {
      fetch(logUrl, {
        method: "POST",
        mode: "no-cors",
        keepalive: true,
        headers: {
          "Content-Type": "text/plain;charset=UTF-8"
        },
        body: JSON.stringify(payload)
      }).catch(() => {});
    } catch (e) {}
  };
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [company, setCompany] = useState("");
  const [email, setEmail] = useState("");
  const [website, setWebsite] = useState("");
  const [selectedLists, setSelectedLists] = useState(() => lists.map(l => l.key));
  const [status, setStatus] = useState("idle");
  const [errorMessage, setErrorMessage] = useState("");
  const isSubmitting = status === "loading";
  const [uid] = useState(() => `esp-${Math.random().toString(36).slice(2, 9)}`);
  const firstNameId = `${uid}-first-name`;
  const lastNameId = `${uid}-last-name`;
  const companyId = `${uid}-company`;
  const emailId = `${uid}-email`;
  const websiteId = `${uid}-website`;
  const statusId = `${uid}-status`;
  const toggleList = key => {
    setSelectedLists(prev => prev.includes(key) ? prev.filter(k => k !== key) : [...prev, key]);
  };
  const handleSubmit = async event => {
    event.preventDefault();
    setErrorMessage("");
    if (website.trim().length > 0) {
      setStatus("success");
      setFirstName("");
      setLastName("");
      setCompany("");
      setEmail("");
      return;
    }
    const trimmedFirstName = firstName.trim();
    const trimmedLastName = lastName.trim();
    const trimmedCompany = company.trim();
    const trimmedEmail = email.trim();
    if (!trimmedFirstName || trimmedFirstName.length > MAX_TEXT_LENGTH) {
      setStatus("error");
      setErrorMessage("Please enter your first name (up to 120 characters).");
      return;
    }
    if (!trimmedLastName || trimmedLastName.length > MAX_TEXT_LENGTH) {
      setStatus("error");
      setErrorMessage("Please enter your last name (up to 120 characters).");
      return;
    }
    if (!trimmedCompany || trimmedCompany.length > MAX_TEXT_LENGTH) {
      setStatus("error");
      setErrorMessage("Please enter your company (up to 120 characters).");
      return;
    }
    if (!EMAIL_REGEX.test(trimmedEmail)) {
      setStatus("error");
      setErrorMessage("Please enter a valid email address.");
      return;
    }
    if (selectedLists.length === 0) {
      setStatus("error");
      setErrorMessage("Select at least one list to subscribe to.");
      return;
    }
    if (!webhookUrl) {
      setStatus("error");
      setErrorMessage("This form is not configured yet. Please try again later.");
      return;
    }
    setStatus("loading");
    const payload = {
      firstName: trimmedFirstName,
      lastName: trimmedLastName,
      company: trimmedCompany,
      email: trimmedEmail,
      lists: selectedLists,
      action: "subscribe",
      source: typeof window !== "undefined" ? window.location.pathname : "",
      userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
      submittedAt: new Date().toISOString()
    };
    logSubmissionAttempt(payload);
    try {
      const response = await fetch(webhookUrl, {
        method: "POST",
        mode: "no-cors",
        headers: {
          "Content-Type": "text/plain;charset=UTF-8"
        },
        body: JSON.stringify(payload)
      });
      if (response.type !== "opaque" && !response.ok) {
        throw new Error(`Request failed with status ${response.status}`);
      }
      setStatus("success");
      setFirstName("");
      setLastName("");
      setCompany("");
      setEmail("");
      setSelectedLists(lists.map(l => l.key));
    } catch (err) {
      setStatus("error");
      setErrorMessage("We couldn't complete your subscription right now. Please try again in a moment.");
    }
  };
  const inputClass = "w-full rounded-md border border-zinc-300 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 focus:border-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600/30 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500";
  const labelClass = "block text-sm font-medium text-zinc-700 dark:text-zinc-200 mb-1";
  const checkboxLabelClass = "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-200";
  const checkboxClass = "mt-0.5 h-4 w-4 rounded border-zinc-300 text-blue-600 focus:ring-blue-600/30 dark:border-zinc-700 dark:bg-zinc-900";
  return <div className="email-form not-prose my-4 rounded-xl border border-zinc-200 bg-zinc-50 p-5 dark:border-zinc-800 dark:bg-zinc-900/40">
      <h3 className="m-0 text-base font-semibold text-zinc-900 dark:text-zinc-50">
        {heading}
      </h3>
      {description ? <p className="mt-1 mb-0 text-sm text-zinc-600 dark:text-zinc-300">{description}</p> : null}

      <form onSubmit={handleSubmit} noValidate className="mt-4 flex flex-col gap-3">
        <div className="grid gap-3 md:grid-cols-2">
          <div>
            <label htmlFor={firstNameId} className={labelClass}>
              First name
            </label>
            <input id={firstNameId} type="text" name="firstName" autoComplete="given-name" required maxLength={MAX_TEXT_LENGTH} value={firstName} onChange={e => setFirstName(e.target.value)} disabled={isSubmitting} className={inputClass} placeholder="Jane" />
          </div>
          <div>
            <label htmlFor={lastNameId} className={labelClass}>
              Last name
            </label>
            <input id={lastNameId} type="text" name="lastName" autoComplete="family-name" required maxLength={MAX_TEXT_LENGTH} value={lastName} onChange={e => setLastName(e.target.value)} disabled={isSubmitting} className={inputClass} placeholder="Doe" />
          </div>
        </div>

        <div className="grid gap-3 md:grid-cols-2">
          <div>
            <label htmlFor={companyId} className={labelClass}>
              Company
            </label>
            <input id={companyId} type="text" name="company" autoComplete="organization" required maxLength={MAX_TEXT_LENGTH} value={company} onChange={e => setCompany(e.target.value)} disabled={isSubmitting} className={inputClass} placeholder="Acme Corp" />
          </div>
          <div>
            <label htmlFor={emailId} className={labelClass}>
              Work email
            </label>
            <input id={emailId} type="email" name="email" autoComplete="email" required value={email} onChange={e => setEmail(e.target.value)} disabled={isSubmitting} className={inputClass} placeholder="jane@acme.com" />
          </div>
        </div>

        <fieldset className="mt-1 flex flex-col gap-2">
          <legend className={labelClass}>Subscribe me to:</legend>
          {lists.map(list => {
    const checkboxId = `${uid}-list-${list.key}`;
    return <label key={list.key} htmlFor={checkboxId} className={checkboxLabelClass}>
                <input id={checkboxId} type="checkbox" name="lists" value={list.key} checked={selectedLists.includes(list.key)} onChange={() => toggleList(list.key)} disabled={isSubmitting} className={checkboxClass} />
                <span className="flex flex-col">
                  <span className="font-medium">{list.label}</span>
                  {list.description ? <span className="text-zinc-500 dark:text-zinc-400">{list.description}</span> : null}
                </span>
              </label>;
  })}
        </fieldset>

        <div aria-hidden="true" style={{
    position: "absolute",
    left: "-10000px",
    top: "auto",
    width: "1px",
    height: "1px",
    overflow: "hidden"
  }}>
          <label htmlFor={websiteId}>Website (leave blank)</label>
          <input id={websiteId} type="text" name="website" tabIndex={-1} autoComplete="off" value={website} onChange={e => setWebsite(e.target.value)} />
        </div>

        <button type="submit" disabled={isSubmitting} className="mt-1 inline-flex w-full items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-blue-500 dark:hover:bg-blue-400">
          {isSubmitting ? "Subscribing…" : buttonLabel}
        </button>

        <div id={statusId} role="status" aria-live="polite" className="min-h-[1.25rem] text-sm">
          {status === "success" ? <span className="text-emerald-700 dark:text-emerald-400">
              Thanks — your subscription was submitted.
            </span> : null}
          {status === "error" ? <span className="text-red-700 dark:text-red-400">{errorMessage}</span> : null}
        </div>
      </form>

      <p className="mt-3 mb-0 text-xs text-zinc-500 dark:text-zinc-400">
        Already subscribed?{" "}
        <a href="/release-notes/unsubscribe" className="underline hover:text-zinc-700 dark:hover:text-zinc-200">
          Unsubscribe
        </a>
        .
      </p>
    </div>;
};

We're always trying to improve our platform for our customers. Here, you can get an inside look at what's currently in beta at Elementum.

Remember, this list is provided as a heads-up—not as an invitation to test or a promise of what capabilities may be included. Most customers will not see these features until general availability. For what's available today, see the [most recent release notes](/release-notes/june-2026).

## What to expect

<Info>
  **Heads-up, not a commitment.** The information below explains how we treat beta features and why delivery and availability can change.
</Info>

* **Currently being tested.** These features are in active development and testing. Behavior and scope may change.
* **No guaranteed delivery date.** We do not commit to a specific release date for any feature listed here.
* **No guarantee of delivery.** Priorities shift based on resources and customer needs. A feature in beta may be delayed, changed, or not released to General Availability.
* **Limited availability.** Some features may be tested by select customers before being released to General Availability. Broader access is not guaranteed until a feature is generally available.
* **Labs badge.** Experimental features available in the platform but still being validated are marked with an orange **Labs** badge in the Elementum UI. Treat these the same way—behavior, scope, and availability may change before General Availability.

## Features in beta

This list is updated as new beta features become available to give you an idea of upcoming improvements likely to occur in Elementum. The colored badges indicate the area of Elementum where you will see the update.

<Update label="Updated Agent Overview Page" rss={{ title: "Updated Agent Overview Page - June 29, 2026", description: "The agent detail page now opens to a refreshed overview that surfaces read-only configuration settings at a glance, matching the design of the gateway page. Click into any section to jump straight into editing that part of the agent's configuration." }}>
  ### Updated Agent Overview Page <Badge color="blue" size="sm">Intelligence</Badge>

  The agent detail page now opens to a refreshed overview that surfaces read-only configuration settings at a glance, matching the design of the gateway page. Click into any section to jump straight into editing that part of the agent's configuration.
</Update>

<Update label="Org Default Column Layouts for Table Widgets" rss={{ title: "Org Default Column Layouts for Table Widgets - June 26, 2026", description: "Dashboard admins can now open Display Settings on a Table View Widget to choose which columns appear and save that layout as the default for everyone in the org. Individual users can still adjust their own column view on top of the default whenever they need to." }}>
  ### Org Default Column Layouts for Table Widgets <Badge color="orange" size="sm">Apps</Badge>

  Dashboard admins can now open Display Settings on a Table View Widget to choose which columns appear and save that layout as the default for everyone in the org. Individual users can still adjust their own column view on top of the default whenever they need to.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** App Administrators.

      **Where to find it:** **App** → **Dashboard View** → **Table Widget** → <img src="https://mintcdn.com/elementum/m1Z8iohbYDb7uicT/images/icons/ellipsis-vertical.svg?fit=max&auto=format&n=m1Z8iohbYDb7uicT&q=85&s=95eb1df478aa639b46276122cd086257" alt="More icon" className="inline-ui-icon" width="24" height="24" data-path="images/icons/ellipsis-vertical.svg" /> **More** → **Display Settings**.

      **Configuration steps:**

      1. Open a dashboard view in your app and click the <img src="https://mintcdn.com/elementum/m1Z8iohbYDb7uicT/images/icons/ellipsis-vertical.svg?fit=max&auto=format&n=m1Z8iohbYDb7uicT&q=85&s=95eb1df478aa639b46276122cd086257" alt="More icon" className="inline-ui-icon" width="24" height="24" data-path="images/icons/ellipsis-vertical.svg" /> **More** icon on the table widget you want to configure.
      2. Select **Display Settings**.
      3. At the top of the popup, select the **Everyone** tab to manage the layout for all users with permission to view the widget.
      4. Choose which columns are **Visible** or **Hidden** and arrange them in the order you want.
      5. Click **Save for Everyone** to publish the layout as the org default.

      **Behavior notes:**

      * Individual users can still customize their own column view on top of the org default whenever they need to.
      * If a user has personalized the layout, they can return to **Display Settings** and click **Reset to Default** to restore the admin-defined view.
      * **Reset to Default** cannot be undone—if a user wants their previous personalized layout back, they will need to recreate it manually.
    </Accordion>
  </AccordionGroup>
</Update>

<Update label="Conditional Record View" rss={{ title: "Conditional Record View - June 11, 2026", description: "Record detail pages now render a dynamic layout that shows the fields, sections, and components defined for each record type, including rich content like attachments, tags, assignees, and rich text. Admins can add conditional visibility rules driven by other field values, stage, or user and group permissions so people see only the information relevant to the record in front of them." }}>
  ### Conditional Record View <Badge color="orange" size="sm">Apps</Badge>

  Record detail pages now render a dynamic layout that shows the fields, sections, and components defined for each record type, including rich content like attachments, tags, assignees, and rich text. Admins can add conditional visibility rules driven by other field values, stage, or user and group permissions so people see only the information relevant to the record in front of them.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** App Administrators.

      **Where to find it:** **App** → **User Interface** → **Record Details Layout**.

      **Configuration steps:**

      1. Open the **Record Details Layout** and switch to **Dynamic Layout** at the top of the page.
      2. Click the **Import** button in the top-right corner to import the field configuration from the static layout, then click **Import and Replace**.
      3. Continue editing the layout as desired by choosing to add or update components or fields.
      4. In the **Edit** popup for a field or component, click the **Conditional Visibility** tab.
      5. Edit the filters to choose when the field or component is visible to your users (for example, based on other field values, stage, or user and group permissions).
      6. Click **Save** when finished.
      7. Click **Layout Inactive** at the top of the page to make the dynamic layout the visible layout for your users.

      **Behavior notes:**

      * The dynamic layout coexists with the static layout—you can keep building and previewing the dynamic layout while the static layout remains active, then activate the dynamic layout when you're ready.
      * Conditional visibility rules are evaluated per user and per record, so the same record can show different fields and components depending on who is viewing it and the record's current state.
    </Accordion>
  </AccordionGroup>
</Update>

<Update label="Customizable Side Nav" rss={{ title: "Customizable Side Nav - June 8, 2026", description: "Pin the Apps, Agents, Conversations, Views, Elements, Tables, and Tasks you reach for most to a dedicated section of the side nav, hide the items and sections you don't use, and drag to reorder your pins. Search makes adding pins fast, and your layout persists across sessions, page navigation, and logins." }}>
  ### Customizable Side Nav <Badge color="teal" size="sm">Navigation</Badge>

  Pin the Apps, Agents, Conversations, Views, Elements, Tables, and Tasks you reach for most to a dedicated section of the side nav, hide the items and sections you don't use, and drag to reorder your pins. Search makes adding pins fast, and your layout persists across sessions, page navigation, and logins.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** All Users (each user personalizes their own side nav).

      **Where to find it:** The left navigation menu in Elementum.

      **Configuration steps:**

      *Pin an object:*

      1. Click the **+** icon in the left navigation menu.
      2. Select the type of object you'd like to pin (for example, **Apps**, **Views**, **Agents**, **Chats**, **Elements**, **Tasks**, or **Tables**).
      3. Choose the specific object from the list by searching or scrolling.

      *Rearrange or remove pinned items:*

      1. Click the **+** icon, then click **Manage**.
      2. In the **Pinned** section, drag and drop a pin to move it, or click the <img src="https://mintcdn.com/elementum/WBtBRoedx7pT-MEg/images/icons/trash.png?fit=max&auto=format&n=WBtBRoedx7pT-MEg&q=85&s=5d4518c1ae0f6ec1d34fb58721c472ff" alt="Trash icon" className="inline-ui-icon" width="24" height="24" data-path="images/icons/trash.png" /> **Trash** icon to remove it from your main navigation.

      *Personalize Main nav and System nav sections:*

      1. From the **Manage nav** panel, open the **Main** or **System** section.
      2. Use the checkboxes next to individual items to show or hide them, or use the section's checkbox to toggle the entire section at once.

      **Behavior notes:**

      * Your side nav layout persists across sessions, page navigation, and logins.
      * Hidden items remain accessible through search and direct links; they are only removed from the visible side nav.
    </Accordion>
  </AccordionGroup>
</Update>

<Update label="Custom Platform Branding" rss={{ title: "Custom Platform Branding - June 5, 2026", description: "Organization administrators can set a custom accent color that carries across the platform, including primary buttons, links, active states in navigation, icon hover states, checkboxes, and the login flow. Branding the workspace helps external collaborators immediately recognize which customer they're working in, reducing wrong-account mistakes." }}>
  ### Custom Platform Branding <Badge color="purple" size="sm">Org Settings</Badge>

  Organization administrators can set a custom accent color that carries across the platform, including primary buttons, links, active states in main and sub navigation, icon hover states, checkboxes, and the login flow. Branding the workspace helps external collaborators immediately recognize which customer they're working in, reducing wrong-account mistakes.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** Organization Administrators.

      **Where to find it:** **Organization Settings** → **General** → **Platform Branding**.

      **Configuration steps:**

      1. In the **Platform Branding** section, check the **Enable Custom Branding** box.
      2. Upload your **Company Logo** and a dedicated **Login Page Logo** (displayed above the email field on the login screen).
      3. Set the **Accent Color** for both **Light Mode** and **Dark Mode** to match your organization's style guide.
      4. Use the **Live Preview** on the right side of the page to confirm how your accent color appears across primary buttons, links, navigation states, checkboxes, and focus states in each theme.
      5. Click **Save** when finished.

      **Behavior notes:**

      * The Company Logo is managed in General Settings above the Platform Branding section and is reused across the platform.
      * Accent colors apply globally to primary buttons, links, active navigation states, icon hover states, checkboxes, and the login flow.
    </Accordion>
  </AccordionGroup>
</Update>

<Update label="Studio Agents: Flow Builder" rss={{ title: "Studio Agents: Flow Builder - May 21, 2026", description: "A new coding-based agent type that builds automations, agents, and flows in Elementum through conversation. Describe what you need, and the Studio Agent generates the flow—lowering the barrier to creating complex workflows on the platform." }}>
  ### Studio Agents: Flow Builder <Badge color="blue" size="sm">Intelligence</Badge>

  A new coding-based agent type that builds automations, agents, and flows in Elementum through conversation. Describe what you need, and the Studio Agent generates the flow—lowering the barrier to creating complex workflows on the platform.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** App Administrators.

      **Where to find it:** **App** → **Flows**.

      **Configuration steps:**

      1. Open the **Flows** page and use the natural language chat at the top to describe the workflow you want to build. Explain the stages and automations you'd like included.
      2. Click the **Send** icon to hand the request off to the Studio Agent.
      3. Follow along in the chat pop-up as the agent writes the TypeScript that builds the flow. Click any action to see more details about what the agent did.
      4. Use the **Preview** pane to verify the workflow is being built as expected.
      5. If something doesn't look right, keep chatting with the agent to refine the flow.
      6. Click **Publish** when the flow is ready. After publishing, you can manually edit each step of the flow.

      **Behavior notes:**

      * In-progress builds appear under **Studio Agent Sessions** on the **Flows** page so you can resume a draft session later.
      * Pre-built prompt templates (for example, **Expense approval**, **Customer onboarding**, **Incident triage**) are available below the chat as starting points.
    </Accordion>
  </AccordionGroup>
</Update>

<Update label="SIP Trunking Provider" rss={{ title: "SIP Trunking Provider - April 9, 2026", description: "Bring your own telephony provider to Elementum by connecting SIP trunk-enabled phone providers to power voice agents." }}>
  ### SIP Trunking Provider <Badge color="purple" size="sm">Org Settings</Badge>

  Bring your own telephony provider to Elementum by connecting SIP trunk-enabled phone providers to power voice agents.

  <AccordionGroup>
    <Accordion title="Context & configuration">
      **Who can configure it:** Organization Administrators.

      **Where to find it:** **Organization Settings** → **Integrations** → **Phone**.

      **Prerequisites:**

      * A SIP trunk-enabled account with a supported provider (for example, Twilio, Telnyx, or any standards-compliant SIP provider).
      * Credentials and the SIP URI/domain from your provider.

      **Configuration steps:**

      1. Add a new phone provider and select **SIP Trunk** as the connection type.
      2. Enter a **Name**, add one or more **SIP Gateways** (the IP addresses or hostnames that will handle call routing), and select an **Authentication Type**.
         * **IP Based** — authenticate using the gateway's IP address; no additional credentials required.
         * **Credentials** — enter a username and password.
      3. Use the provided **callback URL** to complete the provider configuration.
      4. Test the connection to confirm Elementum can register with the trunk.
      5. Assign inbound/outbound phone numbers from your provider to specific voice agents.

      **Behavior notes:**

      * Using your own SIP trunk lets you keep existing carrier contracts and phone numbers while routing calls through Elementum's voice agent stack.
      * Per-call audio, transcripts, and telemetry still flow through Elementum for logging and agent memory.
      * Outbound caller ID and number porting are handled by your telephony provider, not by Elementum.
    </Accordion>
  </AccordionGroup>
</Update>

## Stay in the loop

<EmailSubscriptionPicker webhookUrl="https://elementum.elementum.io/api/v1/webhooks/38fad8b3-0323-44ba-be52-d622b1a29289" heading="Subscribe to release email updates" description="Choose what you'd like to hear about. Both lists are selected by default." />
