Skip to content

CSV Import

Short URL supports bulk-creating short URLs from a CSV file, via an admin form or a Drush command.

CSV format

The CSV file uses positional columns in this order:

Column Required Default Description
slug Custom mode only — The vanity slug. Ignored for base36 and auto-increment modes.
destination Yes — External URL or internal path (/about, stored as internal:/about)
slug_mode No base36 custom, base36, or auto_increment
label No — Optional human-readable label; an update without one keeps the short URL's label
langcode No Site default Language code (e.g. en, fr); an update without one keeps the short URL's language
redirect_status No Site default 301, 302, or 307; an update without one keeps the short URL's status

Columns that other modules add come after these (see Columns from other modules).

Example

slug,destination,slug_mode,label,langcode,redirect_status
promo,https://example.com/spring-sale,custom,Spring Sale,en,301
,https://example.com/page1,base36,,,
,https://example.com/page2,auto_increment,,,

Format auto-detection

When uploading a CSV file, the importer automatically detects known formats by matching the header row. Currently supported:

Format Expected headers Column mapping
YOURLS source, target, hits source → slug, target → destination

Detection is BOM-safe (UTF-8 BOM is stripped) and tolerates extra trailing columns beyond the expected headers. A BOM is also stripped from files without a recognized header.

When a format is detected, a format defaults step appears where you can configure default values for columns not present in the source file (e.g. language, redirect status). Some values may be enforced by the format — for example, YOURLS enforces slug_mode=custom since all YOURLS URLs have custom slugs.

Other modules can register additional formats via hook_shorturl_csv_formats_alter(). See ShortUrlCsvFormatInterface for the format contract.

Updating existing short URLs

When importing slugs that already exist in the database, the importer can update the existing short URL instead of skipping the row. This is controlled by:

  • Admin form: the "Update existing short URLs" checkbox (enabled by default)
  • Drush: the --update-existing flag (enabled by default, disable with --no-update-existing)

In the preview step, the Action column shows whether each row will Create a new short URL or Update an existing one. The summary and import button also display separate counts.

An update sets the destination and slug mode from the row. An empty label, language or redirect status keeps the short URL's own, so re-importing a file without those columns, such as a YOURLS export, changes nothing else. The preview shows Unchanged for the label, language and redirect status.

Admin form

Navigate to Content > Short URLs and click the Import short URLs action link, or go directly to /admin/content/short-urls/import.

Step 1: Upload

  • Select a CSV file (.csv or .txt, max 10 MB)
  • Choose the delimiter (comma, semicolon, or tab)
  • Check "Skip first row" if the file has a header row
  • Check "Update existing short URLs" to update rows with existing slugs instead of skipping them
  • Click Preview

Step 1.5: Format defaults (auto-detected formats only)

When a known format is detected (e.g. YOURLS), this step appears to let you configure defaults for unmapped columns:

  • Slug mode — enforced by YOURLS (custom), editable for other formats
  • Language — none by default: new short URLs get the site default language, and short URLs being updated keep their own
  • Redirect status — none by default: new short URLs get the site-wide redirect status, and short URLs being updated keep their own

Click Preview to continue or Back to change the file.

Step 2: Preview and confirm

The form displays:

  • A summary of valid and invalid rows with create/update counts
  • A table listing each valid row with its columns and action
  • Error details for invalid rows (with line numbers)

Rows with errors are skipped during import. Click Import to create or update nodes for all valid rows via Batch API.

Permission required: Import short URLs from CSV. Each row also needs what the node form needs: Short URL: Create new content to create a short URL, and the right to edit the short URL a row updates (Short URL: Edit any content, or Short URL: Edit own content for one's own).

Drush command

drush shorturl:import /path/to/urls.csv --user=editor

Known CSV formats (e.g. YOURLS) are automatically detected from the header row. When a format is detected, columns are mapped automatically and the header is skipped.

The import runs as the account --user names, by ID or name, and refuses to start without one. Rows are checked against that account's permissions, and the short URLs the import creates belong to it. Naming user 1 bypasses every permission check.

Options

Option Default Description
--user none Account to import as (required)
--delimiter , CSV delimiter character
--skip-header No Skip the first row
--update-existing Yes Update existing slugs instead of skipping
--dry-run No Validate only, do not create or update nodes

Examples

# Basic import
drush shorturl:import urls.csv --user=editor --skip-header

# Import a YOURLS export (auto-detected)
drush shorturl:import yourls_export.csv --user=editor

# Semicolon-delimited file
drush shorturl:import urls.csv --user=editor --delimiter=";" --skip-header

# Skip existing slugs instead of updating
drush shorturl:import urls.csv --user=editor --no-update-existing

# Validate without importing
drush shorturl:import urls.csv --user=editor --skip-header --dry-run

The command displays validation results, asks for confirmation when errors are found, and shows a progress log during import.

Validation rules

Each row is validated before import:

  • Destination is required and must be a valid URL (external) or internal path (starting with /)
  • Slug is required for custom mode, must contain only a-z, 0-9, _, and -, and cannot be a path the site already answers (the same rules as the node form); the slug of a short URL being updated is kept as it is
  • Slug uniqueness is checked against other rows in the same CSV. Existing slugs in the database are either marked for update or rejected, depending on the update-existing setting
  • Slug mode must be custom, base36, or auto_increment
  • Permission — the importing user must have the permission for the slug mode used in each row, and may only create a short URL, or update an existing one, as the node form would let them
  • Language must be an installed language
  • Redirect status must be 301, 302, or 307
  • Columns from other modules are validated by the module that adds them

Columns from other modules

Other modules can add columns to the import. They follow Short URL's own columns, and a detected format can map a source column to them. Each module validates its column, can scope slug uniqueness by it, and sets its value on the short URL.

Domain Short URL adds a domain column this way, as the seventh column. Each row can name a domain machine name to put the short URL on that domain, and slug uniqueness is then checked per domain:

slug,destination,slug_mode,label,langcode,redirect_status,domain
blog,https://example.com/blog,custom,Blog,en,301,example_com

To add a column, implement Drupal\shorturl\ShortUrlCsvColumnInterface and tag the service with shorturl.csv_column:

services:
  Drupal\my_module\MyCsvColumn:
    tags:
      - { name: shorturl.csv_column }