Skip to content

Issue-836 Document Types - #1072

Open
scotthillson wants to merge 13 commits into
mainfrom
issue-836
Open

Issue-836 Document Types#1072
scotthillson wants to merge 13 commits into
mainfrom
issue-836

Conversation

@scotthillson

Copy link
Copy Markdown
Collaborator

Description

Allow all file types in Documents except for images and videos. The Media collection is for images and videos. See issue for more detail.

Related Issues

#836

Key Changes

Adds a layout field to the Document block so editors can choose between embedding a PDF in an iframe or showing a download link button. Also removes the hardcoded mimeTypes allowlist from the Documents collection and replaces it with a validation hook that blocks only images and videos, allowing any other file type (PDFs, KML, XML, etc.) to be uploaded without needing to enumerate every MIME type.
The iframe title was generalized from "Document PDF" to "Document" since non-PDF files can now be embedded.

How to test

Upload a PDF to the Documents collection — should succeed
Try to upload an image file into documents, you'll see a toast error

Screenshots / Demo video

Migration Explanation

Adds a layout column (nullable select) to the blocks_document table. No data is destroyed; existing rows get null which the component treats as embed.

Adds a `layout` select field (Download Link / Embed) to the Document
block so editors can choose how a file is presented. Replaces the
hardcoded MIME type allowlist with a hook that rejects only images and
videos, directing those to the Media collection instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scotthillson and others added 2 commits May 9, 2026 10:01
The layout column was already added by 20260502_155913, so 20260509_162047 caused a duplicate column error on deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scotthillson and others added 2 commits May 9, 2026 10:13
20260502_155913 already adds the layout column, but later migration snapshots didn't carry it forward. 20260509_170253 was auto-generated as a result. Making it a no-op so it doesn't fail on prod while its JSON snapshot advances the tracked schema state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Preview deployment: https://issue-836.preview.avy-fx.org

@busbyk busbyk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution @scotthillson!

I've requested a few changes. Let me know if you have any questions.

Comment thread src/blocks/Document/config.ts Outdated
Comment thread src/blocks/Document/Component.tsx Outdated
Comment thread src/migrations/index.ts

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added to our docs/coding-guide.md to outline our preferences for migration naming and resolving migration conflicts when merging in main has new migrations. Can you please follow those instructions and update this PR's migrations accordingly? #1077

Comment thread src/scripts/check-migrations.ts Outdated
Comment thread src/blocks/Document/Component.tsx Outdated
return (
<div className={cn('my-4', { container: isLayoutBlock })}>
<iframe src={src} width="100%" height="600px" title="Document PDF" />
<iframe src={src} width="100%" height="600px" title="Document" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no browser viewer for a given file type and the layout is set to embed, the file is automatically downloaded when visiting the page that the DocumentBlock is on:

Image

I can't find the behavior documented anywhere but it seems that if we set an iframe's src attribute to a file with a MIME type that it does not have a built-in viewer for, the file is automatically downloaded when the iframe is rendered. We definitely don't want this behavior so we need to account for this.

It seems that PDF, HTML, Plain text, and XML/KML can all be rendered in an iframe. So we need conditional logic in the collection that only allows setting the displayAs field to embed when the file's MIME type is one of ['application/pdf', 'text/html', 'text/plain', 'text/xml', 'application/xml', 'application/vnd.google-earth.kml+xml', '.kml']. If we discover there are other MIME types that browsers can reliably render, we could add to that list.

Comment thread src/blocks/Document/Component.tsx Outdated
const filename = document.filename ?? 'Download'

// Treat missing layout as 'embed' so existing blocks keep their current behavior
const resolvedLayout = layout ?? 'embed'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a good assumption to make but I think we should fallback to download due to the browser behavior that might result in a file being automatically downloaded if the browser can't render it in an iframe. See my other comment.

We only have one page with DocumentBlocks in production, so this would be easy enough to go and update manually after we deploy this.

@rchlfryn

rchlfryn commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

@scotthillson Checking in to see if you need any help. Let us know!

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Migration Safety Check

Found 4 potential issues:

20260809_234316_add_display_as_to_document_block.ts

Warning (line 6): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`home_pages_blocks_document_block\` ADD \`display_as\` text DEFAULT 'download';`,

Warning (line 9): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`_home_pages_v_blocks_document_block\` ADD \`display_as\` text DEFAULT 'download';`,

Warning (line 12): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`pages_blocks_document_block\` ADD \`display_as\` text DEFAULT 'download';`,

Warning (line 15): ALTER keyword detected - review for data loss

sql`ALTER TABLE \`_pages_v_blocks_document_block\` ADD \`display_as\` text DEFAULT 'download';`,

Review these patterns and add backup/restore logic if needed. See docs/migration-safety.md for guidance.

@scotthillson

Copy link
Copy Markdown
Collaborator Author

Hey! @busbyk I think this is ready for another pass. Fingers crossed I have this knocked out in just the 1-2 passes!

@rchlfryn
rchlfryn requested a review from busbyk June 18, 2026 19:43

@rchlfryn rchlfryn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the delay in the review. Other priorities kept getting in the way. Because of the delay you will need to merge/rebase main and rerun the migration to get the latest.

],
upload: {
staticDir: path.resolve(dirname, '../../../public/documents'),
mimeTypes: [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't allow HTML and re-breaks the old allowlisted types.

Uploading HTML fails with Restricted file type detected -- set 'allowRestrictedFileTypes' to true. That's Payload core (checkFileRestrictions), not the hook. When mimeTypes is absent, it enforces a built-in blocklist (text/html, php, js, exe…) and throws before the hook runs. Removing the allowlist also drops the pdf/php/xml/kml types the old list permitted.

Fix — keep an allowlist instead of removing it:

 upload: {
   staticDir: path.resolve(dirname, '../../../public/documents'),
   mimeTypes: [
     'application/pdf',
     'application/xml',
     'text/xml',
     'application/octet-stream',
     'application/vnd.google-earth.kml+xml',
     '.kml',
     'text/html',
     'text/csv',
   ],
 },

A set mimeTypes bypasses the blocklist, so HTML passes while unlisted executables stay blocked. validateNotImageOrVideo can be dropped (image/video just aren't listed).

The alternative (remove mimeTypes + allowRestrictedFileTypes: true) matches the issue literally but also permits .exe/.js on public URLs. Given staff-only uploads of static docs, the allowlist seems safer.

Comment thread src/blocks/Document/Component.tsx Outdated
const filename = document.filename ?? 'Download'

const isEmbeddable = document.mimeType != null && EMBEDDABLE_MIME_TYPES.has(document.mimeType)
const resolvedDisplay = displayAs === 'embed' && isEmbeddable ? 'embed' : 'download'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing Document blocks render as embedded iframes today. After this PR they flip to download links. The migration backfills display_as to 'download', and this line resolves anything not explicitly 'embed' to download. So every published PDF becomes a download button on deploy. I think we can just pass displayAs as is instead of creating a place for this to silently fail. Remove this and update to check displayAs below.

Suggested change
const resolvedDisplay = displayAs === 'embed' && isEmbeddable ? 'embed' : 'download'

Also adding required: true will remove the possibility of this being null

options: [
{ label: 'Download Link', value: 'download' },
{ label: 'Embed (iframe)', value: 'embed' },
],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add required: true here to remove the possibility of null

Comment thread src/blocks/Document/Component.tsx Outdated
Comment on lines +22 to +23
// displayAs is present in the block config but absent from generated types until pnpm generate:types is run
displayAs?: 'download' | 'embed' | null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be passed in DocumentBlockProps so we don't need to add it here

Suggested change
// displayAs is present in the block config but absent from generated types until pnpm generate:types is run
displayAs?: 'download' | 'embed' | null

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants