diff --git a/changelog/entries/unreleased/feature/3170_added_a_shortcut_in_the_publishing_modal_so_that_domains_can.json b/changelog/entries/unreleased/feature/3170_added_a_shortcut_in_the_publishing_modal_so_that_domains_can.json
new file mode 100644
index 000000000..2661fedbf
--- /dev/null
+++ b/changelog/entries/unreleased/feature/3170_added_a_shortcut_in_the_publishing_modal_so_that_domains_can.json
@@ -0,0 +1,8 @@
+{
+    "type": "feature",
+    "message": "Added a shortcut in the publishing modal so that domains can be created if an application doesn't yet have any.",
+    "domain": "builder",
+    "issue_number": 3170,
+    "bullet_points": [],
+    "created_at": "2025-03-13"
+}
\ No newline at end of file
diff --git a/web-frontend/modules/builder/components/domain/CustomDomainForm.vue b/web-frontend/modules/builder/components/domain/CustomDomainForm.vue
index 450a7ea96..742b956d1 100644
--- a/web-frontend/modules/builder/components/domain/CustomDomainForm.vue
+++ b/web-frontend/modules/builder/components/domain/CustomDomainForm.vue
@@ -7,6 +7,7 @@
       :error-message="getFirstErrorMessage('domain_name') || serverErrorMessage"
     >
       <FormInput
+        ref="domainName"
         v-model="v$.values.domain_name.$model"
         size="large"
         @input="handleInput"
@@ -45,6 +46,9 @@ export default {
         : ''
     },
   },
+  mounted() {
+    this.$refs.domainName.focus()
+  },
   methods: {
     handleInput() {
       this.serverErrors.domain_name = null
diff --git a/web-frontend/modules/builder/components/domain/DomainCard.vue b/web-frontend/modules/builder/components/domain/DomainCard.vue
index a5d5e61b9..557f18d92 100644
--- a/web-frontend/modules/builder/components/domain/DomainCard.vue
+++ b/web-frontend/modules/builder/components/domain/DomainCard.vue
@@ -26,6 +26,13 @@
           />
         </div>
       </div>
+      <Alert
+        v-if="!domain.last_published"
+        type="warning"
+        class="margin-bottom-0"
+      >
+        <p>{{ $t('domainCard.unpublishedDomainWarning') }}</p>
+      </Alert>
     </template>
     <component
       :is="domainType.detailsComponent"
diff --git a/web-frontend/modules/builder/components/domain/DomainForm.vue b/web-frontend/modules/builder/components/domain/DomainForm.vue
index 2d15118f1..89058994a 100644
--- a/web-frontend/modules/builder/components/domain/DomainForm.vue
+++ b/web-frontend/modules/builder/components/domain/DomainForm.vue
@@ -105,6 +105,7 @@ export default {
         })
         this.hideError()
         this.hideForm()
+        this.$emit('created')
       } catch (error) {
         this.handleAnyError(error)
       }
diff --git a/web-frontend/modules/builder/components/page/header/PublishActionModal.vue b/web-frontend/modules/builder/components/page/header/PublishActionModal.vue
index e9a83f1c7..46cbb5cb0 100644
--- a/web-frontend/modules/builder/components/page/header/PublishActionModal.vue
+++ b/web-frontend/modules/builder/components/page/header/PublishActionModal.vue
@@ -14,11 +14,12 @@
           :key="domain.id"
           class="publish-action-modal__container"
         >
-          <Radio v-model="selectedDomain" :value="domain.id">
+          <Radio v-model="selectedDomainId" :value="domain.id">
             <span class="publish-action-modal__domain-name">{{
               domain.domain_name
             }}</span>
             <a
+              v-if="domain.last_published"
               v-tooltip="$t('action.copyToClipboard')"
               class="publish-action-modal__copy-domain"
               tooltip-position="top"
@@ -30,6 +31,7 @@
               <Copied ref="domainCopied" />
             </a>
             <a
+              v-if="domain.last_published"
               v-tooltip="$t('publishActionModal.openInNewTab')"
               tooltip-position="top"
               class="publish-action-modal__domain-link"
@@ -46,6 +48,7 @@
           />
         </div>
       </template>
+      <div v-else-if="fetchingDomains" class="loading-spinner"></div>
       <p v-else>{{ $t('publishActionModal.noDomain') }}</p>
     </template>
 
@@ -54,6 +57,11 @@
         $t('publishActionModal.publishSucceedTitle')
       }}</template>
       <p>{{ $t('publishActionModal.publishSucceedDescription') }}</p>
+      <template #actions>
+        <Button tag="a" :href="getDomainUrl(selectedDomain)" target="_blank">{{
+          $t('publishActionModal.publishSucceedLink')
+        }}</Button>
+      </template>
     </Alert>
 
     <div class="modal-progress__actions">
@@ -64,13 +72,24 @@
       />
       <div class="align-right">
         <Button
+          v-if="domains.length"
           size="large"
           :loading="jobIsRunning || loading"
-          :disabled="loading || jobIsRunning || !selectedDomain"
+          :disabled="loading || jobIsRunning || !selectedDomainId"
           @click="publishSite()"
         >
           {{ $t('publishActionModal.publish') }}
         </Button>
+        <template v-else-if="!fetchingDomains">
+          <Button tag="a" @click="openDomainSettings">
+            {{ $t('publishActionModal.addDomain') }}
+          </Button>
+        </template>
+        <BuilderSettingsModal
+          ref="domainSettingsModal"
+          hide-after-create
+          :builder="builder"
+        />
       </div>
     </div>
   </Modal>
@@ -85,10 +104,12 @@ import PublishedDomainService from '@baserow/modules/builder/services/publishedB
 import { notifyIf } from '@baserow/modules/core/utils/error'
 import { copyToClipboard } from '@baserow/modules/database/utils/clipboard'
 import LastPublishedDomainDate from '@baserow/modules/builder/components/domain/LastPublishedDomainDate'
+import BuilderSettingsModal from '@baserow/modules/builder/components/settings/BuilderSettingsModal'
+import { DomainsBuilderSettingsType } from '@baserow/modules/builder/builderSettingTypes'
 
 export default {
   name: 'PublishActionModal',
-  components: { LastPublishedDomainDate },
+  components: { BuilderSettingsModal, LastPublishedDomainDate },
   mixins: [modal, error, jobProgress],
   props: {
     builder: {
@@ -97,15 +118,23 @@ export default {
     },
   },
   data() {
-    return { selectedDomain: null, loading: false }
+    return { selectedDomainId: null, loading: false, fetchingDomains: false }
   },
   computed: {
     ...mapGetters({ domains: 'domain/getDomains' }),
+    selectedDomain() {
+      return this.domains.find((domain) => domain.id === this.selectedDomainId)
+    },
   },
   watch: {
-    selectedDomain() {
+    selectedDomainId() {
       this.job = null
     },
+    domains() {
+      if (!this.selectedDomainId) {
+        this.selectedDomainId = this.domains.length ? this.domains[0].id : null
+      }
+    },
   },
   beforeDestroy() {
     this.stopPollIfRunning()
@@ -119,19 +148,22 @@ export default {
       this.hideError()
       this.job = null
       this.loading = false
-      this.selectedDomain = null
+      this.selectedDomainId = null
+      this.fetchingDomains = true
       try {
         await this.actionFetchDomains({ builderId: this.builder.id })
         this.hideError()
       } catch (error) {
         this.handleError(error)
+      } finally {
+        this.fetchingDomains = false
       }
     },
     async publishSite() {
       this.loading = true
       this.hideError()
       const { data: job } = await PublishedDomainService(this.$client).publish({
-        id: this.selectedDomain,
+        id: this.selectedDomainId,
       })
 
       this.startJobPoller(job)
@@ -145,7 +177,7 @@ export default {
     },
     onJobDone() {
       this.actionForceUpdateDomain({
-        domainId: this.selectedDomain,
+        domainId: this.selectedDomainId,
         values: { last_published: new Date() },
       })
       this.loading = false
@@ -169,6 +201,15 @@ export default {
       }
       return ''
     },
+    openDomainSettings() {
+      // Open the builder settings modal, which is instructed to select the domain
+      // settings instance first, and pass `DomainsBuilderSettingsType.getType()` into
+      // `show` so that the create domain form is immediately presented.
+      this.$refs.domainSettingsModal.show(
+        DomainsBuilderSettingsType.getType(),
+        true
+      )
+    },
   },
 }
 </script>
diff --git a/web-frontend/modules/builder/components/settings/BuilderSettingsModal.vue b/web-frontend/modules/builder/components/settings/BuilderSettingsModal.vue
index bd1bfca49..9311a4aa7 100644
--- a/web-frontend/modules/builder/components/settings/BuilderSettingsModal.vue
+++ b/web-frontend/modules/builder/components/settings/BuilderSettingsModal.vue
@@ -26,7 +26,14 @@
       </ul>
     </template>
     <template v-if="settingSelected" #content>
-      <component :is="settingSelected.component" :builder="builder"></component>
+      <component
+        :is="settingSelected.component"
+        ref="settingSelected"
+        :builder="builder"
+        :hide-after-create="hideAfterCreate"
+        :force-display-form="displaySelectedSettingForm"
+        @hide-modal="hide()"
+      ></component>
     </template>
   </Modal>
 </template>
@@ -43,10 +50,20 @@ export default {
       type: Object,
       required: true,
     },
+    /**
+     * If you want the selected setting form to hide the builder settings modal
+     * after a record is created, set this to `true`.
+     */
+    hideAfterCreate: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
   },
   data() {
     return {
       settingSelected: null,
+      displaySelectedSettingForm: false,
     }
   },
   computed: {
@@ -54,12 +71,44 @@ export default {
       return this.$registry.getOrderedList('builderSettings')
     },
   },
+  watch: {
+    // When the selected setting changes, and we've forcibly displayed
+    // the selected setting's form, then reset the display flag so that
+    // the next setting doesn't immediately display its form.
+    settingSelected(newSetting, oldSetting) {
+      if (
+        oldSetting &&
+        newSetting !== oldSetting &&
+        this.displaySelectedSettingForm
+      ) {
+        this.displaySelectedSettingForm = false
+      }
+    },
+  },
   methods: {
-    show(...args) {
+    show(
+      selectSettingType = null,
+      displaySelectedSettingForm = false,
+      ...args
+    ) {
+      // If we've been instructed to show a specific setting component,
+      // then ensure it's displayed first.
+      if (selectSettingType) {
+        this.settingSelected = this.$registry.get(
+          'builderSettings',
+          selectSettingType
+        )
+      }
+
+      // If no `selectSettingType` was provided then choose the first setting.
       if (!this.settingSelected) {
         this.settingSelected = this.registeredSettings[0]
       }
 
+      // If we've been instructed to show the modal, and make the
+      // selected setting component's form display, then do so.
+      this.displaySelectedSettingForm = displaySelectedSettingForm
+
       const builderApplicationType = this.$registry.get(
         'application',
         BuilderApplicationType.getType()
diff --git a/web-frontend/modules/builder/components/settings/DomainsSettings.vue b/web-frontend/modules/builder/components/settings/DomainsSettings.vue
index 05c596104..30322b53e 100644
--- a/web-frontend/modules/builder/components/settings/DomainsSettings.vue
+++ b/web-frontend/modules/builder/components/settings/DomainsSettings.vue
@@ -28,7 +28,12 @@
       {{ $t('domainSettings.noDomainMessage') }}
     </p>
   </div>
-  <DomainForm v-else :builder="builder" :hide-form="hideForm" />
+  <DomainForm
+    v-else
+    :builder="builder"
+    :hide-form="hideForm"
+    @created="hideModalIfRequired"
+  />
 </template>
 
 <script>
@@ -36,22 +41,12 @@ import { mapActions, mapGetters } from 'vuex'
 import error from '@baserow/modules/core/mixins/error'
 import DomainCard from '@baserow/modules/builder/components/domain/DomainCard'
 import DomainForm from '@baserow/modules/builder/components/domain/DomainForm'
+import builderSetting from '@baserow/modules/builder/components/settings/mixins/builderSetting'
 
 export default {
   name: 'DomainsSettings',
   components: { DomainCard, DomainForm },
-  mixins: [error],
-  props: {
-    builder: {
-      type: Object,
-      required: true,
-    },
-  },
-  data() {
-    return {
-      showForm: false,
-    }
-  },
+  mixins: [error, builderSetting],
   async fetch() {
     try {
       await this.actionFetchDomains({ builderId: this.builder.id })
diff --git a/web-frontend/modules/builder/components/settings/UserSourcesSettings.vue b/web-frontend/modules/builder/components/settings/UserSourcesSettings.vue
index 2b52f543e..4b3bb7899 100644
--- a/web-frontend/modules/builder/components/settings/UserSourcesSettings.vue
+++ b/web-frontend/modules/builder/components/settings/UserSourcesSettings.vue
@@ -1,13 +1,13 @@
 <template>
   <!-- Show user source list -->
   <div
-    v-if="!showCreateForm && editedUserSource === null"
+    v-if="!showForm && editedUserSource === null"
     class="user-sources-settings"
   >
     <h2 class="box__title">{{ $t('userSourceSettings.titleOverview') }}</h2>
     <Error :error="error"></Error>
     <div v-if="!error.visible" class="actions actions--right">
-      <Button icon="iconoir-plus" @click="showForm()">
+      <Button icon="iconoir-plus" @click="displayForm()">
         {{ $t('userSourceSettings.addUserSource') }}
       </Button>
     </div>
@@ -26,7 +26,7 @@
         style="flex: 1"
       />
       <div class="user-source-settings__user-source-actions">
-        <ButtonIcon icon="iconoir-edit" @click="showForm(userSource)" />
+        <ButtonIcon icon="iconoir-edit" @click="displayForm(userSource)" />
         <ButtonIcon icon="iconoir-bin" @click="deleteUserSource(userSource)" />
       </div>
     </div>
@@ -116,23 +116,18 @@ import { clone } from '@baserow/modules/core/utils/object'
 import { notifyIf } from '@baserow/modules/core/utils/error'
 import CreateUserSourceForm from '@baserow/modules/builder/components/userSource/CreateUserSourceForm'
 import UpdateUserSourceForm from '@baserow/modules/builder/components/userSource/UpdateUserSourceForm'
+import builderSetting from '@baserow/modules/builder/components/settings/mixins/builderSetting'
 
 export default {
   name: 'UserSourceSettings',
   components: { CreateUserSourceForm, UpdateUserSourceForm },
-  mixins: [error],
+  mixins: [error, builderSetting],
   provide() {
     return { builder: this.builder }
   },
-  props: {
-    builder: {
-      type: Object,
-      required: true,
-    },
-  },
   data() {
     return {
-      showCreateForm: false,
+      showForm: false,
       editedUserSource: null,
       actionInProgress: false,
       invalidForm: true,
@@ -145,9 +140,6 @@ export default {
     userSources() {
       return this.$store.getters['userSource/getUserSources'](this.builder)
     },
-    userSourceTypes() {
-      return this.$registry.getAll('userSource')
-    },
   },
   async mounted() {
     try {
@@ -174,17 +166,17 @@ export default {
     onValueChange() {
       this.invalidForm = !this.$refs.userSourceForm.isFormValid()
     },
-    async showForm(userSourceToEdit) {
+    async displayForm(userSourceToEdit) {
       if (userSourceToEdit) {
         this.editedUserSource = userSourceToEdit
       } else {
-        this.showCreateForm = true
+        this.showForm = true
       }
       await this.$nextTick()
       this.onValueChange()
     },
     hideForm() {
-      this.showCreateForm = false
+      this.showForm = false
       this.editedUserSource = null
       this.hideError()
       this.invalidForm = true
@@ -202,6 +194,7 @@ export default {
         this.hideForm()
         // immediately select this user source to edit it.
         this.editedUserSource = createdUserSource
+        this.hideModalIfRequired()
       } catch (error) {
         this.handleError(error)
       }
diff --git a/web-frontend/modules/builder/components/settings/mixins/builderSetting.js b/web-frontend/modules/builder/components/settings/mixins/builderSetting.js
new file mode 100644
index 000000000..e91fdfaf7
--- /dev/null
+++ b/web-frontend/modules/builder/components/settings/mixins/builderSetting.js
@@ -0,0 +1,41 @@
+/**
+ * A mixin for the builder setting components which have forms. This mixin makes
+ * it easier to make them immediately display their "create" form from the parent
+ * builder settings modal. When `force-display-form` is set, the end-user doesn't
+ * have to click a "New" button.
+ */
+export default {
+  props: {
+    builder: {
+      type: Object,
+      required: true,
+    },
+    forceDisplayForm: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+    hideAfterCreate: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  data() {
+    return {
+      showForm: false,
+    }
+  },
+  mounted() {
+    if (this.forceDisplayForm) {
+      this.showForm = true
+    }
+  },
+  methods: {
+    hideModalIfRequired() {
+      if (this.hideAfterCreate) {
+        this.$emit('hide-modal')
+      }
+    },
+  },
+}
diff --git a/web-frontend/modules/builder/locales/en.json b/web-frontend/modules/builder/locales/en.json
index 12ee0a2cd..83157558b 100644
--- a/web-frontend/modules/builder/locales/en.json
+++ b/web-frontend/modules/builder/locales/en.json
@@ -65,11 +65,13 @@
     "publish": "Publish",
     "publishSucceedTitle": "Site published",
     "publishSucceedDescription": "The site has been successfully published.",
+    "publishSucceedLink": "View site",
     "publishFailedTitle": "Site publishing failed",
     "publishFailedDescription": "The site publishing has failed. Please try again later.",
     "openInNewTab": "Open in a new tab",
     "importingState": "Importing",
-    "noDomain": "You need to have at least one domain in order to publish your application."
+    "noDomain": "You need to have at least one domain in order to publish your application.",
+    "addDomain": "Add domain"
   },
   "lastPublishedDomainDate": {
     "neverPublished": "never",
@@ -346,7 +348,8 @@
   },
   "domainCard": {
     "refresh": "Refresh settings",
-    "detailLabel": "Show details"
+    "detailLabel": "Show details",
+    "unpublishedDomainWarning": "Please publish the application to make it available on this domain."
   },
   "domainTypes": {
     "customName": "Custom domain",