Migration Guide

This guide covers the breaking changes and deprecations in Nuxt Scripts v1. For an overview of what's new, see the v1 Release Notes.

Where possible, v1 keeps the old API working with deprecation warnings. The table below summarizes what needs attention vs. what continues to work.

Summary

ChangeStatusWhat to do
Registry entry auto-loads without triggerBuild warningAdd trigger: 'onNuxtReady' or trigger: false
[input, options] tuple formStill worksOptional: switch to flat config
true shorthandDeprecatedUse { trigger: 'onNuxtReady' }
PayPal SDK v5 APIRemovedMigrate to v6 (see below)
ScriptYouTubePlayer width/heightStill workOptional: use ratio prop
ScriptYouTubePlayer placeholder object-fit: contain defaultChanged to coverSet placeholder-object-fit="contain" to restore
GTM onBeforeGtmStart timingNow fires for cached scriptsGuard with if (initialized) return
ScriptGoogleMaps markers/centerMarker propsRemovedUse child <ScriptGoogleMapsMarker>
ScriptGoogleMaps placeholderOptions/placeholderAttrs/aboveTheFold propsRemovedUse <ScriptGoogleMapsStaticMap> in #placeholder
ScriptGoogleMaps :center/:zoom propsDeprecatedUse :map-options="{ center, zoom }"
ScriptGoogleMaps googleMaps ref keyDeprecatedUse mapsApi
ScriptGoogleMapsAdvancedMarkerElementDeprecatedUse ScriptGoogleMapsMarker
ScriptGoogleMapsPinElementRemovedUse #content slot on ScriptGoogleMapsMarker
Type templatesReorganizedRun nuxi prepare

Registry Config

Scripts No Longer Auto-Load Without a trigger

In v0, any configured registry entry auto-loaded globally via defaultScriptOptions.trigger: 'onNuxtReady'. In v1, presence registers infrastructure (types, bundling, proxy routes) but does not inject a <script> tag unless trigger is set. A build warning fires when you supply config values without a trigger. See PR #661.

nuxt.config.ts
 scripts: {
   registry: {
-    googleAnalytics: { id: 'G-XXXXXX' },
+    googleAnalytics: { id: 'G-XXXXXX', trigger: 'onNuxtReady' },
   }
 }

If you load the script on demand from a composable (useScriptGoogleAnalytics(), etc.) rather than globally, set trigger: false explicitly. This silences the build warning and keeps proxy routes, types, and bundling wired up without injecting a <script> tag.

nuxt.config.ts
export default defineNuxtConfig({
  scripts: {
    registry: {
      googleAnalytics: { id: 'G-XXXXXX', trigger: false }, // composable-driven
    }
  }
})

Legacy Shorthand (still works)

The [input, options] tuple form, true shorthand, and 'mock' string form all continue to work. true emits a deprecation warning.

nuxt.config.ts
 scripts: {
   registry: {
-    googleAnalytics: true,
+    googleAnalytics: { trigger: 'onNuxtReady' },
-    plausibleAnalytics: [{ domain: 'mysite.com' }, { trigger: 'onNuxtReady' }],
+    plausibleAnalytics: { domain: 'mysite.com', trigger: 'onNuxtReady' },
     cloudflareWebAnalytics: 'mock', // unchanged
   }
 }

Breaking Changes

PayPal SDK v6 (#628)

PayPal v6 moves to instance-based initialization (createInstance()), eligibility-first rendering, and session-based payment flows. The component API has changed significantly. See the PayPal docs.

  • SDK URLs now use v6 endpoints (/web-sdk/v6/core)
  • PayPalOptions reduced to clientId?, clientToken?, sandbox? (v6 configures at createInstance() time, not via URL query params)
  • Sandbox mode defaults to true in development
  • ScriptPayPalMarks removed; no v6 equivalent, use sdkInstance.findEligibleMethods() instead
  • PayPalNamespace type replaced with PayPalV6Namespace

<ScriptPayPalButtons> no longer renders buttons directly. It now exposes the v6 SDK instance via a #default scoped slot:

-<ScriptPayPalButtons
-  :client-id="clientId"
-  @paypal-payment-success="onSuccess"
-/>
+<ScriptPayPalButtons :client-id="clientId" :components="['paypal-payments']">
+  <template #default="{ sdkInstance }">
+    <button @click="pay(sdkInstance)">Pay with PayPal</button>
+  </template>
+</ScriptPayPalButtons>

Payment flow uses sessions instead of button callbacks:

const eligibility = await sdkInstance.findEligibleMethods()
if (eligibility.isEligible('paypal')) {
  const session = sdkInstance.createPayPalOneTimePaymentSession({
    onApprove: async (data) => { /* capture order */ },
  })
  await session.start({ presentationMode: 'auto' }, createOrderPromise)
}
-import type { PayPalNamespace } from '@paypal/paypal-js'
+import type { PayPalV6Namespace, SdkInstance } from '@paypal/paypal-js/sdk-v6'

YouTube Player

Aspect Ratio

Use ratio prop instead of deriving aspect ratio from width/height. The width and height props still work for iframe dimensions, but no longer drive the wrapper's aspect ratio.

 <ScriptYouTubePlayer
   video-id="..."
-  :width="1280"
-  :height="720"
+  ratio="16/9"
 />

Default is 16/9.

Placeholder Image

Default object-fit changed from contain to cover. To restore v0 behavior:

<ScriptYouTubePlayer video-id="..." placeholder-object-fit="contain" />

Multiple Players

Player instances are now properly isolated. Remove any workarounds you had for running multiple players on the same page.

Google Tag Manager

onBeforeGtmStart Callback

Now fires for cached/pre-initialized scripts. Guard against multiple calls:

+let initialized = false
 useScriptGoogleTagManager({
   onBeforeGtmStart: (gtag) => {
+    if (initialized) return
+    initialized = true
     // your init code
   }
 })

Google Maps

v1 consolidates the Google Maps marker components, removes the legacy google.maps.Marker API, and extracts the static placeholder into a standalone component.

ScriptGoogleMapsAdvancedMarkerElementScriptGoogleMapsMarker (deprecated)

Still works via a thin shim, emits a dev deprecation warning.

-<ScriptGoogleMapsAdvancedMarkerElement :position="{ lat: 0, lng: 0 }" />
+<ScriptGoogleMapsMarker :position="{ lat: 0, lng: 0 }" />

ScriptGoogleMapsPinElement Removed

Use the #content slot on <ScriptGoogleMapsMarker>:

-<ScriptGoogleMapsAdvancedMarkerElement :position="pos">
-  <ScriptGoogleMapsPinElement :options="{ background: 'red' }" />
-</ScriptGoogleMapsAdvancedMarkerElement>
+<ScriptGoogleMapsMarker :position="pos">
+  <template #content>
+    <div class="custom-pin" style="background: red;">📍</div>
+  </template>
+</ScriptGoogleMapsMarker>

markers and centerMarker Props Removed

Use child <ScriptGoogleMapsMarker> components instead:

-<ScriptGoogleMaps
-  :center="center"
-  :markers="[{ position: { lat: 0, lng: 0 } }]"
-  center-marker
-/>
+<ScriptGoogleMaps :map-options="{ center, zoom: 12 }">
+  <ScriptGoogleMapsMarker :position="center" />
+  <ScriptGoogleMapsMarker :position="{ lat: 0, lng: 0 }" />
+</ScriptGoogleMaps>

Static Placeholder Props Removed (#673)

placeholderOptions, placeholderAttrs, and aboveTheFold props removed from <ScriptGoogleMaps>. Compose with the new standalone <ScriptGoogleMapsStaticMap> component inside the #placeholder slot instead. The slot no longer receives a placeholder URL string and is empty by default.

-<ScriptGoogleMaps
-  :center="center"
-  :zoom="7"
-  above-the-fold
-  :placeholder-options="{ maptype: 'satellite' }"
-  :placeholder-attrs="{ class: 'rounded' }"
-/>
+<ScriptGoogleMaps :map-options="{ center, zoom: 7 }">
+  <template #placeholder>
+    <ScriptGoogleMapsStaticMap
+      :center="center"
+      :zoom="7"
+      loading="eager"
+      maptype="satellite"
+      :img-attrs="{ class: 'rounded' }"
+    />
+  </template>
+</ScriptGoogleMaps>

Top-Level :center / :zoom Deprecated (#694)

Deprecated in favour of passing them via :map-options. Both APIs still work; the legacy form emits a dev-mode warning. When both are set, mapOptions wins.

-<ScriptGoogleMaps :center="{ lat, lng }" :zoom="12" />
+<ScriptGoogleMaps :map-options="{ center: { lat, lng }, zoom: 12 }" />

Template Ref googleMapsmapsApi (#695)

Renamed to better reflect what it holds (the google.maps API namespace, not the component itself). The old key still works as a deprecated alias and emits a one-shot dev-mode warning when read.

 const mapRef = ref()
 onMounted(() => {
-  console.log(mapRef.value?.googleMaps)
+  console.log(mapRef.value?.mapsApi)
 })

The same rename applies to <ScriptGoogleMapsOverlayView>: its exposed overlay key is now overlayView, with overlay kept as a deprecated alias.

 const overlayRef = ref()
 onMounted(() => {
-  console.log(overlayRef.value?.overlay)
+  console.log(overlayRef.value?.overlayView)
 })

Type Augmentation

Templates reorganized. Run nuxi prepare after upgrading:

npx nuxi prepare