Netlify-cms: 'relative path' field for uploaded images

Created on 27 Mar 2017  Â·  51Comments  Â·  Source: netlify/netlify-cms

Hi,
I am starting to use Netlify CMS with Gatsby 1.0.
I need a relative path to fetch pictures with the graphQL layer. So in my frontmatter I would like to have a field like:
fieldName: ../images/myImage.jpg
Of course the last part ("myImage.jpg") would be different for each entry depending on the file I upload.

Is there a way to auto generate a string field in the frontmatter that would concatenate a string of my choice (for example '../images/') with the name of a picture I may upload in an image field of the same entry ?

I also tried to set:
public_folder: "../images"
This would theoretically change all my image fields in all collections to get that leading string in the file path but when I create a new file with the CMS I get an auto opening '/'... So in my frontmatter:
image: /../images/myImage.jpg
instead of
image: ../images/myImage.jpg
Of course this breaks my groove as well.

What is the best way to get around this issue?

asset-management configuration

Most helpful comment

@fk @KyleAMathews Hey all, just wanted to let you know I created a simple gatsby plugin to help gatsby-remark-images match the relative path from markdown images without needing to modify netlify-cms. You just add this plugin before gatsby-remark-images in your gatsby-config.js. Check it out here: https://www.npmjs.com/package/gatsby-remark-relative-images

Hopefully, this won't be needed once NetlifyCMS supports relative paths.

All 51 comments

@MarcCoet a simple, non-breaking approach would be to accept a public_folder_relative setting in the config - set it to true to avoid the prepended slash. If you're up for giving this a shot, implementation would probably look like this:

  1. Update the resolvePath helper to accept a third option allowRelative.
  2. Update the two places in that function where slashes are prepended to only do so if the allowRelative param is not truthy.
  3. Update the assetProxy function to pass in the new config value as a third param to resolvePath.

This issue should be at least helped by #350.

@MarcCoet FWIW a quick workaround is to just edit the markdown of your post to the correct relative path from within netlify cms (ie, just toggle the 'Markdown' switch and edit the path)

It'll break the preview, but so far it appears to push fine. here's my config:

...
media_folder: content/blog-posts/media
public_folder: ../media/
collections:
  - name: blog
    label: Blog
    folder: content/blog-posts/posts
...

I still think this use case should be supported, but it merits further discussion.

Hey! 👋 I took a first quick shot at what @erquhart outlined in https://github.com/netlify/netlify-cms/issues/325#issuecomment-290162337 over the weekend.

Previews don't work yet (haven't looked into the code much) but this does enough to let a friend of mine that I'm helping with his portfolio site author his stuff – relative paths are correctly inserted in Markdown. My context is exactly like discussed in #843 (using Gatsby).

I would love to provide a PR for this eventually but most probably will need some guidance.

@fk that's awesome!

In the time since I wrote that comment, I've come to avoid "switches" that affect other settings in the config. A better approach that's still non-breaking would be to just check if the path begins with one or two dots and a slash and don't prepend a slash if so. We'd want to test it out a bit for unexpected consequences, though. Are you up for giving that a shot?

Hey @erquhart! Yes! Up for anything! ;-) :-D

I also have been thinking a little about how to approach this feature in the meantime. I didn't have much time, so I only came as far as to look into assetProxy in regards on how to restore media previews in the editor and preview pane.

I also started to wonder if approaching this change from what I believe is a quite specific, "Gatsby" point of view, is the right thing to do to actually land a PR that is beneficial to anyone else – in other words: "What are other use cases for a relative public path (which couldn't be resolved by just changing the location of the media folder)?" is a question I figured I should ask here before going any further.

A quick recap of my specific use case: For Gatsby to be able to process URLs in Markdown (process images, copy linked files), it requires them to be relative (I still have to check back with @KyleAMathews on that, but I think "it's a Webpack thing") – basically what surfaced in #843.
My initial approach was to add the public_folder_relative option as you suggested (and then somehow make previews work with that), and to put all my markdown content at the same "distance" to the media folder – e.g. if my public_folder_relative was ../../img, all my Markdown content would have to live two folders down from img per convention.

Ideally I thought netlify-cms could resolve the path to be inserted in Markdown relative to the Markdown source and the location of the media folder. But somehow I feel like I'm missing something obvious still…

I've come to avoid "switches" that affect other settings in the config. A better approach that's still non-breaking would be to just check if the path begins with one or two dots and a slash and don't prepend a slash if so.

Yes, totally can follow your thoughts. FWIW, I've been seeing sindresorhus/is-relative-url being used in Gatsby.

We'd want to test it out a bit for unexpected consequences, though. Are you up for giving that a shot?

Yes! How about I adjust my fork to look at the path/remove the new public_folder_relative option and report back?

@fk apologies for the delay - that would be awesome. The problem and solution can really approached quite abstractly: for myriad reasons, folks sometimes want to set their media folder via relative path. I can't think of other specific use cases off the top of my head.

Hugo is going to be supporting image processing soon (before Jan 1!), and if I understand correctly, it will only work with relatively placed images. We probably want to prioritize this a little more.

@erquhart I don't quite understand how the media library will handle relatively placed media. Will it just walk through all the directories to find files that match the media_folder string (via regex?)?

Also, what do we do if an image is added to one entry, and then it is added to a different post via the media library? Do we just add the path relatively to the old location, or would we copy the actual image to the second location as well?

I found another workaround. Maybe it will be useful for somebody else. The path added to the markdown files by Netlify CMS can be adjusted and set (as Gatsby requires) to the relative path from the particular node (markdown file) the image is used in. Adding the following code to gatsby-node.js did the trick for me:

exports.onCreateNode = ({
  node,
  getNode,
  loadNodeContent,
  boundActionCreators,
}) => {
  const { frontmatter } = node
  if (frontmatter) {
    const { image } = frontmatter
    if (image) {
      if (image.indexOf('/img') === 0) {
        frontmatter.image = path.relative(
          path.dirname(node.fileAbsolutePath),
          path.join(__dirname, '/static/', image)
        )
      }
    }
  }
}

This assumes that config.yml has the media file paths set as follows:

media_folder: "static/img"
public_folder: "/img"

Also, the name of the markdown fields this would apply for is hardcoded above as image.

You shouldn't mutate node data. The data should be treated as immutable (I'm actually surprised that worked as I thought we were already passing a copy of data to APIs to prevent exactly this sort of thing. Instead use https://www.gatsbyjs.org/docs/bound-action-creators/#createNodeField

Ok, makes sense. I actually tried to use the spread operator to avoid mutation, but somehow it didn't work. Will try with createNodeField

how would the graphQL query look like with createNodeField?

This works:

markdownRemark{
     frontmatter {
        image
      }
      fields {
        imageRel
      }
}

But nicer would be:

markdownRemark{
     frontmatter {
        image {
          relativePath 
            // and then here the childImageSharp stuff
        }
      }
}

which fails with: GraphQL Error Field "image" must not have a selection since type "String" has no subfields.

If you want to link to a file node, you need to add ___NODE to the end of the field name so image___NODE

I'm trying to get gatsby-image working with netlify cms and I gave this a try. I used @ilyabo's code above and modified it with the createNodeField. I tried to access it in my query with imageLocalFile___NODE but I got this error: GraphQL Error Unknown fieldimageLocalFile___NODEon typefields``

Running into the same issue.

Temporary workaround:

Have a field called ImageSourceCorrected, using the string widget.
Just have whoever is using the CMS copy the address that gets shown under the image, but take out the / in the front.

Then in your code, just query for the imageSourceCorrected field and use that w/ gatsby image

@akadop If it helps at all, the address that gets shown under the image is the media_folder that is set in your config, plus the image filename. As long as you don't set a public_folder, the image field that is saved in any entry created should be that address.

Does anyone know how to use NetlifyCMS to manage images stored as "Page Resources" (relative to the page they are used on in Page Bundles)?

This apparently is required to be able to use Hugo's new native image processing.

@01ivr3 It actually is possible to use Hugo's image processing with a single media folder, see https://stackoverflow.com/questions/48213883/image-processing-outside-bundles.

@tech4him1 very cool! Thank you for the heads up.

Do I have it right that this method can be made to work with NetlifyCMS' media management as long as the images are added to pages using the image widget? Meaning, this breaks the ability to add images using the +Image feature to access the Media Library from within the markdown widget's wysiwyg editor?

@01ivr3 Through the method @talves provided in the linked StackOverflow question, all the media is stored in one folder, instead of beside each page as part of the Page Bundles. It simply allows you to use Hugo's image processing features without storing the media next to the page. Because of that, it works normally with the CMS, including the media library.

Depending on whether storing media next to the page is your main goal, or just using the image processing, it may or may not be an acceptable workaround.

@01ivr3 you can use my method if you are not worried about page bundling your assets in the same paths as your pages. You would have to store all original images in one media folder location. The issue is the location of the preview when you add the image into the markdown because there is not a way to translate back the image to the preview correctly from Hugo. We would have to write a custom widget to be able to know to use the original image path for preview and let Hugo manage the path on build. Hope that makes sense.

NetlifyCMS does not use multiple locations for media at this time, but will be addressing in the future.

@talves & @tech4him1 thank you both for your help.

I've got a site I'm working on setup to use Hugo's built-in image resizing with images stored to a single folder as per @talves StackOverflow examples. But I am a bit confused on the optimum way to configure this with NetlifyCMS.

If images are stored under a single /site/content/media folder, which I want to be CMS manageable and accessible to Hugo's image resizing , I assume the following parameters should be set in NetlifyCMS' config.yml.

backend:
  name: git-gateway
media_folder: "site/content/media"
public_folder: "media"

The Media Library in the cms seems to preview images from the media_folder stored in the project's GitHub repo.

On selecting an image using the image widget, filenames get automatically prefixed with public_folder, (in my case /media/). The widget's preview image is then set using the public folder url.

This works for a basic src link e.g. <img src="{{ .Parmas.some_image }}" />

But auto-prefixing the public_folder to image values seems to be an undesirable behaviour when used with Hugo's native image processing.

For example, if I have the following custom parameter set in a page's front matter: nav_image: SOME_IMAGE.jpg...

When using the following template chunk..

{{ $media := .Site.GetPage "section" "media" }}
{{ $currentPage := . }}
{{ range .Site.Menus.main -}}
  {{- if .Page.Params.nav_image -}}
    <img src="{{ (($media.Resources.GetMatch .Page.nav_image).Resize "880x").RelPermalink }}" />
  {{ end -}}
{{ end }}

Hugo will throw a can't evaluate field Resize in type resource.Resource error once the cms has prefixed media_folder to any SOME_IMAGE.jpg value.

I realize now that I could likely strip off the auto-prefixed folder value with something like {{ strings.TrimLeft .Page.nav_image "/media/" }} but have yet to test this.

When I started rambling this post, I was thinking config.yml should be set as follows:

media_folder: "site/content/media"
public_folder: ""

But I assume this would break the preview images for the image widget, and possibly some other undesirable behaviour.

Any help on the optimum way to set this this up is much appreciated.

This is my first Go with Hugo templating and NetlifyCMS, so there's a damn good chance I'm doing something foolish here. 🤓

Netlify CMS prefixes the public_folder so the resulting can be used as the image src. For your case, where would the live CMS find your published images to show in the preview pane?

I get that. In my case, the live CMS should find the published images to show in the preview pane under /media/. That said, I am not outputting these values directly to an img src. The value is being used with Hugo's built in image processing via @talves workaround to store images to a single folder.

In my above example, the value of the media folder has already been passed to the image resize function as follows {{ (.Site.GetPage "section" "media").Resources.GetMatch .Page.nav_image }}.

In this case, I believe also prefixing media_folder to the image filename is an undesirable behaviour. Hugo will throw an error if the auto-prefixed value is not first stripped out before it is used as the source value for an image processing function.

@01ivr3 since the solution I proposed in that SO, there was a change, because my example is not working the way it was first setup in 0.32 of Hugo. You will have to run a build and see how the folder layout will be published for the correct paths, but it could be this solution will not be viable in the new versions of Hugo.

@talves do you know what version of Hugo breaks the folder structure?

I'm currently using hugo-bin ^0.22.0 as part of Netlify's victor-hugo starter, which includes Hugo v0.35. Images stored under site/content/media still appear to be copied to /media when building site.

Sorry, I am not sure.

Is it possible to hide files from NetlifyCMS' Media Library?

If I store images in a single /content folder to be able to use Hugo's built in image processing, NetlifyCMS will show the required _index.md file in this directory, and give the end user the option to delete this file. How can I set NetlifyCMS to not show this file in its Media Library UI??

@fk @KyleAMathews Hey all, just wanted to let you know I created a simple gatsby plugin to help gatsby-remark-images match the relative path from markdown images without needing to modify netlify-cms. You just add this plugin before gatsby-remark-images in your gatsby-config.js. Check it out here: https://www.npmjs.com/package/gatsby-remark-relative-images

Hopefully, this won't be needed once NetlifyCMS supports relative paths.

@erquhart @talves Is it worth adding @danielmahon's plugin to gatsby-plugin-netlify-cms in some way?

@tech4him1 Not sure if that would work as it is. The plugin basically has to "intercept" the node returned from gatsby-transformer-remark before it gets to gatsby-remark-images.

It bascially just sets the node.url to path.relative(node.parent.dir, file.url).

So, given a node like /src/pages/blog/my-first-post.md, with an image src of /uploads/fat-racoon.jpg, and the referenced image in /static/uploads it changes the node.url to .../.../../static/uploads/fat-racoon.jpg. Which lets gatsby-remark-images resolve it correctly when it does path.join(node.parent.dir, node.url).

You can "maybe" see it in action here:
https://github.com/danielmahon/gatsby-starter-procyon/tree/netlifycms.

I say maybe because I'm still working on that starter, expecially the netlifycms branch.
So get ready to step in some :poop:.

@danielmahon tried your package but nothing appeared to happen. Upon debugging it, for whatever reason the select(markdownAST, "image") and select(markdownAST, "html") return nothing, despite there being plenty of html content. Wondering if you or anyone had any ideas why that might be? For what it's worth, my config is pretty much set up the same as your example.

@brendan-hurley Hmm, not sure. markdownAST is passed to the plugin array via gatsby-transformer-remark. Those calls are mirroring how gatsby-remark-images grabs the images and html nodes so as long as you have the plugin in the correct place (right before gatsby-remark-images) then it should work . Double check your config. gatsby-remark-relative-images should be inside the plugins array of gatsby-transformer-remark and before gatsby-remark-images. If markdownAST isnt returning anything then something else is going on upstream with the remark parsing.

I've tested this with md ![](/path/to/fat-racoon.jpg), and html <img src="/path/to/fat-racoon.jpg"/>. Also works with nested img tags.

...
    {
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          {
            resolve: 'gatsby-remark-relative-images',
            options: {
              name: 'uploads',
            },
          },
          {
            resolve: 'gatsby-remark-images',
            options: {
              // It's important to specify the maxWidth (in pixels) of
              // the content container as this plugin uses this as the
              // base for generating different widths of each image.
              maxWidth: 720,
            },
          },
        ],
      },
    },
...

@danielmahon Aye I have it set up as such:

plugins: [
    'gatsby-plugin-react-next',
    'gatsby-plugin-react-helmet',
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/static/content`,
        name: 'content',
      },
    },
    {
      resolve: 'gatsby-source-filesystem',
      options: {
        path: `${__dirname}/static/assets`,
        name: 'assets',
      },
    },
    'gatsby-transformer-sharp',
    { 
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-relative-images`,
            options: {
              name: 'assets',
            },
          },
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 1200,
            },
          },
        ],
      },
    },
    '@jacobmischka/gatsby-plugin-react-svg',
    {
      resolve: 'gatsby-plugin-postcss-sass',
      options: {
        postCssPlugins: [
          pixrem(),
          autoprefixer({
            browsers: ['last 2 versions']
          }),
        ],
        precision: 8,
      },
    },
    {
      resolve: 'gatsby-plugin-netlify-cms',
    },
    'gatsby-plugin-sharp',
    'gatsby-plugin-netlify',
  ],

@brendan-hurley Open an issue and include a sample markdown string you're trying to parse, and I'll take a look. Trying not to hijack this thread :wink:

fwiw, a 'public_folder' config containing an absolute url is also munged with a leading /WHACK -> -- after having much discussion in gitter, and reading (grepping really...) the source i think the global, and correct, fix is to never touch 'public_folder' , eg. never prepend a /...

understanding that this could break existing customers but, also, from a naming perspective my suggestion is similar to others in that i'd add another key, eg 'media_prefix' and prefer that when expanding links in content. this value should be a promise that would always resolve a media item whether static or dynamic. it would have a very, very simple contract: media_prefix + '/' + media_filename is valid. what that might be so is the customer's problem - not netlify's

Hi everyone!
I had the problem of using Hugos image processing while at the same time maintaining the easy image adding that netlify cms provides.
I used the method talves provided to store all media in a folder in the content directory. Then I created a shortcode which resizes the image and inserts the link. Now I created a custom editor widget for the netlify cms which inserts the shortcode by using the media library and edits the paths, so preview and output work.
I can provide all the files if anyone's interested
(This is my first post in a github discussion - I hope I'm in the right place ˆˆ)

Hi @PaulEibensteiner - welcome, you're in the right place! And your solution sounds awesome! Can you put your code in a gist and provide a link?

@PaulEibensteiner great job. I was planning to eventually get to creating a widget for a Hugo shortcode, but have been really busy with some other fun stuff that will be coming soon.

Sure!
So first you have to create the directory "images" in your content directory, and add the _index.md with following front matter:

title: Media Folder
headless: true
---

Then the shortcode in the layouts/shortcodes directory:
https://gist.github.com/PaulEibensteiner/c88e14439abc1111f7388520ade2cf36

Now the custom editor widget: I have a 1-year knowledge of self taught javascript so this might be a complete mess - also I didn't quite understand the documentation about building a custom widget. So I just added the following code at the bottom of my static/admin/index.html (after the tag):
https://gist.github.com/PaulEibensteiner/c2f1d107deef3a0d7d30fa25ef0a198e
I know that the method to remove and add the path sequence for the folder is really messy. Ideal would probably be to access the public_folder and media_folder configuration and change the path accordingly, I just don't have a clue how to to that - and it works so whatever...
(You could also remove the path like others did in the shortcode via go...but there you wouldn't even have the option to change it based on the folder name I guess)

  • now you only have to move all your images to the new location and change all links to the new shortcodes...and it should work

ps: I might have messed up the naming of elements because I already changed some stuff in the original project but omitted it here for 'simplicity' - I hope it works. Pls let me know if anything is flawed or if my explanations are confusing
Have fun coding - Hugo's pretty awesome ˆˆ

@PaulEibensteiner This is good stuff. Don't worry about the widget, I will eventually formalize yours into an npm module if you don't get to it first. Again, great job. Also, yes Hugo is pretty Awesome!

@talves Thank you, yeah, I don't really know about npm and that stuff ˆˆ But that would be awesome...
Quick question off-topic: Are there custom widgets provided by other users somewhere on the internet? I could imagine there are a lot of examples (like the yt widget in the docs) which a lot of people would find useful? Googled and didn't find anything...

We have been discussing creating a place for people to publish information about their custom widgets on the NetlifyCMS site somewhere. You are right, and a plan to have a full information format will help quite a bit. I am sure we will get there. We just need someone in the community to take it on when they get time. I might tackle it with some other stuff I am doing, but it is lower on the priority at the moment.

I created an issue for discussion here, rather than us keep hijacking this issue.

@zionis137 Responding to an old comment of yours https://github.com/netlify/netlify-cms/issues/325#issuecomment-354538798

If you use createNodeField to convert frontmatter.image to a relative path in gatsby-node.js:

createNodeField({
  node,
  name: 'imageRel',
  value: someConversionToFileSystemPath(node.frontmatter.image)
});

then you should be able to query your image node like this:

markdownRemark{
    frontmatter {
        image // this will still be original string that couldn't resolve to file system path
    }
    fields {
        imageRel { // this will contain a file node
            relativePath 
            // and then here the childImageSharp stuff
        }
    }
}

As I understand it, any paths that Gatsby can identify as a file system path will be converted to file nodes in the query results in which case you can access relativePath, and childImageSharp, etc.

Thanks, that works great, I used a trick like this to let me leave the images in src/static whilst letting me process them in remark-images. Doing that means the netlify CMS previews work as expected as well.

I've found this working damn great!
Image Loading with Gatsby v2 and Netlify CMS v2
Can also find the link to the dude repos in the article if you wanna see the full config.

Has anyone tried this with an animated gif file? I previously used @danielmahon plugin and had that working...but when my client just decided they wanted a gif...I'm now getting errors that childImageSharp is null. Does it behave differently with a gif vs jpg? I had looked at this...but on first try didn't have much luck https://www.gatsbyjs.org/packages/gatsby-remark-copy-linked-files/?=copy-

@joshuarule check the ‘gatbsy-image’ docs but I think GIFs and SVGs aren’t supported...maybe?

@joshuarule I'm assuming this is still the case? https://github.com/gatsbyjs/gatsby/issues/7317#issuecomment-412984851

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TomPichaud picture TomPichaud  Â·  3Comments

dur41d picture dur41d  Â·  3Comments

bkroggel picture bkroggel  Â·  3Comments

calavera picture calavera  Â·  3Comments

BerkeleyTrue picture BerkeleyTrue  Â·  3Comments