# Backend customization
# Routing
./api/**/config/routes.json
files define all available endpoints for the clients.
By default, Strapi generates endpoints for all your Content Types. More information is in the Content API documentation.
# How to create a route?
You have to edit the routes.json
file in one of your APIs folders (./api/**/config/routes.json
) and manually add a new route object into the routes
array.
Path — ./api/**/config/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/restaurants",
"handler": "Restaurant.find",
"config": {
"policies": []
}
},
{
"method": "PUT",
"path": "/restaurants/bulkUpdate",
"handler": "Restaurant.bulkUpdate",
"config": {
"policies": []
}
},
{
"method": "POST",
"path": "/restaurants/:id/reservation",
"handler": "Restaurant.reservation",
"config": {
"policies": ["is-authenticated", "has-credit-card"]
}
}
]
}
method
(string): Method or array of methods to hit the route (e.g.GET
,POST
,PUT
,HEAD
,DELETE
,PATCH
).path
(string): URL starting with/
(e.g./restaurants
).handler
(string): Action to execute when the route is hit following this syntax<Controller>.<action>
.config
policies
(array): Array of policy names or paths (see more)
💡 TIP
You can exclude the entire config
object if you do not want the route to be checked by the Users & Permissions plugin.
# Dynamic parameters
The router used by Strapi allows you to create dynamic routes where you can use parameters and simple regular expressions. These parameters will be exposed in the ctx.params
object. For more details, please refer to the PathToRegex (opens new window) documentation.
{
"routes": [
{
"method": "GET",
"path": "/restaurants/:category/:id",
"handler": "Restaurant.findOneByCategory",
"config": {
"policies": []
}
},
{
"method": "GET",
"path": "/restaurants/:region(\\d{2}|\\d{3})/:id", // Only match when the first parameter contains 2 or 3 digits.
"handler": "Restaurant.findOneByRegion",
"config": {
"policies": []
}
}
]
}
# Example
Route definition with URL params
{
"routes": [
{
"method": "GET",
"path": "/restaurants/:id",
"handler": "Restaurant.findOne",
"config": {
"policies": []
}
}
]
}
Get the URL param in the controller
module.exports = {
findOne: async ctx => {
// const id = ctx.params.id;
const { id } = ctx.params;
return id;
},
};
# Policies
Policies are functions which have the ability to execute specific logic on each request before it reaches the controller's action. They are mostly used for securing business logic easily.
Each route of the project can be associated to an array of policies. For example, you can create a policy named isAdmin
, which obviously checks that the request is sent by an admin user, and use it for critical routes.
The policies are defined in each ./api/**/config/policies/
folders and plugins. They are respectively exposed through strapi.api.**.config.policies
and strapi.plugins.**.config.policies
. The global policies are defined at ./config/policies/
and accessible via strapi.config.policies
.
# How to create a policy?
There are several ways to create a policy.
- Using the CLI
strapi generate:policy is-authenticated
.
Read the CLI documentation for more information. - Manually create a JavaScript file named
is-authenticated.js
in./config/policies/
.
Path — ./config/policies/is-authenticated.js
.
module.exports = async (ctx, next) => {
if (ctx.state.user) {
// Go to next policy or will reach the controller's action.
return await next();
}
ctx.unauthorized(`You're not logged in!`);
};
In this example, we are verifying that a session is open. If it is the case, we call the next()
method that will execute the next policy or controller's action. Otherwise, a 401 error is returned.
# Usage
To apply policies to a route, you need to associate an array of policies to it. There are two kinds of policies: global and scoped.
✋ CAUTION
To apply policies with GraphQL please see the following guide.
# Global policies
The global policies can be associated to any route in your project.
Path — ./api/restaurant/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/restaurants",
"handler": "Restaurant.find",
"config": {
"policies": ["global::is-authenticated"]
}
}
]
}
Before executing the find
action in the Restaurant.js
controller, the global policy is-authenticated
located in ./config/policies/is-authenticated.js
will be called.
💡 TIP
You can put as much policy as you want in this array. However be careful about the performance impact.
# Plugins policies
Plugins can add and expose policies into your app. For example, the plugin Users & Permissions comes with useful policies to ensure that the user is well authenticated or has the rights to perform an action.
Path — ./api/restaurant/config/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/restaurants",
"handler": "Restaurant.find",
"config": {
"policies": ["plugins::users-permissions.isAuthenticated"]
}
}
]
}
The policy isAuthenticated
located in the users-permissions
plugin will be executed before the find
action in the Restaurant.js
controller.
# API policies
The API policies can be associated to the routes defined in the API where they have been declared.
Path — ./api/restaurant/config/policies/isAdmin.js
.
module.exports = async (ctx, next) => {
if (ctx.state.user.role.name === 'Administrator') {
// Go to next policy or will reach the controller's action.
return await next();
}
ctx.unauthorized(`You're not allowed to perform this action!`);
};
Path — ./api/restaurant/config/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/restaurants",
"handler": "Restaurant.find",
"config": {
"policies": ["isAdmin"]
}
}
]
}
The policy isAdmin
located in ./api/restaurant/config/policies/isAdmin.js
will be executed before the find
action in the Restaurant.js
controller.
# Using a policy outside its api
To use a policy in another api you can reference it with the following syntax: {apiName}.{policyName}
.
Path — ./api/category/config/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/categories",
"handler": "Category.find",
"config": {
"policies": ["restaurant.isAdmin"]
}
}
]
}
# Advanced usage
As it's explained above, the policies are executed before the controller's action. It looks like an action that you can make before
the controller's action. You can also execute a logic after
.
Path — ./config/policies/custom404.js
.
module.exports = async (ctx, next) => {
// Indicate to the server to go to
// the next policy or to the controller's action.
await next();
// The code below will be executed after the controller's action.
if (ctx.status === 404) {
ctx.body = 'We cannot find the resource.';
}
};
# Controllers
Controllers are JavaScript files which contain a set of methods called actions reached by the client according to the requested route. It means that every time a client requests the route, the action performs the business logic code and sends back the response. They represent the C in the MVC pattern. In most cases, the controllers will contain the bulk of a project's business logic.
module.exports = {
// GET /hello
async index(ctx) {
return 'Hello World!';
},
};
In this example, any time a web browser is pointed to the /hello
URL on your app, the page will display the text: Hello World!
.
The controllers are defined in each ./api/**/controllers/
folder. Every JavaScript file put in these folders will be loaded as a controller. They are also available through the strapi.controllers
and strapi.api.**.controllers
global variables.
# Core controllers
When you create a new Content Type
you will see a new empty controller has been created. This is because Strapi builds a generic controller for your models by default and allows you to override and extend it in the generated files.
# Extending a Model Controller
Here are the core methods (and their current implementation). You can simply copy and paste this code in your own controller file to customize the methods.
✋ CAUTION
In the following example we will assume your controller, service and model are named restaurant
.
# Utils
First require the utility functions
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
parseMultipartData
: This function parses Strapi's formData format.sanitizeEntity
: This function removes all private fields from the model and its relations.
# Collection Type
# Single Type
# Custom controllers
You can also create custom controllers to build your own business logic and API endpoints.
There are two ways to create a controller:
- Using the CLI
strapi generate:controller restaurant
.
Read the CLI documentation for more information. - Manually create a JavaScript file in
./api/**/controllers
.
# Adding Endpoints
Each controller’s action must be an async
function.
Every action receives a context
(ctx
) object as first parameter containing the request context and the response context.
# Example
In this example, we are defining a specific route in ./api/hello/config/routes.json
that takes Hello.index
as handler. For more information on routing, please see the Routing documentation
It means that every time a request GET /hello
is sent to the server, Strapi will call the index
action in the Hello.js
controller.
Our index
action will return Hello World!
. You can also return a JSON object.
Path — ./api/hello/config/routes.json
.
{
"routes": [
{
"method": "GET",
"path": "/hello",
"handler": "Hello.index",
"config": {
"policies": []
}
}
]
}
Path — ./api/hello/controllers/Hello.js
.
module.exports = {
// GET /hello
async index(ctx) {
ctx.send('Hello World!');
},
};
💡 TIP
A route handler can only access the controllers defined in the ./api/**/controllers
folders.
# Requests & Responses
# Requests
The context object (ctx
) contains all the requests related information. They are accessible through ctx.request
, from controllers and policies.
Strapi passes the body
on ctx.request.body
and files
through ctx.request.files
For more information, please refer to the Koa request documentation (opens new window).
# Responses
The context object (ctx
) contains a list of values and functions useful to manage server responses. They are accessible through ctx.response
, from controllers and policies.
For more information, please refer to the Koa response documentation (opens new window).
# Services
Services are a set of reusable functions. They are particularly useful to respect the DRY (don’t repeat yourself) programming concept and to simplify controllers logic.
# Core services
When you create a new Content Type
or a new model, you will see a new empty service has been created. It is because Strapi builds a generic service for your models by default and allows you to override and extend it in the generated files.
# Extending a Model Service
Here are the core methods (and their current implementation). You can simply copy and paste this code to your own service file to customize the methods.
You can read about strapi.query
calls here.
💡 TIP
In the following example your controller, service and model are named restaurant
.
# Utils
If you're extending the create
or update
service, first require the following utility function:
const { isDraft } = require('strapi-utils').contentTypes;
isDraft
: This function checks if the entry is a draft.
# Collection Type
# Single Type
# Custom services
You can also create custom services to build your own business logic.
There are two ways to create a service.
- Using the CLI
strapi generate:service restaurant
.
Read the CLI documentation for more information. - Manually create a JavaScript file named in
./api/**/services/
.
# Example
The goal of a service is to store reusable functions. An email
service could be useful to send emails from different functions in our codebase:
Path — ./api/email/services/Email.js
.
const nodemailer = require('nodemailer');
// Create reusable transporter object using SMTP transport.
const transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'user@gmail.com',
pass: 'password',
},
});
module.exports = {
send: (from, to, subject, text) => {
// Setup e-mail data.
const options = {
from,
to,
subject,
text,
};
// Return a promise of the function that sends the email.
return transporter.sendMail(options);
},
};
💡 TIP
please make sure you installed nodemailer
(npm install nodemailer
) for this example.
The service is now available through the strapi.services
global variable. We can use it in another part of our codebase. For example a controller like below:
Path — ./api/user/controllers/User.js
.
module.exports = {
// GET /hello
signup: async ctx => {
// Store the new user in database.
const user = await User.create(ctx.query);
// Send an email to validate his subscriptions.
strapi.services.email.send('welcome@mysite.com', user.email, 'Welcome', '...');
// Send response to the server.
ctx.send({
ok: true,
});
},
};
# Queries
Strapi provides a utility function strapi.query
to make database queries.
You can just call strapi.query('modelName', 'pluginName')
to access the query API for any model.
These queries handle for you specific Strapi features like components
, dynamic zones
, filters
and search
.
# API Reference
# Custom Queries
When you want to customize your services or create new ones you will have to build your queries with the underlying ORM models.
To access the underlying model:
strapi.query(modelName, plugin).model;
Then you can run any queries available on the model. You should refer to the specific ORM documentation for more details:
# Models
# Concepts
# Content Type's models
Models are a representation of the database's structure. They are split into two separate files. A JavaScript file that contains the model options (e.g: lifecycle hooks), and a JSON file that represents the data structure stored in the database.
Path — ./api/restaurant/models/Restaurant.js
.
module.exports = {
lifecycles: {
// Called before an entry is created
beforeCreate(data) {},
// Called after an entry is created
afterCreate(result) {},
},
};
Path — ./api/restaurant/models/Restaurant.settings.json
.
{
"kind": "collectionType",
"connection": "default",
"info": {
"name": "restaurant",
"description": "This represents the Restaurant Model"
},
"attributes": {
"cover": {
"collection": "file",
"via": "related",
"plugin": "upload"
},
"name": {
"default": "",
"type": "string"
},
"description": {
"default": "",
"type": "text"
}
}
}
In this example, there is a Restaurant
model which contains the attributes cover
, name
and description
.
# Component's models
Another type of model is named components
. A component is a data structure that can be used in one or many other API's model. There is no lifecycle related, only a JSON file definition.
Path — ./components/default/simple.json
{
"connection": "default",
"collectionName": "components_default_simples",
"info": {
"name": "simple",
"icon": "arrow-circle-right"
},
"options": {},
"attributes": {
"name": {
"type": "string"
}
}
}
In this example, there is a Simple
component which contains the attribute name
. And the component is in the category default
.
# Where are the models defined?
The Content Types models are defined in each ./api/**/models/
folder. Every JavaScript or JSON file in these folders will be loaded as a model. They are also available through the strapi.models
and strapi.api.**.models
global variables. Usable everywhere in the project, they contain the ORM model object that they refer to. By convention, a model's name should be written in lowercase.
The Components models are defined in the ./components
folder. Every component has to be inside a subfolder (the category name of the component).
# How to create a model?
💡 TIP
If you are just starting out it is very convenient to generate some models with the Content-Types Builder directly in the admin interface. You can then review the generated model mappings on the code level. The UI takes over a lot of validation tasks and gives you a feeling for available features.
# For Content Types models
Use the CLI and run the following command strapi generate:model restaurant name:string description:text
.
Read the CLI documentation for more information.
This will create two files located at ./api/restaurant/models
:
Restaurant.settings.json
: contains the list of attributes and settings. The JSON format makes the file easily editable.Restaurant.js
: importsRestaurant.settings.json
and extends it with additional settings and life cycle callbacks.
💡 TIP
When you create a new API using the CLI (strapi generate:api <name>
), a model is automatically created.
# For Components models
To create a component you will have to use the Content-Types Builder from the Admin panel, there is not a cli generator for components.
Or you can create your component manually by following the file path described previously and by following the file structure described below.
# Model settings
Additional settings can be set on models:
kind
(string) - Define if the model is a Collection Type (collectionType
) of a Single Type (singleType
) - only for Content Typesconnection
(string) - Connection name which must be used. Default value:default
.collectionName
(string) - Collection name (or table name) in which the data should be stored.globalId
(string) - Global variable name for this model (case-sensitive) - only for Content Typesattributes
(object) - Define the data structure of your model. Find available options below.
Path — Restaurant.settings.json
.
{
"kind": "collectionType",
"connection": "mongo",
"collectionName": "Restaurants_v1",
"globalId": "Restaurants",
"attributes": {}
}
In this example, the model Restaurant
will be accessible through the Restaurants
global variable. The data will be stored in the Restaurants_v1
collection or table and the model will use the mongo
connection defined in ./config/database.js
✋ CAUTION
If not set manually in the JSON file, Strapi will adopt the filename as globalId
.
The globalId
serves as a reference to your model within relations and Strapi APIs. If you chose to rename it (either by renaming your file or by changing the value of the globalId
), you'd have to migrate your tables manually and update the references.
Please note that you should not alter the Strapi's models globalId
(plugins and core models) since they are used directly within Strapi APIs and other models' relations.
💡 TIP
The connection
value can be changed whenever you want, but you should be aware that there is no automatic data migration process. Also if the new connection doesn't use the same ORM you will have to rewrite your queries.
# Model information
The info key on the model-json states information about the model. This information is used in the admin interface, when showing the model.
name
: The name of the model, as shown in admin interface.description
: The description of the model.icon
: The fontawesome V5 name - only for Components
Path — Restaurant.settings.json
.
{
"info": {
"name": "restaurant",
"description": ""
}
}
# Model options
The options key on the model-json states.
timestamps
: This tells the model which attributes to use for timestamps. Accepts eitherboolean
orArray
of strings where first element is create date and second element is update date. Default value when set totrue
for Bookshelf is["created_at", "updated_at"]
and for MongoDB is["createdAt", "updatedAt"]
.privateAttributes
: This configuration allows to treat a set of attributes as private, even if they're not actually defined as attributes in the model. Accepts anArray
of strings. It could be used to remove from API responses timestamps or_v
when using MongoDB. The set ofprivateAttributes
defined in the model are merged with theprivateAttributes
defined in the global Strapi configuration.populateCreatorFields
: Configure whether the API response should includecreated_by
andupdated_by
fields or not. Accepts aboolean
. The default value isfalse
.draftAndPublish
: Enable the draft and publish feature. Accepts aboolean
. The default value isfalse
.
Path — Restaurant.settings.json
.
{
"options": {
"timestamps": true,
"privateAttributes": ["id", "created_at"],
"populateCreatorFields": true,
"draftAndPublish": false
}
}
# Define the attributes
The following types are currently available:
string
text
richtext
email
password
integer
biginteger
float
decimal
date
time
datetime
boolean
enumeration
json
uid
# Validations
You can apply basic validations to attributes. The following supported validations are only supported by MongoDB database connections. If you're using SQL databases, you should use the native SQL constraints to apply them.
required
(boolean) — If true, adds a required validator for this property.unique
(boolean) — Whether to define a unique index on this property.index
(boolean) — Adds an index on this property, this will create a single field index (opens new window) that will run in the background. Only supported by MongoDB.max
(integer) — Checks if the value is greater than or equal to the given maximum.min
(integer) — Checks if the value is less than or equal to the given minimum.
Security validations
To improve the Developer Experience when developing or using the administration panel, the framework enhances the attributes with these "security validations":
private
(boolean) — If true, the attribute will be removed from the server response. (This is useful to hide sensitive data).configurable
(boolean) - If false, the attribute isn't configurable from the Content-Types Builder plugin.autoPopulate
(boolean) - If false, the related data will not populate within REST responses. (This will not stop querying the relational data on GraphQL)
# Exceptions
uid
targetField
(string) — The value is the name of an attribute that hasstring
of thetext
type.options
(string) — The value is a set of options passed to the underlyinguid
generator (opens new window). A caveat is that the resultinguid
must abide to the following RegEx/^[A-Za-z0-9-_.~]*$
.
# Example
Path — Restaurant.settings.json
.
{
...
"attributes": {
"title": {
"type": "string",
"min": 3,
"max": 99,
"unique": true
},
"description": {
"default": "My description",
"type": "text",
"required": true
},
"slug": {
"type": "uid",
"targetField": "title"
}
...
}
}
# Relations
Relations let you create links (relations) between your Content Types.
# Components
Component fields let you create a relation between your Content Type and a Component structure.
# Example
Lets say we created an openinghours
component in restaurant
category.
Path — ./api/restaurant/models/Restaurant.settings.json
.
{
"attributes": {
"openinghours": {
"type": "component",
"repeatable": true,
"component": "restaurant.openinghours"
}
}
}
repeatable
(boolean): Could betrue
orfalse
that let you create a list of data.component
(string): It follows this format<category>.<componentName>
.
# Dynamic Zone
Dynamic Zone fields let you create a flexible space in which to compose content, based on a mixed list of components.
# Example
Lets say we created an slider
and content
component in article
category.
Path — ./api/article/models/Article.settings.json
.
{
"attributes": {
"body": {
"type": "dynamiczone",
"components": ["article.slider", "article.content"]
}
}
}
components
(array): Array of components that follows this format<category>.<componentName>
.
# Lifecycle hooks
The lifecycle hooks are functions that get triggered when the Strapi queries
are called. They will get triggered automatically when you manage your content in the Admin Panel or when you develop custom code using queries
·
To configure a ContentType
lifecycle hook you can set a lifecycles
key in the {modelName}.js
file located in the ./api/{apiName}/models
folder.
# Available Lifecycle hooks
# Example
Path — ./api/restaurant/models/Restaurant.js
.
module.exports = {
/**
* Triggered before user creation.
*/
lifecycles: {
async beforeCreate(data) {
data.isTableFull = data.numOfPeople === 4;
},
},
};
💡 TIP
You can mutate one of the parameters to change its properties. Make sure not to reassign the parameter as it will have no effect:
This will work:
module.exports = {
lifecycles: {
beforeCreate(data) {
data.name = 'Some fixed name';
},
},
};
This will NOT work:
module.exports = {
lifecycles: {
beforeCreate(data) {
data = {
...data,
name: 'Some fixed name',
};
},
},
};
# Custom use
When you are building custom ORM specific queries the lifecycles will not be triggered. You can however call a lifecycle function directly if you wish.
Bookshelf example
Path - ./api/{apiName}/services/{serviceName}.js
module.exports = {
async createCustomEntry() {
const ORMModel = strapi.query(modelName).model;
const newCustomEntry = await ORMModel.forge().save();
// trigger manually
ORMModel.lifecycles.afterCreate(newCustomEntry.toJSON());
},
};
💡 TIP
When calling a lifecycle function directly, you will need to make sure you call it with the expected parameters.
# Webhooks
Webhook is a construct used by an application to notify other applications that an event occurred. More precisely, webhook is a user-defined HTTP callback. Using a webhook is a good way to tell third party providers to start some processing (CI, build, deployment ...).
The way a webhook works is by delivering information to a receiving application through HTTP requests (typically POST requests).
# User content type webhooks
To prevent from unintentionally sending any user's information to other applications, Webhooks will not work for the User content type.
If you need to notify other applications about changes in the Users collection, you can do so by creating Lifecycle hooks inside the file ./extensions/users-permissions/models/User.js
.
# Available configurations
You can set webhook configurations inside the file ./config/server.js
.
webhooks
defaultHeaders
: You can set default headers to use for your webhook requests. This option is overwritten by the headers set in the webhook itself.
Example configuration
module.exports = {
webhooks: {
defaultHeaders: {
'Custom-Header': 'my-custom-header',
},
},
};
# Securing your webhooks
Most of the time, webhooks make requests to public URLs, therefore it is possible that someone may find that URL and send it wrong information.
To prevent this from happening you can send a header with an authentication token. Using the Admin panel you would have to do it for every webhook.
Another way is to define defaultHeaders
to add to every webhook requests.
You can configure these global headers by updating the file at ./config/server.js
:
If you are developing the webhook handler yourself you can now verify the token by reading the headers.
# Available events
By default Strapi webhooks can be triggered by the following events:
Name | Description |
---|---|
entry.create | Triggered when a Content Type entry is created. |
entry.update | Triggered when a Content Type entry is updated. |
entry.delete | Triggered when a Content Type entry is deleted. |
entry.publish | Triggered when a Content Type entry is published.* |
entry.unpublish | Triggered when a Content Type entry is unpublished.* |
media.create | Triggered when a media is created. |
media.update | Triggered when a media is updated. |
media.delete | Triggered when a media is deleted. |
*only when draftAndPublish
is enabled on this Content Type.
# Payloads
💡 NOTE
Private fields and passwords are not sent in the payload.
# Headers
When a payload is delivered to your webhook's URL, it will contain specific headers:
Header | Description |
---|---|
X-Strapi-Event | Name of the event type that was triggered. |
# entry.create
This event is triggered when a new entry is created.
Example payload
{
"event": "entry.create",
"created_at": "2020-01-10T08:47:36.649Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"created_at": "2020-01-10T08:47:36.264Z",
"updated_at": "2020-01-10T08:47:36.264Z",
"cover": null,
"images": []
}
}
# entry.update
This event is triggered when an entry is updated.
Example payload
{
"event": "entry.update",
"created_at": "2020-01-10T08:58:26.563Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"created_at": "2020-01-10T08:47:36.264Z",
"updated_at": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}
# entry.delete
This event is triggered when an entry is deleted.
Example payload
{
"event": "entry.delete",
"created_at": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"created_at": "2020-01-10T08:47:36.264Z",
"updated_at": "2020-01-10T08:58:26.210Z",
"cover": null,
"images": []
}
}
# entry.publish
This event is triggered when an entry is published.
Example payload
{
"event": "entry.publish",
"created_at": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"created_at": "2020-01-10T08:47:36.264Z",
"updated_at": "2020-01-10T08:58:26.210Z",
"published_at": "2020-08-29T14:20:12.134Z",
"cover": null,
"images": []
}
}
# entry.unpublish
This event is triggered when an entry is unpublished.
Example payload
{
"event": "entry.unpublish",
"created_at": "2020-01-10T08:59:35.796Z",
"model": "address",
"entry": {
"id": 1,
"geolocation": {},
"city": "Paris",
"postal_code": null,
"category": null,
"full_name": "Paris",
"created_at": "2020-01-10T08:47:36.264Z",
"updated_at": "2020-01-10T08:58:26.210Z",
"published_at": null,
"cover": null,
"images": []
}
}
# media.create
This event is triggered when you upload a file on entry creation or through the media interface.
Example payload
{
"event": "media.create",
"created_at": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2020-01-10T10:58:41.095Z",
"updated_at": "2020-01-10T10:58:41.095Z",
"related": []
}
}
# media.update
This event is triggered when you replace a media or update the metadata of a media through the media interface.
Example payload
{
"event": "media.update",
"created_at": "2020-01-10T10:58:41.115Z",
"media": {
"id": 1,
"name": "image.png",
"hash": "353fc98a19e44da9acf61d71b11895f9",
"sha256": "huGUaFJhmcZRHLcxeQNKblh53vtSUXYaB16WSOe0Bdc",
"ext": ".png",
"mime": "image/png",
"size": 228.19,
"url": "/uploads/353fc98a19e44da9acf61d71b11895f9.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2020-01-10T10:58:41.095Z",
"updated_at": "2020-01-10T10:58:41.095Z",
"related": []
}
}
# media.delete
This event is triggered only when you delete a media through the media interface.
Example payload
{
"event": "media.delete",
"created_at": "2020-01-10T11:02:46.232Z",
"media": {
"id": 11,
"name": "photo.png",
"hash": "43761478513a4c47a5fd4a03178cfccb",
"sha256": "HrpDOKLFoSocilA6B0_icA9XXTSPR9heekt2SsHTZZE",
"ext": ".png",
"mime": "image/png",
"size": 4947.76,
"url": "/uploads/43761478513a4c47a5fd4a03178cfccb.png",
"provider": "local",
"provider_metadata": null,
"created_at": "2020-01-07T19:34:32.168Z",
"updated_at": "2020-01-07T19:34:32.168Z",
"related": []
}
}