Title: Customizing Share a Draft with code, WP-CLI, and Abilities
Author: Jacob
Published: October 20, 2026

---

 1. [Plugins](https://docs.wpvip.com/plugins/)
 2. [Automattic plugins](https://docs.wpvip.com/plugins/automattic-plugins/)
 3. [Share a Draft](https://docs.wpvip.com/plugins/automattic-plugins/share-a-draft/)
 4. Customizing Share a Draft with code, WP-CLI, and Abilities

#  Customizing Share a Draft with code, WP-CLI, and Abilities

Share a Draft works without configuration. When a site needs different behavior,
filters and actions change how links behave, from the lifetimes offered to what 
reviewers are told. The same operations available in the block editor are also available
from [WP-CLI](https://wp-cli.org/) and from the WordPress [Abilities API](https://developer.wordpress.org/apis/abilities/),
so scripts and AI assistants can create and manage links under the same rules.

Add the code on this page to a plugin in the application’s repository. On the WordPress
VIP Platform, code reaches an environment through deployment, not through the WordPress
Admin dashboard.

## Prerequisites

To customize Share a Draft, the following must be in place:

 * Share a Draft must be enabled for the application.
 * The site must run WordPress 6.9 or later, which includes the Abilities API.
 * For WP-CLI, [VIP-CLI](https://docs.wpvip.com/vip-cli/) must be installed and 
   authenticated for the application.

## Limitations

 * Lifetimes, limits, and restrictions are fixed when a link is created, and there
   is no command or ability to edit a link. To change a link, revoke it and create
   a new one.
 * Turning off a restriction, such as named reviewers, stops new links from using
   it. Links that already use the restriction keep enforcing it.
 * Code-based settings take precedence over settings in the VIP Dashboard.

## Changing link lifetimes

The editor offers links that last 1 hour, 8 hours, 24 hours, or 7 days. To offer
other lifetimes, return a list from the `shareadraft_expiration_options` filter.
Each option is a number of seconds and a label, in the order they should appear:

    ```wp-block-preformatted
    add_filter(  'shareadraft_expiration_options',  function ( array $options ): array {    $options[] = array(      'seconds' => 30 * DAY_IN_SECONDS,      'label'   => __( '30 days', 'my-plugin' ),    );​    return $options;  });
    ```

Only lifetimes in this list are accepted, whether a link is created in the editor,
with WP-CLI, or through the Abilities API.

To change which lifetime is selected when the editor opens, return a number of seconds
from `shareadraft_default_expiration`:

    ```wp-block-preformatted
    add_filter( 'shareadraft_default_expiration', fn () => DAY_IN_SECONDS );
    ```

## Turning off reviewer or IP restrictions

Named reviewers and IP restrictions are optional. To switch either off for the site,
return `false` from the matching filter:

    ```wp-block-preformatted
    add_filter( 'shareadraft_recipients_enabled', '__return_false' );add_filter( 'shareadraft_ip_allowlist_enabled', '__return_false' );
    ```

The fields disappear from the editor, the **Preview Links** screen, and the Abilities
API, and WP-CLI refuses to use them.

## Changing the verification email

When a link is bound to named reviewers, each reviewer is emailed a 6-digit code.
To change the subject or wording, use the `shareadraft_verification_email` filter.
It receives the subject and plain-text message, the recipient’s address, and the
code. If the message is replaced entirely, it must still include the code:

    ```wp-block-preformatted
    add_filter(  'shareadraft_verification_email',  function ( array $mail, string $email, string $code ): array {    /* translators: %s: Site title. */    $mail['subject'] = sprintf( __( 'Your review code for %s', 'my-plugin' ), get_bloginfo( 'name' ) );​    /* translators: %s: Six-digit verification code. */    $mail['message'] = sprintf( __( 'Your code is %s. It expires in a few minutes; please do not share it.', 'my-plugin' ), $code );​    return $mail;  },  10,  3);
    ```

## Changing what reviewers are told

When a link has expired, been revoked, or been used up, the reviewer sees a short
notice on a simple page that carries the site’s name and icon. The email verification
steps use the same page.

To change the text inside the page, use `shareadraft_notice_content`. It receives
the page’s HTML content, its title, and its HTTP status. Whatever the filter returns
is output as is, so escape anything added. To replace the whole page, hook the `
wp_die_handler` filter, because every notice is shown through `wp_die()`.

To say less about why a link stopped working, return `false` from `shareadraft_disclose_denial_reason`.
Every reason then collapses into one general message. The filter also receives the
reason (`expired`, `revoked`, `exhausted`, or `links_disabled`), so individual reasons
can be hidden:

    ```wp-block-preformatted
    // Say when a link has expired, but not when it has been revoked.add_filter(  'shareadraft_disclose_denial_reason',  fn ( bool $disclose, string $reason ): bool => 'revoked' !== $reason,  10,  2);
    ```

Only someone holding a genuine link for that draft reaches these notices, so this
is a matter of tone, not security.

## Changing how long expired links are kept

Expired and revoked links are kept for 21 days, so a returning reviewer is told 
why the link stopped working. A daily cleanup then deletes them. To change the period,
return a number of seconds from `shareadraft_dead_link_grace_period`:

    ```wp-block-preformatted
    add_filter( 'shareadraft_dead_link_grace_period', fn () => 7 * DAY_IN_SECONDS );
    ```

Returning `0` deletes expired and revoked links at the next cleanup. The retention
period can also be set with the **Expired link retention** setting in the VIP Dashboard.
The code filter takes precedence.

## Recognizing more link previewers

Chat apps and social networks fetch a link as soon as it is pasted. Share a Draft
recognizes these services and crawlers by user agent and shows them an empty placeholder,
so they neither see the draft nor use one of the link’s views. To add another service,
extend the regular expression in `shareadraft_bot_user_agent_pattern`:

    ```wp-block-preformatted
    add_filter(  'shareadraft_bot_user_agent_pattern',  fn ( string $pattern ): string => str_replace( '/bot|', '/bot|mattermost|', $pattern ));
    ```

## Revoking a person’s links on other events

A person’s links are revoked automatically when their user account is deleted, but
not when their role changes. To revoke them on other events, run the `shareadraft_revoke_user_links`
action with the user’s ID:

    ```wp-block-preformatted
    // Revoke a user's preview links when they lose edit access.add_action(  'set_user_role',  function ( int $user_id, string $role ): void {    if ( ! in_array( $role, array( 'administrator', 'editor', 'author' ), true ) ) {      do_action( 'shareadraft_revoke_user_links', $user_id );    }  },  10,  2);​// Multisite: revoke when a user is removed from this site.add_action(  'remove_user_from_blog',  function ( int $user_id ): void {    do_action( 'shareadraft_revoke_user_links', $user_id );  });
    ```

After all of a user’s links have been revoked, however the revocation was triggered,
the `shareadraft_revoked_user_links` action runs with the user’s ID, the number 
of links revoked, and the ID of the user who started it. The third value is `0` 
when the revocation happened automatically. Use the action to record offboarding
in an audit log:

    ```wp-block-preformatted
    add_action(  'shareadraft_revoked_user_links',  function ( int $user_id, int $count, int $actor ): void {    // For example, send to an audit log.  },  10,  3);
    ```

## Managing links with WP-CLI

Share a Draft adds a `wp shareadraft` command. A link created or revoked with WP-
CLI follows the same rules as one created in the editor.

| Command | Description | 
| `wp shareadraft create <post-id>` | Creates a link and prints its URL. This is the only moment the URL exists in plain text. | 
| `wp shareadraft list [<post-id>]` | Lists a post’s live links, or every live link on the site when no post is given. | 
| `wp shareadraft revoke [<post-id>] [<link>]` | Revokes one link, all of a post’s links, or every link a user created. | 
| `wp shareadraft disable` | Pauses every preview link on the site. | 
| `wp shareadraft enable` | Switches paused links back on. | 
| `wp shareadraft prune` | Deletes expired and revoked links past their retention period immediately. |

Options for `create`:

| Option | Description | 
| `--expiration=<seconds>` | How long the link lasts. Must be one of the lifetimes on offer. | 
| `--max-uses=<count>` | Maximum number of viewers. | 
| `--allowed-ips=<ranges>` | Addresses or ranges allowed to open the link. | 
| `--recipients=<emails>` | Reviewers who must confirm their email address. | 
| `--porcelain` | Prints only the URL, for use in scripts. |

Run `wp help shareadraft <command>` for every option.

WP-CLI runs without a logged-in user, so a link created from the command line is
attributed to no one unless the global `--user=` flag is passed. Links attributed
to no one do not appear under a person’s name on the **Preview Links** screen and
are not revoked when a person leaves.

## Using Abilities

Share a Draft registers its operations with the Abilities API, so software acting
for a user can create and manage links. This includes AI assistants connected through
the [MCP Adapter](https://github.com/WordPress/mcp-adapter), the WordPress AI Client,
and scripts that call the abilities REST endpoints.

An ability runs as the WordPress user who authorized the client and applies the 
same permission checks as the block editor and WP-CLI. An assistant can never do
more with a link than the person it acts for.

All 6 abilities are in the `shareadraft` category:

| Ability | Description | Who can use it | 
| `shareadraft/create-preview-link` | Creates a link for a draft with the same options as the editor and returns its URL. | Anyone who can edit the post | 
| `shareadraft/list-preview-links` | Lists a post’s working links, or every working link on the site. Entries show usage, expiry, and the last four characters of the token, never the URL. | Anyone who can edit the post; editors and administrators for the whole site | 
| `shareadraft/revoke-preview-link` | Revokes one link, all of a post’s links, everything one person created, or every link on the site. | Anyone who can edit the post; editors for one person’s links; administrators for the whole site | 
| `shareadraft/prune-preview-links` | Deletes expired and revoked links past their retention period. | Administrators | 
| `shareadraft/set-preview-links-enabled` | Pauses every link on the site, or lets links work again. | Administrators | 
| `shareadraft/get-preview-links-status` | Reports whether links work and, if paused, who paused them and when. | Anyone who can edit posts |

Because the URL of a link is shown only when it is created, an assistant that needs
to share a link must capture the URL returned by `shareadraft/create-preview-link`.
Listing links later cannot retrieve it.

Last updated: October 20, 2026