← Back to all tutorials

Creating a Gruntfile.js

Learn the structure and anatomy of a Gruntfile.js — the wrapper function, config object, plugin loading, and task registration.

Creating a Gruntfile.js

The Gruntfile.js is the heart of your Grunt workflow. It is where you configure tasks, load plugins, and register custom commands. Every Grunt project needs one in the project root. In this episode you will learn the anatomy of a Gruntfile and create your first one.

The Basic Gruntfile Structure

module.exports = function(grunt) {

    // 1. Configure tasks
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Task configurations go here
    });

    // 2. Load plugins
    // grunt.loadNpmTasks('plugin-name');

    // 3. Register tasks
    grunt.registerTask('default', []);

};

Anatomy of a Gruntfile

Every Gruntfile has three main sections wrapped inside the module.exports function:

SectionPurposeMethod
ConfigurationDefine how each task should run (inputs, outputs, options)grunt.initConfig({})
Plugin LoadingLoad installed Grunt plugins so their tasks are availablegrunt.loadNpmTasks()
Task RegistrationCreate named commands that run one or more tasksgrunt.registerTask()

The Wrapper Function

module.exports = function(grunt) {
    // All Grunt code goes inside this function
};

The entire Gruntfile is wrapped in a module.exports function. This is a Node.js module pattern — Grunt requires this file and passes the grunt object which provides all the API methods you need.

grunt.initConfig — Configuration

grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    concat: {
        options: {
            separator: ';\n',
        },
        dist: {
            src: ['src/js/*.js'],
            dest: 'dist/js/bundle.js',
        },
    },
});

The initConfig object contains a property for each task. Each task property matches the plugin name (e.g., concat for grunt-contrib-concat). Inside each task, you define targets, options, source files, and destination files.

Understanding the pkg Property

pkg: grunt.file.readJSON('package.json')

This reads your package.json into a variable. You can then reference project metadata in your configuration using template strings like <%= pkg.name %> and <%= pkg.version %>. This is useful for adding banners or naming output files dynamically.

Task Configuration Structure

taskName: {
    options: {
        // Global options for this task
    },
    targetName: {
        options: {
            // Target-specific options (override global)
        },
        src: ['source/files/**/*.js'],
        dest: 'output/file.js',
    },
    anotherTarget: {
        src: ['other/**/*.js'],
        dest: 'other-output.js',
    },
}
PropertyPurpose
optionsConfiguration options for the task (top level) or target (nested level)
targetNameA named sub-task — you can have multiple targets per task
srcInput files — supports glob patterns like *.js and **/*.js
destOutput file or directory

Glob Patterns for File Matching

PatternMatches
*.jsAll .js files in the current directory
**/*.jsAll .js files in any subdirectory (recursive)
src/js/*.jsAll .js files in src/js/
!src/js/vendor.jsExclude a specific file (prefix with !)
['a.js', 'b.js']Specific files in order

grunt.loadNpmTasks — Loading Plugins

grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-sass');

Each plugin you install must be explicitly loaded with loadNpmTasks. This registers the plugin's tasks so they can be used in your configuration and run from the command line.

grunt.registerTask — Custom Commands

// Register the default task (runs when you type just "grunt")
grunt.registerTask('default', ['concat', 'uglify', 'sass']);

// Register a named task
grunt.registerTask('build', ['concat', 'uglify']);

// Register a task that runs a single plugin task
grunt.registerTask('styles', ['sass']);

Registered tasks are the commands you type in the terminal. grunt default (or just grunt) runs the default task. grunt build runs the build task. Each registered task specifies an array of plugin tasks to run in order.

Running Tasks

# Run the default task
grunt

# Run a specific registered task
grunt build

# Run a specific plugin task
grunt concat

# Run a specific target of a task
grunt concat:dist

A Complete Starter Gruntfile

module.exports = function(grunt) {

    // 1. Configuration
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Placeholder — tasks will be added in next episodes
    });

    // 2. Load plugins
    // (none yet — we'll add them in the next episodes)

    // 3. Register default task
    grunt.registerTask('default', []);

};

Save this as Gruntfile.js in your project root. Running grunt in the terminal should now work without errors — it just does nothing yet because we have not added any plugins.

Testing Your Gruntfile

grunt

If everything is set up correctly, you will see:

Done, without errors.

If you see an error like "Unable to find local grunt," make sure you ran npm install grunt --save-dev. If you see "Cannot find module," check that your Gruntfile.js is in the project root directory.

Key Takeaways

  • Every Gruntfile is a module.exports wrapper function that receives the grunt object
  • Three sections: initConfig (configure tasks), loadNpmTasks (load plugins), registerTask (create commands)
  • Task configurations match plugin names and can have multiple targets
  • pkg: grunt.file.readJSON('package.json') provides access to project metadata
  • Glob patterns (*.js, **/*.js) specify source files flexibly
  • The default task runs when you type grunt without arguments