Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

More Formats: HAL & CSV

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

API Platform supports multiple input and output formats. You can see this by going to /api/cheeses.json to get "raw" JSON or .jsonld or even .html, which loads the HTML documentation. But adding the extension like this is kind of a "hack" that API Platform added just to make things easier to play with.

Instead, you're supposed to choose what "format", or "representation" you want for a resource via content negotiation. The documentation already does this and shows it in the examples: it sends an Accept header, which API Platform uses to figure out which format the serializer should use.

Adding a new Format: HAL

Out-of-the-box, API Platform uses 3 formats... but it actually supports a bunch more: JSON-API, HAL JSON, XML, YAML and CSV. Find your terminal and run:

php bin/console debug:config api_platform

This is our current API Platform configuration, including default values. Check out formats. Hey! It shows the 3 formats that we've seen so far and the mime types for each - that's the value that should be sent in the Accept header to activate them.

Let's add another format. To do that, copy this entire formats section. Then open config/packages/api_platform.yaml and paste here.

... lines 2 - 3
- application/ld+json
- application/json
- text/html

This will make sure that we keep these three formats. Now, let's add a new one: jsonhal. This is one of the other formats that API Platform supports out-of-the box. Below, add mime_types: then the standard content type for this format: application/hal+json.

... lines 2 - 13
- application/hal+json

Cool! And just like that... our entire API supports a new format! Refresh the docs and open the GET operation to see cheese listing 1. Before you hit execute, open the format drop down and... hey hey! Select application/hal+json. Execute!

Say hello to the JSON HAL format: a, sort of "competing" format with JSON-LD or JSON-API, all of which aim to standardize how you should structure your JSON: where you data should live, where links should live, etc.

In HAL, you have an _links property. It only has a link to self now, but this often contains links to other resources.

This is more fun if we try the GET collection operation: select application/hal+json and hit Execute. It's kinda cool to see how the different formats "advertise" pagination. HAL uses _links with first, last and next keys. If we were on page 2, there would also be a prev field.

Having this format available may or may not be handy for you - the awesome part is you can choose whatever you want. And, understanding formats unlocks other interesting possibilities.

CSV Format

For example, what if, for some reason, you or someone who uses your API wants to be able to fetch the cheese listing resources as CSV? Yea, that's totally possible! But instead of making that format available globally for every resource, let's activate it for only our CheeseListing.

Back inside that class, once again under this special attributes key, add "formats". If you want to keep all the existing formats, you'll need to list them here: jsonld, json, then... let's see, ah yep, html and jsonhal. To add a new format, say csv, but set this to a new array with text/csv inside.

... lines 1 - 15
* @ApiResource(
... lines 18 - 25
* attributes={
... line 27
* "formats"={"jsonld", "json", "html", "jsonhal", "csv"={"text/csv"}}
* }
* )
... lines 31 - 35
class CheeseListing
... lines 38 - 168

This is the mime type for the format. We didn't need to add mime types for the other formats because they're already set up in our config file.

Let's try it! Go refresh the docs. Suddenly, only for this resource... which, ok, we only have one resource right now... but CheeseListing now has a CSV format. Select it and Execute.

There it is! And we can try this directly in the browser by adding .csv on the end. My browser downloaded it... so let's flip over and cat that file to see what it looks like. The line breaks look a bit weird, but that is valid CSV.

A better example is getting the full list: /api/cheeses.csv. Let's go see what that looks like in the terminal as well. This is awesome! Fastest CSV download feature I've ever built.

And... yea! You can also create your own format and activate it in this same way. It's a powerful idea: our one API Resource can be represented in any number of different ways, including formats - like CSV - which you don't need... until that one random situation when you suddenly really need them.

Next, it's time to stop letting users create cheese listings with any crazy data they want. It's time to add validation!

Leave a comment!

Login or Register to join the conversation
Igor Avatar

Hi ,

how do I add format application/x-www-form-urlencode ?
why the default formats aren't supported?



That's output format, why do you need to output in application/x-www-form-urlencode format? It's not very useful as I know.


Benoit-L Avatar
Benoit-L Avatar Benoit-L | posted 1 year ago


When I add this :

- application/ld+json
- application/json
- text/html

in the yaml file, I have the following error that is displayed : The routing file "C:\wamp64\www\bookshop-api\config/routes/api_platform.yaml" contains unsupported keys for "api_platform": "formats". Expected one of: "resource", "type", "prefix", "path", "host", "schemes", "methods", "defaults", "requirements", "options", "condition", "controller", "name_prefix", "trailing_slash_on_root", "locale", "format", "utf8", "exclude", "stateless" in C:\wamp64\www\bookshop-api\config/routes/api_platform.yaml (which is being imported from "C:\wamp64\www\bookshop-api\src\Kernel.php").

David-G Avatar
David-G Avatar David-G | Benoit-L | posted 1 year ago | edited

I reply very late, but maybe that can be useful for other members.

This error is because you modify the wrong files. the right file is in the folder config/packages/api_platform.yaml (not config/routes/...)

1 Reply

Hey David-G,

Thank you for this tip! I bet it might be useful to someone.



Hey Benoit L.

This is odd, I just checked our course code, and also ApiPlatform source and this key should be named formats:. Have you updated course code? or not? Can you try to reinstall vendors and manually clear the caches.


Benoit-L Avatar

I had to create the project using this command : composer create-project symfony/skeleton bookshop-api because the server was not running without that.


Hey Benoit L.

and which version on ApiPlatform do you have installed? Do you still have this error?



Hey guys,
1) what about exporting word docx formats. What we should exactlly do ? Do we need to create a normolizer like this https://api-platform.com/do... and what we should put there?
2) Does there a way to acess to _format inside my controller, In fact apiplatform they force remove the _format from the open api document.



Hey ahmedbhs!

1) Hmm, I'm not sure! I guess docx is just a special XML format that you can generate? In the case, you would create a custom encoder for it I believe - I don't think you would need a normalizer (but I could be wrong). The normalizer takes the Object and converts it to an array structure.Then, the encoder takes that array structure and converts it into the final format - like xml, json, etc. Hence, I think (?) you might only need a custom encoder, but it's possible you might also need a custom normalizer. In general, this is more of a "Symfony serializer" question than an API Platform question: if you're able to teach the serializer how to export to docx, then API Platform should do it. Here are some details about that: https://symfony.com/doc/current/serializer.html#adding-normalizers-and-encoders

2) You should be able to access it either via $request->getRequestFormat() or via $request->attributes->get('_format').

Let me know how it goes :)


Jean-tilapin Avatar
Jean-tilapin Avatar Jean-tilapin | posted 2 years ago

I need to display dots on a map (using mapbox), with some other informations (address, name, etc.). It works best if I can provide to my map GeoJson data...What is the best way to do that?

Of course, I can make my user's device process the regular collection it gets from the "GET" method, but API Platform should be able to do that more efficiently, right? Through a controller with a specific route, generating my GeoJson "manually"? Through Content Negotiation? Or any other way? What would you recommend? (I don't *always* need to get that GeoJson format for my list, only for that map)


Hey @Xav!

Hmm, I’m not sure. Do you already have an api endpoint of “locations” (or something)? And now you want to be able to sometimes turn that same collection, but formatted in GeoJson? If so, a custom format might indeed make sense.

I’ve never done that myself (we use formats in this video that are already supported in core). They do have Docs on this - https://api-platform.com/do... - but I’ve never tried that before. You basically want a custom normalizer for this situation that builds your data... and then you want to use the standard json encoder.

Let me know if this solution seems to fit you’re use case or not :).



The CSV format is a feature desired in most enterprise applications. It allows users to export static and other data like trial balances to spread sheets for further processing., Is there a export to PDF available or being planning or is there a way to incorporate an existing symfony or PHP library ? This is something that I would like to see in future versions atleast it helps us generate reports using the server resources and a cron job without having any user intervention sample use cases : credit card statements, invoices, regulatory reports etc. Wouldn't want to generate at the browser end.


Hey Sridhar,

I'm not 100% sure if it is going to be implemented in future versions, most probably not, but I can't quickly find any info about it. So feel free to open an issue in the ApiPlatform project repository to be more sure or get feedback from maintainers directly why it won't be implemented.

For now, you probably need only custom solution for this, I can recommend you to take a look at KnpSnappyBundle that allow you to generate PDF files on server side thanks to Wkhtmltopdf that you also need to install on your server.

I hope this helps!


1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9