---
title: "Registry Scripts"
description: "Configure, reuse, and extend typed integrations from the Nuxt Scripts registry."
canonical_url: "https://scripts.nuxt.com/docs/guides/registry-scripts"
last_updated: "2026-08-10T04:32:05.481Z"
---

Registry scripts are preconfigured integrations for common third-party services. Browse the available integrations in the [Script Registry](/scripts).

## Features

### Safe initialization

Registry composables initialize the required global state before they add the external script.

### Loading controls

Each registry entry declares the bundling, proxying, and Partytown capabilities it supports. Composable-driven integrations also accept a loading trigger.

### Types

Registry scripts include types for their configuration and exposed API, so editors can complete calls such as `gtag()` without a separate global declaration.

### Development-time validation

Registry scripts use [Valibot schemas](https://valibot.dev/guides/schemas/) to validate configuration during development. For example, the Cloudflare Web Analytics schema rejects a token shorter than 32 characters.

<code-group>

```ts [Schema]
export const CloudflareWebAnalyticsOptions = object({
  /**
   * The Cloudflare Web Analytics token.
   */
  token: pipe(string(), minLength(32)),
  /**
   * Cloudflare Web Analytics enables measuring SPAs automatically by overriding the History API’s pushState function
   * and listening to the onpopstate. Hash-based router is not supported.
   *
   * @default true
   */
  spa: optional(boolean()),
})
```

```ts [Example]
useScriptCloudflareWebAnalytics({
  token: '123', // skipped in development because the token is too short
})
```

</code-group>

Production builds omit the validation code. During development, Nuxt Scripts skips an invalid script and logs the schema issues.

### Runtime config

Register a script in `nuxt.config.ts` and Nuxt Scripts creates public runtime config fields for the environment-backed inputs declared by that integration. You can then supply fields such as its ID or token through `.env` instead of hardcoding them.

<code-group>

```text [.env]
NUXT_PUBLIC_SCRIPTS_CLOUDFLARE_WEB_ANALYTICS_TOKEN=YOUR_TOKEN
```

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  scripts: {
    registry: {
      cloudflareWebAnalytics: { trigger: 'client' },
    },
  },
})
```

</code-group>

## Usage

### Disabling in development

Set a registry entry to `mock` when development code calls an API such as `gtag`, but you do not want to load the vendor script. Mock mode registers a manual context and skips option validation.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  scripts: {
    registry: {
      googleTagManager: { trigger: 'onNuxtReady' },
    },
  },
  $development: {
    scripts: {
      registry: {
        googleTagManager: 'mock',
      },
    },
  },
})
```

### Load multiple instances

Registry scripts deduplicate by `src` or `key`. Give each call a unique `key` when you need multiple instances with different configuration.

```ts
const { proxy: gaOne } = useScriptGoogleAnalytics({
  id: 'G-TR58L0EF8P',
})

const { proxy: gaTwo } = useScriptGoogleAnalytics({
  // without a key the first script instance will be returned
  key: 'gtag2',
  id: 'G-1234567890',
})
```

A custom key also changes the runtime config path. For `key: 'gtag2'`, declare the matching path yourself:

```ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      scripts: {
        gtag2: {
          id: '', // NUXT_PUBLIC_SCRIPTS_GTAG2_ID
        },
      },
    },
  },
})
```

### Use script options and script input

Registry scripts expose the core [`useScript()`](/docs/api/use-script) inputs through two fields:

- `scriptOptions`: [useScript options](/docs/api/use-script#nuxtusescriptoptions), such as `trigger`.
- `scriptInput`: [script element input](/docs/api/use-script#usescriptinput), such as `data-*` attributes.

```ts
import { useTimeout } from '@vueuse/core'
import { useScriptGoogleAnalytics } from '#imports'

const ready = useTimeout(5000)
useScriptGoogleAnalytics({
  id: 'G-XXXXXXXX',
  // HTML attributes to pass to the script element
  scriptInput: {
    'data-test': 'true',
  },
  // useScript options used for advanced features
  scriptOptions: {
    trigger: ready,
  },
})
```

### Reuse one instance

If several pages use the same integration, configure it once in `app.vue` or `nuxt.config`. Later composable calls return that script instance and do not need the options again.

<code-group>

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  scripts: {
    registry: {
      // The explicit trigger loads the script globally.
      fathomAnalytics: {
        site: 'SITE_ID',
        trigger: 'onNuxtReady',
      }
    }
  }
})
```

```vue [components/any-component.vue]
<script setup lang="ts">
const { proxy } = useScriptFathomAnalytics() // no options required
</script>

<template>
  <button @click="proxy.trackGoal('GOAL_ID', 0)">
    Track Goal
  </button>
</template>
```

</code-group>

You can also keep the shared configuration in your own composable:

```ts
export function useFathomAnalytics() {
  return useScriptFathomAnalytics({
    site: 'SITE_ID',
  })
}
```

## Extending the Script Registry

Use the `scripts:registry` hook in `nuxt.config.ts` to add an integration:

```ts [nuxt.config.ts]
import { createResolver } from '@nuxt/kit'

const { resolve } = createResolver(import.meta.url)

export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],

  hooks: {
    'scripts:registry': function (registry) {
      registry.push({
        category: 'custom',
        label: 'My Custom Analytics',
        logo: '<svg>...</svg>', // optional
        import: {
          name: 'useScriptMyAnalytics',
          from: resolve('./composables/useScriptMyAnalytics'),
        },
      })
    },
  },

  devtools: {
    enabled: true,
  },
})
```

Then create your custom script composable:

```ts [composables/useScriptMyAnalytics.ts]
import type { RegistryScriptInput } from '#nuxt-scripts/types'
import { object, string } from 'valibot'
import { useRegistryScript } from '#nuxt-scripts/utils'

export interface MyAnalyticsApi {
  track: (event: string, data?: Record<string, any>) => void
  identify: (userId: string) => void
}

declare global {
  interface Window {
    MyAnalytics: MyAnalyticsApi & { init: (apiKey?: string) => void }
  }
}

// Schema for validation and DevTools metadata
export const MyAnalyticsSchema = object({
  apiKey: string(),
})

export type MyAnalyticsInput = RegistryScriptInput<typeof MyAnalyticsSchema>

export function useScriptMyAnalytics<T extends MyAnalyticsApi>(options?: MyAnalyticsInput) {
  return useRegistryScript<T, typeof MyAnalyticsSchema>('myAnalytics', resolvedOptions => ({
    scriptInput: {
      src: 'https://analytics.example.com/sdk.js',
    },
    schema: import.meta.dev ? MyAnalyticsSchema : undefined,
    scriptOptions: {
      ...options?.scriptOptions,
      use() {
        if (!window.MyAnalytics)
          return
        window.MyAnalytics.init(resolvedOptions.apiKey)
        return window.MyAnalytics as T
      },
    },
  }), options)
}
```

### Using Custom Registry Scripts

Nuxt auto-imports the registered composable:

```vue [pages/index.vue]
<script setup lang="ts">
// Auto-imported from your registry
const { proxy, status } = useScriptMyAnalytics({
  apiKey: 'your-api-key',
  scriptOptions: {
    trigger: 'onNuxtReady'
  }
})

// Use the script API
function trackClick() {
  proxy.track('button_click', { button: 'hero-cta' })
}
</script>

<template>
  <button @click="trackClick">
    Track This Click
  </button>
  <div>Status: {{ status }}</div>
</template>
```

### DevTools Integration

When you include a validation schema, Nuxt Scripts uses its required fields to populate the script's DevTools metadata in development.
