WordPress has a [video] shortcode built into core, so you can put a video on a page without installing anything. For one MP4 in a post it does the job in a single line. Then you hit something the documentation does not explain: a plain blue link instead of a player, a width you did not set, or nothing at all with no error.
Each of those has an exact cause inside wp_video_shortcode(), and none of them appears on the official support page. That page has other gaps. It documents eight attributes when the function accepts fourteen. It also tells you to write loop="off" to stop a video looping, and "off" is the one value that turns looping on.
To use it properly you need all fourteen attributes and their real defaults, the four filters that rewrite the output, and the line of code behind each symptom. That is what this reference covers, read from the WordPress source rather than from the docs.
TL;DR
WordPress core's [video] shortcode registers 14 attributes, not the 8 on the support page. Nine are fixed, and wp_get_video_extensions() adds one fallback attribute per format. The support page gets five things wrong. The worst one: it says loop, autoplay and muted take "on" or "off", but "off" evaluates to true. Four filters change the output, and all four are absent from the support page. What it cannot do is the bigger story. One file at one bitrate, no adaptive streaming, no captions attribute, no access control, no playback data.
Every attribute the WordPress video shortcode accepts
The shortcode registers fourteen attributes. Nine are fixed. The function builds the other five at runtime. It loops over `wp_get_video_extensions()` and adds one attribute per supported format, which is how the fallback source syntax works.
| Attribute | Type | Default | What it does |
|---|---|---|---|
| src | string | empty | Source URL. If empty, the function uses the first video attached to the post. |
| poster | string | empty | Poster image URL. Omitted from output when empty. |
| loop | string | empty | Restarts on end. Passed through wp_validate_boolean(). |
| autoplay | string | empty | Plays on load. Passed through wp_validate_boolean(). |
| muted | string | 'false' | Mutes playback. Required by browsers for autoplay to work. |
| preload | string | 'metadata' | Accepts none, metadata or auto. Any other value is discarded. |
| width | int | $content_width or 640 | Clamped to $content_width on the front end. |
| height | int | 360 | Recalculated when width is clamped. |
| class | string | 'wp-video-shortcode' | Class on the <video> element, not the wrapper. |
| mp4 | string | empty | Fallback source for this format. |
| m4v | string | empty | Fallback source for this format. |
| webm | string | empty | Fallback source for this format. |
| ogv | string | empty | Fallback source for this format. |
| flv | string | empty | Fallback source for this format. |
There is no controls attribute. The function hardcodes controls="controls" on the video element, so the control bar cannot be removed through the shortcode. There is no track or captions attribute either.
Five places the documentation will send you wrong
Five things on the official support page are wrong, and each one sends somebody to the forums. You can check every one of them by reading the function.
| What the documentation says | What the function does |
|---|---|
| Lists 8 attributes | Registers 14, omitting class and the five per-format attributes |
| height and width are "(required)" | Both default, to 360 and 640, and the shortcode renders without either |
| src fallbacks include wmv | wp_get_video_extensions() returns mp4, m4v, webm, ogv, flv. No wmv |
| loop, autoplay and muted take "on" or "off" | Values go through wp_validate_boolean(), where "off" evaluates to true |
| muted defaults to "off" | The registered default is the string 'false' |
Two smaller problems sit alongside those. On the video shortcode page, the Usage section opens by describing an audio file. And its three worked examples render as broken markdown links instead of code, so the examples a beginner needs most cannot be read.
Why the video shortcode outputs a link instead of a player
This is the most common failure, and one condition causes it. When src is set and the URL is neither YouTube nor Vimeo, wp_video_shortcode() runs the extension through wp_check_filetype() and checks it against the supported list. If it is not there, the function returns early:
return sprintf(
'<a class="wp-embedded-video" href="%s">%s</a>',
esc_url( $atts['src'] ),
esc_html( $atts['src'] )
);An .mov from a phone, an .mkv from a screen recorder, or an .avi from an archive all produce that anchor tag. So does .wmv, which the support page still lists as supported. If you see a bare blue link with the class wp-embedded-video where the player should be, the extension is the reason. Transcode to MP4 and it plays. Which container actually plays where is covered in the guide to video formats, codecs and containers.
Why WordPress changes your video width on the front end
The function clamps width in two places, against two different values. In wp-admin, a width above 640 drops to 640. On the front end, a width above the theme's $content_width global drops to $content_width. Height is recalculated both times to hold the aspect ratio. So the number you wrote in the shortcode is not the number in the markup.
$content_width is a global set by your theme, in pixels, declaring the widest an embedded item may render. WordPress core reads it and shrinks anything larger.The wrapper makes it worse. The output is a <div class="wp-video"> with an inline width style in pixels, and an inline style beats anything in your stylesheet. That is why a responsive fix on .wp-video often does nothing. You have to override the inline style, or rewrite the wrapper with the wp_video_shortcode filter. The broader problem of sizing a player across breakpoints is covered in optimising video player layouts for any screen size.
How the video shortcode handles YouTube and Vimeo URLs
YouTube and Vimeo URLs skip the extension check entirely. The function matches them with two regular expressions, sets the source type to video/youtube or video/vimeo, then hands the URL to MediaElement.js rather than treating it as a file.
It also rewrites the URL before output. For YouTube it strips the feature query argument and forces the scheme to HTTPS. For Vimeo it drops every query argument, rebuilds the URL from host and path alone, then adds a single loop parameter. Both rewrites work around how MediaElement.js handles URLs. Both also strip any tracking or start-time parameter you put on the URL before the player ever sees it.
The four filters that change video shortcode output
All four hooks live on the developer reference, not the support page. That is why most people never find them. They fire in this order.
| Filter | Fires | Use it to |
|---|---|---|
| wp_video_shortcode_override | Before anything is generated | Return your own markup and skip the core player entirely |
| wp_video_shortcode_library | After attributes are parsed | Return something other than mediaelement to stop core enqueueing its player assets |
| wp_video_shortcode_class | Before the video tag is built | Change the class on the <video> element |
| wp_video_shortcode | On the finished HTML | Rewrite the output, including the wrapper div and its inline width |
A fifth filter sits upstream. wp_video_extensions changes the list wp_get_video_extensions() returns. Add a format there and two things happen: the shortcode accepts it, and it gains a matching fallback attribute.
Two behaviours nobody documents
Neither of these appears on either official page, and both surprise people reading the generated HTML.
The function adds a cache-busting query argument to every source URL. It keeps a counter that ticks up once per shortcode on the page, then writes that number onto each source as ?_=1, ?_=2 and so on. The same counter also builds the element id, as video-{post_id}-{instance}.
The second one is what happens with no source at all. Leave out src and every format attribute, and the function calls get_attached_media( 'video', $post_id ) and plays the first video attached to that post. If the post has none, the function returns null. The shortcode renders nothing, with no error and no comment in the markup.
What the WordPress video shortcode cannot do
Most questions that reach the support forums are not about syntax. They are about things the shortcode was never built to do.
- One file, one bitrate. Multiple
sourceelements are format fallbacks, not quality levels. There is no manifest and no bitrate switching, so a viewer on a poor connection gets the same file as everyone else and buffers. The difference is set out in ABR vs MBR: adaptive vs multi-bitrate streaming. - No captions through an attribute. Track support lives in the block editor's video block. The shortcode will append raw
<track>elements passed as shortcode content, which is a workaround rather than a feature. - No access control. The
srcis a plain URL in the page source. Anyone who views source can copy it, and it keeps working. Protecting video content with signed URLs covers the model that fixes this. - No playback data. Nothing reports whether the video played, how far anyone watched, or whether it stalled.
- Delivery is your origin's problem. The file is served by whatever serves the rest of the site, so concurrent viewers compete with page requests for the same bandwidth. How a CDN reduces bandwidth costs covers the first step out of that.
When those limits start to hurt, you can keep the editing workflow and change what sits behind it. The FastPix WordPress plugin registers its own [fastpix] shortcode with a separate parameter set. Its access model signs playback at render time instead of exposing a file URL. It is a different shortcode, not a drop-in replacement, and the attributes do not carry across. You can test it against a real adaptive stream first: the free plan covers 10 videos and 100K streaming minutes a month, no card.
Replace the core player without changing how you write posts
If the ceiling above is what you actually hit, the next step is not a longer shortcode. Read the FastPix WordPress plugin setup guide for the three-step wizard and the [fastpix] parameter table. Then check the migration tool if you already have a library in wp-content. Migration substitutes the player at render time and never edits post content, so existing [video] embeds keep working while you move. The free plan covers 10 videos and 100K streaming minutes a month, no card required.
Frequently Asked Questions (FAQs)
How many attributes does the WordPress video shortcode accept?
Fourteen. Nine are fixed. Those are src, poster, loop, autoplay, muted, preload, width, height and class. The other five come from wp_get_video_extensions() at runtime, one per supported format. The official support page documents eight of the fourteen.
Why does the WordPress video shortcode return a link instead of a player?
The file extension in src is not in the list returned by wp_get_video_extensions(). When the extension is unrecognised and the URL is not YouTube or Vimeo, the function returns an anchor tag with the class wp-embedded-video and stops. A .mov, .mkv or .wmv file produces this. Transcoding to MP4 fixes it, and the guide to video formats, codecs and containers explains which container plays where.
Why does loop="off" still loop the video?
The value passes through wp_validate_boolean(), which returns false only for a boolean false, the string "false", or an empty value. The string "off" is none of those, so it casts to true and the loop attribute is written into the video tag. Omit the attribute entirely, or pass loop="false". The same applies to autoplay and muted.
Can the WordPress video shortcode add captions or subtitles?
Not through an attribute. There is no track or captions parameter. Raw track elements passed as shortcode content are appended inside the video tag, but the block editor's video block is the supported route for caption tracks.
Which video formats does the WordPress video shortcode support?
Five by default: MP4, M4V, WebM, OGV and FLV. The list comes from wp_get_video_extensions() and can be changed with the wp_video_extensions filter. WMV appears on the official support page but is not in the current list, so a .wmv source renders as a bare link.
Does the WordPress video shortcode support adaptive bitrate streaming?
No. It writes source elements pointing at whole files and lets the browser pick the first it can play. There is no manifest and no bitrate switching, so playback quality is fixed at whatever the source file is. The difference is set out in ABR vs MBR: adaptive vs multi-bitrate streaming.
Why does my WordPress video width change on the front end?
The function clamps width to the theme's $content_width global when the requested width is larger, and recalculates height proportionally. In wp-admin the same clamp applies against 640 pixels. The wrapper div also carries an inline width style, which is why CSS alone often fails to make it responsive. See optimising video player layouts.
How do I remove the controls from the WordPress video shortcode?
You cannot do it with an attribute. The function hardcodes controls="controls" on the video element. To remove it, use the wp_video_shortcode filter to rewrite the finished HTML. Or use wp_video_shortcode_override to replace the output before it is built.









