Skip to content

[3.x] Proposal to remove the MediaFile::EXTENSIONS constant #2539

Description

@emmadesilva

HydePHP media copying in v3 (Deep Research)

Abstract

HydePHP has historically used a configurable list of file extensions to determine which files are copied from the _media directory to _site/media.

For HydePHP v3, I want to reconsider whether that allow-list still provides enough value to justify its complexity. Since _media is already a directory specifically intended for public assets, should Hyde simply copy everything placed there?

A possible middle ground would be to copy all files while excluding a small set of known junk or placeholder files, such as .gitkeep, .DS_Store, Thumbs.db, and AppleDouble ._* files.

This document evaluates the costs and benefits of each approach and recommends the behavior that makes the most sense for HydePHP v3.

Background

In HydePHP v2 and earlier, Hyde used MediaFile::EXTENSIONS to determine which files in _media qualified as media files and should be copied into _site/media.

The default extension list contained:

png, svg, jpg, jpeg, webp, gif, ico, css, js

Users could extend or replace this list through the hyde.media_extensions configuration option.

This provided a clear definition of what Hyde considered a media file, but it also meant that files outside the configured list were silently excluded from the generated site. Legitimate static assets such as PDFs, fonts, JSON files, web manifests, source maps, and downloadable archives therefore required additional configuration.

HydePHP v3 is currently being developed on the master branch. This gives us an opportunity to reconsider the old behavior without introducing a breaking change into an existing stable release.

Recommendation

For HydePHP v3, _media should become a true passthrough directory:

  1. Recursively copy all files from _media to _site/media.

  2. Exclude a small set of known junk and placeholder files.

  3. Allow users to configure additional exclusion patterns.

  4. Stop using MediaFile::EXTENSIONS as the primary rule for deciding which files are copied.

The directory itself should define publication intent:

Everything placed in _media is public unless it is explicitly excluded.

There was value in the extension constant, particularly as a guardrail against accidentally publishing unrelated files. However, that value was limited in a directory already dedicated to publishable assets. The allow-list also introduced recurring costs through unexpected behavior, documentation requirements, configuration discovery, and maintenance whenever new asset types became common.

Existing user expectations

Hyde’s documentation has consistently described _media as the location for static assets that are copied into the generated site.

This creates a straightforward user expectation:

Put a file in _media, and Hyde will publish it under _site/media.

An extension allow-list weakens that contract. A user can place a valid asset in the correct directory and still find that it was not copied because Hyde did not recognize its extension.

That behavior is especially surprising for common static assets such as:

  • PDF documents

  • Font files such as .woff, .woff2, and .ttf

  • Web manifests

  • JSON data files

  • Source maps

  • Text files

  • Downloadable archives

The user should not need to discover an advanced configuration option before Hyde will copy an otherwise ordinary static file.

Comparable public-directory models

The closest comparison to Hyde’s _media directory is the public or static directory used by other static-site generators.

Tools such as Astro and Hugo treat these directories as passthrough locations. Files placed there are copied into the generated site without being restricted to a predefined set of extensions.

This works because the directory name already communicates the contract: its contents are intended for publication.

Jekyll takes a more exclusion-oriented approach because it processes a broader source tree containing both public files and internal project files. It therefore needs additional include and exclude rules.

Hyde’s _media directory is already isolated from the rest of the project. It does not need the same defensive filtering as a generator that scans a mixed source directory.

Eleventy requires users to configure passthrough copy rules explicitly. Hyde already provides _media as a built-in convention, so requiring additional declarations would add configuration without improving the common case.

For Hyde, the Astro and Hugo model is the most relevant: a dedicated public directory should copy arbitrary files by default.

Cost-benefit analysis

Option 1: Keep the extension allow-list

Under this option, Hyde would continue copying only files whose extensions appear in MediaFile::EXTENSIONS or hyde.media_extensions.

Benefits

  • Prevents many unrelated files from being published accidentally.

  • Automatically filters common operating-system metadata and placeholder files.

  • Provides a narrow and explicit definition of what Hyde considers media.

  • Preserves existing behavior and configuration semantics.

Costs

  • Legitimate static assets are silently ignored unless their extensions are configured.

  • Users must learn about an advanced option to publish ordinary files.

  • The default list requires maintenance as frontend asset types evolve.

  • Documentation must explain that _media does not necessarily copy everything.

  • The behavior conflicts with the natural interpretation of a dedicated public-assets directory.

  • Extension-based file discovery adds implementation complexity.

  • A file’s extension is not always a reliable indicator of whether it should be public.

The allow-list provides protection, but it optimizes for accidental misuse of _media rather than its intended use.

Option 2: Copy everything

Under this option, Hyde would recursively copy every file from _media into _site/media.

Benefits

  • Provides the simplest possible mental model.

  • Supports all current and future asset types automatically.

  • Matches the documented purpose of _media.

  • Removes the need to maintain a media-extension registry.

  • Eliminates configuration for common files such as PDFs and fonts.

  • Produces behavior consistent with other dedicated public-directory systems.

  • Makes the implementation easier to understand and test.

Costs

  • Placeholder files such as .gitkeep may be published.

  • Operating-system metadata such as .DS_Store and Thumbs.db may be published.

  • Users who treat _media as general storage may unintentionally expose files.

  • Version-control metadata could be copied if it somehow exists inside _media.

This option has the strongest developer experience, but publishing obvious junk files would make the generated output unnecessarily messy.

Option 3: Copy everything with explicit exclusions

Under this option, Hyde would recursively copy all files except those matching a small exclusion list.

Benefits

  • Preserves the simple passthrough-directory model.

  • Supports arbitrary asset types without additional configuration.

  • Prevents common junk and placeholder files from being published.

  • Allows advanced users to exclude project-specific files or directories.

  • Avoids maintaining an ever-growing registry of valid extensions.

  • Makes publication rules explicit and easy to explain.

  • Better aligns the implementation with the purpose of _media.

Costs

  • Requires a small amount of exclusion-matching logic.

  • The default exclusion list must be maintained.

  • An obscure junk file not included in the defaults could still be copied.

  • Changes to the existing configuration model require migration documentation.

This option provides most of the simplicity of copying everything while preserving the useful protection offered by the old allow-list.

Comparison

Option | Developer experience | Legitimate asset support | Junk-file protection | Maintenance burden | Ecosystem fit -- | -- | -- | -- | -- | -- Keep the extension allow-list | Low | Medium | High | High | Low Copy everything | High | High | Low | Low | High Copy everything with exclusions | High | High | High enough | Low to medium | High

Copying everything with a small explicit exclusion list provides the best overall balance.

Proposed v3 behavior

HydePHP v3 should:

  1. Treat _media as a recursive passthrough directory.

  2. Copy all files regardless of extension.

  3. Skip a small default set of known junk files.

  4. Introduce a configurable exclusion list, such as hyde.media_exclude.

  5. Deprecate or remove hyde.media_extensions as a file-discovery mechanism.

A possible default configuration could look like this:

'media_exclude' => [
    '.gitkeep',
    '.DS_Store',
    'Thumbs.db',
    '._*',
    '.git/**',
    '.hg/**',
    '.svn/**',
],

The exact pattern syntax would need to be defined, but it should support both filenames and recursive paths.

Default exclusions

The default list should remain intentionally small.

Good candidates include:

.gitkeep
.DS_Store
Thumbs.db
._*
.git/
.hg/
.svn/

These files have little or no legitimate purpose in generated website output.

Hyde should not exclude every dotfile by default. Unlike a general project source directory, _media is specifically intended for publication. Some hidden files may be intentionally public, and excluding all of them would recreate the same surprise caused by the extension allow-list.

The rule should therefore be:

Exclude known junk, not broad categories of potentially valid files.

Why .gitignore should not control publication

Hyde could technically reuse .gitignore rules when deciding which media files to copy, but this should not be the default.

.gitignore describes source-control policy. It does not necessarily describe publication policy.

A file may be excluded from Git because it is generated locally while still needing to be published. Conversely, a file may be tracked in Git but intentionally excluded from the generated site.

Using a dedicated media_exclude setting keeps these concerns separate and makes Hyde’s behavior easier to understand.

Support for .gitignore-aware exclusions could be introduced later as an explicit opt-in feature, but it should not define the default behavior.

Validation behavior

Changing _media to a passthrough directory does not require weakening Hyde’s asset validation.

Hyde can continue validating explicitly referenced assets and throwing a helpful FileNotFoundException when a referenced file does not exist.

These are separate concerns:

  • Passthrough copying determines which files are eligible to be published.

  • Asset validation determines whether an explicitly referenced file exists.

Allowing arbitrary file types in _media only broadens publication support. It does not prevent Hyde from reporting missing assets clearly.

Implementation outline

The implementation can remain simple:

  1. Recursively enumerate files under the configured media directory.

  2. Normalize each path relative to the _media root.

  3. Test the relative path against the default and configured exclusions.

  4. Create the corresponding parent directory under _site/media.

  5. Copy each remaining file.

Laravel’s filesystem utilities or Symfony Finder can both support this model cleanly.

The implementation should avoid extension-based glob patterns. Direct recursive enumeration followed by explicit filtering is easier to understand, more portable, and easier to test.

Suggested tests

The v3 test suite should verify that:

  • Existing image, CSS, and JavaScript files continue to copy.

  • Files outside the old allow-list are copied.

  • Nested files preserve their directory structure.

  • Default junk files are skipped.

  • Custom exclusion rules work recursively.

  • Legitimate dotfiles can still be published.

  • Empty directories do not need to be reproduced unless explicitly required.

  • Explicitly referenced missing assets continue to produce helpful errors.

Example files should include:

docs/manual.pdf
fonts/app.woff2
data/site.json
manifest.webmanifest
downloads/example.zip
nested/assets/example.txt

Default exclusion tests should include:

.gitkeep
.DS_Store
Thumbs.db
._logo.png
.git/config
.hg/store/example
.svn/entries

Migration strategy

The cleanest v3 migration would be to replace extension-based discovery entirely.

Documentation should describe _media in plain language:

All files in _media are copied to _site/media, except files matching the configured exclusion rules.

Any documentation presenting media_extensions as the normal way to enable additional static files should be removed or updated.

There are two possible compatibility approaches.

Clean break

Remove hyde.media_extensions from copy discovery in v3.

This is the clearest option and is appropriate for a major release. Existing projects would generally gain support for more files rather than lose functionality.

The main observable difference would be that previously ignored files might now be copied unless explicitly excluded.

Transitional deprecation

Continue reading hyde.media_extensions temporarily while emitting a deprecation notice explaining that _media now uses passthrough semantics.

This provides a softer migration path, but it may also make the behavior harder to explain. It would need to be clear whether the extension setting still filters files, is ignored, or is translated into another rule.

Unless there is a significant compatibility concern, the clean break is preferable.

Conclusion

MediaFile::EXTENSIONS provided a useful guardrail in earlier versions of HydePHP, but it is not the best default publication model for v3.

Because _media is already a dedicated public-assets directory, the most intuitive rule is that files placed there should be published. Restricting publication by extension creates more friction and maintenance than value.

HydePHP v3 should therefore copy all files from _media recursively while excluding a small, explicit set of known junk and placeholder files. Additional exclusions should be configurable through a dedicated setting.

This gives Hyde a simple and durable contract:

Everything in _media is public unless explicitly excluded.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions