Skip to main content
If you need to build a fully custom UI, use the Daily call object.

Feature configuration

Many parts of the in-call experience can be customized by changing domain, room, or meeting token properties.

Domain and room properties

If a property is set at both the domain and room level, the room setting takes precedence.
Domain and room level properties
enable_people_ui
enable_prejoin_ui
enable_network_ui
enable_emoji_reactions
enable_hand_raising

Room and meeting token properties

If a property is set at both the room and meeting token level, the meeting token setting takes precedence.
Room and meeting token level properties
enable_screenshare
enable_recording
start_with_video_off
These UI-related properties can be specified at the room level:
Room level properties
enable_knocking
enable_chat
And these UI-related properties can be configured using meeting tokens:
Meeting token level properties
is_owner
start_cloud_recording
close_tab_on_exit
redirect_on_meeting_exit (can also be set at the domain level)

Pre-join experience

If you’re using Daily Prebuilt, setting enable_prejoin_ui to true at either the room or domain level turns on a lobby experience for call participants. They can check their camera and microphone before joining a call, and also wait to be admitted by the host if the room is private and they are joining without a token. You can also take advantage of a suite of Daily methods and events geared towards implementing a custom lobby if you’re building on top of the Daily call object. With these methods and events you can: These methods and events are also all available when using Daily Prebuilt if you’d like to build similar waiting room experiences around the call embed.

Leave behavior

Two properties control what happens when a user leaves a meeting from a standalone browser tab: close_tab_on_exit and redirect_on_meeting_exit. If close_tab_on_exit is set to true, the browser tab closes when the user clicks the leave meeting button in the in-call menu bar. If the redirect_on_meeting_exit property is set, the property value will be interpreted as a URL that the browser should redirect to when the user clicks the leave meeting button. A query string that includes the parameter recent-call=<domain>/<room> is appended to the URL.
By default, there is no leave meeting button when you embed Daily Prebuilt. You control that meeting flow with your own UI code outside the iframe. Or you can add a leave call button to the embedded UI with our Javascript API.

Branding

By default, Daily branding is shown in the call UI. Call UI with Daily branding visible There is no associated cost to remove Daily branding from the call UI; you simply need to add your credit card information to your account and hide Daily branding via the hide_daily_branding domain property.

JavaScript API customization

The following customizations are applied via the daily-js API rather than room or domain configuration. They require a call instance created with createFrame() or wrap(). For color theming, see Customizing Daily Prebuilt calls with color themes.

Custom CSS

Inject CSS into the Prebuilt iframe for styling beyond what the theme colors allow. You can pass CSS options at construction time via createFrame():
const call = Daily.createFrame({
  bodyClass: 'my-call-theme',
  cssFile: 'https://example.com/daily-overrides.css',
  cssText: `
    .daily-video-tile { border-radius: 12px; }
    .daily-controls { background: transparent; }
  `,
});
Or update CSS after construction using loadCss():
call.loadCss({
  bodyClass: 'updated-theme',
  cssFile: 'https://example.com/updated.css',
  cssText: '.my-class { color: red; }',
});
bodyClass
string
CSS class name(s) added to the <body> element inside the Prebuilt iframe.
cssFile
string
URL of an external stylesheet to load inside the iframe.
cssText
string
Inline CSS string injected as a <style> tag inside the iframe.

Layout configuration

Control the number of video tiles shown in grid view via layoutConfig at construction time:
const call = Daily.createFrame({
  layoutConfig: {
    grid: {
      maxTilesPerPage: 9,
      minTilesPerPage: 1,
    },
  },
});

Custom integrations

Embed external web apps as a sidebar panel or main area overlay inside Prebuilt. Define integrations with setCustomIntegrations(), then start and stop them with startCustomIntegrations() and stopCustomIntegrations().
call.setCustomIntegrations({
  'my-app': {
    label: 'My App',
    location: 'sidebar',          // 'sidebar' | 'main'
    src: 'https://app.example.com/embed',
    iconURL: 'https://app.example.com/icon.png',
    controlledBy: 'owners',       // '*' | 'owners' | string[]
    shared: true,                 // sync open/close state across all participants
    allow: 'camera; microphone',
    sandbox: 'allow-scripts allow-same-origin',
    loading: 'lazy',
  },
});

// Start or stop integrations
call.startCustomIntegrations('my-app');
call.stopCustomIntegrations('my-app');

// Read currently registered integrations
const integrations = call.customIntegrations();
Key options:
OptionDescription
location'sidebar' renders as a panel; 'main' renders as an overlay in the call area
controlledByWho can start/stop the integration: '*' (all), 'owners', or a list of session IDs
sharedWhen true, starting/stopping is synchronized for all participants
loading'lazy' (default) loads the iframe on first open; 'eager' loads immediately

Custom tray buttons

Add custom buttons to Prebuilt’s control bar with updateCustomTrayButtons(), then listen for clicks via the custom-button-click event.
call.updateCustomTrayButtons({
  'raise-hand': {
    iconPath: 'https://example.com/icons/hand.svg',
    iconPathDarkMode: 'https://example.com/icons/hand-dark.svg',
    label: 'Raise hand',
    tooltip: 'Raise or lower your hand',
    visualState: 'default',
  },
});

call.on('custom-button-click', ({ button_id }) => {
  if (button_id === 'raise-hand') {
    toggleHandRaise();

    // Update the button's visual state to reflect the toggle
    const current = call.customTrayButtons();
    const isActive = current['raise-hand'].visualState === 'active';
    call.updateCustomTrayButtons({
      'raise-hand': {
        ...current['raise-hand'],
        visualState: isActive ? 'default' : 'active',
      },
    });
  }
});
visualState controls the button’s appearance: 'default', 'active' (toggled/pressed style), or 'sidebar-open' (indicates an open sidebar panel).