Add a custom action on admin content page
Here is a sample code which creates a custom action plugin which modifies node title.
action_plugin_examples/src/Plugin/Action/ArchiveNode.php
&lt;?php

namespace Drupal\action_plugin_examples\Plugin\Action;

use Drupal\Core\Action\ActionBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\pathauto\PathautoState;

/**
 * Provides an Archive Node Action.
 *
 * @Action(
 *   id = &quot;action_plugin_examples_archive_node&quot;,
 *   label = @Translation(&quot;Archive Node&quot;),
 *   type = &quot;node&quot;,
 *   category = @Translation(&quot;Custom&quot;)
 * )
 */
class ArchiveNode extends ActionBase implements ContainerFactoryPluginInterface {


  /**
   * The Messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Logger service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $logger;

  /**
   * The path alias manager.
   *
   * @var \Drupal\path_alias\AliasManagerInterface
   */
  protected $aliasManager;

  /**
   * Language manager for retrieving the default Langcode.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    $instance = new static($configuration, $plugin_id, $plugin_definition);
    $instance-&gt;logger = $container-&gt;get('logger.factory')-&gt;get('action_plugin_examples');
    $instance-&gt;messenger = $container-&gt;get('messenger');
    $instance-&gt;aliasManager = $container-&gt;get('path_alias.manager');
    $instance-&gt;languageManager = $container-&gt;get('language_manager');
    return $instance;
  }

  /**
   * {@inheritdoc}
   */
  public function access($node, AccountInterface $account = NULL, $return_as_object = FALSE) {
    /** @var \Drupal\node\NodeInterface $node */
    $access = $node-&gt;access('update', $account, TRUE)
      -&gt;andIf($node-&gt;title-&gt;access('edit', $account, TRUE));
    return $return_as_object ? $access : $access-&gt;isAllowed();
  }

  /**
   * {@inheritdoc}
   */
  public function execute($node = NULL) {

    /** @var \Drupal\node\NodeInterface $node */

    $language = $this-&gt;languageManager-&gt;getCurrentLanguage()-&gt;getId();

    $old_alias = $this-&gt;aliasManager-&gt;getAliasByPath('/node/' . $node-&gt;id(), $language);

    $title = $node-&gt;getTitle();
    $date = $node-&gt;created-&gt;value;
    $year = date('Y', $date);
    // $old_alias = $node-&gt;path-&gt;alias;
    $new_title = $this-&gt;t('[Archive] | @title', ['@title' =&gt; $title]);
    $node-&gt;setTitle($new_title);
    $node-&gt;setSticky(FALSE);
    $node-&gt;setPromoted(FALSE);

    $new_alias = '/archive/' . $year . $old_alias;
    $node-&gt;set(&quot;path&quot;, [
      'alias' =&gt; $new_alias,
      'langcode' =&gt; $language,
      'pathauto' =&gt; PathautoState::SKIP,
    ]);

    $node-&gt;save();

    $message = $this-&gt;t('Node with NID : @id Archived.', ['@id' =&gt; $node-&gt;id()]);

    $this-&gt;logger-&gt;notice($message);
    $this-&gt;messenger-&gt;addMessage($message);

  }

}

action_plugin_examples/config/optional/system.action.action_plugin_examples_archive_node.yml
langcode: en
status: true
dependencies:
  enforced:
    module:
      - action_plugin_examples
  module:
    - node
    - pathauto
id: action_plugin_examples_archive_node
label: 'Archive Node'
type: node
plugin: action_plugin_examples_archive_node
configuration: {  }

Note : plugin yml file needs to be in the below format.

system.action.&lt;plugin_id&gt;.yml