Skip to content

Breaking changes

MongoDB Methods in the server

As mentioned in the Frequently Asked Questions, insert, update, remove, find, findOne, upsert methods no longer work in the server.

You should migrate to use their Async counterparts.

js
const docs = MyCollection.find({ _id: '123' }).fetch(); This will not work in the server
const doc = MyCollection.findOne({ _id: '123' }); This will not work in the server


// in Meteor 3.x you should use the Async methods

const docs = await MyCollection.find({ _id: '123' }).fetchAsync(); This will work in the server
const doc = await MyCollection.findOneAsync({ _id: '123' }); This will work in the server

CLI

vue2

The --vue2 flag is no longer available. We droped support for vue2. You can see more information in this PR.

Why?

This was decided because vue2 reached its end of life on 2023-12-31, the team decided to drop support for it.

reset

The meteor reset command clears only the local cache by default. Using the --db option will also delete the local Mongo database. Ensure your CI flows accommodate any changes by passing the --db option if needed.

Why?

This command is often recommended to fix your development project by clearing the cache. Previously, it also cleared the local MongoDB, which could accidentally delete important data.

Node v20

Meteor 3.0 is now using Node v20. This means that if you have any dependencies or usages of Node v14, you will need to update them to be compatible with Node v20.

NPM Installer Update

The npm installer for Meteor has been changed. For the official release, you can install Meteor with this command:

bash
npx meteor

While we’re in the Release Candidate phase, use:

bash
npx meteor

or specify a version directly:

bash
npx meteor@<version>

Ensure you're using Node version 20.0.0 or higher, especially in your CI/CD workflows, to be compatible with the latest Meteor version.

Call x CallAsync

TIP

You can check call x callAsync page for a full overview.

Due to how meteor now works with async/await, you should use callAsync instead of call in your methods.

In Meteor 2.x this was a common pattern:

js
import { Meteor } from 'meteor/meteor'

Meteor.methods({
  async getAllData() {
    return await MyCollection.find().fetch(); 
  },
  async otherMethod() {
    return await MyCollection.find().fetch(); 
  }
});


Meteor.call('getAllData') 
Meteor.call('otherMethod') 

Now in Meteor 3.x it should become:

js
import { Meteor } from 'meteor/meteor'

Meteor.methods({
  async getAllData() {
    return await MyCollection.find().fetchAsync(); 
  },
  async otherMethod() {
    return await MyCollection.find().fetchAsync(); 
  }
});

await Meteor.callAsync('getAllData') 
await Meteor.callAsync('otherMethod') 

WebApp Switches to Express

TIP

WebApp has switched to Express from Connect. This upgrade lets you use all the Express features in your Meteor app. If you've customized the WebApp package before, please verify if those customizations work with Express.

The webapp package now exports these new properties:

ts
type ExpressModule = {
  (): express.Application;
  json: typeof express.json;
  raw: typeof express.raw;
  Router: typeof express.Router;
  static: typeof express.static;
  text: typeof express.text;
  urlencoded: typeof express.urlencoded;
};

export declare module WebApp {
  // ...
  /**
   * @deprecated use handlers instead
   */
  var connectHandlers: express.Application;
  var handlers: express.Application; 
  /**
   * @deprecated use rawHandlers instead
   */
  var rawConnectHandlers: express.Application;
  var rawHandlers: express.Application;
  var httpServer: http.Server;
  var expressApp: express.Application;
  var express: ExpressModule; 
  // ...
}

// import { WebApp } from 'meteor/webapp';

Routes with WebApp and Express

To add Express routes in your app, check out the Express Guide and follow this example:

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express(); you can use as a normal express app

app.get('/hello', (req, res) => {
  res.send('Hello World');
});

WebApp.handlers.use(app);

The code below is an example of how you can use the handlers property to create a route in your app:

js
import { WebApp } from 'meteor/webapp';

WebApp.handlers.get('/hello', (req, res) => {
  res.send('Hello World');
});

Middlewares with WebApp and Express

To include Router-level Express middleware in your app, check out the Express Guide and follow this example:

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express();
const router = WebApp.express.Router();

// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
    console.log('Router-level - Time:', Date.now());
    next();
})

// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
    console.log('Router-level - Request URL:', req.originalUrl);
    next();
}, (req, res, next) => {
    console.log('Router-level - Request Type:', req.method);
    next();
})

// mount the router on the app
app.use('/', router);

WebApp.handlers.use(app);

To include Application-level Express middleware in your app, check out the Express Guide and follow this example:

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express();
const router = WebApp.express.Router()

// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
    console.log('Router-level - Time:', Date.now());
    next();
})

// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
    console.log('Router-level - Request URL:', req.originalUrl);
    next();
}, (req, res, next) => {
    console.log('Router-level - Request Type:', req.method);
    next();
})

// mount the router on the app
app.use('/', router);

WebApp.handlers.use(app);

New API Names

Having switched from Connect to Express, we updated API names to align with Express. See the details below:

  • WebApp.connectHandlers.use(middleware) is now WebApp.handlers.use(middleware)
  • WebApp.rawConnectHandlers.use(middleware) is now WebApp.rawHandlers.use(middleware)
  • WebApp.connectApp is now WebApp.expressApp

A few methods from WebApp internals are now async:

  • WebAppInternals.reloadClientPrograms()
  • WebAppInternals.pauseClient()
  • WebAppInternals.generateClientProgram()
  • WebAppInternals.generateBoilerplate()
  • WebAppInternals.setInlineScriptsAllowed()
  • WebAppInternals.enableSubresourceIntegrity()
  • WebAppInternals.setBundledJsCssUrlRewriteHook()
  • WebAppInternals.setBundledJsCssPrefix()
  • WebAppInternals.getBoilerplate

Meteor.userAsync

You should use Meteor.userAsync instead of Meteor.user in your code, especially if you want isomorphism or want to get your user in the server.

js
// Before
const user = Meteor.user(); 
// After
const user = await Meteor.userAsync(); 

Environment Variables

Meteor provides the Meteor.EnvironmentVariable class, which helps maintain context across different boundaries.

With Meteor 3.0, we added support for asynchronous flows and improved how context is managed. As a result, some packages began losing context data, as described in this issue.

If your app or package uses EnvironmentVariable, make sure to use EnvironmentVariable.withValue at the top level to correctly preserve and propagate the context.

For instance, when updating publish behavior and introducing a new EnvironmentVariable context, you need to adjust your code as follows:

javascript
const _publishConnectionId = new Meteor.EnvironmentVariable<
  string | undefined
  >();

// Before
function patchPublish(publish: typeof Meteor.publish) {
  return function (this: typeof Meteor, name, func, ...args) {
    return publish.call( 
      this,
      name,
      function (...args) {
        return _publishConnectionId.withValue(this?.connection?.id, () =>
          func.apply(this, args),
        );
      },
      ...args,
    ); 
  } as typeof Meteor.publish;
}

// After
function patchPublish(publish: typeof Meteor.publish) {
  return function (this: typeof Meteor, name, func, ...args) {
    return _publishConnectionId.withValue(this?.connection?.id, () => { 
      return publish.call(
        this,
        name,
        function (...args) {
          return func.apply(this, args);
        },
        ...args,
      );
    }); 
  } as typeof Meteor.publish;
}

This example demonstrates the migration applied to the universe:i18n package.