Media - files are content too

Contents

In this chapter, you’ll upload an image, inspect how it is stored as content with an attachment, and use Guillotine to get URLs for its original file and for cropped and filtered versions. The media APIs deliver the files at those URLs.

Upload a file

Start by uploading an image that you’ll use throughout the chapter.

Task: Upload an image

  1. Choose a photograph - Download this lion photograph by Keyur Nandaniya, or choose a JPEG with its main subject away from the center. Save it as lion.jpg if you want the filename to match the examples.

  2. Create the Images folder - In your "My First Project" content project, create a folder named "Images" at the root, with the path /images.

  3. Choose the upload destination - Select the Images folder, click New, and choose the Media tab. Check that the destination shown is /images/.

  4. Upload the file - Click the upload area to select your file, or drag the file onto it.

    New Content dialog with the Media tab selected and /images/ as the upload destination
    Figure 1. The Media tab in the New Content dialog

The uploaded image now appears as a content item in the Images folder. It is saved in Draft and has not been published.

Inspect the uploaded content

Your uploaded image is now a content item, just like the artists you created earlier. Let’s inspect its data and find the file attached to it.

Task: Find the image and its attachment

  1. Find the uploaded image - Open Query Playground, select "My First Project" and keep Draft selected throughout this chapter. Run this query to find up to ten images anywhere in the project:

    {
      guillotine {
        queryDsl(
          query: {
            term: {
              field: "type"
              value: { string: "media:image" }
            }
          }
        ) {
          displayName
          _path
          type
          dataAsJson
          attachments {
            name
            label
            mimeType
            size
          }
        }
      }
    }

    Below is one item from the data.guillotine.queryDsl array. This example shows the image immediately after upload; your filename, dimensions, file size and additional metadata may differ:

    The image content and its attachment
    {
      "displayName": "lion",
      "_path": "/images/lion.jpg",
      "type": "media:image",
      "dataAsJson": {
        "media": {
          "imageWidth": 2400,
          "attachment": "lion.jpg",
          "imageHeight": 1600
        }
      },
      "attachments": [
        {
          "name": "lion.jpg",
          "label": "source",
          "mimeType": "image/jpeg",
          "size": 550171
        }
      ]
    }
  2. Keep the content path - Copy your image’s _path for the following queries.

The dataAsJson field shows the stored content data, as it did for your artists. The attachments field lists the attached files and their metadata. Both are common content fields, so this query does not need an inline fragment.

Notice how the content and file fit together:

  • type is media:image: XP detected the uploaded file and created an image content item.

  • dataAsJson.media holds image data, including dimensions and the name of the source attachment.

  • attachments contains an entry with that same name, lion.jpg. Its label is source, identifying it as the original uploaded file; mimeType and size describe its format and size in bytes. The file’s binary data is stored as an attachment; it is not embedded in this JSON response.

This upload-and-retrieve pattern is the same for any kind of file, including a PDF, an audio recording or a video. Each upload creates a media content item with the original file attached and labeled source. XP maps the file to the appropriate media content type: an image becomes media:image, while other files become types such as Audio, Video or Document. The type and its metadata fields vary, but the attachment structure and the mediaUrl field used to retrieve the original file work the same way. The CMS media reference explains media structure, type mapping and file handling in more detail.

XP automatically extracts readable text from known file formats, such as PDF, and indexes it for search. See Media indexing for what becomes searchable and how to query it.

Retrieve the attached file

You’ve found the attachment’s metadata; now retrieve the file itself. For media content, mediaUrl gives you the URL components for its source attachment, lion.jpg in this example. The attachment API, media:attachment, delivers the file at that URL. Like Guillotine, it is a Universal API: on your sandbox, its address starts with http://localhost:8080/api/media:attachment.

Task: Open and download the attachment

  1. Fetch the attachment URL - Use get with the content path from your response. Replace /images/lion.jpg if your image has a different path:

    {
      guillotine {
        get(key: "/images/lion.jpg") {
          displayName
          ... on media_Image {
            mediaUrl {
              path
              queryString
            }
          }
        }
      }
    }

    The data.guillotine.get object looks like this, with your own content ID, fingerprint and filename:

    The source attachment’s URL components
    {
      "displayName": "lion",
      "mediaUrl": {
        "path": "/media:attachment/my-first-project:draft/3cff3960-08e9-4c92-828e-917187c1dc20:927e557892810fabb5e2c76cd686a7e7/lion.jpg",
        "queryString": ""
      }
    }
  2. Build the attachment address - Use your own response: start with http://localhost:8080/api, then append mediaUrl.path and mediaUrl.queryString exactly as returned. An empty queryString adds nothing. For the example above, the address would be:

    http://localhost:8080/api/media:attachment/my-first-project:draft/3cff3960-08e9-4c92-828e-917187c1dc20:927e557892810fabb5e2c76cd686a7e7/lion.jpg
  3. View the original file - Open your assembled URL in a new tab in the same browser where you are logged in to Content Studio. It displays the original uploaded image. Draft media requires an authenticated request; copying the example URL above will not retrieve your own image.

  4. Download the attachment - Change mediaUrl to mediaUrl(download: true) and run the query again. Assemble and open the new URL, including its queryString; the browser will download the attachment.

The CMS content type is media:image; its GraphQL object type is media_Image. As with Artist in the previous chapter, the inline fragment gives access to fields belonging to that GraphQL type. mediaUrl is available on all media types.

The content item’s _path, such as /images/lion.jpg, identifies the item in queries. The media URL’s path identifies the attached file through the attachment API.

Request a cropped image

The imageUrl field returns a URL served by the image API, media:image. It can resize, crop and filter images when delivering them, while the original attachment remains unchanged.

The scale argument controls resizing and cropping. For example, width(600) scales proportionally to a width of 600 pixels; block(400,600) fills a 400-by-600-pixel rectangle, cropping what does not fit. Omitting scale means no resizing, but other processing and saved image edits can still affect the result. Use mediaUrl when you need the original uploaded file.

Task: Open a portrait crop

  1. Request a portrait crop - Use get to retrieve the image whose content path you copied above. Replace /images/lion.jpg if your image has a different path:

    {
      guillotine {
        get(key: "/images/lion.jpg") {
          displayName
          ... on media_Image {
            imageUrl(scale: "block(400,600)") {
              path
              queryString
            }
          }
        }
      }
    }

    The data.guillotine.get object contains:

    Image with a crop URL
    {
      "displayName": "lion",
      "imageUrl": {
        "path": "/media:image/my-first-project:draft/3cff3960-08e9-4c92-828e-917187c1dc20:4f827305024b80384b0aa939496e4688/block-400-600/lion.jpg",
        "queryString": ""
      }
    }
  2. View the crop - Assemble the URL from http://localhost:8080/api plus your returned imageUrl.path and imageUrl.queryString, then open it in your browser. Without a focal point, the crop is centered, which can cut off an off-center subject:

    Portrait crop with part of the lion cut off at the left edge
    Figure 2. Portrait crop before setting a focal point

Guide cropping with a focal point

A focal point identifies the subject to prioritize when the image API crops an image to a different aspect ratio. It guides the crop; it does not guarantee that the entire subject fits every shape.

Task: Set a focal point and compare

  1. Open the image editor - Return to Content Studio, select your image and click Edit. The content form contains fields such as Caption and Alternative text, and the image preview includes editing controls.

    Content Studio image editor showing the lion preview, editorial fields and image editing toolbar
    Figure 3. The image content form and preview
  2. Position the focal point - In the toolbar above the image preview, click Set Autofocus to edit the focal point. Click the lion’s face, or drag the marker onto the subject of your own image.

    Image preview in focal-point mode with the marker on the lion’s face and Apply and Cancel controls
    Figure 4. Positioning the focal point
  3. Save the focal point - Click Apply to accept the position, then Save in the Content Editor toolbar to save the content item.

  4. Compare the new crop - Return to Query Playground and run the same portrait-crop query again. Assemble and open the newly returned URL so you see the saved change.

    The new crop prioritizes the selected subject:

    Portrait crop after setting the focal point, with the lion’s face in view
    Figure 5. The same portrait crop with a focal point

Image edits are stored on the content item; they do not rewrite the original attachment. The Content Studio image documentation covers cropping, rotation, resetting edits and image metadata.

Task: Add a filter

  1. Request image filters - Keep the same image and scale, and add a filter argument:

    {
      guillotine {
        get(key: "/images/lion.jpg") {
          displayName
          ... on media_Image {
            imageUrl(
              scale: "block(400,600)"
              filter: "grayscale(); border(10, 0x000000)"
            ) {
              path
              queryString
            }
          }
        }
      }
    }

    Use your image’s content path if it differs from the example. The semicolon separates two filters: grayscale and a black border.

  2. View the filtered image - Build and open the URL as before, including the complete queryString returned by Guillotine.

    Portrait crop of the lion in grayscale with a black border
    Figure 6. The cropped image with filters

The Image API reference describes the available scaling methods and filters.

For presentation effects such as grayscale or borders, consider CSS or similar client-side styling. Use server-side filters when the effect needs to be part of the delivered image file, for example in a download or an email.

Media URLs and caching

The returned paths identify the content project and branch. In these examples, my-first-project:draft selects Draft; published content uses my-first-project without a branch suffix. Let Guillotine generate these paths and query strings rather than constructing or editing their internal parts yourself.

The fingerprint in a media URL identifies a version of the file or processed image and supports caching. After changing an image or its editing settings, query again for the current URL. Actual caching follows the response’s Cache-Control header, with different settings for public and private content; a fingerprint does not make draft content public.

Returning URL components also lets your front-end use a separate base URL for media, for example when serving published images and downloads through a content delivery network (CDN). GraphQL requests can still go directly to the Enonic server. The platform’s virtual host configuration reference explains how to give the media APIs their own address.

Coming up

You’ve uploaded an image, inspected its content data and attachment, downloaded the original file, and requested cropped and filtered versions that use a focal point. In the next chapter, you’ll take a closer look at form items, the building blocks of content forms.


Contents

Contents