# Add device fingerprinting to a Vue or Nuxt app

> Add device fingerprinting to a Vue 3 or Nuxt app: install the plugin or module, identify with a composable, and verify the result in a server route.

Last updated: 2026-11-12

This tutorial adds **device fingerprinting** to a Vue 3 app or a Nuxt app. A plugin or module gives every component one shared identification as readonly refs, a composable asks for it when the visitor acts, and your server reads the stored result before it decides.

## Before you start

- Vue 3.3 or newer, or Nuxt 3 or 4. In Nuxt, use the module: it registers the Vue plugin and auto-imports the composables.
- A public key with your app's origin in its allowed origins, and a secret key for your server.
- A decision about consent: the SDK collects nothing until `consent` is `granted`.

## Get your keys

### Step 1: Create your keys

In the Fingerly dashboard, open **Integration > SDK keys** and create a **public** key in the **development** environment. Add the origin your app runs on as an allowed origin. A public key with no allowed origins refuses every browser request. Then create a **secret** key in the same environment for your server.

Development keys run the same detection as production and cost nothing. See [API keys and environments](https://docs.fingerly.io/docs/api-keys).

## Add it to a Vue 3 app

### Step 1: Install the plugin

```bash npm
npm install @fingerly/vue
```

```bash pnpm
pnpm add @fingerly/vue
```

```bash yarn
yarn add @fingerly/vue
```

```ts main.ts
import { createApp } from 'vue'
import { createFingerly } from '@fingerly/vue'
import App from './App.vue'

createApp(App)
  // consent: 'granted' only once you have consent (or another lawful basis)
  .use(createFingerly({ apiKey: 'fly_pk_development_…', consent: 'granted' }))
  .mount('#app')
```

Installing collects nothing. `consent` defaults to `pending`, and you can change it at runtime with `useFingerly().setConsent()`.

### Step 2: Identify when the visitor acts

```vue SignUpForm.vue
<script setup lang="ts">
import { ref } from 'vue'
import { useIdentify } from '@fingerly/vue'

const email = ref('')
const { identify, isLoading, error } = useIdentify({ tag: 'signup' })

async function onSubmit() {
  let requestId: string | undefined
  try {
    ;({ requestId } = await identify())
  } catch {
    // Carry on: your server treats a missing request ID as missing evidence.
  }
  await fetch('/api/signup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: email.value, requestId }),
  })
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input v-model="email" type="email" required />
    <button :disabled="isLoading">{{ isLoading ? 'Checking…' : 'Create account' }}</button>
    <p v-if="error">{{ error.message }}</p>
  </form>
</template>
```

Every returned field is a readonly ref, and `isReady` and `isSuspicious` are computed refs. A call while an identification is in flight gets that identification, and a finished result is reused until you call `refresh()`.

## Add it to a Nuxt app

### Step 1: Install the module

```bash npm
npm install @fingerly/nuxt
```

```bash pnpm
pnpm add @fingerly/nuxt
```

```bash yarn
yarn add @fingerly/nuxt
```

```ts nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@fingerly/nuxt'],
  fingerly: {
    apiKey: 'fly_pk_development_…',
    // 'granted' only once you have consent (or another lawful basis)
    consent: 'granted',
  },
})
```

Or leave the key out of the config and set `NUXT_PUBLIC_FINGERLY_API_KEY` in the environment. The environment wins, and Nuxt reads it when the server starts, so one build can move through staging and production with a different key in each.

### Step 2: Identify in any component

`useIdentify` and `useFingerly` are auto-imported. The plugin runs on the server and the client, so the composables work during server rendering, where they return the unidentified state. Identification itself only happens in the browser.

```vue components/SignUpForm.vue
<script setup lang="ts">
const { identify, isLoading } = useIdentify({ tag: 'signup' })

async function onSubmit(form: { email: string }) {
  const { requestId } = await identify()
  await $fetch('/api/signup', { method: 'POST', body: { ...form, requestId } })
}
</script>
```

## Verify in a server route

Whichever front end you use, decide on the server. In Nuxt, a Nitro route reads the stored event with your secret key and checks it belongs to the action. In a plain Vue app, put the same code in whichever backend you run.

```ts server/api/signup.post.ts
import { load } from '@fingerly/node'

const fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })

export default defineEventHandler(async (event) => {
  const { email, requestId } = await readBody(event)
  const result = await fingerly.events.get(requestId)
  if (result.tag !== 'signup') throw createError({ statusCode: 400 })
  // decide with result.suspect_level
})
```

Check the event is recent and has not been used before as well, and answer a pending event with a retry rather than a refusal. [Server-side verification](https://docs.fingerly.io/docs/server-side-verification) lists the four checks.

> **Tip:** Run the app, submit the form and open **Identification > Events** in the dashboard to see the visitor, score and signals for your own session.

## Get past content blockers

Mount a first-party proxy at the path the dashboard issues and give the plugin or module that path as `endpoints`. The [Nuxt SDK page](https://docs.fingerly.io/docs/sdks/nuxt#serve-it-from-your-own-site) has the Nitro route, and [proxy integrations](https://docs.fingerly.io/docs/proxy-integrations) explains the model.

## Common questions

### How do I get a visitor ID in Vue?

Install the plugin, call `identify()` from `useIdentify()` when the visitor acts, and read `visitorId` from the result. Send the `requestId` to your server and read the stored event there.

### Does it work with the Options API?

Yes. The plugin adds `this.$fingerly`, so `await this.$fingerly.identify()` works in `mounted()` or a method. The [Vue SDK page](https://docs.fingerly.io/docs/sdks/vue#options-api) has an example.

### Does it work with server rendering?

Yes. `immediate` runs in `onMounted`, which never runs on the server, and calling `identify()` during server rendering rejects with `FingerlyServerError`. In a custom SSR setup, create the plugin per app instance rather than at module scope.

### How do I set the key per environment in Nuxt?

Set `NUXT_PUBLIC_FINGERLY_API_KEY`. The options are published to `runtimeConfig.public.fingerly`, so the key can differ between staging and production without a rebuild.

### What if the key is missing?

The Nuxt build still succeeds because the key can arrive from the environment later. The plugin warns once per process, the app renders, and `identify()` fails with a missing-key error until a key is set.

- [Vue SDK](https://docs.fingerly.io/docs/sdks/vue): Plugin, composables and errors.
- [Nuxt SDK](https://docs.fingerly.io/docs/sdks/nuxt): Module options and the proxy route.
- [Server-side verification](https://docs.fingerly.io/docs/server-side-verification): The four checks before you trust a result.
- [Bot, VPN and Tor detection](https://docs.fingerly.io/docs/bot-and-vpn-detection): Read the network and automation fields.
