Roconpaas

Blog

How to Add Custom Field to WordPress Media: Rocon Quick Guide

August 18, 2025 by William

WordPress Keeps Logging Me Out

Introduction

Add custom field to WordPress media: The default WordPress Media Library lets you add a Title, Caption, Alt Text, and Description. That’s plenty for a lot of webpages. But if you run a brand library, a news site, an e-commerce catalog, or a portfolio, you’ll rapidly want unique fields.

These could include the photographer, copyright holder, license type, product SKU, external source URL, model release status, shoot date, or credit line. This guide shows you how to add such fields in a method that works, save them safely, present them on the front end, and keep everything running smoothly and easy to manage.

What You’ll Learn

  • How WordPress stores media and where custom fields fit in
  • Three reliable ways to add custom media fields:
    1. Pure PHP hooks (lightweight, no plugin)
    2. ACF (no-code/low-code)
    3. Taxonomies for media organization
  • How to save, retrieve, and display those fields
  • How to expose fields to the REST API and use them in Gutenberg
  • How to add a column in the Media screen and support bulk workflows
  • Security, performance, migration, testing, and troubleshooting tips

How Media Works in WordPress (Quick Basics)

Every upload becomes a post with post_type = attachment. The actual file lives in wp-content/uploads, while the attachment post holds metadata (dimensions, mime type, etc.) plus post meta—which is where your custom fields live. You’ll interact with:

  • get_post_meta( $attachment_id, $key, true )
  • update_post_meta( $attachment_id, $key, $value )

Those two are the backbone of most solutions you’ll build below.

Choosing the Right Approach

  • Pure PHP hooks: Best for performance, code control, versioning, small/simple sites, or when you need zero plugin dependencies.
  • ACF (Advanced Custom Fields): Best when editors need a great UI, multiple field types (repeater, select, date, URL), and fast iteration without writing PHP for every tweak.
  • Taxonomy on attachments: Best to group media into reusable buckets (e.g., License: editorial vs. commercial; Photographer names as terms) and to leverage built-in term queries.

Often, you’ll combine them: store scalar values (e.g., “Credit: John Doe”) as meta; use a taxonomy for categories like license type.

Method 1: Pure PHP (Hooks) — Lightweight and Reliable

Below is a complete, drop-in snippet for your theme’s functions.php (or a small must-use/plugin file). It adds a single text field (“Photographer Name”), shows it in the Media edit screen and modal, saves it safely, and makes it easy to output on the front end.

1) Add the field to the Media edit form

/**

 * Add a custom field to the Media edit screen (and media modal “Attachment Details”).

 */

function mymedia_add_custom_field( $form_fields, $post ) {

    $value = get_post_meta( $post->ID, ‘_mymedia_photographer’, true );

 

    // Add a nonce field once per screen load (safe to repeat—WP will de-duplicate by name).

    $form_fields[‘mymedia_nonce’] = array(

        ‘label’ => ”,

        ‘input’ => ‘html’,

        ‘html’  => wp_nonce_field( ‘mymedia_save_field_’ . $post->ID, ‘mymedia_nonce’, true, false ),

    );

 

    $form_fields[‘mymedia_photographer’] = array(

        ‘label’ => __( ‘Photographer’, ‘your-textdomain’ ),

        ‘input’ => ‘text’,

        ‘value’ => $value,

        ‘helps’ => __( ‘Enter the photographer or credit line.’, ‘your-textdomain’ ),

    );

 

    return $form_fields;

}

add_filter( ‘attachment_fields_to_edit’, ‘mymedia_add_custom_field’, 10, 2 );

 

2) Save the field securely

/**

 * Save our custom field from Media edit screen.

 */

function mymedia_save_custom_field( $post, $attachment ) {

    $attachment_id = isset( $post[‘ID’] ) ? (int) $post[‘ID’] : 0;

 

    if ( ! $attachment_id || ! current_user_can( ‘edit_post’, $attachment_id ) ) {

        return $post; // Respect capabilities

    }

 

    // Nonce check

    if ( empty( $_POST[‘mymedia_nonce’] ) || ! wp_verify_nonce( $_POST[‘mymedia_nonce’], ‘mymedia_save_field_’ . $attachment_id ) ) {

        return $post;

    }

 

    if ( isset( $attachment[‘mymedia_photographer’] ) ) {

        $value = sanitize_text_field( $attachment[‘mymedia_photographer’] );

        update_post_meta( $attachment_id, ‘_mymedia_photographer’, $value );

    }

 

    return $post;

}

add_filter( ‘attachment_fields_to_save’, ‘mymedia_save_custom_field’, 10, 2 );

 

3) Display the field on the front end

/**

 * Output a credit below a featured image or any attachment.

 */

function mymedia_output_credit( $attachment_id ) {

    $credit = get_post_meta( $attachment_id, ‘_mymedia_photographer’, true );

    if ( $credit ) {

        echo ‘<p class=”photo-credit”>Photo: ‘ . esc_html( $credit ) . ‘</p>’;

    }

}

 

Use mymedia_output_credit( get_post_thumbnail_id() ); in templates, or call it when rendering galleries.

Show the credit automatically with images:

You can filter the markup generated by wp_get_attachment_image() attributes:

add_filter( ‘wp_get_attachment_image_attributes’, function( $attr, $attachment ) {

    $credit = get_post_meta( $attachment->ID, ‘_mymedia_photographer’, true );

    if ( $credit ) {

        $attr[‘data-photo-credit’] = $credit; // handy for JS or tooltips

    }

    return $attr;

}, 10, 2 );

 

Optional: Add a column in the Media Library list

This helps editors see/use the field without clicking into each item.

// Register column

add_filter( ‘manage_upload_columns’, function( $cols ) {

    $cols[‘mymedia_photographer’] = __( ‘Photographer’, ‘your-textdomain’ );

    return $cols;

} );

 

// Render column

add_action( ‘manage_media_custom_column’, function( $column_name, $attachment_id ) {

    if ( ‘mymedia_photographer’ === $column_name ) {

        $val = get_post_meta( $attachment_id, ‘_mymedia_photographer’, true );

        echo $val ? esc_html( $val ) : ‘—’;

    }

}, 10, 2 );

You can also make it sortable with manage_upload_sortable_columns and a pre_get_posts modification, if desired.

Method 2: ACF (Advanced Custom Fields) — No-Code/Low-Code

If your team prefers a polished UI with many field types:

  1. Install and activate ACF.
  2. Custom Fields → Add New → “Media Metadata”.
  3. Add fields (e.g., Photographer = Text, License URL = URL, Shoot Date = Date Picker).
  4. LocationPost Type is equal to Attachment.
  5. Publish.

ACF will inject fields on the Media edit screen (and the media modal’s “Attachment Details”).

Retrieving values:

$credit = get_field( ‘photographer’, $attachment_id );

$license_url = get_field( ‘license_url’, $attachment_id );

Show in REST: In ACF’s field group settings (or via code), enable show_in_rest or use ACF’s REST integration so headless/front-end apps (or Gutenberg blocks) can access these values.

Pros: Editor-friendly, lots of field types, validation, conditional logic.
Cons: Adds a plugin dependency; minor performance overhead compared to bare PHP.

Method 3: Use a Taxonomy on Attachments

Some metadata fits better as categories/tags. Example: License Type (Editorial, Commercial, Creative Commons), or Photographer as a reusable term.

/**

 * Register a “media_license” taxonomy for attachments.

 */

function mymedia_register_media_taxonomy() {

    register_taxonomy(

        ‘media_license’,

        ‘attachment’,

        array(

            ‘label’             => __( ‘Media License’, ‘your-textdomain’ ),

            ‘public’            => false,

            ‘show_ui’           => true,

            ‘show_admin_column’ => true,

            ‘hierarchical’      => false,

            ‘show_in_rest’      => true,     // expose to REST/Gutenberg

        )

    );

}

add_action( ‘init’, ‘mymedia_register_media_taxonomy’ );

Editors can then tag images with terms like “Editorial Use Only.” You can query by taxonomy, create filters, and present license badges on the front end.

Exposing Media Fields to the REST API (Headless & Gutenberg)

If you’re not using ACF, register your meta with register_meta() so it’s available via REST:

/**

 * Register attachment meta for REST access.

 */

function mymedia_register_rest_meta() {

    register_meta( ‘post’, ‘_mymedia_photographer’, array(

        ‘object_subtype’ => ‘attachment’,

        ‘show_in_rest’   => array(

            ‘schema’ => array(

                ‘type’ => ‘string’,

                ‘description’ => ‘Photographer credit’,

            ),

        ),

        ‘single’         => true,

        ‘type’           => ‘string’,

        ‘auth_callback’  => function() { return current_user_can( ‘upload_files’ ); },

    ) );

}

add_action( ‘init’, ‘mymedia_register_rest_meta’ );

With this, GET /wp-json/wp/v2/media/<id> includes your field, and editors (with permission) can update it via POST/PATCH.

Using Custom Media Fields in Gutenberg

You have a few options:

  1. Dynamic block: Build a server-rendered block that prints an image plus credit pulled via get_post_meta().
  2. Block variations: Wrap core/image and append a credit element.
  3. Theme template: If you rely on featured images, use your theme’s single.php (or block theme template) to call mymedia_output_credit() after the image.

A lightweight example of a dynamic block render callback:

function mymedia_block_render( $attributes, $content, $block ) {

    if ( empty( $attributes[‘attachmentId’] ) ) {

        return $content;

    }

    $credit = get_post_meta( (int) $attributes[‘attachmentId’], ‘_mymedia_photographer’, true );

    if ( ! $credit ) {

        return $content;

    }

    return $content . ‘<figcaption class=”photo-credit”>Photo: ‘ . esc_html( $credit ) . ‘</figcaption>’;

}

Shortcode for Quick Use Anywhere

Shortcodes are simple for editors who don’t touch templates:

/**

 * [photo_credit id=”123″]

 */

function mymedia_shortcode_credit( $atts ) {

    $atts = shortcode_atts( array( ‘id’ => 0 ), $atts );

    $credit = get_post_meta( (int) $atts[‘id’], ‘_mymedia_photographer’, true );

    return $credit ? ‘<span class=”photo-credit”>Photo: ‘ . esc_html( $credit ) . ‘</span>’ : ”;

}

add_shortcode( ‘photo_credit’, ‘mymedia_shortcode_credit’ );

Bulk Workflows and Migration

Add a Media List Filter

For taxonomy-based fields, WordPress auto-adds the dropdown filter in Media > Library (list view). For pure meta, you can add a custom filter UI and modify pre_get_posts to query by meta key/value.

WP-CLI: Batch import/update

If you have a CSV mapping attachment_id,photographer, you can loop through it with WP-CLI:

# Example: update all images missing a photographer to “Unknown”

wp eval ‘

$ids = get_posts([“post_type”=>”attachment”,”posts_per_page”=>-1,”fields”=>”ids”]);

foreach ($ids as $id) {

  $v = get_post_meta($id, “_mymedia_photographer”, true);

  if (!$v) { update_post_meta($id, “_mymedia_photographer”, “Unknown”); }

}

echo “Done\n”;

Migrate from Alt Text or EXIF

You can seed your custom field from existing data:

add_action( ‘admin_init’, function() {

    if ( ! current_user_can( ‘manage_options’ ) ) return;

    if ( isset( $_GET[‘migrate_credit_once’] ) ) {

        $ids = get_posts( array( ‘post_type’ => ‘attachment’, ‘posts_per_page’ => -1, ‘fields’ => ‘ids’ ) );

        foreach ( $ids as $id ) {

            $existing = get_post_meta( $id, ‘_mymedia_photographer’, true );

            if ( $existing ) continue;

            $alt = get_post_meta( $id, ‘_wp_attachment_image_alt’, true );

            if ( $alt ) update_post_meta( $id, ‘_mymedia_photographer’, sanitize_text_field( $alt ) );

        }

        wp_die( ‘Migration complete.’ );

    }

} );

Run once at /wp-admin/?migrate_credit_once=1 (then remove the code).

Security, Validation, and Capability Checks

  • Nonces: Always add and verify a nonce before saving (wp_verify_nonce).
  • Sanitization:
    • Text: sanitize_text_field()
    • URL: esc_url_raw()
    • Email: sanitize_email()
    • Checkbox: cast to bool or ‘1’/
  • Permissions: Confirm current_user_can( ‘edit_post’, $attachment_id ).
  • Escaping on output: esc_html(), esc_attr(), esc_url().

Performance Tips

  • Batch updates: Prefer WP-CLI or single admin pages that save many items at once instead of saving per page load.
  • Avoid heavy queries: If you need to sort/filter by meta, consider a taxonomy or a dedicated index (or cache results).
  • Object caching: Meta is cached per post; use persistent object cache in large libraries.
  • Keep fields lean: Store only what you actually need. For complex data, consider a structured array (and maybe_serialize)—or a custom table if you’re at serious scale.

Multisite and Translation Notes

  • Multisite: Meta lives per site. If images are “shared,” replicate meta where needed or build a sync routine.
  • Translation: If using multilingual plugins, confirm whether attachment meta is per-language or shared. For translatable fields (like credit line), consider per-language entries or language-aware filters.

Testing & QA Checklist

  1. Create/Edit: Add/edit custom field on new and existing media.
  2. Media Modal: Confirm the field appears and saves from the modal (Upload → Attachment Details).
  3. Front-End: Check templates, shortcodes, and Gutenberg output.
  4. Roles: Test with Editor/Author roles—can they see and edit appropriately?
  5. REST: GET /wp-json/wp/v2/media/<id> returns the meta when authorized; test updating if you support write operations.
  6. Search/Filter: If you built a column or filter, ensure it doesn’t slow down large libraries.

Accessibility: Credits should not replace proper alt attributes; they complement them.

Troubleshooting

  • Field doesn’t show: Confirm your attachment_fields_to_edit filter runs (no typos, code loaded in admin).
  • Value won’t save: Verify nonce, capability, and that you used the correct $attachment[‘your_field_key’].
  • Shows in edit screen but not in modal: Clear browser cache; ensure your filter returns an array formatted like above; media modal “Attachment Details” shows the same compat fields.
  • Sorting/filtering slow: Move repetitive scalar values to a taxonomy or add custom indexing/caching.
  • Not in REST: Did you register_meta() with show_in_rest and the correct object_subtype => ‘attachment’?

Example: Minimal Plugin You Can Drop In

If you’d prefer a tiny plugin instead of mixing into your theme, create wp-content/plugins/mymedia-credit/mymedia-credit.php:

<?php

/**

 * Plugin Name: MyMedia Credit Field

 * Description: Adds a Photographer credit field to attachments.

 * Version: 1.0.0

 * Author: You

 */

if ( ! defined( ‘ABSPATH’ ) ) exit;

add_filter( ‘attachment_fields_to_edit’, function( $form_fields, $post ) {

    $val = get_post_meta( $post->ID, ‘_mymedia_photographer’, true );

    $form_fields[‘mymedia_nonce’] = array(

        ‘label’ => ”,

        ‘input’ => ‘html’,

        ‘html’  => wp_nonce_field( ‘mymedia_save_field_’ . $post->ID, ‘mymedia_nonce’, true, false ),

    );

    $form_fields[‘mymedia_photographer’] = array(

        ‘label’ => __( ‘Photographer’, ‘mymedia’ ),

        ‘input’ => ‘text’,

        ‘value’ => $val,

    );

    return $form_fields;

}, 10, 2 );

 

add_filter( ‘attachment_fields_to_save’, function( $post, $attachment ) {

    $id = (int) $post[‘ID’];

    if ( empty( $_POST[‘mymedia_nonce’] ) || ! wp_verify_nonce( $_POST[‘mymedia_nonce’], ‘mymedia_save_field_’ . $id ) ) {

        return $post;

    }

    if ( current_user_can( ‘edit_post’, $id ) && isset( $attachment[‘mymedia_photographer’] ) ) {

        update_post_meta( $id, ‘_mymedia_photographer’, sanitize_text_field( $attachment[‘mymedia_photographer’] ) );

    }

    return $post;

}, 10, 2 );

 

add_action( ‘init’, function() {

    register_meta( ‘post’, ‘_mymedia_photographer’, array(

        ‘object_subtype’ => ‘attachment’,

        ‘show_in_rest’   => array(

            ‘schema’ => array( ‘type’ => ‘string’, ‘description’ => ‘Photographer credit’ ),

        ),

        ‘single’         => true,

        ‘type’           => ‘string’,

        ‘auth_callback’  => function() { return current_user_can( ‘upload_files’ ); },

    ) );

} );

 

Activate it, and you’re done.

When to Prefer Each Approach (Quick Matrix)

  • I need a single text field and want speedPure PHP hooks
  • I need multiple field types, conditionals, or editor-friendly UIACF
  • I need to group/search media by categories/labelsAttachment taxonomy
  • Headless or custom front-end app → Ensure fields are registered in REST via register_meta() or ACF REST settings

Final Thoughts

Adding custom media fields makes your content workflow better: credits are automatic, licensing is easy to check, and your site is more consistent. Start with one or two fields, connect them to your templates, and then add more. For performance and version control, use pure PHP. For quick UI, use ACF. For classification, use taxonomies. If you’re making modern front ends, don’t forget the basics: nonce checks, sanitization, capability verification, and REST exposing.

I can customize the code to fit your specific field list (such Photographer, License URL, Usage Notes, Shoot Date, and Editorial Only checkbox) or package it as a plugin that is ready to install with a settings page and exportable JSON.

Start the conversation.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Recommended articles

    WordPress

    How to Add Custom Field to WordPress Media: Rocon Quick Guide

    William

    Icon

    8 Min Read

    WordPress

    7 Best Bluehost Alternatives in 2025: Tired of Bluehost?

    Ankit

    Icon

    6 Min Read

    WordPress

    Briefly Unavailable for Scheduled Maintenance: Quick Fix

    Sreekar

    Icon