Skip to content
On this page

Realtime Data


If you are looking for the VueFire Options API guide, make sure to also check the dedicated page. This page still includes a more information than the Options API page, which focuses more on its syntax.

In VueFire, subscriptions to data changes are handled transparently. That's why we always talk about binding: you only provide the data source (Collection, Query or Document), and VueFire takes care of the rest!

When using Firebase Database and Firestore, you can either retrieve the data once or subscribe to changes with methods like onSnapshot() and onValue(). VueFire will automatically handle the subscription for you, and update the data when it changes by internally using these functions, greatly simplifying the whole process of connecting your Vue Data to the realtime data from Firebase. It exposes a few composables to create these realtime bindings, it's important to note that, like other composables, these functions are meant to be used within a setup() function or a <script setup>. You can still use them outside of these contexts for advanced scenarios like Vuex/Pinia or global bindings but we will focus on the most common use case here. You can also use the Options API equivalent, in this section of the docs we will focus on the Composition API version and give you the equivalent for the Options API.

Declarative realtime data

Use the useCollection(), useDatabaseList(), useDocument(), and useDatabaseObject() composables to create a realtime data connected to Firestore and/or a Realtime Database. These functions take a source reference to a Collection, Query, or Document. They also accept reactive versions of these as ref() or computed():

<script setup>
import { useCollection } from 'vuefire'
import { collection } from 'firebase/firestore'

const todos = useCollection(collection(db, 'todos'))
const someTodo = useDocument(doc(collection(db, 'todos'), 'someId'))

    <li v-for="todo in todos" :key="">
     <span>{{ todo.text }}</span>

These composables all a Vue Ref containing the data. Note this is a readonly data, you shouldn't mutate it directly, you should instead use the Firebase SDK. VueFire will automatically keep the data in sync with the database.

Sometimes, you need to start observing a different document or collection, let's say you have a collection of contacts and that you display a specific contact based on the URL, e.g. displaying the contact with an id equal to 24 on /contacts/24, you can achieve this this by passing a reactive variable of the data source to the useDocument(), useDatabaseObject(), etc composables:

const route = useRoute()
// since route is reactive, `contactSource` will be reactive too
const contactSource = computed(
  () => doc(collection(db, 'contacts'),
// contact will always be in sync with the data source
const contact = useDocument(contactSource)

This way, when the route changes, the document will be updated to the new one, automatically unsubscribing from the previous one and subscribing to the new one.


If you can't use a computed(), use shallowRef()s instead of ref()s to store the data sources. This is because shallowRef() doesn't try to recursively observe the object it's given, which in the case of a Firebase data source, would be worse in terms of performance.

Subscription state

All of the composables can also be destructured to access other useful data like is the initial load still pending? or did the subscription fail?. You only need to destructure the returned value from the composables:

// instead of writing
const contact = useDocument(contactSource)
// write
const {
  // rename the Ref to something more meaningful
  data: contact,
  // is the subscription still pending?
  // did the subscription fail?
  // A promise that resolves or rejects when the initial state is loaded
} = useDocument(contactSource)

Notice how we rename data to whatever makes more sense for the context.


All of the properties that can be defined on the Ref are defined as non-enumerable properties which means they won't be copied over when using the spread operator e.g. const { data, } = useDocument(contactSource). This is to ensure they are completely ignored and do not create problems in other places like devtools.

VueFire additions

VueFire adds a few properties to the data snapshot to make it easier to work with.

Document's id

Each document/object, has an convenient id property that is the id/key of the document/object. It's set as a non enumerable property so it won't be copied over when using the spread operator.

js // jORwjIykFn1NmkdzTkhU
// the id is non enumerable
Object.keys(this.user).includes('id') // false

// it originally comes from the `id` attribute
doc(collection(db, 'users'), 'jORwjIykFn1NmkdzTkhU').id // 'jORwjIykFn1NmkdzTkhU'
// More at

This behavior can be customized through the serialize/converter option. Note that in both cases, you must keep the id property for VueFire to correctly work.

GeoPoints (Firestore only)

In Firestore you can store GeoPoints. They are retrieved as-is by VueFire, meaning that you can directly use methods like isEqual and access its properties latitude and longitude.

import { GeoPoint } from 'firebase/firestore'

// add Paris to the list of cities and wait for the operation
// to be finished
await addDoc(collection(db, 'cities'), {
  name: 'Paris',
  location: new GeoPoint(48.8588377, 2.2770206),

// somewhere else...
// we consider `cities` to be the result af `useCollection(collection(db, 'cities'))`
// we retrieve Paris that was just added
const paris =
paris.location.latitude // 48.8588377
paris.location.longitude // 2.2770206

Timestamps (Firestore only)

In Firestore you can store Timestamps. They are stored as-is by VueFire, meaning that you can directly use methods like isEqual, toDate and access its properties seconds and nanoseconds.

import { Timestamp } from 'firebase/firestore'

// Add "La prise de la Bastille" to a list of events
// and wait for th operation to be finished
await addDoc(collection(db, 'events'), {
  name: 'Prise de la Bastille',
  date: Timestamp.fromDate(new Date('1789-07-14')),

// somewhere else...
// we consider `events` to be the result af `useCollection(collection(db, 'events'))`
// we retrieve the event we just added
const prise = // -5694969600 // 0
prise.toDate() // Tue Jul 14 1789

References (Firestore only)

In Firestore you can store Nested References. You can think of this as pointers to Documents within a Document. VueFire automatically bind References found in Collections and Documents. This also works for nested references (References found in bound References). By default, VueFire will stop at that level (2 level nesting) but you can change that with maxRefDepth.

Given some users with documents that are being viewed by other users. This could be users/1:

  name: 'Jessica',
  documents: [
    // The document is stored somewhere else. Here we are only holding a reference
    doc(collection(db, 'documents'), 'gift-list'),

In the example above, documents is an array of References. Let's look at the document identified by gift-list:

  content: '...',
  sharedWith: [
    doc(collection(db, 'users'), '2'),
    doc(collection(db, 'users'), '3'),

sharedWith is also an array of References, but those references are users. Users also contain references to documents, therefore, if we automatically bind every nested reference, we could end up with an infinite-memory-consuming binding. By default, if we bind users/1 with VueFire, this is what we end up having:

  name: 'Jessica',
  documents: [
      content: '...',
      sharedWith: [
          name: 'Alex',
          documents: [
          name: 'Robin',
          documents: [

documents.sharedWith.documents end up as arrays of strings. Those strings are actually paths that can be passed to doc() as in doc(db, 'documents/robin-book') to get the actual reference to the document. By being a string instead of a Reference, it is possible to display a bound document with VueFire as plain text.

It is possible to customize this behavior by providing a maxRefDepth option:

// override the default value of 2 for maxRefDepth
useDocument(doc(db, 'users/1'), { maxRefDepth: 1 })

Read more about writing References to the Database in the writing data section.

Primitive values (Database only)

In Realtime Database, you can push primitive values like strings, numbers, booleans, etc. When calling useDatabaseList() on a database ref containing primitive values, you will get a slightly different value. Instead of an array of values, you will get an array of objects with a $value and an id property. This is because VueFire needs to keep track of the key of each value in order to add, update, or remove them.

import { ref as databaseRef, push } from 'firebase/database'

const numbersRef = databaseRef(db, 'numbers')
// add some numbers
push(numbersRef, 24)
push(numbersRef, 7)
push(numbersRef, 10)

const numberList = useDatabaseList(numbersRef)
// [{ $value: 24, id: '...' }, { $value: 7, id: '...' }, { $value: 10, id: '...' }]
// note the order might be different


To enforce a type, you only need to pass a generic type when using the different composables functions:

const contacts = useCollection<Contact>(collection(db, 'contacts'))
const settings = useDocument<Settings>(
  doc(collection(db, 'settings'), 'someId')

Note this is only a type annotation, it does not perform any runtime validation. If you want a runtime validation, you can use the withConverter() method as shown below.

Firestore .withConverter()

The recommended Firebase approach is to use the withConverter() for Firestore:


.withConverter() is a Firestore feature that doesn't have an equivalent in Database but you can use VueFire's serialize() option instead.

import { firestoreDefaultConverter } from 'vuefire'

interface TodoI {
  text: string
  finished: boolean

const todoList = useDocument(
  doc(db, 'todos').withConverter<TodoI>({
    fromFirestore: (snapshot) => {
      const data = firestoreDefaultConverter.fromFirestore(snapshot)
      // usually you can do data validation here
      if (!data || !isValidTodoItem(data)) return null

      return data
    toFirestore: firestoreDefaultConverter.toFirestore,

Released under the MIT License.