Publish and subscribe

Documentation of Meteor's publication and subscription API.

These functions control how Meteor servers publish sets of records andhow clients can subscribe to those sets.

Server

Meteor.publish(name, func)

import { Meteor } from 'meteor/meteor' (ddp-server/livedata_server.js, line 1518)

Publish a record set.

Arguments

  • nameString or Object
  • If String, name of the record set. If Object, publications Dictionary of publish functions by name. If null, the set has no name, and the record set is automatically sent to all connected clients.

  • funcFunction

  • Function called on the server each time a client subscribes. Inside the function, this is the publish handler object, described below. If the client passed arguments to subscribe, the function is called with the same arguments.

To publish records to clients, call Meteor.publish on the server withtwo parameters: the name of the record set, and a _publish function_that Meteor will call each time a client subscribes to the name.

Publish functions can return aCollection.Cursor, in which case Meteorwill publish that cursor’s documents to each subscribed client. You canalso return an array of Collection.Cursors, in which case Meteor willpublish all of the cursors.

If you return multiple cursors in an array, they currently must all be fromdifferent collections. We hope to lift this restriction in a future release.

A client will see a document if the document is currently in the publishedrecord set of any of its subscriptions. If multiple publications publish adocument with the same _id to the same collection the documents will bemerged for the client. If the values of any of the top level fieldsconflict, the resulting value will be one of the published values, chosenarbitrarily.

  1. // Server: Publish the `Rooms` collection, minus secret info...
  2. Meteor.publish('rooms', function () {
  3. return Rooms.find({}, {
  4. fields: { secretInfo: 0 }
  5. });
  6. });
  7. // ...and publish secret info for rooms where the logged-in user is an admin. If
  8. // the client subscribes to both publications, the records are merged together
  9. // into the same documents in the `Rooms` collection. Note that currently object
  10. // values are not recursively merged, so the fields that differ must be top
  11. // level fields.
  12. Meteor.publish('adminSecretInfo', function () {
  13. return Rooms.find({ admin: this.userId }, {
  14. fields: { secretInfo: 1 }
  15. });
  16. });
  17. // Publish dependent documents and simulate joins.
  18. Meteor.publish('roomAndMessages', function (roomId) {
  19. check(roomId, String);
  20. return [
  21. Rooms.find({ _id: roomId }, {
  22. fields: { secretInfo: 0 }
  23. }),
  24. Messages.find({ roomId })
  25. ];
  26. });

Alternatively, a publish function can directly control its published record setby calling the functions added (to add a new document to thepublished record set), changed (to change or clear somefields on a document already in the published record set), andremoved (to remove documents from the published recordset). These methods are provided by this in your publish function.

If a publish function does not return a cursor or array of cursors, it isassumed to be using the low-level added/changed/removed interface, and itmust also call ready once the initial record set iscomplete.

Example (server):

  1. // Publish the current size of a collection.
  2. Meteor.publish('countsByRoom', function (roomId) {
  3. check(roomId, String);
  4. let count = 0;
  5. let initializing = true;
  6. // `observeChanges` only returns after the initial `added` callbacks have run.
  7. // Until then, we don't want to send a lot of `changed` messages—hence
  8. // tracking the `initializing` state.
  9. const handle = Messages.find({ roomId }).observeChanges({
  10. added: (id) => {
  11. count += 1;
  12. if (!initializing) {
  13. this.changed('counts', roomId, { count });
  14. }
  15. },
  16. removed: (id) => {
  17. count -= 1;
  18. this.changed('counts', roomId, { count });
  19. }
  20. // We don't care about `changed` events.
  21. });
  22. // Instead, we'll send one `added` message right after `observeChanges` has
  23. // returned, and mark the subscription as ready.
  24. initializing = false;
  25. this.added('counts', roomId, { count });
  26. this.ready();
  27. // Stop observing the cursor when the client unsubscribes. Stopping a
  28. // subscription automatically takes care of sending the client any `removed`
  29. // messages.
  30. this.onStop(() => handle.stop());
  31. });
  32. // Sometimes publish a query, sometimes publish nothing.
  33. Meteor.publish('secretData', function () {
  34. if (this.userId === 'superuser') {
  35. return SecretData.find();
  36. } else {
  37. // Declare that no data is being published. If you leave this line out,
  38. // Meteor will never consider the subscription ready because it thinks
  39. // you're using the `added/changed/removed` interface where you have to
  40. // explicitly call `this.ready`.
  41. return [];
  42. }
  43. });

Example (client):

  1. // Declare a collection to hold the count object.
  2. const Counts = new Mongo.Collection('counts');
  3. // Subscribe to the count for the current room.
  4. Tracker.autorun(() => {
  5. Meteor.subscribe('countsByRoom', Session.get('roomId'));
  6. });
  7. // Use the new collection.
  8. const roomCount = Counts.findOne(Session.get('roomId')).count;
  9. console.log(`Current room has ${roomCount} messages.`);

Meteor will emit a warning message if you call Meteor.publish in aproject that includes the autopublish package. Your publish functionwill still work.

Read more about publications and how to use them in theData Loading article in the Meteor Guide.

Server

this.userId

(ddp-server/livedata_server.js, line 1006)

Access inside the publish function. The id of the logged-in user, or null if no user is logged in.

This is constant. However, if the logged-in user changes, the publishfunction is rerun with the new value, assuming it didn’t throw an error at the previous run.

Server

this.added(collection, id, fields)

(ddp-server/livedata_server.js, line 1251)

Call inside the publish function. Informs the subscriber that a document has been added to the record set.

Arguments

  • collectionString
  • The name of the collection that contains the new document.

  • idString

  • The new document's ID.

  • fieldsObject

  • The fields in the new document. If _id is present it is ignored.

Server

this.changed(collection, id, fields)

(ddp-server/livedata_server.js, line 1274)

Call inside the publish function. Informs the subscriber that a document in the record set has been modified.

Arguments

  • collectionString
  • The name of the collection that contains the changed document.

  • idString

  • The changed document's ID.

  • fieldsObject

  • The fields in the document that have changed, together with their new values. If a field is not present in fields it was left unchanged; if it is present in fields and has a value of undefined it was removed from the document. If _id is present it is ignored.

Server

this.removed(collection, id)

(ddp-server/livedata_server.js, line 1290)

Call inside the publish function. Informs the subscriber that a document has been removed from the record set.

Arguments

  • collectionString
  • The name of the collection that the document has been removed from.

  • idString

  • The ID of the document that has been removed.

Server

this.ready()

(ddp-server/livedata_server.js, line 1307)

Call inside the publish function. Informs the subscriber that an initial, complete snapshot of the record set has been sent. This will trigger a call on the client to the onReady callback passed to Meteor.subscribe, if any.

Server

this.onStop(func)

(ddp-server/livedata_server.js, line 1225)

Call inside the publish function. Registers a callback function to run when the subscription is stopped.

Arguments

  • funcFunction
  • The callback function

If you call observe or observeChanges in yourpublish handler, this is the place to stop the observes.

Server

this.error(error)

(ddp-server/livedata_server.js, line 1193)

Call inside the publish function. Stops this client's subscription, triggering a call on the client to the onStop callback passed to Meteor.subscribe, if any. If error is not a Meteor.Error, it will be sanitized.

Arguments

  • errorError
  • The error to pass to the client.

Server

this.stop()

(ddp-server/livedata_server.js, line 1211)

Call inside the publish function. Stops this client's subscription and invokes the client's onStop callback with no error.

Server

this.connection

(ddp-server/livedata_server.js, line 964)

Access inside the publish function. The incoming connection for this subscription.

Client

Meteor.subscribe(name, [arg1, arg2…], [callbacks])

import { Meteor } from 'meteor/meteor' (ddp-client/common/livedata_connection.js, line 347)

Subscribe to a record set. Returns a handle that providesstop() and ready() methods.

Arguments

  • nameString
  • Name of the subscription. Matches the name of theserver's publish() call.

  • arg1, arg2…EJSON-able Object

  • Optional arguments passed to publisherfunction on server.

  • callbacksFunction or Object

  • Optional. May include onStopand onReady callbacks. If there is an error, it is passed as anargument to onStop. If a function is passed instead of an object, itis interpreted as an onReady callback.

When you subscribe to a record set, it tells the server to send records to theclient. The client stores these records in local Minimongocollections, with the same name as the collectionargument used in the publish handler’s added,changed, and removedcallbacks. Meteor will queue incoming records until you declare theMongo.Collection on the client with the matchingcollection name.

  1. // It's okay to subscribe (and possibly receive data) before declaring the
  2. // client collection that will hold it. Assume 'allPlayers' publishes data from
  3. // the server's 'players' collection.
  4. Meteor.subscribe('allPlayers');
  5. ...
  6. // The client queues incoming 'players' records until the collection is created:
  7. const Players = new Mongo.Collection('players');

The client will see a document if the document is currently in the publishedrecord set of any of its subscriptions. If multiple publications publish adocument with the same _id for the same collection the documents are merged forthe client. If the values of any of the top level fields conflict, the resultingvalue will be one of the published values, chosen arbitrarily.

Currently, when multiple subscriptions publish the same document only the toplevel fields are compared during the merge. This means that if the documentsinclude different sub-fields of the same top level field, not all of them willbe available on the client. We hope to lift this restriction in a future release.

The onReady callback is called with no arguments when the server marks thesubscription as ready. The onStop callback is called witha Meteor.Error if the subscription fails or is terminated bythe server. If the subscription is stopped by calling stop on the subscriptionhandle or inside the publication, onStop is called with no arguments.

Meteor.subscribe returns a subscription handle, which is an object with thefollowing properties:

  • stop()
  • Cancel the subscription. This will typically result in the server directing theclient to remove the subscription’s data from the client’s cache.

  • ready()

  • True if the server has marked the subscription as ready. Areactive data source.

  • subscriptionId

  • The id of the subscription this handle is for. When you run Meteor.subscribeinside of Tracker.autorun, the handles you get will always have the samesubscriptionId field. You can use this to deduplicate subscription handlesif you are storing them in some data structure.

If you call Meteor.subscribe within a reactive computation,for example usingTracker.autorun, the subscription will automatically becancelled when the computation is invalidated or stopped; it is not necessaryto call stop onsubscriptions made from inside autorun. However, if the next iterationof your run function subscribes to the same record set (same name andparameters), Meteor is smart enough to skip a wastefulunsubscribe/resubscribe. For example:

  1. Tracker.autorun(() => {
  2. Meteor.subscribe('chat', { room: Session.get('currentRoom') });
  3. Meteor.subscribe('privateMessages');
  4. });

This subscribes you to the chat messages in the current room and to your privatemessages. When you change rooms by calling Session.set('currentRoom','newRoom'), Meteor will subscribe to the new room’s chat messages,unsubscribe from the original room’s chat messages, and continue tostay subscribed to your private messages.