Medias and attachments
Contents
Naturally, XP can also store any files you would want to serve or associate with your content. This chapter explores how XP stores uploaded data and focuses on how its image tools work. At the end of the chapter we’ll walk through how to use images in a headless fashion.
The tasks and examples in this chapter are based on work done in previous chapters (starting with the sandboxes chapter). If you want to follow along with the examples, make sure you’re all caught up. This chapter requires that you’ve got your site up and running. |
Media
Types
Media types are standard content types that ship with XP. When you upload a file to XP, XP will assign it one of a number of preexisting media types based on the file’s content. Some common media types are images, audio, video, and executables.
For a full overview of all the media types baked into XP, see the reference docs for media content types.
Upload
To upload files to Content Studio, use the "create content" dialog. You can either click the "upload" icon in the dialog or you can drag and drop the files from your file explorer.

This process is the same for whatever media type you want to upload.
To get familiar with uploading media to XP, let’s start with one of the most common media types on the web: images! As mentioned above, this process will also work for any other media type.
-
Find an image to upload. For the purposes of the tasks in this chapter, you should work with an image where the important part isn’t centered in the shot. If you have one yourself, feel free to use that. If not, how about using this picture of a lion by Keyur Nandaniya?
-
Create a folder for your images. Call it "images". Just like the "artist" folder we created previously, you may place the folder anywhere in your project structure.
-
Open the "create content" dialog. Press the upload button to bring up your OS’s file browsing dialog and select your image. You can also drag and drop the image from your file system browser onto the dialog.
And that’s all it takes to upload files to Content Studio!
Data Toolbox
Like we saw in the chapter on content, Data Toolbox lets you view and edit your content. Images and other media are also available in Data Toolbox as content, but in addition to the same properties you saw previously, media also have a property called attachment
. The attachment specifies the actual media file that you have uploaded.
For instance, if you have a look at the image you uploaded earlier, you should see that it points to the image file you uploaded.
If you added a focal point, you’ll see that specified under the media
property too.
If you view the content as JSON, some relevant parts of the structure are the data
and attachment
properties:
// ....
"data": {
"media": {
"attachment": "lion.jpg", (1)
"focalPoint": { (2)
"x": 0.246875,
"y": 0.6653906106948853
}
}
},
"attachment": { (3)
"name": "lion.jpg",
"label": "source",
"binary": "lion.jpg",
"mimeType": "image/jpeg",
"size": 256018,
"text": ""
}
// ....
1 | References the name of the attachment specified in the attachment property. |
2 | If you have set a focal point, its position will also show up here. See the next task for how to set one. |
3 | The image attachment with data about the file, such as MIME type and size. |
XP’s image service
Whenever XP serves you an image, the image is served via XP’s image service.
The image service takes care of scaling and applying filters to your images based on parameters you give it. For instance, it’s common to want to have all images in a section conform to the same dimensions. But because images are generally of different shapes and sizes, this might require cropping the image. The image service takes care of that for you as long as you specify what you want.
The image service works in tandem with Content Studio’s image editing tools, so it respects crops, flips, and focal points that you create.
Editing images
Content Studio comes with a suite of image editing tools built in. After uploading an image to Content Studio, you can find it in the content grid and edit it to bring up the image’s content form.

The content form allows you to provide an image caption, alt text, copyright and tag info.
You can also add image properties such as dimensions, and where the image was taken and with what gear.
Changing these properties, including size, width, height, etc, does not affect the image in any way. |
The image editing tools allow you to crop, flip, and rotate the image as well as to set what’s known as autofocus.
The autofocus control allows you to set an image focal point that XP will always keep in view, regardless of how the image is cropped.
Without setting a focal point, XP will focus around the center of the image when cropping. |
You can always reset the edits you’ve made to an image by using the 'reset filters' button.
You can read more about how Enonic simplifies your everyday image problems to get more info about how to use images in a traditional CMS model and for a quick overview of the autofocus feature. |
Autofocus
Because we chose an image with an off-center subject, the image might get cropped such that the lion disappears from the image. For instance, a small, square crop might turn out like this:

To get around this, try the autofocus tool.
-
Click the autofocus tool. A red circle will show up over the image. This circle indicates where the focal point is.
-
Move the circle onto the lion (or whatever your focal point is) and press "apply".
Figure 4. The focal point editor
Now, when the image gets cropped, XP will take care of keeping the lion in focus, both in traditional CMS solutions and in headless modes:

Attachments
XP has an input type named AttachmentUploader that enables uploading of one or more files that will be stored as attachments to the current content.
This differs from the previously presented media content, which is a separate content itself that can be linked on other content types, for example, by using the ImageSelector input type.
To take a look on how attachments work, let’s update the Artist content type to use the input type AttachmentUploader.
-
For that, update the Artist.xml file accordingly:
<content-type> <display-name>Artist</display-name> <description>Information about an artist</description> <super-type>base:structured</super-type> <form> <input name="name" type="TextLine"> <label>Name</label> <help-text> The artist's name (if different from their professional moniker). </help-text> </input> <input type="TextArea" name="about"> <label>About the artist</label> </input> <input type="Date" name="birthday" > <label>Birthday</label> </input> <input type="AttachmentUploader" name="attachments"> <label>Attachments</label> <occurrences minimum="0" maximum="0"/> (1) </input> </form> </content-type>
1 minimum="0" shows that this is not a required field, while maxiumum="0" shows that we can upload as many attachments as we want. -
Redeploy your app
-
Add some attachments to each of the three artists. In my case I’ll add images of some of their albums.
With that, we have not only enhanced the Artist content type with a new structure, but also added files as attachments to all three artists.
On the next section we’ll show how we can retrieve this information through queries.
GraphQL API
Guillotine makes it easy to fetch the data that you’ve uploaded.
Some functionality is shared between all media types, while other queries only work on specific media types.
We’ll look at how to fetch general media and then have a closer look at what we can do with images and attachments.
General media type
To get the resource URL of media you have uploaded, use the mediaUrl
property on any media type. For instance, to get the URL of the raw image you uploaded previously, you could use a query like this:
{
guillotine {
queryDsl(
query: {
term: {
field: "type",
value: {
string: "media:image"
}
}
}
) {
displayName
... on media_Image { (1)
mediaUrl (2)
}
}
}
}
1 | This allows us to query properties that belong to the media:Image GraphQL type. |
2 | The mediaUrl property (also available on all other media types) gives you a URL from where the raw file can be downloaded. |
{
"data": {
"guillotine": {
"queryDsl": [
{
"displayName": "keyur-nandaniya-8oLnCRz7hwM-unsplash",
"mediaUrl": "/admin/site/preview/my-first-project/draft/_/attachment/inline/21f827e0-ba57-4824-b3bc-f101ae08568b:9475135781b315daad078d91c27746bc3222d024/keyur-nandaniya-8oLnCRz7hwM-unsplash.jpg"
}
]
}
}
}
Image media type
XP supports a number of image manipulation functions on the fly. When querying for an image via the GraphQL API, you can use the imageUrl
function to specify scale and filters for the result you want.
The scale argument is required
and allows you to specify how you want to crop the image. Common scaling functions include block, which allows you to specify width and height and width which scales the image proportionally to the specified width.
The scaling methods reference docs show off all the available scaling functions with examples of how they affect the image. |
As the name implies, the filters argument lets you apply filters to the image. Use filters to apply coloration functions and to rotate and flip images on the fly. To combine filters, separate them with a semicolon.
The filters documentation has an exhaustive list of the available filters. |
For instance, we could get all the images under the images
folder, scaled to 600x700 pixels, in grayscale, with a query like this:
images
folder
{
guillotine {
queryDsl(
query: {
term: {
field: "type",
value: {
string: "media:image"
}
}
}
) {
displayName
... on media_Image {
imageUrl( (1)
type: absolute, (2)
scale: "block(600,700)", (3)
filter: "grayscale(); border(10, 0x000000)") (4)
}
}
}
}
1 | The imageUrl function lets you specify how you want the images to look. |
2 | The type argument specifies whether the URL should be absolute or relative to the server. |
3 | The scale argument specifies the dimensions of the image. |
4 | The filter argument lets you apply filters. |
The above query should return a response that looks a little something like this:
{
"data": {
"guillotine": {
"queryDsl": [
{
"displayName": "keyur-nandaniya-8oLnCRz7hwM-unsplash",
"imageUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/image/21f827e0-ba57-4824-b3bc-f101ae08568b:6d89657e4bfae6eaa62de22bd6fb63b2697f091d/block-600-700/keyur-nandaniya-8oLnCRz7hwM-unsplash.jpg?filter=grayscale%28%29%3B+border%2810%2C+0x000000%29"
}
]
}
}
}
Which, if you have followed the steps in this chapter, should show you something like this if you try and navigate to the imageUrl
property value:

Attachments
Let’s understand how we can retrieve artists attachments through queries.
First, access the GraphQL API docs on query playground and search for the Content
schema. Scroll down a bit and you’ll note that there is a field name attachments. Now look for the com_example_myproject_Artist_Data
schema. You’ll also note that, after the changes on the Attachments section, we also have a attachments field there, of the same type Attachment of the previous one
This means that we can get the attachments for each artist in two ways:
-
By directly querting the attachments field in the
Content
schema, without the need of inline fragments -
Using inline fragments and accessing the
com_example_myproject_Artist_Data
schema, since there is also a attachments field there
Let’s access the attachments in both ways, starting with the query that access the attachments field on the Content
schema level:
{
guillotine {
getChildren(key: "/artists") {
... on com_example_myproject_Artist {
_id
data {
name
about
}
}
attachments {
name
attachmentUrl(type: absolute)
}
}
}
}
And if everything went fine, you should expect a response that looks like this:
{
"data": {
"guillotine": {
"getChildren": [
{
"_id": "4d6862b1-3867-46cc-9705-b0291543b807",
"data": {
"name": "Alecia Beth Moore",
"about": "Alecia Beth Moore (born September 8, 1979), known professionally as Pink (stylized as P!nk), is an American singer and songwriter."
},
"attachments": [
{
"name": "The_Truth_About_Love.png",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/4d6862b1-3867-46cc-9705-b0291543b807:9b8af457d28aeb4332d4ed4b6cc35e63ed462a64/The_Truth_About_Love.png"
},
{
"name": "cant-take-me-home-pink.webp",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/4d6862b1-3867-46cc-9705-b0291543b807:c0bb06a49c8c862c5d6a01bb64bf3ba0aa777da9/cant-take-me-home-pink.webp"
}
]
},
{
"_id": "46d8e610-3b31-4ea4-85c8-a7ae1751977f",
"data": {
"name": "Melissa Arnette Elliott",
"about": "Melissa Arnette Elliott (born July 1, 1971) is an American rapper, singer, songwriter, and record producer."
},
"attachments": [
{
"name": "Under_Construction_Cover.jpg",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/46d8e610-3b31-4ea4-85c8-a7ae1751977f:dd920d59b9ba0ded890d85bbc614cf752a43899a/Under_Construction_Cover.jpg"
},
{
"name": "supa-dupa-fly-album-cover.jpg",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/46d8e610-3b31-4ea4-85c8-a7ae1751977f:9a54d122bb13f50fa6bc368b22843cb661ddebc0/supa-dupa-fly-album-cover.jpg"
}
]
},
{
"_id": "79b30fd8-93b5-4f1c-b17c-36e34bc0767f",
"data": {
"name": "Belcalis Marlenis Almánzar",
"about": "Belcalis Marlenis Almánzar (born October 11, 1992), known professionally as Cardi B, is an American rapper and songwriter."
},
"attachments": [
{
"name": "Cardi_B_-_Invasion_of_Privacy.png",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/79b30fd8-93b5-4f1c-b17c-36e34bc0767f:15db0b6d84901296b64994d682fdcd904b0bdf8c/Cardi_B_-_Invasion_of_Privacy.png"
}
]
}
]
}
}
}
Now accessing attachments field on com_example_myproject_Artist_Data
schema level:
{
guillotine {
getChildren(key: "/artists") {
... on com_example_myproject_Artist {
_id
data {
name
about
attachments {
name
attachmentUrl(type: absolute)
}
}
}
}
}
}
And if everything went fine, you should finally expect a response that looks like this:
{
"data": {
"guillotine": {
"getChildren": [
{
"_id": "4d6862b1-3867-46cc-9705-b0291543b807",
"data": {
"name": "Alecia Beth Moore",
"about": "Alecia Beth Moore (born September 8, 1979), known professionally as Pink (stylized as P!nk), is an American singer and songwriter.",
"attachments": [
{
"name": "The_Truth_About_Love.png",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/4d6862b1-3867-46cc-9705-b0291543b807:9b8af457d28aeb4332d4ed4b6cc35e63ed462a64/The_Truth_About_Love.png"
},
{
"name": "cant-take-me-home-pink.webp",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/4d6862b1-3867-46cc-9705-b0291543b807:c0bb06a49c8c862c5d6a01bb64bf3ba0aa777da9/cant-take-me-home-pink.webp"
}
]
}
},
{
"_id": "46d8e610-3b31-4ea4-85c8-a7ae1751977f",
"data": {
"name": "Melissa Arnette Elliott",
"about": "Melissa Arnette Elliott (born July 1, 1971) is an American rapper, singer, songwriter, and record producer.",
"attachments": [
{
"name": "Under_Construction_Cover.jpg",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/46d8e610-3b31-4ea4-85c8-a7ae1751977f:dd920d59b9ba0ded890d85bbc614cf752a43899a/Under_Construction_Cover.jpg"
},
{
"name": "supa-dupa-fly-album-cover.jpg",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/46d8e610-3b31-4ea4-85c8-a7ae1751977f:9a54d122bb13f50fa6bc368b22843cb661ddebc0/supa-dupa-fly-album-cover.jpg"
}
]
}
},
{
"_id": "79b30fd8-93b5-4f1c-b17c-36e34bc0767f",
"data": {
"name": "Belcalis Marlenis Almánzar",
"about": "Belcalis Marlenis Almánzar (born October 11, 1992), known professionally as Cardi B, is an American rapper and songwriter.",
"attachments": [
{
"name": "Cardi_B_-_Invasion_of_Privacy.png",
"attachmentUrl": "http://localhost:8080/admin/site/preview/my-first-project/draft/_/attachment/inline/79b30fd8-93b5-4f1c-b17c-36e34bc0767f:15db0b6d84901296b64994d682fdcd904b0bdf8c/Cardi_B_-_Invasion_of_Privacy.png"
}
]
}
}
]
}
}
}
As can be seen, both ways retrieve the same data, but since their access is different, the response structure is also slighly different.
Under the hood, the attachment field argument attachmentUrl uses Enonic’s javascript lib portal attachmentUrl function, so you can follow the link to better understand the arguments that you pass to this field when creating queries that uses it. |