diff --git src/wp-includes/js/wp-hooks.js src/wp-includes/js/wp-hooks.js
new file mode 100644
index 0000000..2d557f7
--- /dev/null
+++ src/wp-includes/js/wp-hooks.js
@@ -0,0 +1,276 @@
+( function( wp ) {
+	'use strict';
+
+	/**
+	 * Contains the registered hooks, keyed by hook type. Each hook type is an
+	 * array of objects with priority and callback of each registered hook.
+	 */
+	var HOOKS = {};
+
+	/**
+	 * Returns a function which, when invoked, will add a hook.
+	 *
+	 * @param  {string}   type Type for which hooks are to be added
+	 * @return {Function}      Hook added
+	 */
+	function createAddHookByType( type ) {
+		/**
+		 * Adds the hook to the appropriate hooks container
+		 *
+		 * @param {string}   hook     Name of hook to add
+		 * @param {Function} callback Function to call when the hook is run
+		 * @param {?number}  priority Priority of this hook (default=10)
+		 */
+		return function( hook, callback, priority ) {
+			var hookObject, hooks;
+			if ( typeof hook !== 'string' || typeof callback !== 'function' ) {
+				return;
+			}
+
+			// Assign default priority
+			if ( 'undefined' === typeof priority ) {
+				priority = 10;
+			} else {
+				priority = parseInt( priority, 10 );
+			}
+
+			// Validate numeric priority
+			if ( isNaN( priority ) ) {
+				return;
+			}
+
+			// Check if adding first of type
+			if ( ! HOOKS[ type ] ) {
+				HOOKS[ type ] = {};
+			}
+
+			hookObject = {
+				callback: callback,
+				priority: priority
+			};
+
+			if ( HOOKS[ type ].hasOwnProperty( hook ) ) {
+				// Append and re-sort amongst existing
+				hooks = HOOKS[ type ][ hook ];
+				hooks.push( hookObject );
+				hooks = sortHooks( hooks );
+			} else {
+				// First of its type needs no sort
+				hooks = [ hookObject ];
+			}
+
+			HOOKS[ type ][ hook ] = hooks;
+		};
+	}
+
+	/**
+	 * Returns a function which, when invoked, will remove a specified hook.
+	 *
+	 * @param  {string}   type Type for which hooks are to be removed
+	 * @return {Function}      Hook remover
+	 */
+	function createRemoveHookByType( type ) {
+		/**
+		 * Removes the specified hook by resetting its value.
+		 *
+		 * @param {string}    hook     Name of hook to remove
+		 * @param {?Function} callback The specific callback to be removed. If
+		 *                             omitted, clears all callbacks.
+		 */
+		return function( hook, callback ) {
+			var handlers, i;
+
+			// Baily early if no hooks exist by this name
+			if ( ! HOOKS[ type ] || ! HOOKS[ type ].hasOwnProperty( hook ) ) {
+				return;
+			}
+
+			if ( callback ) {
+				// Try to find specified callback to remove
+				handlers = HOOKS[ type ][ hook ];
+				for ( i = handlers.length - 1; i >= 0; i-- ) {
+					if ( handlers[ i ].callback === callback ) {
+						handlers.splice( i, 1 );
+					}
+				}
+			} else {
+				// Reset hooks to empty
+				delete HOOKS[ type ][ hook ];
+			}
+		};
+	}
+
+	/**
+	 * Returns a function which, when invoked, will execute all registered
+	 * hooks of the specified type by calling upon runner with its hook name
+	 * and arguments.
+	 *
+	 * @param  {string}   type   Type for which hooks are to be run, one of 'action' or 'filter'.
+	 * @param  {Function} runner Function to invoke for each hook callback
+	 * @return {Function}        Hook runner
+	 */
+	function createRunHookByType( type, runner ) {
+		/**
+		 * Runs the specified hook.
+		 *
+		 * @param  {string} hook The hook to run
+		 * @param  {...*}   args Arguments to pass to the action/filter
+		 * @return {*}           Return value of runner, if applicable
+		 * @private
+		 */
+		return function( /* hook, ...args */ ) {
+			var args, hook;
+
+			args = Array.prototype.slice.call( arguments );
+			hook = args.shift();
+
+			if ( typeof hook === 'string' ) {
+				return runner( hook, args );
+			}
+		};
+	}
+
+	/**
+	 * Performs an action if it exists.
+	 *
+	 * @param {string} action The action to perform.
+	 * @param {...*}   args   Optional args to pass to the action.
+	 * @private
+	 */
+	function runDoAction( action, args ) {
+		var handlers, i;
+		if ( HOOKS.actions ) {
+			handlers = HOOKS.actions[ action ];
+		}
+
+		if ( ! handlers ) {
+			return;
+		}
+
+		HOOKS.actions.current = action;
+
+		for ( i = 0; i < handlers.length; i++ ) {
+			handlers[ i ].callback.apply( null, args );
+			HOOKS.actions[ action ]['runs'] = HOOKS.actions[ action ]['runs'] ? HOOKS.actions[ action ]['runs'] + 1 : 1;
+		}
+	}
+
+	/**
+	 * Performs a filter if it exists.
+	 *
+	 * @param  {string} filter The filter to apply.
+	 * @param  {...*}   args   Optional args to pass to the filter.
+	 * @return {*}             The filtered value
+	 * @private
+	 */
+	function runApplyFilters( filter, args ) {
+		var handlers, i;
+		if ( HOOKS.filters ) {
+			handlers = HOOKS.filters[ filter ];
+		}
+
+		if ( ! handlers ) {
+			return args[ 0 ];
+		}
+
+		HOOKS.filters.current = filter;
+		HOOKS.filters[ filter ]['runs'] = HOOKS.filters[ filter ]['runs'] ? HOOKS.filters[ filter ]['runs'] + 1 : 1;
+
+		for ( i = 0; i < handlers.length; i++ ) {
+			args[ 0 ] = handlers[ i ].callback.apply( null, args );
+		}
+
+		return args[ 0 ];
+	}
+
+	/**
+	 * Use an insert sort for keeping our hooks organized based on priority.
+	 *
+	 * @see http://jsperf.com/javascript-sort
+	 *
+	 * @param  {Array} hooks Array of the hooks to sort
+	 * @return {Array}       The sorted array
+	 * @private
+	 */
+	function sortHooks( hooks ) {
+		var i, tmpHook, j, prevHook;
+		for ( i = 1; i < hooks.length; i++ ) {
+			tmpHook = hooks[ i ];
+			j = i;
+			while ( ( prevHook = hooks[ j - 1 ] ) && prevHook.priority > tmpHook.priority ) {
+				hooks[ j ] = hooks[ j - 1 ];
+				--j;
+			}
+			hooks[ j ] = tmpHook;
+		}
+
+		return hooks;
+	}
+
+	/**
+	 * Checks to see if an action is currently being executed.
+	 *
+	 * @param  {string} type   Type of hooks to check, one of 'action' or 'filter'.
+	 * @param {string}  action The name of the action to check for, if omitted will check for any action being performed.
+	 *
+	 * @return {[type]}      [description]
+	 */
+	function createDoingHookByType( type ) {
+		return function( action ) {
+
+			// If the action was not passed, check for any current hook.
+			if ( 'undefined' === typeof action ) {
+				return ! 'undefined' === typeof HOOKS[ type ].current;
+			}
+
+			// Return the current hook.
+			return HOOKS[ type ] && HOOKS[ type ].current ?
+				action === HOOKS[ type ].current :
+				false;
+		}
+	}
+
+	/**
+	 * Retrieve the number of times an action is fired.
+	 *
+	 * @param  {string} type   Type for which hooks to check, one of 'action' or 'filter'.
+	 * @param {string}  action The action to check.
+	 *
+	 * @return {[type]}      [description]
+	 */
+	function createDidHookByType( type ) {
+		return function( action ) {
+			return HOOKS[ type ] && HOOKS[ type ][ action ] && HOOKS[ type ][ action ]['runs'] ?
+				HOOKS[ type ][ action ]['runs'] :
+				0;
+		}
+	}
+
+	/**
+	 * Check to see if an action is registered for a hook.
+	 *
+	 * @param  {string} type   Type for which hooks to check, one of 'action' or 'filter'.
+	 * @param {string}  action  The action to check.
+	 *
+	 * @return {bool}      Whether an action has been registered for a hook.
+	 */
+	function createHasHookByType( type ) {
+		return function( action ) {
+			return HOOKS[ type ] && HOOKS[ type ][ action ] ?
+				!! HOOKS[ type ][ action ] :
+				false;
+		}
+	}
+
+	wp.hooks = {
+		removeFilter: createRemoveHookByType( 'filters' ),
+		applyFilters: createRunHookByType( 'filters', runApplyFilters ),
+		addFilter: createAddHookByType( 'filters' ),
+		removeAction: createRemoveHookByType( 'actions' ),
+		doAction: createRunHookByType( 'actions', runDoAction ),
+		addAction: createAddHookByType( 'actions' ),
+		doingAction: createDoingHookByType( 'actions' ),
+		didAction: createDidHookByType( 'actions' ),
+		hasAction: createHasHookByType( 'actions' ),
+	};
+} )( window.wp = window.wp || {} );
diff --git src/wp-includes/plugin.php src/wp-includes/plugin.php
index 86f1c3b..86f9db8 100644
--- src/wp-includes/plugin.php
+++ src/wp-includes/plugin.php
@@ -363,7 +363,7 @@ function doing_filter( $filter = null ) {
 }
 
 /**
- * Retrieve the name of an action currently being processed.
+ * Retrieve whether action currently being processed.
  *
  * @since 3.9.0
  *
diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
index def438c..4916c8f 100644
--- src/wp-includes/script-loader.php
+++ src/wp-includes/script-loader.php
@@ -85,6 +85,8 @@ function wp_default_scripts( &$scripts ) {
 
 	$scripts->add( 'wp-a11y', "/wp-includes/js/wp-a11y$suffix.js", array( 'jquery' ), false, 1 );
 
+	$scripts->add( 'wp-hooks', "/wp-includes/js/wp-hooks$suffix.js", array(), false, 1 );
+
 	$scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 );
 
 	$scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 );
diff --git tests/qunit/index.html tests/qunit/index.html
index 0c9d820..fca4114 100644
--- tests/qunit/index.html
+++ tests/qunit/index.html
@@ -50,6 +50,7 @@
 		<script src="../../src/wp-includes/js/customize-base.js"></script>
 		<script src="../../src/wp-includes/js/customize-models.js"></script>
 		<script src="../../src/wp-includes/js/shortcode.js"></script>
+		<script src="../../src/wp-includes/js/wp-hooks.js"></script>
 		<script src="../../src/wp-admin/js/customize-controls.js"></script>
 		<script src="../../src/wp-includes/js/wp-api.js"></script>
 
@@ -70,6 +71,7 @@
 		<script src="wp-admin/js/customize-base.js"></script>
 		<script src="wp-admin/js/customize-header.js"></script>
 		<script src="wp-includes/js/shortcode.js"></script>
+		<script src="wp-includes/js/wp-hooks.js"></script>
 		<script src="wp-includes/js/wp-api.js"></script>
 		<script src="wp-admin/js/customize-controls.js"></script>
 		<script src="wp-admin/js/customize-controls-utils.js"></script>
diff --git tests/qunit/wp-includes/js/wp-hooks.js tests/qunit/wp-includes/js/wp-hooks.js
new file mode 100644
index 0000000..248c466
--- /dev/null
+++ tests/qunit/wp-includes/js/wp-hooks.js
@@ -0,0 +1,191 @@
+/* global wp */
+( function( QUnit ) {
+	QUnit.module( 'wp-hooks' );
+
+	function filter_a( str ) {
+		return str + 'a';
+	}
+	function filter_b( str ) {
+		return str + 'b';
+	}
+	function filter_c( str ) {
+		return str + 'c';
+	}
+	function action_a() {
+		window.actionValue += 'a';
+	}
+	function action_b() {
+		window.actionValue += 'b';
+	}
+	function action_c() {
+		window.actionValue += 'c';
+	}
+	window.actionValue = '';
+
+	QUnit.test( 'add and remove a filter', function() {
+		expect( 1 );
+		wp.hooks.addFilter( 'test.filter', filter_a );
+		wp.hooks.removeFilter( 'test.filter' );
+		equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'test' );
+	} );
+
+	QUnit.test( 'add a filter and run it', function() {
+		expect( 1 );
+		wp.hooks.addFilter( 'test.filter', filter_a );
+		equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testa' );
+		wp.hooks.removeFilter( 'test.filter' );
+	} );
+
+	QUnit.test( 'add 2 filters in a row and run them', function() {
+		expect( 1 );
+		wp.hooks.addFilter( 'test.filter', filter_a );
+		wp.hooks.addFilter( 'test.filter', filter_b );
+		equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testab' );
+		wp.hooks.removeFilter( 'test.filter' );
+	} );
+
+	QUnit.test( 'add 3 filters with different priorities and run them', function() {
+		expect( 1 );
+		wp.hooks.addFilter( 'test.filter', filter_a );
+		wp.hooks.addFilter( 'test.filter', filter_b, 2 );
+		wp.hooks.addFilter( 'test.filter', filter_c, 8 );
+		equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testbca' );
+		wp.hooks.removeFilter( 'test.filter' );
+	} );
+
+	QUnit.test( 'add and remove an action', function() {
+		expect( 1 );
+		window.actionValue = '';
+		wp.hooks.addAction( 'test.action', action_a );
+		wp.hooks.removeAction( 'test.action' );
+		wp.hooks.doAction( 'test.action' );
+		equal( window.actionValue, '' );
+	} );
+
+	QUnit.test( 'add an action and run it', function() {
+		expect( 1 );
+		window.actionValue = '';
+		wp.hooks.addAction( 'test.action', action_a );
+		wp.hooks.doAction( 'test.action' );
+		equal( window.actionValue, 'a' );
+		wp.hooks.removeAction( 'test.action' );
+	} );
+
+	QUnit.test( 'add 2 actions in a row and then run them', function() {
+		expect( 1 );
+		window.actionValue = '';
+		wp.hooks.addAction( 'test.action', action_a );
+		wp.hooks.addAction( 'test.action', action_b );
+		wp.hooks.doAction( 'test.action' );
+		equal( window.actionValue, 'ab' );
+		wp.hooks.removeAction( 'test.action' );
+	} );
+
+	QUnit.test( 'add 3 actions with different priorities and run them', function() {
+		expect( 1 );
+		window.actionValue = '';
+		wp.hooks.addAction( 'test.action', action_a );
+		wp.hooks.addAction( 'test.action', action_b, 2 );
+		wp.hooks.addAction( 'test.action', action_c, 8 );
+		wp.hooks.doAction( 'test.action' );
+		equal( window.actionValue, 'bca' );
+		wp.hooks.removeAction( 'test.action' );
+	} );
+
+	QUnit.test( 'pass in two arguments to an action', function() {
+		var arg1 = 10,
+			arg2 = 20;
+
+		expect( 4 );
+
+		wp.hooks.addAction( 'test.action', function( a, b ) {
+			equal( arg1, a );
+			equal( arg2, b );
+		} );
+		wp.hooks.doAction( 'test.action', arg1, arg2 );
+		wp.hooks.removeAction( 'test.action' );
+
+		equal( arg1, 10 );
+		equal( arg2, 20 );
+	} );
+
+	QUnit.test( 'fire action multiple times', function() {
+		var func;
+		expect( 2 );
+
+		func = function() {
+			ok( true );
+		};
+
+		wp.hooks.addAction( 'test.action', func );
+		wp.hooks.doAction( 'test.action' );
+		wp.hooks.doAction( 'test.action' );
+		wp.hooks.removeAction( 'test.action' );
+	} );
+
+	QUnit.test( 'remove specific action callback', function() {
+		window.actionValue = '';
+		wp.hooks.addAction( 'test.action', action_a );
+		wp.hooks.addAction( 'test.action', action_b, 2 );
+		wp.hooks.addAction( 'test.action', action_c, 8 );
+
+		wp.hooks.removeAction( 'test.action', action_b );
+		wp.hooks.doAction( 'test.action' );
+		equal( window.actionValue, 'ca' );
+		wp.hooks.removeAction( 'test.action' );
+	} );
+
+	QUnit.test( 'remove specific filter callback', function() {
+		wp.hooks.addFilter( 'test.filter', filter_a );
+		wp.hooks.addFilter( 'test.filter', filter_b, 2 );
+		wp.hooks.addFilter( 'test.filter', filter_c, 8 );
+
+		wp.hooks.removeFilter( 'test.filter', filter_b );
+		equal( wp.hooks.applyFilters( 'test.filter', 'test' ), 'testca' );
+		wp.hooks.removeFilter( 'test.filter' );
+	} );
+
+	// Test doingAction, didAction, hasAction.
+	QUnit.test( 'Test doingAction, didAction and hasAction.', function() {
+
+		// Reset state for testing.
+		wp.hooks.removeAction( 'test.action' );
+		wp.hooks.addAction( 'another.action', function(){} );
+		wp.hooks.doAction( 'another.action' );
+
+		// Verify no action is running yet.
+		notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' );
+		equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
+		notOk( wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' );
+
+		wp.hooks.addAction( 'test.action', action_a );
+
+		// Verify action added, not running yet.
+		notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is not running.' );
+		equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
+		ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' );
+
+		wp.hooks.doAction( 'test.action' );
+
+		// Verify action added and running.
+		ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
+		equal( wp.hooks.didAction( 'test.action' ), 1, 'The test.action has run once.' );
+		ok( wp.hooks.hasAction( 'test.action' ), 'The test.action is registered.' );
+
+		wp.hooks.doAction( 'test.action' );
+		equal( wp.hooks.didAction( 'test.action' ), 2, 'The test.action has run twice.' );
+
+		wp.hooks.removeAction( 'test.action' );
+
+		// Verify state is reset appropriately.
+		ok( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
+		equal( wp.hooks.didAction( 'test.action' ), 0, 'The test.action has not run.' );
+		notOk( wp.hooks.hasAction( 'test.action' ), 'The test.action is not registered.' );
+
+		wp.hooks.doAction( 'another.action' );
+		notOk( wp.hooks.doingAction( 'test.action' ), 'The test.action is running.' );
+
+
+	} );
+
+} )( window.QUnit );
