Index: src/wp-includes/shortcodes.php
===================================================================
--- src/wp-includes/shortcodes.php	(revision 38492)
+++ src/wp-includes/shortcodes.php	(working copy)
@@ -321,6 +321,24 @@
 		return $m[0];
 	}
 
+	/**
+	 * Filters whether to call a shortcode callback.
+	 *
+	 * Passing a truthy value to the filter will effectively short-circuit the
+	 * shortcode generation process, returning that value instead.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param bool|string $return      Short-circuit return value. Either false or a URL string.
+	 * @param string      $tag         Shortcode name.
+	 * @param array       $attr        Shortcode attributes array,
+	 * @param array       $m           Regular expression match array.
+	 */
+	$return = apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m );
+	if ( false !== $return ) {
+		return $return;
+	}
+
 	if ( isset( $m[5] ) ) {
 		// enclosing tag - extra parameter
 		return $m[1] . call_user_func( $shortcode_tags[$tag], $attr, $m[5], $tag ) . $m[6];
Index: tests/phpunit/tests/shortcode.php
===================================================================
--- tests/phpunit/tests/shortcode.php	(revision 38492)
+++ tests/phpunit/tests/shortcode.php	(working copy)
@@ -676,4 +676,67 @@
 		$expected = "<img alt=\"Hello :-) World\" />\n";
 		$this->assertEquals( $expected, $out );
 	}
+
+	/**
+	 * @ticket 37906
+	 */
+	public function test_pre_do_shortcode_tag() {
+		// does nothing if no filters are set up
+		$str = 'shortcode_37906';
+		add_shortcode( $str, array( $this, '_shortcode_37906' ) );
+		$result_nofilter = do_shortcode( "[{$str}]" );
+		$this->assertSame( 'foo', $result_nofilter );
+
+		// short-circuit with filter
+		add_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_bar' ) );
+		$result_filter = do_shortcode( "[{$str}]" );
+		$this->assertSame( 'bar', $result_filter );
+
+		// respect priority
+		add_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_p11' ), 11 );
+		$result_priority = do_shortcode( "[{$str}]" );
+		$this->assertSame( 'p11', $result_priority );
+
+		// pass arguments
+		$arr = array(
+			'return'	=> 'p11',
+			'key'			=> $str,
+			'atts' 		=> array( 'a'=>'b', 'c'=>'d' ),
+			'm'				=> array(
+				"[{$str} a='b' c='d']",
+				"",
+				$str,
+				" a='b' c='d'",
+				"",
+				"",
+				"",
+			),
+		);
+		add_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_attr' ), 12, 4 );
+		$result_atts = do_shortcode( "[{$str} a='b' c='d']" );
+		$this->assertSame( json_encode( $arr ), $result_atts );
+
+		remove_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_attr' ), 12, 4 );
+		remove_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_p11' ), 11 );
+		remove_filter( 'pre_do_shortcode_tag', array( $this, '_filter_37906_bar' ) );
+		remove_shortcode( $str, array( $this, '_shortcode_37906' ) );
+	}
+	public function _shortcode_37906( $atts = array(), $content = '' ) {
+		return 'foo';
+	}
+	public function _filter_37906_bar( ) {
+		return 'bar';
+	}
+	public function _filter_37906_p11( ) {
+		return 'p11';
+	}
+	public function _filter_37906_attr( $return, $key, $atts, $m ){
+		$arr = array(
+			'return'	=> $return,
+			'key'			=> $key,
+			'atts' 		=> $atts,
+			'm'				=> $m
+		);
+		return json_encode($arr);
+	}
 }
